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

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

このコミットは、Go言語のsyscallパッケージにおけるPlan 9オペレーティングシステム向けの環境変数操作ルーチン(Getenv, Setenv, Environ)のバグ修正と改善を目的としています。具体的には、ファイルディスクリプタの操作方法、エラーハンドリング、およびファイルオープン時のフラグ指定をPlan 9のシステムコール規約に適合させる変更が行われています。

コミット

commit aa3dbf2947a5bce8c5a3595fafd0e26f184dabf5
Author: Lucio De Re <lucio.dere@gmail.com>
Date:   Fri Nov 18 13:36:06 2011 -0500

    syscall: fix env routines for Plan 9

    R=golang-dev
    CC=ality, golang-dev, rsc
    https://golang.org/cl/5364063

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

https://github.com/golang/go/commit/aa3dbf2947a5bce8c5a3595fafd0e26f184dabf5

元コミット内容

syscall: fix env routines for Plan 9

このコミットメッセージは、Go言語のsyscallパッケージ内で、Plan 9オペレーティングシステムにおける環境変数関連のルーチン(関数)に存在する問題を修正したことを示しています。

変更の背景

この変更の背景には、Go言語が様々なオペレーティングシステムをサポートする上で、それぞれのOSのシステムコール規約やファイルシステムセマンティクスに適切に対応する必要があるという点があります。特にPlan 9は、そのユニークな「全てをファイルとして扱う」という設計思想により、環境変数も/envという特殊なファイルシステムパスを通じてファイルとして扱われます。

元の実装では、Plan 9における環境変数の読み書きにおいて、以下のような問題があったと考えられます。

  1. ファイルディスクリプタの不適切なクローズ: f.Close()というメソッド呼び出しが使用されていましたが、syscallパッケージで開かれたファイルディスクリプタは、Goの標準ライブラリのos.Fileとは異なる型である可能性があり、syscallパッケージが提供するClose(f)関数を使用する必要がありました。これにより、リソースリークや予期せぬ動作が発生する可能性がありました。
  2. ファイルオープンフラグの欠如: OpenCreateシステムコールにおいて、ファイルを開く際のモード(読み取り専用、読み書き可能など)が明示的に指定されていませんでした。これは、意図しないアクセス権でのファイルオープンや、セキュリティ上の問題を引き起こす可能性があります。
  3. エラーハンドリングの改善: Getenv関数において、環境変数が存在しない場合や無効なキーが指定された場合に、Goの慣習に沿ったfoundブーリアン値の返却や、より具体的なエラーメッセージの生成が求められていました。元のコードではEINVALENOENVといったUnixライクなエラーコードが直接返されていましたが、Goのエラーハンドリングのベストプラクティスに合わせる必要がありました。
  4. Readdirnamesの呼び出し方法: Environ関数でディレクトリの内容を読み取る際に、f.Readdirnames(-1)というメソッド呼び出しが使用されていましたが、これもsyscallパッケージの規約に合わせたreaddirnames(f)という関数呼び出しに修正する必要がありました。

これらの問題は、Plan 9上でのGoプログラムの安定性、正確性、および移植性に影響を与えるため、修正が必要とされました。

前提知識の解説

Plan 9 from Bell Labs

Plan 9は、ベル研究所で開発された分散オペレーティングシステムです。その最も特徴的な設計思想は「全てをファイルとして扱う」というものです。プロセス、ネットワーク接続、デバイス、そして環境変数さえも、ファイルシステム上のパスとして表現され、標準的なファイル操作(読み書き、オープン、クローズなど)を通じてアクセスされます。

  • /envファイルシステム: Plan 9では、各プロセスの環境変数は/envという特殊なファイルシステムツリーの下にファイルとして存在します。例えば、PATH環境変数の値は/env/PATHというファイルを読み取ることで取得できます。

Go言語のsyscallパッケージ

Go言語のsyscallパッケージは、オペレーティングシステムの低レベルなプリミティブ関数(システムコール)へのインターフェースを提供します。これにより、GoプログラムはOSのカーネルと直接対話し、ファイル操作、プロセス管理、ネットワーク通信など、OS固有の機能を利用できます。 このパッケージはOSごとに異なる実装を持つことが多く、特定のOS(この場合はPlan 9)のシステムコール規約に合わせたコードが記述されます。

ファイルディスクリプタとファイル操作

  • ファイルディスクリプタ (File Descriptor, FD): オペレーティングシステムがファイルやI/Oリソースを識別するために使用する抽象的なハンドルです。ファイルを開くと、OSはFDを返します。
  • Open, Create: ファイルを開いたり、新しく作成したりするためのシステムコールです。通常、ファイルパスとオープンモード(読み取り、書き込みなど)を指定します。
  • Seek: ファイルポインタの位置を変更するためのシステムコールです。ファイルの特定の位置から読み書きを開始するために使用されます。
  • Read, Write: ファイルからデータを読み取ったり、ファイルにデータを書き込んだりするためのシステムコールです。
  • Close: 開いているファイルディスクリプタを閉じるためのシステムコールです。リソースを解放し、ファイルシステムへの変更を永続化するために重要です。
  • O_RDONLY, O_RDWR: ファイルオープン時に指定するフラグです。
    • O_RDONLY: 読み取り専用でファイルを開きます。
    • O_RDWR: 読み書き両用でファイルを開きます。
  • ファイルパーミッション (例: 0666): ファイル作成時に指定するアクセス権限です。0666は、所有者、グループ、その他のユーザーに対して読み取りと書き込みの権限を与えることを意味します。

