[インデックス 16248] ファイルの概要
このコミットは、Go言語のsyscall
パッケージにおけるLinux上でのStartProcess
関数におけるファイルディスクリプタのシャッフルに関するバグ修正を扱っています。具体的には、子プロセスを起動する際にファイルディスクリプタが誤って上書きされる可能性があった問題を解決し、そのためのテストケースを追加しています。
コミット
commit b493f0a868982711903c01f759a56c448d908b12
Author: Cosmos Nicolaou <cnicolaou@google.com>
Date: Tue Apr 30 11:52:23 2013 -0700
syscall: fix a bug in the shuffling of file descriptors in StartProcess on Linux.
R=iant, iant, r, bradfitz
CC=golang-dev
https://golang.org/cl/8334044
---
src/pkg/os/exec/exec_test.go | 127 ++++++++++++++++++++++++++++++++++++++++++\n src/pkg/syscall/exec_linux.go | 10 +++-\n 2 files changed, 135 insertions(+), 2 deletions(-)
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/b493f0a868982711903c01f759a56c448d908b12
元コミット内容
syscall: fix a bug in the shuffling of file descriptors in StartProcess on Linux.
このコミットは、Linux上でのStartProcess
におけるファイルディスクリプタのシャッフルに関するバグを修正します。
変更の背景
Go言語のos/exec
パッケージは、外部コマンドを実行するための機能を提供します。その内部では、OS固有のシステムコールを扱うsyscall
パッケージが利用されます。特に、StartProcess
関数は新しいプロセスを起動し、その際に親プロセスのファイルディスクリプタ(FD)を子プロセスに引き継ぎます。この引き継ぎの過程で、子プロセス側のFD番号を整理(シャッフル)する処理が行われます。
このコミット以前のsyscall.StartProcess
の実装には、Linux環境において、ファイルディスクリプタのシャッフル処理にバグが存在していました。具体的には、ProcAttr.Files
(標準入力、標準出力、標準エラー出力、およびExtraFiles
の連結)として渡されたファイルディスクリプタを子プロセス内の連続したFDにマッピングする際に、特定の条件下でFDが誤って上書きされてしまう可能性がありました。特に、標準エラー出力(stderr)のFDが、ExtraFiles
として渡された別のFDによって上書きされてしまうという問題が報告されていました。
このバグは、FDの値やProcAttr.Files
内のFDのインデックスに依存するデータ依存のケースで発生するため、再現が困難でした。特に、FD 3がProcAttr.Files
のインデックス4以降に存在し、かつStderrPipe()
によって取得される標準エラー出力パイプの書き込み側のFDがProcAttr.Files
のサイズと同じである場合に問題が発生しやすかったようです。このような状況は、テストケースの配置や実行環境によってFDの割り当てが変わるため、発見が遅れたと考えられます。
この修正は、このようなファイルディスクリプタの衝突と上書きを防ぎ、プロセスの起動が意図通りに行われるようにするために導入されました。
前提知識の解説
- ファイルディスクリプタ (File Descriptor, FD): Unix系OSにおいて、ファイルやソケット、パイプなどのI/Oリソースを識別するためにカーネルがプロセスに割り当てる非負の整数値です。標準入力は0、標準出力は1、標準エラー出力は2というFDが慣習的に割り当てられています。
os/exec
パッケージ: Go言語で外部コマンドを実行するためのパッケージです。Command
構造体を通じてコマンドのパス、引数、環境変数、標準入出力などを設定し、Start()
やRun()
メソッドで実行します。syscall
パッケージ: OS固有のシステムコールへの低レベルなインターフェースを提供するパッケージです。os/exec
のような高レベルなパッケージの内部で利用され、プロセスの生成、ファイルディスクリプタの操作など、OSカーネルと直接やり取りする機能を提供します。StartProcess
システムコール: 新しいプロセスを生成し、指定された実行可能ファイルをそのプロセス空間で実行するためのシステムコールです。この際、親プロセスのファイルディスクリプタの一部を子プロセスに引き継ぐことができます。ProcAttr.Files
:syscall.ProcAttr
構造体の一部で、子プロセスに引き継ぐファイルディスクリプタのリストを定義します。このリストの最初の3つの要素は通常、標準入力、標準出力、標準エラー出力に対応し、それ以降はExtraFiles
として追加のFDが指定されます。dup2
システムコール: 既存のファイルディスクリプタを、指定された新しいファイルディスクリプタ番号に複製するシステムコールです。これにより、あるFDが指すリソースを別のFD番号で参照できるようになります。プロセスの起動時に、親プロセスのFDを子プロセスの特定のFD番号にマッピングする際によく使用されます。nextfd
: 新しいファイルディスクリプタを割り当てる際に、次に利用可能なFD番号の候補を示す内部変数です。この値が適切に管理されていないと、既存のFDを上書きしてしまう可能性があります。
技術的詳細
このバグは、syscall/exec_linux.go
内のforkAndExecInChild
関数、特にファイルディスクリプタをシャッフルするロジックに起因していました。StartProcess
は、ProcAttr.Files
に指定されたFDを、子プロセス内で0, 1, 2, ... といった連続したFD番号にマッピングし直します。このマッピングはdup2
システムコールを使って行われます。
問題は、dup2
を実行する際に、一時的に使用するFD番号(nextfd
)が、子プロセスに引き継がれるべき既存のFDと衝突する可能性があったことです。特に、fd[i] < i
となるようなFDが存在する場合、つまり、親プロセスのFD番号が子プロセスで割り当てられるべきFD番号よりも小さい場合に、dup2
が既存のFDを上書きしてしまうリスクがありました。
修正前は、nextfd
が単にlen(attr.Files)
(引き継ぐFDの総数)から始まるように設定されていました。しかし、もしattr.Files
に含まれるFDの中にlen(attr.Files)
よりも大きな値が存在し、かつそのFDが子プロセスで重要な役割(例えば標準エラー出力)を担っている場合、dup2
がそのFDを上書きしてしまう可能性がありました。
このコミットでは、nextfd
の計算方法が変更されました。
- まず、
attr.Files
に含まれるすべてのFDを走査し、その中で最も大きなFD番号を見つけます。 nextfd
を、その最大FD番号に1を加えた値に設定します。
これにより、nextfd
は子プロセスに引き継がれるどのFD番号とも衝突しない、十分に大きな値が保証されます。このnextfd
を使って一時的なdup2
操作を行うことで、既存の重要なFDが誤って上書きされることを防ぎます。
また、このバグを再現し、修正を検証するためにTestExtraFilesFDShuffle
という新しいテストケースがsrc/pkg/os/exec/exec_test.go
に追加されました。このテストは、特定のFD番号(特にFD 3)とパイプの組み合わせを意図的に作成し、stderr
がExtraFiles
によって上書きされないことを確認します。テストの複雑さは、バグが特定のFD割り当てに依存していたことを示しています。
コアとなるコードの変更箇所
src/pkg/os/exec/exec_test.go
TestExtraFilesFDShuffle
という新しいテスト関数が追加されました。このテストは、syscall.StartProcess
がファイルディスクリプタを正しくシャッフルし、特にstderr
がExtraFiles
によって上書きされないことを検証します。- テスト内で、
os.Pipe()
を使って特定のFD番号(例: FD 3)を持つパイプを作成し、Cmd.ExtraFiles
に含めることで、バグが再現しやすい条件をシミュレートしています。 - 子プロセス側では、
helperCommand
の"extraFilesAndPipes"
ケースが追加され、親から渡されたパイプからデータを読み取り、標準エラー出力に書き出すことで、FDのシャッフルが正しく行われたかを確認します。 TestExtraFilesRace
関数に、ExtraFiles
を閉じるためのdefer
ステートメントが追加されました。
src/pkg/syscall/exec_linux.go
-
forkAndExecInChild
関数内のファイルディスクリプタのシャッフルロジックが変更されました。 -
以前は
nextfd = int(len(fd))
と単純に設定されていたnextfd
の初期値が、attr.Files
内のすべてのFDをチェックし、その中で最大のFD番号よりも1大きい値に設定されるようになりました。// 修正前: // nextfd = int(len(fd)) // 修正後: // Guard against side effects of shuffling fds below. // Make sure that nextfd is beyond any currently open files so // that we can't run the risk of overwriting any of them. fd := make([]int, len(attr.Files)) nextfd = len(attr.Files) // まずは引き継ぐFDの数で初期化 for i, ufd := range attr.Files { if nextfd < int(ufd) { // 既存のFDの中で最大のものを探す nextfd = int(ufd) } fd[i] = int(ufd) } nextfd++ // 最大のFDよりも1大きい値に設定
コアとなるコードの解説
src/pkg/syscall/exec_linux.go
の変更点
この変更の核心は、forkAndExecInChild
関数におけるnextfd
の計算ロジックです。
// Guard against side effects of shuffling fds below.
// Make sure that nextfd is beyond any currently open files so
// that we can't run the risk of overwriting any of them.
fd := make([]int, len(attr.Files))
nextfd = len(attr.Files) // (1) まず、引き継ぐFDの総数でnextfdを初期化
for i, ufd := range attr.Files {
if nextfd < int(ufd) { // (2) attr.Files内の各FDをチェックし、nextfdよりも大きいFDがあればnextfdを更新
nextfd = int(ufd)
}
fd[i] = int(ufd) // (3) fdスライスにFDをコピー
}
nextfd++ // (4) 最終的に、nextfdをattr.Files内の最大のFD番号よりも1大きい値に設定
nextfd = len(attr.Files)
:nextfd
は、dup2
システムコールで一時的に使用されるファイルディスクリプタ番号の候補です。まず、子プロセスに引き継がれるファイルディスクリプタの総数(len(attr.Files)
)で初期化されます。これは、通常、子プロセスで0, 1, 2...と割り当てられるFDの範囲外の番号を確保しようとする意図があります。for i, ufd := range attr.Files { if nextfd < int(ufd) { nextfd = int(ufd) } ... }
: ここが重要な変更点です。attr.Files
には、親プロセスで開かれている実際のファイルディスクリプタ番号が含まれています。このループでは、attr.Files
内のすべてのFD番号を走査し、もし現在のnextfd
の値よりも大きなFD番号が見つかった場合、nextfd
をその大きなFD番号に更新します。これにより、nextfd
はattr.Files
に含まれるすべてのFD番号よりも少なくとも同じか大きい値になります。fd[i] = int(ufd)
: これは、attr.Files
の値をfd
という内部スライスにコピーしているだけです。nextfd++
: ループが終了した後、nextfd
はattr.Files
内の最大のFD番号と同じかそれ以上の値になっています。最後にnextfd
をインクリメントすることで、nextfd
はattr.Files
内のどのFD番号とも衝突しない、確実に利用可能なFD番号となります。このnextfd
が、dup2
システムコールで一時的なFDとして使用されることで、既存の重要なFDが上書きされるリスクがなくなります。
この修正により、StartProcess
が子プロセスを起動する際に、ファイルディスクリプタのシャッフル処理がより堅牢になり、予期せぬFDの上書きバグが解消されました。
src/pkg/os/exec/exec_test.go
の TestExtraFilesFDShuffle
このテストは、バグの再現と修正の検証のために非常に巧妙に設計されています。
- 特定のFD割り当ての強制:
rd, wr, _ := os.Pipe()
: パイプを作成し、rd.Fd()
が3
であることを確認しています。これは、バグがFD 3と関連していたため、テスト環境でこの特定のFD番号を確保しようとしています。c.ExtraFiles = append(c.ExtraFiles, rd)
: このFD 3をExtraFiles
の最後に配置しています。stderrFd := int(stderr.(*os.File).Fd())
: 標準エラー出力のFDを取得し、それが((len(c.ExtraFiles) + 3) - 1)
と一致するかをチェックしています。これは、バグが特定のFD割り当ての組み合わせで発生したため、その条件を再現しようとしています。
- データフローの検証:
- 親プロセスで複数のパイプを作成し、それぞれに異なるデータ("a", "b")を書き込み、
ExtraFiles
として子プロセスに渡します。 - 子プロセス(
"extraFilesAndPipes"
ヘルパー)は、渡されたFDからデータを読み取り、それらを連結して標準エラー出力に書き出します。 - 親プロセスは子プロセスの標準エラー出力を読み取り、期待される文字列("child: ab_LAST")と一致するかを検証します。
- 親プロセスで複数のパイプを作成し、それぞれに異なるデータ("a", "b")を書き込み、
- タイムアウト:
time.After(5 * time.Second)
を使って、子プロセスからの読み取りがタイムアウトしないことを確認し、デッドロックやハングアップがないことを保証しています。
このテストは、単に機能が動作するかどうかだけでなく、特定の条件下でのファイルディスクリプタの正確なマッピングと、それによるデータ破損がないことを厳密に検証しています。
関連リンク
- Go言語の
os/exec
パッケージのドキュメント: https://pkg.go.dev/os/exec - Go言語の
syscall
パッケージのドキュメント: https://pkg.go.dev/syscall dup2
システムコールに関する情報 (Linux man pageなど):man 2 dup2
参考にした情報源リンク
- Go言語のソースコード (GitHub): https://github.com/golang/go
- Goのコードレビューシステム (Gerrit): https://go-review.googlesource.com/ (コミットメッセージに記載されている
https://golang.org/cl/8334044
は、Gerritの変更リストへのリンクです。) - ファイルディスクリプタ、
dup2
システムコールに関する一般的なUnix/Linuxプログラミングの資料。 - Go言語のプロセスの起動に関する内部実装の解説記事(もしあれば)。
- GoのIssueトラッカー(もしこのバグに関するIssueが公開されていれば)。
[インデックス 16248] ファイルの概要
このコミットは、Go言語のsyscall
パッケージにおけるLinux上でのStartProcess
関数におけるファイルディスクリプタのシャッフルに関するバグ修正を扱っています。具体的には、子プロセスを起動する際にファイルディスクリプタが誤って上書きされる可能性があった問題を解決し、そのためのテストケースを追加しています。
コミット
commit b493f0a868982711903c01f759a56c448d908b12
Author: Cosmos Nicolaou <cnicolaou@google.com>
Date: Tue Apr 30 11:52:23 2013 -0700
syscall: fix a bug in the shuffling of file descriptors in StartProcess on Linux.
R=iant, iant, r, bradfitz
CC=golang-dev
https://golang.org/cl/8334044
---
src/pkg/os/exec/exec_test.go | 127 ++++++++++++++++++++++++++++++++++++++++++\n src/pkg/syscall/exec_linux.go | 10 +++-\n 2 files changed, 135 insertions(+), 2 deletions(-)
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/b493f0a868982711903c01f759a56c448d908b12
元コミット内容
syscall: fix a bug in the shuffling of file descriptors in StartProcess on Linux.
このコミットは、Linux上でのStartProcess
におけるファイルディスクリプタのシャッフルに関するバグを修正します。
変更の背景
Go言語のos/exec
パッケージは、外部コマンドを実行するための機能を提供します。その内部では、OS固有のシステムコールを扱うsyscall
パッケージが利用されます。特に、StartProcess
関数は新しいプロセスを起動し、その際に親プロセスのファイルディスクリプタ(FD)を子プロセスに引き継ぎます。この引き継ぎの過程で、子プロセス側のFD番号を整理(シャッフル)する処理が行われます。
このコミット以前のsyscall.StartProcess
の実装には、Linux環境において、ファイルディスクリプタのシャッフル処理にバグが存在していました。具体的には、ProcAttr.Files
(標準入力、標準出力、標準エラー出力、およびExtraFiles
の連結)として渡されたファイルディスクリプタを子プロセス内の連続したFDにマッピングする際に、特定の条件下でFDが誤って上書きされてしまう可能性がありました。特に、標準エラー出力(stderr)のFDが、ExtraFiles
として渡された別のFDによって上書きされてしまうという問題が報告されていました。
このバグは、FDの値やProcAttr.Files
内のFDのインデックスに依存するデータ依存のケースで発生するため、再現が困難でした。特に、FD 3がProcAttr.Files
のインデックス4以降に存在し、かつStderrPipe()
によって取得される標準エラー出力パイプの書き込み側のFDがProcAttr.Files
のサイズと同じである場合に問題が発生しやすかったようです。このような状況は、テストケースの配置や実行環境によってFDの割り当てが変わるため、発見が遅れたと考えられます。
この修正は、このようなファイルディスクリプタの衝突と上書きを防ぎ、プロセスの起動が意図通りに行われるようにするために導入されました。
前提知識の解説
- ファイルディスクリプタ (File Descriptor, FD): Unix系OSにおいて、ファイルやソケット、パイプなどのI/Oリソースを識別するためにカーネルがプロセスに割り当てる非負の整数値です。標準入力は0、標準出力は1、標準エラー出力は2というFDが慣習的に割り当てられています。
os/exec
パッケージ: Go言語で外部コマンドを実行するためのパッケージです。Command
構造体を通じてコマンドのパス、引数、環境変数、標準入出力などを設定し、Start()
やRun()
メソッドで実行します。syscall
パッケージ: OS固有のシステムコールへの低レベルなインターフェースを提供するパッケージです。os/exec
のような高レベルなパッケージの内部で利用され、プロセスの生成、ファイルディスクリプタの操作など、OSカーネルと直接やり取りする機能を提供します。StartProcess
システムコール: 新しいプロセスを生成し、指定された実行可能ファイルをそのプロセス空間で実行するためのシステムコールです。この際、親プロセスのファイルディスクリプタの一部を子プロセスに引き継ぐことができます。ProcAttr.Files
:syscall.ProcAttr
構造体の一部で、子プロセスに引き継ぐファイルディスクリプタのリストを定義します。このリストの最初の3つの要素は通常、標準入力、標準出力、標準エラー出力に対応し、それ以降はExtraFiles
として追加のFDが指定されます。dup2
システムコール: 既存のファイルディスクリプタを、指定された新しいファイルディスクリプタ番号に複製するシステムコールです。これにより、あるFDが指すリソースを別のFD番号で参照できるようになります。プロセスの起動時に、親プロセスのFDを子プロセスの特定のFD番号にマッピングする際によく使用されます。nextfd
: 新しいファイルディスクリプタを割り当てる際に、次に利用可能なFD番号の候補を示す内部変数です。この値が適切に管理されていないと、既存のFDを上書きしてしまう可能性があります。
技術的詳細
このバグは、syscall/exec_linux.go
内のforkAndExecInChild
関数、特にファイルディスクリプタをシャッフルするロジックに起因していました。StartProcess
は、ProcAttr.Files
に指定されたFDを、子プロセス内で0, 1, 2, ... といった連続したFD番号にマッピングし直します。このマッピングはdup2
システムコールを使って行われます。
問題は、dup2
を実行する際に、一時的に使用するFD番号(nextfd
)が、子プロセスに引き継がれるべき既存のFDと衝突する可能性があったことです。特に、fd[i] < i
となるようなFDが存在する場合、つまり、親プロセスのFD番号が子プロセスで割り当てられるべきFD番号よりも小さい場合に、dup2
が既存のFDを上書きしてしまうリスクがありました。
修正前は、nextfd
が単にlen(attr.Files)
(引き継ぐFDの総数)から始まるように設定されていました。しかし、もしattr.Files
に含まれるFDの中にlen(attr.Files)
よりも大きな値が存在し、かつそのFDが子プロセスで重要な役割(例えば標準エラー出力)を担っている場合、dup2
がそのFDを上書きしてしまう可能性がありました。
このコミットでは、nextfd
の計算方法が変更されました。
- まず、
attr.Files
に含まれるすべてのFDを走査し、その中で最も大きなFD番号を見つけます。 nextfd
を、その最大FD番号に1を加えた値に設定します。
これにより、nextfd
は子プロセスに引き継がれるどのFD番号とも衝突しない、十分に大きな値が保証されます。このnextfd
を使って一時的なdup2
操作を行うことで、既存の重要なFDが誤って上書きされることを防ぎます。
また、このバグを再現し、修正を検証するためにTestExtraFilesFDShuffle
という新しいテストケースがsrc/pkg/os/exec/exec_test.go
に追加されました。このテストは、特定のFD番号(特にFD 3)とパイプの組み合わせを意図的に作成し、stderr
がExtraFiles
によって上書きされないことを確認します。テストの複雑さは、バグが特定のFD割り当てに依存していたことを示しています。
コアとなるコードの変更箇所
src/pkg/os/exec/exec_test.go
TestExtraFilesFDShuffle
という新しいテスト関数が追加されました。このテストは、syscall.StartProcess
がファイルディスクリプタを正しくシャッフルし、特にstderr
がExtraFiles
によって上書きされないことを検証します。- テスト内で、
os.Pipe()
を使って特定のFD番号(例: FD 3)を持つパイプを作成し、Cmd.ExtraFiles
に含めることで、バグが再現しやすい条件をシミュレートしています。 - 子プロセス側では、
helperCommand
の"extraFilesAndPipes"
ケースが追加され、親から渡されたパイプからデータを読み取り、標準エラー出力に書き出すことで、FDのシャッフルが正しく行われたかを確認します。 TestExtraFilesRace
関数に、ExtraFiles
を閉じるためのdefer
ステートメントが追加されました。
src/pkg/syscall/exec_linux.go
-
forkAndExecInChild
関数内のファイルディスクリプタのシャッフルロジックが変更されました。 -
以前は
nextfd = int(len(fd))
と単純に設定されていたnextfd
の初期値が、attr.Files
内のすべてのFDをチェックし、その中で最大のFD番号よりも1大きい値に設定されるようになりました。// 修正前: // nextfd = int(len(fd)) // 修正後: // Guard against side effects of shuffling fds below. // Make sure that nextfd is beyond any currently open files so // that we can't run the risk of overwriting any of them. fd := make([]int, len(attr.Files)) nextfd = len(attr.Files) // まずは引き継ぐFDの数で初期化 for i, ufd := range attr.Files { if nextfd < int(ufd) { // 既存のFDの中で最大のものを探す nextfd = int(ufd) } fd[i] = int(ufd) } nextfd++ // 最大のFDよりも1大きい値に設定
コアとなるコードの解説
src/pkg/syscall/exec_linux.go
の変更点
この変更の核心は、forkAndExecInChild
関数におけるnextfd
の計算ロジックです。
// Guard against side effects of shuffling fds below.
// Make sure that nextfd is beyond any currently open files so
// that we can't run the risk of overwriting any of them.
fd := make([]int, len(attr.Files))
nextfd = len(attr.Files) // (1) まず、引き継ぐFDの総数でnextfdを初期化
for i, ufd := range attr.Files {
if nextfd < int(ufd) { // (2) attr.Files内の各FDをチェックし、nextfdよりも大きいFDがあればnextfdを更新
nextfd = int(ufd)
}
fd[i] = int(ufd) // (3) fdスライスにFDをコピー
}
nextfd++ // (4) 最終的に、nextfdをattr.Files内の最大のFD番号よりも1大きい値に設定
nextfd = len(attr.Files)
:nextfd
は、dup2
システムコールで一時的に使用されるファイルディスクリプタ番号の候補です。まず、子プロセスに引き継がれるファイルディスクリプタの総数(len(attr.Files)
)で初期化されます。これは、通常、子プロセスで0, 1, 2...と割り当てられるFDの範囲外の番号を確保しようとする意図があります。for i, ufd := range attr.Files { if nextfd < int(ufd) { nextfd = int(ufd) } ... }
: ここが重要な変更点です。attr.Files
には、親プロセスで開かれている実際のファイルディスクリプタ番号が含まれています。このループでは、attr.Files
内のすべてのFD番号を走査し、もし現在のnextfd
の値よりも大きなFD番号が見つかった場合、nextfd
をその大きなFD番号に更新します。これにより、nextfd
はattr.Files
に含まれるすべてのFD番号よりも少なくとも同じか大きい値になります。fd[i] = int(ufd)
: これは、attr.Files
の値をfd
という内部スライスにコピーしているだけです。nextfd++
: ループが終了した後、nextfd
はattr.Files
内の最大のFD番号と同じかそれ以上の値になっています。最後にnextfd
をインクリメントすることで、nextfd
はattr.Files
内のどのFD番号とも衝突しない、確実に利用可能なFD番号となります。このnextfd
が、dup2
システムコールで一時的なFDとして使用されることで、既存の重要なFDが上書きされるリスクがなくなります。
この修正により、StartProcess
が子プロセスを起動する際に、ファイルディスクリプタのシャッフル処理がより堅牢になり、予期せぬFDの上書きバグが解消されました。
src/pkg/os/exec/exec_test.go
の TestExtraFilesFDShuffle
このテストは、バグの再現と修正の検証のために非常に巧妙に設計されています。
- 特定のFD割り当ての強制:
rd, wr, _ := os.Pipe()
: パイプを作成し、rd.Fd()
が3
であることを確認しています。これは、バグがFD 3と関連していたため、テスト環境でこの特定のFD番号を確保しようとしています。c.ExtraFiles = append(c.ExtraFiles, rd)
: このFD 3をExtraFiles
の最後に配置しています。stderrFd := int(stderr.(*os.File).Fd())
: 標準エラー出力のFDを取得し、それが((len(c.ExtraFiles) + 3) - 1)
と一致するかをチェックしています。これは、バグが特定のFD割り当ての組み合わせで発生したため、その条件を再現しようとしています。
- データフローの検証:
- 親プロセスで複数のパイプを作成し、それぞれに異なるデータ("a", "b")を書き込み、
ExtraFiles
として子プロセスに渡します。 - 子プロセス(
"extraFilesAndPipes"
ヘルパー)は、渡されたFDからデータを読み取り、それらを連結して標準エラー出力に書き出します。 - 親プロセスは子プロセスの標準エラー出力を読み取り、期待される文字列("child: ab_LAST")と一致するかを検証します。
- 親プロセスで複数のパイプを作成し、それぞれに異なるデータ("a", "b")を書き込み、
- タイムアウト:
time.After(5 * time.Second)
を使って、子プロセスからの読み取りがタイムアウトしないことを確認し、デッドロックやハングアップがないことを保証しています。
このテストは、単に機能が動作するかどうかだけでなく、特定の条件下でのファイルディスクリプタの正確なマッピングと、それによるデータ破損がないことを厳密に検証しています。
関連リンク
- Go言語の
os/exec
パッケージのドキュメント: https://pkg.go.dev/os/exec - Go言語の
syscall
パッケージのドキュメント: https://pkg.go.dev/syscall dup2
システムコールに関する情報 (Linux man pageなど):man 2 dup2
参考にした情報源リンク
- Go言語のソースコード (GitHub): https://github.com/golang/go
- Goのコードレビューシステム (Gerrit): https://go-review.googlesource.com/ (コミットメッセージに記載されている
https://golang.org/cl/8334044
は、Gerritの変更リストへのリンクです。) - ファイルディスクリプタ、
dup2
システムコールに関する一般的なUnix/Linuxプログラミングの資料。 - Go言語のプロセスの起動に関する内部実装の解説記事(もしあれば)。
- GoのIssueトラッカー(もしこのバグに関するIssueが公開されていれば)。