[インデックス 18582] ファイルの概要
このコミットは、Go言語のsyscall
パッケージ内のexec_plan9.go
ファイルに対する変更です。このファイルは、Plan 9オペレーティングシステム上でのプロセス実行(exec
)に関連するシステムコールを扱うためのGo言語のインターフェースを提供します。具体的には、子プロセスの起動やエラーハンドリングのロジックが含まれています。
コミット
このコミットは、Plan 9環境におけるexec
パッケージからのエラーメッセージが正しく終端されず、余分な文字(ガベージ)が付加されて表示される問題を修正します。具体的には、エラーバッファからGoの文字列を生成する際に、実際に読み取られたバイト数n
を使用して文字列を正確に切り詰めることで、この問題を解決しています。
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/3e37720bcebefe87459a7dd8f41164c0e2cfa4bc
元コミット内容
syscall: terminate error string in exec package on Plan 9
Try to prevent messages like this:
'./pack' file does not exist
TBR=adonovan
LGTM=adonovan
R=adonovan
CC=golang-codereviews
https://golang.org/cl/66270043
変更の背景
この変更の背景には、Plan 9オペレーティングシステム上でGoプログラムが外部コマンドを実行(exec
)した際に、エラーメッセージが期待通りに表示されないという問題がありました。具体的には、エラーメッセージの末尾に意味不明な文字(例: ``)が多数付加されてしまう現象が発生していました。これは、エラーメッセージを格納するバッファが、実際のメッセージよりも大きく確保されており、かつGoの文字列に変換する際にバッファ全体が使われてしまうために、バッファの未使用部分に含まれるゴミデータがそのまま表示されてしまうことが原因でした。
開発者は、ユーザーが正確なエラー情報を得られるように、この視覚的なノイズを取り除く必要がありました。
前提知識の解説
Plan 9
Plan 9 from Bell Labsは、ベル研究所で開発された分散オペレーティングシステムです。Unixの概念をさらに推し進め、すべてのリソース(ファイル、デバイス、ネットワーク接続など)をファイルシステムとして表現するという哲学を持っています。Go言語は、その設計思想の一部をPlan 9から継承しており、初期のGo開発者にはPlan 9の設計者が多く含まれていました。そのため、Goの標準ライブラリにはPlan 9固有のシステムコールを扱うためのコードが含まれています。
Go言語のsyscall
パッケージ
syscall
パッケージは、Goプログラムから基盤となるオペレーティングシステムのシステムコールに直接アクセスするための低レベルなインターフェースを提供します。これにより、ファイル操作、プロセス管理、ネットワーク通信など、OS固有の機能を利用できます。OSごとに実装が異なるため、syscall
パッケージ内にはsyscall_linux.go
、syscall_windows.go
、そしてこのコミットで関連するsyscall_plan9.go
のように、OS固有のファイルが存在します。
exec
システムコール
exec
は、現在のプロセスを新しいプログラムで置き換えるシステムコールです。これにより、新しいプロセスを生成することなく、実行中のプログラムを別のプログラムに切り替えることができます。Go言語のos/exec
パッケージは、このexec
システムコールをより高レベルで抽象化して提供していますが、その内部ではsyscall
パッケージの機能を利用しています。
C言語スタイルの文字列とGo言語の文字列
この問題の根源は、C言語スタイルの文字列とGo言語の文字列の扱いの違いにあります。
- C言語スタイルの文字列: C言語では、文字列は文字の配列として扱われ、その終端はヌル文字(
\0
)によって示されます。文字列の長さを知るには、ヌル文字が見つかるまで配列を走査する必要があります。 - Go言語の文字列: Go言語では、文字列は不変のバイトスライスであり、その長さは文字列自体に付随するメタデータとして管理されます。ヌル終端は必要ありません。
syscall
パッケージがOSからエラーメッセージを受け取る際、多くの場合、それはC言語スタイルのヌル終端された文字列としてバッファに書き込まれます。しかし、Goの文字列に変換する際に、バッファの実際の有効なデータ長を考慮せずにバッファ全体を文字列として解釈してしまうと、ヌル文字以降の未使用領域に含まれるゴミデータも文字列の一部として扱われてしまい、結果としてガベージ文字が表示されることになります。
技術的詳細
このコミットが修正している問題は、forkExec
関数内で子プロセスがエラーを返した場合の処理にあります。子プロセスがエラーを発生させると、そのエラーメッセージはerrbuf
というバイトスライスに書き込まれます。このerrbuf
は、エラーメッセージを格納するために事前に確保された固定サイズのバッファです。
元のコードでは、エラーメッセージがerrbuf
に書き込まれた後、その内容をGoの文字列に変換する際に、string(errbuf[:])
という形式を使用していました。これはerrbuf
スライス全体の容量を文字列として解釈することを意味します。しかし、実際にエラーメッセージが書き込まれたのはerrbuf
の先頭からn
バイト目までであり、n
は実際に書き込まれたバイト数を示します。
したがって、errbuf
のn
バイト目以降には、以前のデータや初期化されていないメモリの内容が残っている可能性があり、これらがGoの文字列に変換されると、ユーザーには意味不明な文字として表示されていました。
このコミットでは、この問題を解決するために、string(errbuf[:])
をstring(errbuf[:n])
に変更しました。この変更により、errbuf
スライスの先頭からn
バイト目までのみがGoの文字列に変換されるようになります。これにより、実際にエラーメッセージとして有効な部分だけが文字列として扱われ、余分なガベージ文字が取り除かれ、クリーンなエラーメッセージが表示されるようになります。
コアとなるコードの変更箇所
--- a/src/pkg/syscall/exec_plan9.go
+++ b/src/pkg/syscall/exec_plan9.go
@@ -486,7 +486,7 @@ func forkExec(argv0 string, argv []string, attr *ProcAttr) (pid int, err error)\
if err != nil || n != 0 {
if n != 0 {
- err = NewError(string(errbuf[:]))
+ err = NewError(string(errbuf[:n]))
}
// Child failed; wait for it to exit, to make sure
コアとなるコードの解説
変更された行は以下の通りです。
- err = NewError(string(errbuf[:]))
+ err = NewError(string(errbuf[:n]))
errbuf
: これは、子プロセスから返されたエラーメッセージを格納するためのバイトスライス(バッファ)です。n
: これは、errbuf
に実際に書き込まれたバイト数、つまりエラーメッセージの実際の長さを表す変数です。errbuf[:]
: これは、errbuf
スライスの全体を指します。元のコードでは、このスライス全体がGoの文字列に変換されていました。errbuf[:n]
: これは、errbuf
スライスの先頭からn
バイト目まで(n
を含まない)を指す新しいスライスを作成します。この新しいスライスは、実際にエラーメッセージが格納されている有効な部分のみを含みます。string(...)
: バイトスライスをGoの文字列に変換する組み込み関数です。NewError(...)
:syscall
パッケージ内で定義されている、エラー文字列から新しいError
型を生成する関数です。
この変更により、NewError
関数に渡される文字列は、errbuf
の有効なデータ部分のみから生成されるようになり、結果としてエラーメッセージの末尾に付加されていた不要な文字が取り除かれました。これは、バッファの実際のデータ長を正確に指定することで、Goの文字列変換が意図しないメモリ領域を読み込むことを防ぐ、シンプルかつ効果的な修正です。
関連リンク
- https://golang.org/cl/66270043 (Go Code Review - Change 66270043)
参考にした情報源リンク
- Go言語の公式ドキュメント (
syscall
パッケージ、文字列の扱いなど) - Plan 9に関する一般的な情報源 (Wikipediaなど)
- C言語における文字列の扱いに関する一般的な情報源
- GitHubのコミットページ (
https://github.com/golang/go/commit/3e37720bcebefe87459a7dd8f41164c0e2cfa4bc
) - Go Code Review (
https://golang.org/cl/66270043
)