Go言語のエラーハンドリング

Go言語では、エラーは通常、関数の最後の戻り値としてerrorインターフェースを実装した値として返されます。慣習として、エラーがない場合はnilが返されます。このコミットでは、EINVALENOENVといった具体的なエラーコードを直接返すのではなく、errors.Newを使用してよりGoらしいエラーオブジェクトを生成したり、Getenvのようにfoundブーリアン値で成功/失敗を示すパターンに修正されています。

技術的詳細

このコミットの技術的詳細は、Plan 9のファイルシステムセマンティクスとGoのsyscallパッケージの連携に深く関わっています。

  1. ファイルディスクリプタの型と操作:

    • 元のコードでは、OpenCreateが返すファイルディスクリプタを表す変数fに対して、f.Close(), f.Seek(), f.Read(), f.Write(), f.Readdirnames()といったメソッド呼び出しが行われていました。
    • しかし、syscallパッケージがPlan 9向けに提供するこれらの操作は、Goの標準ライブラリのos.File型が持つメソッドとは異なり、syscallパッケージ内のグローバル関数(例: Close(f), Seek(f, offset, whence), Read(f, buf), Write(f, buf), readdirnames(f))として提供されていることが示唆されています。
    • この修正により、GoのsyscallパッケージがPlan 9のシステムコールを正しくラップし、適切な関数呼び出し規約に従うようになりました。
  2. ファイルオープンフラグの明示的な指定:

    • Getenv関数内のOpen("/env/" + key)Open("/env/"+key, O_RDONLY)に、Environ関数内のOpen("/env")Open("/env", O_RDONLY)に変更されました。これにより、環境変数を読み取る際には明示的に読み取り専用モードでファイルを開くことが保証されます。
    • Setenv関数内のCreate("/env/" + key)Create("/env/"+key, O_RDWR, 0666)に変更されました。これにより、環境変数を設定(作成または上書き)する際には、読み書き両用モードでファイルを開き、かつ0666という適切なパーミッション(所有者、グループ、その他全てに読み書き権限)を設定することが保証されます。これは、ファイルシステム上の環境変数ファイルに対する適切なアクセス制御を確立するために重要です。
  3. Getenvのエラーハンドリングの改善:

    • Getenv関数は、環境変数の値と、その変数が「見つかったかどうか」を示すブーリアン値を返すGoの慣習的なパターンに従います。
    • 元のコードでは、キーが空の場合やファイルが見つからない場合にEINVALENOENVといったエラーコードを返していましたが、これらはGoのerrorインターフェースとは直接互換性がありませんでした。
    • 修正後、これらのケースではvalueは空文字列、foundfalseを返すようになりました。これにより、呼び出し元はfoundブーリアン値を確認するだけで、環境変数の存在を簡潔に判断できるようになります。
  4. Setenvのエラーハンドリングの改善:

    • キーが空の場合、元のコードではEINVALを返していましたが、修正後はerrors.New("bad arg in system call")という具体的なerrorオブジェクトを返すようになりました。これは、Goのエラーハンドリングのベストプラクティスに沿った変更です。

これらの変更は、Plan 9上でのGoのsyscallパッケージの堅牢性と正確性を向上させ、Goの慣習に沿ったAPIを提供するために不可欠です。

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

src/pkg/syscall/env_plan9.goファイルが変更されています。

--- a/src/pkg/syscall/env_plan9.go
+++ b/src/pkg/syscall/env_plan9.go
@@ -10,40 +10,40 @@ import "errors"

 func Getenv(key string) (value string, found bool) {
 	if len(key) == 0 {
-		return "", EINVAL
+		return "", false
 	}
-	f, e := Open("/env/" + key)
+	f, e := Open("/env/"+key, O_RDONLY)
 	if e != nil {
-		return "", ENOENV
+		return "", false
 	}
-	defer f.Close()
+	defer Close(f)

-	l, _ := f.Seek(0, 2)
-	f.Seek(0, 0)
+	l, _ := Seek(f, 0, 2)
+	Seek(f, 0, 0)
 	buf := make([]byte, l)
-	n, e := f.Read(buf)
+	n, e := Read(f, buf)
 	if e != nil {
-		return "", ENOENV
+		return "", false
 	}

 	if n > 0 && buf[n-1] == 0 {
 		buf = buf[:n-1]
 	}
-	return string(buf), nil
+	return string(buf), true
 }

 func Setenv(key, value string) error {
 	if len(key) == 0 {
-		return EINVAL
+		return errors.New("bad arg in system call")
 	}

-	f, e := Create("/env/" + key)
+	f, e := Create("/env/"+key, O_RDWR, 0666)
 	if e != nil {
 		return e
 	}
-	defer f.Close()
+	defer Close(f)

-	_, e = f.Write([]byte(value))
+	_, e = Write(f, []byte(value))
 	return nil
 }

