Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 11070] ファイルの概要

このコミットは、Go言語のsyscallパッケージにおけるEnviron関数の挙動を修正し、環境変数の順序が元の順序と一致するように変更するものです。これにより、環境変数の取得順序に関するバグ(Issue 2619)が修正されます。

コミット

commit 024952fb8a75ca12e30cda9d9b52fb9ad653b6c4
Author: Brad Fitzpatrick <bradfitz@golang.org>
Date:   Mon Jan 9 16:51:20 2012 -0800

    syscall: make Environ return original order
    
    Fixes #2619
    
    R=r, rsc
    CC=golang-dev
    https://golang.org/cl/5528058

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/024952fb8a75ca12e30cda9d9b52fb9ad653b6c4

元コミット内容

commit 024952fb8a75ca12e30cda9d9b52fb9ad653b6c4
Author: Brad Fitzpatrick <bradfitz@golang.org>
Date:   Mon Jan 9 16:51:20 2012 -0800

    syscall: make Environ return original order
    
    Fixes #2619
    
    R=r, rsc
    CC=golang-dev
    https://golang.org/cl/5528058
---
 src/pkg/os/env_test.go      | 11 ++++++++\n src/pkg/syscall/env_unix.go | 64 +++++++++++++++++++++++++++++++--------------
 2 files changed, 55 insertions(+), 20 deletions(-)

diff --git a/src/pkg/os/env_test.go b/src/pkg/os/env_test.go
index 04ff390727..991fa4d057 100644
--- a/src/pkg/os/env_test.go
+++ b/src/pkg/os/env_test.go
@@ -6,6 +6,7 @@ package os_test
 
  import (
  	. "os"
 +	"reflect"
  	"testing"
  )
 
