[インデックス 16655] ファイルの概要
このコミットは、Go言語のsyscall
パッケージにおけるWindows環境変数ハンドリングの挙動を修正するものです。具体的には、空の環境変数値を設定したり取得したりする際の挙動が、Unixシステムと一貫するように変更されています。
コミット
commit 04b405c7fc99008b3cf90f4c067e81001af2de29
Author: Alex Brainman <alex.brainman@gmail.com>
Date: Thu Jun 27 10:11:30 2013 +1000
syscall: handle empty environment variable values properly on windows
Setenv("AN_ENV_VAR", "") deletes AN_ENV_VAR instead of setting it
to "" at this moment. Also Getenv("AN_ENV_VAR") returns "not found",
if AN_ENV_VAR is "". Change it, so they behave like unix.
Fixes #5610
R=golang-dev, bradfitz
CC=golang-dev
https://golang.org/cl/10594043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/04b405c7fc99008b3cf90f4c067e81001af2de29
元コミット内容
このコミットは、Windows上での環境変数の扱いに関するバグを修正します。以前の挙動では、Setenv("AN_ENV_VAR", "")
とすると、環境変数AN_ENV_VAR
が削除されてしまい、空文字列が設定されるべきところがそうなりませんでした。また、AN_ENV_VAR
が空文字列である場合にGetenv("AN_ENV_VAR")
を呼び出すと、「見つからない」という結果(found
がfalse
)が返されていました。このコミットは、これらの挙動をUnixシステムでの環境変数ハンドリングと一致させるように変更します。
変更の背景
この変更は、Go言語のIssue #5610に対応するものです。Issue #5610では、Windows環境におけるos.Setenv
とos.Getenv
の挙動が、Unix系OSと異なることが報告されていました。具体的には、Windowsでは空文字列を環境変数に設定しようとすると、その環境変数が削除されてしまうという問題がありました。これは、Goプログラムがクロスプラットフォームで動作する際に、環境変数の扱いに一貫性がないという問題を引き起こしていました。
例えば、Unix系OSではexport MY_VAR=""
とするとMY_VAR
は空文字列として存在しますが、Windowsでは同等の操作を行うとMY_VAR
自体がシステムから削除されてしまいます。Goのsyscall
パッケージは、これらのOS固有の挙動を抽象化し、Goプログラムからは一貫したインターフェースを提供することを目指しています。このコミットは、その目標を達成するための一歩として、Windows上での環境変数ハンドリングをUnixのセマンティクスに近づけることを目的としています。
前提知識の解説
環境変数 (Environment Variables)
環境変数とは、オペレーティングシステムが提供する動的な名前付きの値の集合です。これらは、実行中のプロセスに設定情報やシステムパスなどのデータを提供するために使用されます。例えば、PATH
環境変数は実行可能ファイルの検索パスを定義し、HOME
環境変数はユーザーのホームディレクトリを示します。
syscall
パッケージ
Go言語のsyscall
パッケージは、低レベルのオペレーティングシステムプリミティブへのアクセスを提供します。これには、ファイル操作、プロセス管理、ネットワーク通信、そして環境変数の操作などが含まれます。このパッケージは、OS固有のAPIをGoのインターフェースにラップすることで、クロスプラットフォームなGoプログラムがOSの機能を利用できるようにします。
Windowsにおける環境変数
Windowsでは、環境変数はレジストリやプロセス環境ブロック(PEB)に格納されます。環境変数を設定するAPIとしてSetEnvironmentVariable
関数があり、取得するAPIとしてGetEnvironmentVariable
関数があります。WindowsのSetEnvironmentVariable
関数は、第二引数に空文字列を渡すと、その環境変数を削除するという特殊な挙動を持っています。これが、このコミットで修正される問題の根源でした。
Unixにおける環境変数
Unix系OSでは、環境変数は通常、プロセスの環境リスト(environ
)に格納されます。環境変数を設定するAPIとしてsetenv
関数があり、取得するAPIとしてgetenv
関数があります。Unixのsetenv
関数は、空文字列を値として設定した場合でも、環境変数を削除することなく、その値を空文字列として保持します。
UTF-16エンコーディング
Windows APIの多くは、文字列をUTF-16エンコーディングで扱います。Go言語の文字列はUTF-8でエンコードされているため、Windows APIを呼び出す際には、文字列をUTF-8からUTF-16に変換する必要があります。syscall
パッケージ内のUTF16PtrFromString
関数は、この変換を行います。
技術的詳細
このコミットの技術的な核心は、Windows APIのSetEnvironmentVariable
とGetEnvironmentVariable
の挙動を、Goのsyscall.Setenv
とsyscall.Getenv
がUnixのセマンティクスに沿うようにラップする方法にあります。
syscall.Getenv
の変更点
以前のGetenv
関数では、環境変数の値の長さが0の場合(つまり、環境変数が空文字列である場合)、found
をfalse
として返していました。これは、環境変数が存在しない場合と同じ結果を返すことを意味します。
// 変更前
func Getenv(key string) (value string, found bool) {
// ...
if n == 0 { // nは環境変数の値の長さ
return "", false // 値が空の場合、見つからないと判断
}
return string(utf16.Decode(b[0:n])), true
}
このコミットでは、if n == 0 { return "", false }
のブロックが削除されました。
// 変更後
func Getenv(key string) (value string, found bool) {
// ...
// if n == 0 { // このブロックが削除された
// return "", false
// }
return string(utf16.Decode(b[0:n])), true // nが0でも、""とtrueを返す
}
これにより、GetEnvironmentVariable
が空文字列を返した場合(つまり、環境変数が空文字列として設定されている場合)、Getenv
は""
とtrue
(見つかった)を返すようになります。これはUnixのgetenv
の挙動と一致します。
syscall.Setenv
の変更点
以前のSetenv
関数では、設定しようとするvalue
が空文字列の場合、UTF16PtrFromString(value)
の呼び出しをスキップしていました。これは、WindowsのSetEnvironmentVariable
関数が第二引数にnil
を渡されると環境変数を削除するという挙動を利用したものでした。
// 変更前
func Setenv(key, value string) error {
var v *uint16
var err error
if len(value) > 0 { // 値が空文字列の場合、このブロックは実行されない
v, err = UTF16PtrFromString(value)
if err != nil {
return err
}
}
// len(value) == 0 の場合、vはnilのまま
// SetEnvironmentVariable(keyp, v) が呼ばれると、環境変数が削除される
keyp, err := UTF16PtrFromString(key)
if err != nil {
return err
}
return SetEnvironmentVariable(keyp, v)
}
このコミットでは、if len(value) > 0
のチェックが削除されました。
// 変更後
func Setenv(key, value string) error {
// var v *uint16 // 不要になった
// var err error // 不要になった
// if len(value) > 0 { // このチェックが削除された
v, err := UTF16PtrFromString(value) // valueが空文字列でも常に呼び出される
if err != nil {
return err
}
// }
keyp, err := UTF16PtrFromString(key)
if err != nil {
return err
}
return SetEnvironmentVariable(keyp, v)
}
UTF16PtrFromString("")
は、空文字列に対応するUTF-16のポインタを返します。WindowsのSetEnvironmentVariable
関数は、第二引数に空文字列のポインタが渡された場合、環境変数を空文字列として設定し、削除はしません。これにより、Setenv("AN_ENV_VAR", "")
が環境変数を削除するのではなく、空文字列として設定するようになります。これもUnixのsetenv
の挙動と一致します。
テストの追加
この変更の正しさを検証するために、src/pkg/syscall/syscall_test.go
に新しいテストファイルが追加されました。testSetGetenv
関数は、指定されたキーと値で環境変数を設定し、その後取得して、期待される値と一致するかどうかを確認します。TestEnv
関数は、このtestSetGetenv
を2回呼び出します。1回目は通常の非空文字列で、2回目は空文字列で呼び出し、空文字列が正しく設定・取得されることを確認しています。
コアとなるコードの変更箇所
src/pkg/syscall/env_windows.go
--- a/src/pkg/syscall/env_windows.go
+++ b/src/pkg/syscall/env_windows.go
@@ -28,20 +28,13 @@ func Getenv(key string) (value string, found bool) {
tn = 0
}
}
- if n == 0 {
- return "", false
- }
return string(utf16.Decode(b[0:n])), true
}
func Setenv(key, value string) error {
- var v *uint16
- var err error
- if len(value) > 0 {
- v, err = UTF16PtrFromString(value)
- if err != nil {
- return err
- }
+ v, err := UTF16PtrFromString(value)
+ if err != nil {
+ return err
}
keyp, err := UTF16PtrFromString(key)
if err != nil {
src/pkg/syscall/syscall_test.go
--- /dev/null
+++ b/src/pkg/syscall/syscall_test.go
@@ -0,0 +1,30 @@
+// Copyright 2013 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package syscall_test
+
+import (
+ "syscall"
+ "testing"
+)
+
+func testSetGetenv(t *testing.T, key, value string) {
+ err := syscall.Setenv(key, value)
+ if err != nil {
+ t.Fatalf("Setenv failed to set %q: %v", value, err)
+ }
+ newvalue, found := syscall.Getenv(key)
+ if !found {
+ t.Fatalf("Getenv failed to find %v variable (want value %q)", key, value)
+ }
+ if newvalue != value {
+ t.Fatalf("Getenv(%v) = %q; want %q", key, newvalue, value)
+ }
+}
+
+func TestEnv(t *testing.T) {
+ testSetGetenv(t, "TESTENV", "AVALUE")
+ // make sure TESTENV gets set to "", not deleted
+ testSetGetenv(t, "TESTENV", "")
+}
コアとなるコードの解説
src/pkg/syscall/env_windows.go
-
Getenv
関数の変更:- 以前のコードでは、
GetEnvironmentVariable
から取得した環境変数の値の長さn
が0の場合、return "", false
としていました。これは、環境変数が空文字列である場合でも、その環境変数が「見つからなかった」と報告することを意味していました。 - 変更後、この
if n == 0
のチェックが削除されました。これにより、GetEnvironmentVariable
が空文字列を返した場合(つまり、環境変数が空文字列として存在する場合)、Getenv
は""
とtrue
を返すようになります。これは、Unixシステムでのgetenv
の挙動と一致し、空文字列も有効な値として扱われるようになります。
- 以前のコードでは、
-
Setenv
関数の変更:- 以前のコードでは、
value
の長さが0より大きい場合にのみUTF16PtrFromString(value)
を呼び出していました。value
が空文字列の場合、v
はnil
のままでした。WindowsのSetEnvironmentVariable
関数は、第二引数にnil
が渡されると環境変数を削除するため、これが問題の原因でした。 - 変更後、
if len(value) > 0
の条件分岐が削除され、v, err := UTF16PtrFromString(value)
が常に呼び出されるようになりました。UTF16PtrFromString("")
は、空文字列に対応するUTF-16のポインタを返します。WindowsのSetEnvironmentVariable
関数は、第二引数に空文字列のポインタが渡された場合、環境変数を空文字列として設定し、削除はしません。これにより、Setenv
が空文字列を正しく設定できるようになり、Unixのsetenv
の挙動と一致します。
- 以前のコードでは、
src/pkg/syscall/syscall_test.go
- 新規テストファイルの追加:
- このファイルは、
syscall
パッケージの環境変数関連の機能が正しく動作するかを確認するためのテストを含んでいます。
- このファイルは、
testSetGetenv
関数:- このヘルパー関数は、指定された
key
とvalue
でsyscall.Setenv
を呼び出し、エラーがないことを確認します。 - その後、
syscall.Getenv
を呼び出し、環境変数がfound
(見つかった)であり、かつ取得された値が設定したvalue
と一致することを確認します。
- このヘルパー関数は、指定された
TestEnv
関数:testSetGetenv
を2回呼び出します。- 1回目は
"TESTENV", "AVALUE"
という通常のキーと値のペアでテストします。 - 2回目は
"TESTENV", ""
というキーと空文字列のペアでテストします。このテストケースが、このコミットで修正されたWindowsにおける空文字列の環境変数ハンドリングのバグを直接検証するものです。これにより、Setenv
が空文字列を削除するのではなく、正しく設定し、Getenv
がそれを正しく取得できることを保証します。
- 1回目は
関連リンク
- Go Issue #5610: https://github.com/golang/go/issues/5610
- Go CL 10594043: https://golang.org/cl/10594043
参考にした情報源リンク
- Windows API
SetEnvironmentVariable
documentation: https://learn.microsoft.com/en-us/windows/win32/api/processenv/nf-processenv-setenvironmentvariablew - Windows API
GetEnvironmentVariable
documentation: https://learn.microsoft.com/en-us/windows/win32/api/processenv/nf-processenv-getenvironmentvariablew - Unix
setenv
man page (一般的な情報): https://man7.org/linux/man-pages/man3/setenv.3.html - Unix
getenv
man page (一般的な情報): https://man7.org/linux/man-pages/man3/getenv.3.html - Go
os
package documentation (環境変数関連のGoの標準ライブラリ): https://pkg.go.dev/os - Go
syscall
package documentation: https://pkg.go.dev/syscall - UTF-16エンコーディングに関する一般的な情報