@@ -54,13 +54,13 @@ func Clearenv() {
 func Environ() []string {
 	env := make([]string, 0, 100)

-	f, e := Open("/env")
+	f, e := Open("/env", O_RDONLY)
 	if e != nil {
 		panic(e)
 	}
-	defer f.Close()
+	defer Close(f)

-	names, e := f.Readdirnames(-1)
+	names, e := readdirnames(f)
 	if e != nil {
 		panic(e)
 	}

コアとなるコードの解説

Getenv 関数

  • エラー返却値の変更:
    • if len(key) == 0: キーが空の場合、以前はEINVAL(無効な引数)を返していましたが、return "", falseに変更されました。これは、GoのGetenvの慣習的なシグネチャ(value string, found bool)に合わせ、キーが無効な場合は値が見つからなかったと見なすことを意味します。
    • if e != nil: OpenReadでエラーが発生した場合、以前はENOENV(環境変数なし)を返していましたが、これもreturn "", falseに変更されました。これにより、環境変数が存在しない、または読み取りに失敗した場合も、foundfalseになることで統一的に扱われます。
  • ファイルオープンフラグの追加:
    • f, e := Open("/env/"+key, O_RDONLY): /env/以下の環境変数ファイルを開く際に、明示的にO_RDONLY(読み取り専用)フラグが追加されました。これにより、意図しない書き込みを防ぎ、セキュリティと堅牢性が向上します。
  • ファイルディスクリプタ操作の修正:
    • defer f.Close() -> defer Close(f): f.Close()というメソッド呼び出しから、syscallパッケージが提供するClose(f)という関数呼び出しに変更されました。これは、Openが返すfがGoの標準os.File型ではなく、syscallパッケージ固有のファイルディスクリプタ型であり、そのクローズには専用の関数が必要であることを示しています。
    • f.Seek(0, 2) -> Seek(f, 0, 2)f.Seek(0, 0) -> Seek(f, 0, 0)f.Read(buf) -> Read(f, buf): 同様に、SeekReadもメソッド呼び出しからsyscallパッケージの関数呼び出しに変更されました。
  • 成功時の返却値の修正:
    • return string(buf), nil -> return string(buf), true: 環境変数の値が正常に読み取れた場合、foundブーリアン値をtrueとして返すように修正されました。元のnilは、foundがブーリアン型であるため、論理的に誤りでした。

Setenv 関数

  • エラー返却値の変更:
    • if len(key) == 0: キーが空の場合、以前はEINVALを返していましたが、return errors.New("bad arg in system call")に変更されました。これにより、Goの標準的なエラーオブジェクトが返され、より詳細なエラー情報を提供できるようになります。
  • ファイル作成フラグとパーミッションの追加:
    • f, e := Create("/env/"+key, O_RDWR, 0666): /env/以下の環境変数ファイルを作成または上書きする際に、O_RDWR(読み書き両用)フラグと0666(所有者、グループ、その他全てに読み書き権限)パーミッションが明示的に追加されました。これにより、環境変数ファイルが正しく作成され、適切なアクセス権が設定されることが保証されます。
  • ファイルディスクリプタ操作の修正:
    • defer f.Close() -> defer Close(f): Getenvと同様に、Close関数呼び出しに修正されました。
    • f.Write([]byte(value)) -> Write(f, []byte(value)): Writeも関数呼び出しに修正されました。

Environ 関数

  • ファイルオープンフラグの追加:
    • f, e := Open("/env", O_RDONLY): /envディレクトリを開く際に、明示的にO_RDONLY(読み取り専用)フラグが追加されました。
  • ファイルディスクリプタ操作の修正:
    • defer f.Close() -> defer Close(f): Close関数呼び出しに修正されました。
    • f.Readdirnames(-1) -> readdirnames(f): ディレクトリの内容を読み取るReaddirnamesも、syscallパッケージが提供するreaddirnames関数呼び出しに修正されました。

これらの変更は、Plan 9のシステムコールインターフェースをGoのsyscallパッケージがより正確かつGoの慣習に沿って利用するための重要な修正です。

関連リンク

  • Go言語のsyscallパッケージのドキュメント (Goのバージョンによって内容が異なる可能性があります): https://pkg.go.dev/syscall
  • Plan 9 from Bell Labs 公式サイト: https://9p.io/plan9/
  • Plan 9の環境変数に関する情報 (例: man 5 env): Plan 9のシステムドキュメントを参照

参考にした情報源リンク