@@ -57,3 +58,13 @@ func TestExpand(t *testing.T) {
  		}
  	}
  }\n+\n+func TestConsistentEnviron(t *testing.T) {\n+\te0 := Environ()\n+\tfor i := 0; i < 10; i++ {\n+\t\te1 := Environ()\n+\t\tif !reflect.DeepEqual(e0, e1) {\n+\t\t\tt.Fatalf(\"environment changed\")\n+\t\t}\n+\t}\n+}\ndiff --git a/src/pkg/syscall/env_unix.go b/src/pkg/syscall/env_unix.go
index 2c873cbbad..8b1868c271 100644
--- a/src/pkg/syscall/env_unix.go
+++ b/src/pkg/syscall/env_unix.go
@@ -10,26 +10,40 @@ package syscall
 
  import "sync"
 
 -var env map[string]string
 -var envOnce sync.Once
 -var envs []string // provided by runtime
 +var (
 +	// envOnce guards initialization by copyenv, which populates env.
 +	envOnce sync.Once
  
 +	// envLock guards env and envs.
 +	envLock sync.RWMutex
 +
 +	// env maps from an environment variable to its first occurrence in envs.
 +	env map[string]int
 +
 +	// envs is provided by the runtime. elements are expected to be
 +	// of the form "key=value".
 +	envs []string
 +)
 +
 +// setenv_c is provided by the runtime, but is a no-op if cgo isn\'t
 +// loaded.
  func setenv_c(k, v string)
  
  func copyenv() {
 -	env = make(map[string]string)
 -	for _, s := range envs {
 +	env = make(map[string]int)
 +	for i, s := range envs {
  		for j := 0; j < len(s); j++ {\n \t\t\tif s[j] == \'=\' {\n -	\t\t\tenv[s[0:j]] = s[j+1:]
 +\t\t\t\tkey := s[:j]
 +\t\t\t\tif _, ok := env[key]; !ok {\n+\t\t\t\t\tenv[key] = i
 +\t\t\t\t}\n  \t\t\t\tbreak
  \t\t\t}
  \t\t}\n  \t}\n  }\n  
 -var envLock sync.RWMutex
 -
  func Getenv(key string) (value string, found bool) {
  	envOnce.Do(copyenv)
  	if len(key) == 0 {
@@ -39,11 +53,17 @@ func Getenv(key string) (value string, found bool) {
  	envLock.RLock()
  	defer envLock.RUnlock()
  
 -	v, ok := env[key]
 +	i, ok := env[key]
  	if !ok {
  		return "", false
  	}
 -	return v, true
 +\ts := envs[i]
 +\tfor i := 0; i < len(s); i++ {\n+\t\tif s[i] == \'=\' {\n+\t\t\treturn s[i+1:], true
+\t\t}\n+\t}\n+\treturn "", false
  }\n  
  func Setenv(key, value string) error {
  	envOnce.Do(copyenv)
@@ -55,8 +75,16 @@ func Setenv(key, value string) error {\n  	envLock.Lock()
  	defer envLock.Unlock()
  
 -	env[key] = value
 -	setenv_c(key, value) // is a no-op if cgo isn\'t loaded
 +\ti, ok := env[key]
 +\tkv := key + "=" + value
 +\tif ok {\n+\t\tenvs[i] = kv
 +\t} else {\n+\t\ti = len(envs)
 +\t\tenvs = append(envs, kv)
 +\t}\n+\tenv[key] = i
 +\tsetenv_c(key, value)
  	return nil
  }\n  
 @@ -66,8 +94,8 @@ func Clearenv() {
  	envLock.Lock()
  	defer envLock.Unlock()
  
 -	env = make(map[string]string)
 -
 +\tenv = make(map[string]int)
 +\tenvs = []string{}\n  	// TODO(bradfitz): pass through to C
  }\n  
 @@ -75,11 +103,7 @@ func Environ() []string {
  	envOnce.Do(copyenv)
  	envLock.RLock()
  	defer envLock.RUnlock()
 -	a := make([]string, len(env))
 -	i := 0
 -	for k, v := range env {
 -		a[i] = k + "=" + v
 -		i++
 -	}\n+\ta := make([]string, len(envs))\n+\tcopy(a, envs)\n  	return a
  }\n

変更の背景

このコミットは、Go言語のsyscallパッケージにおけるEnviron関数の既存のバグ(Issue 2619)を修正するために行われました。以前の実装では、Environ関数が環境変数を返す際に、その順序が保証されていませんでした。特に、mapを使用して環境変数を管理していたため、mapの性質上、要素の順序が不定となり、結果としてEnvironが呼び出されるたびに異なる順序で環境変数が返される可能性がありました。

環境変数の順序は、一部のアプリケーションやシェルスクリプトにおいて重要な意味を持つ場合があります。例えば、PATHのような環境変数は、その中に含まれるディレクトリの順序によってコマンドの解決順序が変わるため、順序が保証されないと予期せぬ動作を引き起こす可能性があります。このコミットは、このような順序依存の問題を解決し、Environ関数が常に環境変数を元の順序で返すようにすることで、より予測可能で堅牢な動作を実現することを目的としています。

前提知識の解説

  • 環境変数 (Environment Variables): オペレーティングシステムがプロセスに提供する動的な名前付きの値の集合です。プログラムの動作に影響を与える設定情報(例: PATH, HOME, LANGなど)を格納するために使用されます。各環境変数は通常、「キー=値」の形式で表現されます。
  • syscallパッケージ: Go言語の標準ライブラリの一部で、低レベルのオペレーティングシステムプリミティブへのアクセスを提供します。これには、ファイルシステム操作、プロセス管理、ネットワーク通信、そして環境変数へのアクセスなどが含まれます。
  • syscall.Environ()関数: 現在のプロセスの環境変数をすべて取得し、[]string(文字列のスライス)として返します。各文字列は「キー=値」の形式です。
  • map (Go言語): キーと値のペアを格納するGo言語の組み込みデータ構造です。mapの重要な特性は、要素の順序が保証されないことです。要素をイテレートする際の順序は、Goのランタイムによって決定され、実行ごとに異なる場合があります。
  • sync.Once: Go言語のsyncパッケージにある型で、特定のコードブロックが一度だけ実行されることを保証するために使用されます。このコミットでは、環境変数の初期化処理が複数回行われないようにするために使用されています。
  • sync.RWMutex: 読み書きロック(Reader-Writer Mutex)を提供するGo言語のsyncパッケージにある型です。複数のゴルーチンが同時に読み取りアクセスすることを許可しますが、書き込みアクセスは一度に1つのゴルーチンのみに制限します。これにより、環境変数データへの並行アクセス時のデータ競合を防ぎます。

技術的詳細

このコミットの主要な変更点は、syscallパッケージが環境変数を内部でどのように管理するかという点にあります。以前の実装では、環境変数はmap[string]string型のenv変数に格納されていました。しかし、mapは順序を保証しないため、Environ()関数が呼び出されるたびに環境変数の順序が変動する可能性がありました。

この修正では、以下の変更が導入されました。

  1. 環境変数の格納方法の変更:

    • 以前はvar env map[string]stringとして環境変数のキーと値を直接マップしていましたが、これをvar env map[string]intに変更しました。この新しいenvマップは、環境変数のキーを、envsスライス内のその環境変数のインデックスにマッピングします。
    • 環境変数の実際の「キー=値」形式の文字列は、var envs []stringというスライスに格納されるようになりました。このenvsスライスは、Goランタイムによって提供される元の環境変数の順序を保持します。
  2. copyenv()関数の変更:

    • copyenv()関数は、envsスライスをイテレートし、各環境変数のキーとそのenvsスライス内のインデックスを新しいenvマップに格納するように変更されました。これにより、Getenvでキーから値を検索する際に、元の順序を保持したenvsスライスから値を取得できるようになります。
    • 同じキーの環境変数が複数存在する場合(これは通常は起こりませんが、理論的にはありえます)、envマップには最初に出現した環境変数のインデックスが格納されます。
  3. Getenv()関数の変更:

    • Getenv()関数は、envマップからキーに対応するenvsスライス内のインデックスを取得し、そのインデックスを使用してenvsスライスから実際の環境変数文字列を取得するように変更されました。これにより、Getenvも元の順序を考慮した動作になります。
  4. Setenv()関数の変更:

    • Setenv()関数は、既存の環境変数を更新する場合、envマップから既存のインデックスを取得し、そのインデックスのenvsスライス内の要素を更新します。
    • 新しい環境変数を設定する場合、envsスライスの末尾に新しい環境変数を追加し、その新しいインデックスをenvマップに格納します。これにより、新しい環境変数は追加された順序でenvsスライスに保持されます。
  5. Clearenv()関数の変更:

    • Clearenv()関数は、envマップをクリアするだけでなく、envsスライスも空にするように変更されました。
  6. Environ()関数の変更:

    • Environ()関数は、envマップをイテレートする代わりに、envsスライスのコピーを直接返すように変更されました。これにより、Environ()は常に環境変数がプロセスに渡された元の順序で返されることが保証されます。
  7. テストの追加:

    • os/env_test.goTestConsistentEnvironという新しいテストが追加されました。このテストは、Environ()を複数回呼び出し、返される環境変数のスライスがreflect.DeepEqualで比較して常に同じであることを確認することで、順序の一貫性を検証します。

これらの変更により、syscall.Environ()は環境変数の元の順序を正確に反映するようになり、順序に依存するアプリケーションの互換性と信頼性が向上しました。

コアとなるコードの変更箇所

diff --git a/src/pkg/os/env_test.go b/src/pkg/os/env_test.go
index 04ff390727..991fa4d057 100644
--- a/src/pkg/os/env_test.go
+++ b/src/pkg/os/env_test.go
@@ -6,6 +6,7 @@ package os_test
 
  import (
  	. "os"
 +	"reflect"
  	"testing"
  )
 
@@ -57,3 +58,13 @@ func TestExpand(t *testing.T) {
  		}
  	}
  }\n+\n+func TestConsistentEnviron(t *testing.T) {\n+\te0 := Environ()\n+\tfor i := 0; i < 10; i++ {\n+\t\te1 := Environ()\n+\t\tif !reflect.DeepEqual(e0, e1) {\n+\t\t\tt.Fatalf("environment changed")\n+\t\t}\n+\t}\n+}\ndiff --git a/src/pkg/syscall/env_unix.go b/src/pkg/syscall/env_unix.go
index 2c873cbbad..8b1868c271 100644
--- a/src/pkg/syscall/env_unix.go
+++ b/src/pkg/syscall/env_unix.go
@@ -10,26 +10,40 @@ package syscall
 
  import "sync"
 
 -var env map[string]string
 -var envOnce sync.Once
 -var envs []string // provided by runtime
 +var (
 +	// envOnce guards initialization by copyenv, which populates env.
 +	envOnce sync.Once
  
 +	// envLock guards env and envs.
 +	envLock sync.RWMutex
 +
 +	// env maps from an environment variable to its first occurrence in envs.
 +	env map[string]int
 +
 +	// envs is provided by the runtime. elements are expected to be
 +	// of the form "key=value".
 +	envs []string
 +)
 +
 +// setenv_c is provided by the runtime, but is a no-op if cgo isn\'t
 +// loaded.
  func setenv_c(k, v string)
  
  func copyenv() {
 -	env = make(map[string]string)
 -	for _, s := range envs {
 +	env = make(map[string]int)
 +	for i, s := range envs {
  		for j := 0; j < len(s); j++ {\n \t\t\tif s[j] == \'=\' {\n -	\t\t\tenv[s[0:j]] = s[j+1:]
 +\t\t\t\tkey := s[:j]
 +\t\t\t\tif _, ok := env[key]; !ok {\n+\t\t\t\t\tenv[key] = i
 +\t\t\t\t}\n  \t\t\t\tbreak
  \t\t\t}
  \t\t}\n  \t}\n  }\n  
 -var envLock sync.RWMutex
 -
  func Getenv(key string) (value string, found bool) {
  	envOnce.Do(copyenv)
  	if len(key) == 0 {
@@ -39,11 +53,17 @@ func Getenv(key string) (value string, found bool) {
  	envLock.RLock()
  	defer envLock.RUnlock()
  
 -	v, ok := env[key]
 +	i, ok := env[key]
  	if !ok {
  		return "", false
  	}
 -	return v, true
 +\ts := envs[i]
 +\tfor i := 0; i < len(s); i++ {\n+\t\tif s[i] == \'=\' {\n+\t\t\treturn s[i+1:], true
+\t\t}\n+\t}\n+\treturn "", false
  }\n  
  func Setenv(key, value string) error {
  	envOnce.Do(copyenv)
@@ -55,8 +75,16 @@ func Setenv(key, value string) error {\n  	envLock.Lock()
  	defer envLock.Unlock()
  
 -	env[key] = value
 -	setenv_c(key, value) // is a no-op if cgo isn\'t loaded
 +\ti, ok := env[key]
 +\tkv := key + "=" + value
 +\tif ok {\n+\t\tenvs[i] = kv
 +\t} else {\n+\t\ti = len(envs)
 +\t\tenvs = append(envs, kv)
 +\t}\n+\tenv[key] = i
 +\tsetenv_c(key, value)
  	return nil
  }\n  
 @@ -66,8 +94,8 @@ func Clearenv() {
  	envLock.Lock()
  	defer envLock.Unlock()
  
 -	env = make(map[string]string)
 -
 +\tenv = make(map[string]int)
 +\tenvs = []string{}\n  	// TODO(bradfitz): pass through to C
  }\n  
 @@ -75,11 +103,7 @@ func Environ() []string {
  	envOnce.Do(copyenv)
  	envLock.RLock()
  	defer envLock.RUnlock()
 -	a := make([]string, len(env))
 -	i := 0
 -	for k, v := range env {
 -		a[i] = k + "=" + v
 -		i++
 -	}\n+\ta := make([]string, len(envs))\n+\tcopy(a, envs)\n  	return a
  }\n```

## コアとなるコードの解説

このコミットにおける主要なコード変更は、`src/pkg/syscall/env_unix.go`ファイルに集中しています。

1.  **変数定義の変更**:
    *   以前は`env map[string]string`と`envs []string`が別々に定義されていましたが、新しいコードでは`env`が`map[string]int`型に変更され、`envs`スライスはそのまま残されています。
    *   `env`マップは、環境変数名(キー)から`envs`スライス内の対応する環境変数のインデックスへのマッピングを保持するようになりました。これにより、環境変数の検索を効率的に行いつつ、`envs`スライスで元の順序を維持できます。
    *   `envLock`(読み書きミューテックス)は、`env`と`envs`の両方を保護するように明示的にコメントが追加されました。

2.  **`copyenv()`関数の変更**:
    *   この関数は、環境変数を初期化する際に呼び出されます。
    *   変更前は、`envs`スライスから直接`env`マップにキーと値をコピーしていました。
    *   変更後では、`envs`スライスをイテレートし、各環境変数のキーと、その環境変数が`envs`スライス内で最初に出現するインデックスを`env`マップに格納します。これにより、`env`マップは検索用インデックスとして機能し、実際の値は順序が保証された`envs`スライスから取得されるようになります。

3.  **`Getenv()`関数の変更**:
    *   環境変数の値を取得する関数です。
    *   変更前は、`env`マップから直接値を取得していました。
    *   変更後では、`env`マップからキーに対応する`envs`スライス内のインデックス`i`を取得します。その後、そのインデックス`i`を使用して`envs[i]`から環境変数文字列全体を取得し、そこから値の部分を抽出して返します。これにより、`Getenv`も`envs`スライスの順序を間接的に利用する形になります。

4.  **`Setenv()`関数の変更**:
    *   環境変数を設定する関数です。
    *   変更前は、単に`env`マップに新しいキーと値を設定していました。
    *   変更後では、設定しようとしているキーが既に`env`マップに存在するかどうかを確認します。
        *   もし存在すれば、そのキーに対応する`envs`スライス内の既存の要素を新しい「キー=値」の文字列で更新します。
        *   存在しない場合は、新しい「キー=値」の文字列を`envs`スライスの末尾に追加し、その新しいインデックスを`env`マップに格納します。これにより、環境変数の追加順序が`envs`スライスに反映されます。

5.  **`Clearenv()`関数の変更**:
    *   すべての環境変数をクリアする関数です。
    *   変更前は`env`マップのみをクリアしていました。
    *   変更後では、`env`マップをクリアするとともに、`envs`スライスも空の新しいスライスに置き換えることで、環境変数の状態を完全にリセットします。

6.  **`Environ()`関数の変更**:
    *   すべての環境変数を文字列のスライスとして返す関数です。
    *   変更前は、`env`マップをイテレートして新しいスライスを構築していました。この方法では`map`の順序不定性により、返されるスライスの順序も不定でした。
    *   変更後では、`envs`スライスのコピーを直接返します。`envs`スライスは元の環境変数の順序を保持しているため、`Environ()`は常に元の順序で環境変数を返すことが保証されます。

7.  **テストファイルの変更 (`src/pkg/os/env_test.go`)**:
    *   `reflect`パッケージがインポートされました。これは、Goのデータ構造(この場合は文字列のスライス)のディープ比較を行うために使用されます。
    *   `TestConsistentEnviron`という新しいテスト関数が追加されました。このテストは、`Environ()`関数を複数回呼び出し、それぞれの呼び出しで返される環境変数のスライスが`reflect.DeepEqual`によって完全に一致することを確認します。これにより、`Environ()`が常に一貫した順序で環境変数を返すことが保証されます。

これらの変更により、Goの環境変数管理は、順序の保証という重要な特性を獲得し、より堅牢で予測可能な動作を提供するようになりました。

## 関連リンク

*   Go Issue 2619: [https://github.com/golang/go/issues/2619](https://github.com/golang/go/issues/2619)
*   Go CL 5528058: [https://golang.org/cl/5528058](https://golang.org/cl/5528058)

## 参考にした情報源リンク

*   [https://github.com/golang/go/commit/024952fb8a75ca12e30cda9d9b52fb9ad653b6c4](https://github.com/golang/go/commit/024952fb8a75ca12e30cda9d9b52fb9ad653b6c4)
*   Go言語のドキュメント (syscall, os, syncパッケージ)
*   Go言語の`map`の特性に関する一般的な知識