[インデックス 18720] ファイルの概要
コミット
commit be8aa4b073450b799d9a711b19862b6d915fe9d1
Author: Mikio Hara <mikioh.mikioh@gmail.com>
Date: Tue Mar 4 09:27:29 2014 +0900
os: handle file creation with close-on-exec flag correctly on darwin, freebsd
Fixes #7187.
Update #7193
LGTM=bradfitz
R=golang-codereviews, dave, rsc, minux.ma, bradfitz
CC=golang-codereviews
https://golang.org/cl/64510043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/be8aa4b073450b799d9a711b19862b6d915fe9d1
元コミット内容
このコミットは、Go言語のos
パッケージにおいて、ファイル作成時にclose-on-exec
フラグ(O_CLOEXEC
)をDarwin(macOS)およびFreeBSDシステムで正しく処理するように修正するものです。具体的には、O_CLOEXEC
フラグがサポートされているかどうかをプラットフォームごとに動的または静的に判断し、その結果に基づいてsyscall.CloseOnExec
の呼び出しを条件付けすることで、ファイルディスクリプタが子プロセスに意図せず継承される問題を解決します。この変更は、Issue #7187を修正し、Issue #7193に関連する更新を含んでいます。
変更の背景
このコミットの背景には、Goプログラムが子プロセスを起動する際に、親プロセスで開かれたファイルディスクリプタが意図せず子プロセスに継承されてしまうという問題がありました。これは特に、セキュリティ上のリスクやリソースリークの原因となる可能性があります。
Unix系システムでは、fork()
とexec()
システムコールを組み合わせて新しいプロセスを起動します。fork()
は親プロセスのメモリ空間とファイルディスクリプタを複製し、exec()
は新しいプログラムをロードして実行します。この際、親プロセスで開かれたファイルディスクリプタは、デフォルトでは子プロセスにも継承されます。これを防ぐために、ファイルディスクリプタにFD_CLOEXEC
フラグを設定することが可能です。このフラグが設定されているファイルディスクリプタは、exec()
システムコールが実行される際に自動的に閉じられます。
現代のUnix系OSでは、open()
システムコールにO_CLOEXEC
フラグを渡すことで、ファイルを開く際にアトミックにFD_CLOEXEC
フラグを設定できます。これにより、ファイルを開いてからfcntl(fd, F_SETFD, FD_CLOEXEC)
を呼び出すまでの間に、別のスレッドがfork()
とexec()
を実行してしまうという競合状態(race condition)を回避できます。
しかし、Goがサポートする一部のOS(特に古いバージョンのDarwinやFreeBSD)では、O_CLOEXEC
フラグがopen()
システムコールでサポートされていませんでした。Goの既存の実装では、O_CLOEXEC
がサポートされていない場合やDarwinの場合に無条件でsyscall.CloseOnExec
を呼び出していましたが、これはO_CLOEXEC
がサポートされている新しいOSバージョンでは冗長であり、また、O_CLOEXEC
がサポートされているにもかかわらずsyscall.CloseOnExec
を呼び出すことで、アトミック性が損なわれる可能性がありました。
このコミットは、この問題を解決するために、各プラットフォームがO_CLOEXEC
をサポートしているかどうかを正確に検出し、それに応じてsyscall.CloseOnExec
の呼び出しを最適化することを目的としています。
- Issue #7187: "os: O_CLOEXEC not respected on Darwin 10.6" - Darwin 10.6 (OS X Snow Leopard)で
O_CLOEXEC
フラグが正しく機能しないという報告。 - Issue #7193: "os: OpenFile should use O_CLOEXEC" -
os.OpenFile
がO_CLOEXEC
を使用すべきであるという提案。
前提知識の解説
ファイルディスクリプタ (File Descriptor, FD)
Unix系オペレーティングシステムにおいて、ファイルやソケット、パイプなどのI/Oリソースを識別するために使用される非負の整数です。プロセスはファイルディスクリプタを通じてこれらのリソースにアクセスします。
fork()
システムコール
現在のプロセス(親プロセス)の複製を作成し、新しいプロセス(子プロセス)を生成します。子プロセスは親プロセスのメモリ空間、開いているファイルディスクリプタ、その他のリソースのコピーを受け取ります。
exec()
システムコール
現在のプロセスイメージを、指定された新しいプログラムイメージで置き換えます。exec()
が成功すると、新しいプログラムが現在のプロセスIDで実行を開始します。この際、FD_CLOEXEC
フラグが設定されていないファイルディスクリプタは、新しいプログラムに継承されます。
FD_CLOEXEC
フラグ
ファイルディスクリプタに設定できるフラグの一つで、「close-on-exec」を意味します。このフラグが設定されたファイルディスクリプタは、exec()
システムコールが実行される際に自動的に閉じられます。これにより、子プロセスに不要なファイルディスクリプタが継承されるのを防ぎ、リソースリークやセキュリティ上の問題を回避できます。
O_CLOEXEC
フラグ
open()
システムコールに渡すことができるフラグの一つで、ファイルを開く際にFD_CLOEXEC
フラグをアトミックに設定します。これにより、ファイルを開いてからFD_CLOEXEC
を設定するまでの間にfork()
とexec()
が実行されることによる競合状態を防ぐことができます。
syscall.CloseOnExec(fd int)
Go言語のsyscall
パッケージにある関数で、指定されたファイルディスクリプタfd
にFD_CLOEXEC
フラグを設定します。これはfcntl(fd, F_SETFD, FD_CLOEXEC)
システムコールをラップしたものです。
syscall.Sysctl(name string)
/ syscall.SysctlUint32(name string)
Unix系システムでカーネルの情報を取得するためのシステムコールです。
kern.osrelease
: オペレーティングシステムのリリースバージョンを示す文字列(例: "13.0.0")。kern.osreldate
: オペレーティングシステムのリリース日を示す整数(例: FreeBSDの803000
は8.3リリースを意味する)。
これらの情報を用いて、実行中のOSが特定の機能(この場合はO_CLOEXEC
)をサポートしているかどうかを判断します。
技術的詳細
このコミットの主要な技術的変更点は、Goのos
パッケージがO_CLOEXEC
フラグのサポートをプラットフォームごとに動的に、または静的に判断するメカニズムを導入したことです。これにより、syscall.CloseOnExec
の呼び出しが不要な場合にスキップされ、アトミックなファイルオープン処理が可能なシステムではその利点を最大限に活用できるようになります。
具体的には、以下の変更が行われました。
-
supportsCloseOnExec
変数の導入:- 各プラットフォーム固有の
os
パッケージ(sys_darwin.go
,sys_freebsd.go
,sys_nacl.go
,sys_unix.go
)にsupportsCloseOnExec
というグローバル変数が導入されました。この変数は、そのプラットフォームがO_CLOEXEC
フラグをサポートしているかどうかを示すブール値です。
- 各プラットフォーム固有の
-
プラットフォームごとの
supportsCloseOnExec
の初期化:- Darwin (
sys_darwin.go
):init()
関数内でsyscall.Sysctl("kern.osrelease")
を呼び出し、OSのリリースバージョン文字列を取得します。- 取得したバージョン文字列(例: "11.0.0" for OS X 10.7)を解析し、OS X 10.7 (Darwin 11.0.0)以降であれば
supportsCloseOnExec
をtrue
に設定します。これは、O_CLOEXEC
がOS X 10.7で導入されたためです。
- FreeBSD (
sys_freebsd.go
):init()
関数内でsyscall.SysctlUint32("kern.osreldate")
を呼び出し、OSのリリース日を示す整数値を取得します。- 取得した値が
803000
(FreeBSD 8.3)以上であればsupportsCloseOnExec
をtrue
に設定します。これは、O_CLOEXEC
がFreeBSD 8.3で導入されたためです。
- NaCl (
sys_nacl.go
):supportsCloseOnExec
をconst false
として定義します。NaCl (Native Client)はO_CLOEXEC
をサポートしないためです。
- その他のUnix系システム (
sys_unix.go
):dragonfly
,linux
,netbsd
,openbsd
,solaris
などのシステムでは、supportsCloseOnExec
をconst true
として定義します。これらのシステムでは一般的にO_CLOEXEC
がサポートされているためです。
- Darwin (
-
os/file_unix.go
の変更:OpenFile
関数内で、以前はsyscall.O_CLOEXEC == 0 || runtime.GOOS == "darwin"
という条件でsyscall.CloseOnExec(r)
を呼び出していましたが、これを!supportsCloseOnExec
という条件に変更しました。- これにより、
O_CLOEXEC
がサポートされているプラットフォームではsyscall.CloseOnExec
の呼び出しがスキップされ、ファイルオープン時のアトミック性が維持されます。サポートされていないプラットフォームでは、引き続きsyscall.CloseOnExec
が呼び出され、FD_CLOEXEC
フラグが設定されます。
このアプローチにより、Goは各プラットフォームのO_CLOEXEC
サポート状況に柔軟に対応できるようになり、ファイルディスクリプタの継承に関する競合状態のリスクを低減しつつ、不要なシステムコール呼び出しを避けることができます。
コアとなるコードの変更箇所
このコミットにおけるコアとなるコードの変更箇所は以下のファイルです。
-
src/pkg/os/file_unix.go
:OpenFile
関数内のsyscall.CloseOnExec
の呼び出し条件が変更されました。
--- a/src/pkg/os/file_unix.go +++ b/src/pkg/os/file_unix.go @@ -81,12 +81,7 @@ func OpenFile(name string, flag int, perm FileMode) (file *File, err error) { // There's a race here with fork/exec, which we are // content to live with. See ../syscall/exec_unix.go. - // On OS X 10.6, the O_CLOEXEC flag is not respected. - // On OS X 10.7, the O_CLOEXEC flag works. - // Without a cheap & reliable way to detect 10.6 vs 10.7 at - // runtime, we just always call syscall.CloseOnExec on Darwin. - // Once >=10.7 is prevalent, this extra call can removed. - if syscall.O_CLOEXEC == 0 || runtime.GOOS == "darwin" { // O_CLOEXEC not supported + if !supportsCloseOnExec { syscall.CloseOnExec(r) }
-
src/pkg/os/sys_darwin.go
(新規ファイル):- Darwinプラットフォーム向けの
supportsCloseOnExec
変数の定義と初期化ロジック。
package os import "syscall" var supportsCloseOnExec bool func init() { osver, err := syscall.Sysctl("kern.osrelease") if err != nil { return } var i int for i = range osver { if osver[i] != '.' { continue } } // The O_CLOEXEC flag was introduced in OS X 10.7 (Darwin // 11.0.0). See http://support.apple.com/kb/HT1633. if i > 2 || i == 2 && osver[0] >= '1' && osver[1] >= '1' { supportsCloseOnExec = true } }
- Darwinプラットフォーム向けの
-
src/pkg/os/sys_freebsd.go
(新規ファイル):- FreeBSDプラットフォーム向けの
supportsCloseOnExec
変数の定義と初期化ロジック。
package os import "syscall" var supportsCloseOnExec bool func init() { osrel, err := syscall.SysctlUint32("kern.osreldate") if err != nil { return } // The O_CLOEXEC flag was introduced in FreeBSD 8.3. // See http://www.freebsd.org/doc/en/books/porters-handbook/freebsd-versions.html. if osrel >= 803000 { supportsCloseOnExec = true } }
- FreeBSDプラットフォーム向けの
-
src/pkg/os/sys_nacl.go
(新規ファイル):- NaClプラットフォーム向けの
supportsCloseOnExec
変数の定義。
package os const supportsCloseOnExec = false
- NaClプラットフォーム向けの
-
src/pkg/os/sys_unix.go
(新規ファイル):- その他のUnix系プラットフォーム向けの
supportsCloseOnExec
変数の定義。
package os // +build dragonfly linux netbsd openbsd solaris const supportsCloseOnExec = true
- その他のUnix系プラットフォーム向けの
コアとなるコードの解説
このコミットの核心は、os.OpenFile
関数におけるFD_CLOEXEC
フラグの設定方法を、各OSのO_CLOEXEC
サポート状況に応じて最適化することです。
以前のos/file_unix.go
のOpenFile
関数では、syscall.O_CLOEXEC == 0
(O_CLOEXEC
が定義されていない、つまりサポートされていない場合)またはruntime.GOOS == "darwin"
(Darwinの場合)という条件で無条件にsyscall.CloseOnExec(r)
を呼び出していました。これは、古いDarwinバージョンでO_CLOEXEC
が機能しないという既知の問題に対応するためでしたが、新しいDarwinバージョンや他のO_CLOEXEC
をサポートするUnix系システムでは冗長であり、アトミックなファイルオープンを妨げる可能性がありました。
このコミットでは、この条件を!supportsCloseOnExec
というより汎用的なものに置き換えました。supportsCloseOnExec
は、各プラットフォーム固有のファイル(sys_darwin.go
, sys_freebsd.go
, sys_nacl.go
, sys_unix.go
)で定義され、そのプラットフォームがO_CLOEXEC
をサポートしているかどうかを示します。
sys_darwin.go
: Darwinでは、kern.osrelease
をsyscall.Sysctl
で取得し、OS X 10.7 (Darwin 11.0.0)以降であるかを動的に判断します。これにより、古いOS XバージョンではsupportsCloseOnExec
がfalse
となり、syscall.CloseOnExec
が呼び出されますが、新しいバージョンではtrue
となり、syscall.CloseOnExec
はスキップされます。sys_freebsd.go
: FreeBSDでは、kern.osreldate
をsyscall.SysctlUint32
で取得し、FreeBSD 8.3以降であるかを動的に判断します。同様に、古いFreeBSDバージョンではsyscall.CloseOnExec
が呼び出され、新しいバージョンではスキップされます。sys_nacl.go
: NaClはO_CLOEXEC
をサポートしないため、supportsCloseOnExec
は常にfalse
と定義されます。sys_unix.go
:dragonfly
,linux
,netbsd
,openbsd
,solaris
などの一般的なUnix系システムでは、O_CLOEXEC
が広くサポートされているため、supportsCloseOnExec
は常にtrue
と定義されます。
この設計により、Goは各プラットフォームの特性に合わせて最適なFD_CLOEXEC
設定戦略を適用できるようになりました。O_CLOEXEC
がサポートされているシステムでは、open()
システムコール自体がアトミックにFD_CLOEXEC
を設定するため、後からsyscall.CloseOnExec
を呼び出す必要がなくなり、競合状態のリスクが排除されます。一方、O_CLOEXEC
がサポートされていないシステムでは、引き続きsyscall.CloseOnExec
を呼び出すことで、ファイルディスクリプタが子プロセスに意図せず継承されるのを防ぎます。
この変更は、Goのos
パッケージの堅牢性と移植性を向上させ、特に子プロセスを起動するアプリケーションにおけるファイルディスクリプタの管理をより安全かつ効率的にします。
関連リンク
- Go Change-Id: https://golang.org/cl/64510043
- Go Issue #7187: https://github.com/golang/go/issues/7187
- Go Issue #7193: https://github.com/golang/go/issues/7193
参考にした情報源リンク
- Apple Support - OS X: About the kern.osrelease and kern.osversion sysctl values: https://support.apple.com/kb/HT1633 (このリンクは古い可能性があり、現在のAppleのドキュメントでは見つからないかもしれません。当時の情報源として記載)
- FreeBSD Handbook - Chapter 23. Ports and Packages: 23.2. FreeBSD Versions: http://www.freebsd.org/doc/en/books/porters-handbook/freebsd-versions.html
- man 2 open (Linux manual page):
O_CLOEXEC
フラグに関する情報 - man 2 fcntl (Linux manual page):
FD_CLOEXEC
フラグに関する情報 - The Linux Programming Interface by Michael Kerrisk:
fork
,exec
,close-on-exec
に関する詳細な解説# [インデックス 18720] ファイルの概要
コミット
commit be8aa4b073450b799d9a711b19862b6d915fe9d1
Author: Mikio Hara <mikioh.mikioh@gmail.com>
Date: Tue Mar 4 09:27:29 2014 +0900
os: handle file creation with close-on-exec flag correctly on darwin, freebsd
Fixes #7187.
Update #7193
LGTM=bradfitz
R=golang-codereviews, dave, rsc, minux.ma, bradfitz
CC=golang-codereviews
https://golang.org/cl/64510043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/be8aa4b073450b799d9a711b19862b6d915fe9d1
元コミット内容
このコミットは、Go言語のos
パッケージにおいて、ファイル作成時にclose-on-exec
フラグ(O_CLOEXEC
)をDarwin(macOS)およびFreeBSDシステムで正しく処理するように修正するものです。具体的には、O_CLOEXEC
フラグがサポートされているかどうかをプラットフォームごとに動的または静的に判断し、その結果に基づいてsyscall.CloseOnExec
の呼び出しを条件付けすることで、ファイルディスクリプタが子プロセスに意図せず継承される問題を解決します。この変更は、Issue #7187を修正し、Issue #7193に関連する更新を含んでいます。
変更の背景
このコミットの背景には、Goプログラムが子プロセスを起動する際に、親プロセスで開かれたファイルディスクリプタが意図せず子プロセスに継承されてしまうという問題がありました。これは特に、セキュリティ上のリスクやリソースリークの原因となる可能性があります。
Unix系システムでは、fork()
とexec()
システムコールを組み合わせて新しいプロセスを起動します。fork()
は親プロセスのメモリ空間とファイルディスクリプタを複製し、exec()
は新しいプログラムをロードして実行します。この際、親プロセスで開かれたファイルディスクリプタは、デフォルトでは子プロセスにも継承されます。これを防ぐために、ファイルディスクリプタにFD_CLOEXEC
フラグを設定することが可能です。このフラグが設定されているファイルディスクリプタは、exec()
システムコールが実行される際に自動的に閉じられます。
現代のUnix系OSでは、open()
システムコールにO_CLOEXEC
フラグを渡すことで、ファイルを開く際にアトミックにFD_CLOEXEC
フラグを設定できます。これにより、ファイルを開いてからfcntl(fd, F_SETFD, FD_CLOEXEC)
を呼び出すまでの間に、別のスレッドがfork()
とexec()
を実行してしまうという競合状態(race condition)を回避できます。
しかし、Goがサポートする一部のOS(特に古いバージョンのDarwinやFreeBSD)では、O_CLOEXEC
フラグがopen()
システムコールでサポートされていませんでした。Goの既存の実装では、O_CLOEXEC
がサポートされていない場合やDarwinの場合に無条件でsyscall.CloseOnExec
を呼び出していましたが、これはO_CLOEXEC
がサポートされている新しいOSバージョンでは冗長であり、また、O_CLOEXEC
がサポートされているにもかかわらずsyscall.CloseOnExec
を呼び出すことで、アトミック性が損なわれる可能性がありました。
このコミットは、この問題を解決するために、各プラットフォームがO_CLOEXEC
をサポートしているかどうかを正確に検出し、それに応じてsyscall.CloseOnExec
の呼び出しを最適化することを目的としています。
- Issue #7187: "os: O_CLOEXEC not respected on Darwin 10.6" - Darwin 10.6 (OS X Snow Leopard)で
O_CLOEXEC
フラグが正しく機能しないという報告。 - Issue #7193: "os: OpenFile should use O_CLOEXEC" -
os.OpenFile
がO_CLOEXEC
を使用すべきであるという提案。
前提知識の解説
ファイルディスクリプタ (File Descriptor, FD)
Unix系オペレーティングシステムにおいて、ファイルやソケット、パイプなどのI/Oリソースを識別するために使用される非負の整数です。プロセスはファイルディスクリプタを通じてこれらのリソースにアクセスします。
fork()
システムコール
現在のプロセス(親プロセス)の複製を作成し、新しいプロセス(子プロセス)を生成します。子プロセスは親プロセスのメモリ空間、開いているファイルディスクリプタ、その他のリソースのコピーを受け取ります。
exec()
システムコール
現在のプロセスイメージを、指定された新しいプログラムイメージで置き換えます。exec()
が成功すると、新しいプログラムが現在のプロセスIDで実行を開始します。この際、FD_CLOEXEC
フラグが設定されていないファイルディスクリプタは、新しいプログラムに継承されます。
FD_CLOEXEC
フラグ
ファイルディスクリプタに設定できるフラグの一つで、「close-on-exec」を意味します。このフラグが設定されたファイルディスクリプタは、exec()
システムコールが実行される際に自動的に閉じられます。これにより、子プロセスに不要なファイルディスクリプタが継承されるのを防ぎ、リソースリークやセキュリティ上の問題を回避できます。
O_CLOEXEC
フラグ
open()
システムコールに渡すことができるフラグの一つで、ファイルを開く際にFD_CLOEXEC
フラグをアトミックに設定します。これにより、ファイルを開いてからFD_CLOEXEC
を設定するまでの間にfork()
とexec()
が実行されることによる競合状態を防ぐことができます。
syscall.CloseOnExec(fd int)
Go言語のsyscall
パッケージにある関数で、指定されたファイルディスクリプタfd
にFD_CLOEXEC
フラグを設定します。これはfcntl(fd, F_SETFD, FD_CLOEXEC)
システムコールをラップしたものです。
syscall.Sysctl(name string)
/ syscall.SysctlUint32(name string)
Unix系システムでカーネルの情報を取得するためのシステムコールです。
kern.osrelease
: オペレーティングシステムのリリースバージョンを示す文字列(例: "13.0.0")。kern.osreldate
: オペレーティングシステムのリリース日を示す整数(例: FreeBSDの803000
は8.3リリースを意味する)。
これらの情報を用いて、実行中のOSが特定の機能(この場合はO_CLOEXEC
)をサポートしているかどうかを判断します。
技術的詳細
このコミットの主要な技術的変更点は、Goのos
パッケージがO_CLOEXEC
フラグのサポートをプラットフォームごとに動的に、または静的に判断するメカニズムを導入したことです。これにより、syscall.CloseOnExec
の呼び出しが不要な場合にスキップされ、アトミックなファイルオープン処理が可能なシステムではその利点を最大限に活用できるようになります。
具体的には、以下の変更が行われました。
-
supportsCloseOnExec
変数の導入:- 各プラットフォーム固有の
os
パッケージ(sys_darwin.go
,sys_freebsd.go
,sys_nacl.go
,sys_unix.go
)にsupportsCloseOnExec
というグローバル変数が導入されました。この変数は、そのプラットフォームがO_CLOEXEC
フラグをサポートしているかどうかを示すブール値です。
- 各プラットフォーム固有の
-
プラットフォームごとの
supportsCloseOnExec
の初期化:- Darwin (
sys_darwin.go
):init()
関数内でsyscall.Sysctl("kern.osrelease")
を呼び出し、OSのリリースバージョン文字列を取得します。- 取得したバージョン文字列(例: "11.0.0" for OS X 10.7)を解析し、OS X 10.7 (Darwin 11.0.0)以降であれば
supportsCloseOnExec
をtrue
に設定します。これは、O_CLOEXEC
がOS X 10.7で導入されたためです。
- FreeBSD (
sys_freebsd.go
):init()
関数内でsyscall.SysctlUint32("kern.osreldate")
を呼び出し、OSのリリース日を示す整数値を取得します。- 取得した値が
803000
(FreeBSD 8.3)以上であればsupportsCloseOnExec
をtrue
に設定します。これは、O_CLOEXEC
がFreeBSD 8.3で導入されたためです。
- NaCl (
sys_nacl.go
):supportsCloseOnExec
をconst false
として定義します。NaCl (Native Client)はO_CLOEXEC
をサポートしないためです。
- その他のUnix系システム (
sys_unix.go
):dragonfly
,linux
,netbsd
,openbsd
,solaris
などのシステムでは、supportsCloseOnExec
をconst true
として定義します。これらのシステムでは一般的にO_CLOEXEC
がサポートされているためです。
- Darwin (
-
os/file_unix.go
の変更:OpenFile
関数内で、以前はsyscall.O_CLOEXEC == 0 || runtime.GOOS == "darwin"
という条件でsyscall.CloseOnExec(r)
を呼び出していましたが、これを!supportsCloseOnExec
という条件に変更しました。- これにより、
O_CLOEXEC
がサポートされているプラットフォームではsyscall.CloseOnExec
の呼び出しがスキップされ、ファイルオープン時のアトミック性が維持されます。サポートされていないプラットフォームでは、引き続きsyscall.CloseOnExec
が呼び出され、FD_CLOEXEC
フラグが設定されます。
このアプローチにより、Goは各プラットフォームのO_CLOEXEC
サポート状況に柔軟に対応できるようになり、ファイルディスクリプタの継承に関する競合状態のリスクを低減しつつ、不要なシステムコール呼び出しを避けることができます。
コアとなるコードの変更箇所
このコミットにおけるコアとなるコードの変更箇所は以下のファイルです。
-
src/pkg/os/file_unix.go
:OpenFile
関数内のsyscall.CloseOnExec
の呼び出し条件が変更されました。
--- a/src/pkg/os/file_unix.go +++ b/src/pkg/os/file_unix.go @@ -81,12 +81,7 @@ func OpenFile(name string, flag int, perm FileMode) (file *File, err error) { // There's a race here with fork/exec, which we are // content to live with. See ../syscall/exec_unix.go. - // On OS X 10.6, the O_CLOEXEC flag is not respected. - // On OS X 10.7, the O_CLOEXEC flag works. - // Without a cheap & reliable way to detect 10.6 vs 10.7 at - // runtime, we just always call syscall.CloseOnExec on Darwin. - // Once >=10.7 is prevalent, this extra call can removed. - if syscall.O_CLOEXEC == 0 || runtime.GOOS == "darwin" { // O_CLOEXEC not supported + if !supportsCloseOnExec { syscall.CloseOnExec(r) }
-
src/pkg/os/sys_darwin.go
(新規ファイル):- Darwinプラットフォーム向けの
supportsCloseOnExec
変数の定義と初期化ロジック。
package os import "syscall" var supportsCloseOnExec bool func init() { osver, err := syscall.Sysctl("kern.osrelease") if err != nil { return } var i int for i = range osver { if osver[i] != '.' { continue } } // The O_CLOEXEC flag was introduced in OS X 10.7 (Darwin // 11.0.0). See http://support.apple.com/kb/HT1633. if i > 2 || i == 2 && osver[0] >= '1' && osver[1] >= '1' { supportsCloseOnExec = true } }
- Darwinプラットフォーム向けの
-
src/pkg/os/sys_freebsd.go
(新規ファイル):- FreeBSDプラットフォーム向けの
supportsCloseOnExec
変数の定義と初期化ロジック。
package os import "syscall" var supportsCloseOnExec bool func init() { osrel, err := syscall.SysctlUint32("kern.osreldate") if err != nil { return } // The O_CLOEXEC flag was introduced in FreeBSD 8.3. // See http://www.freebsd.org/doc/en/books/porters-handbook/freebsd-versions.html. if osrel >= 803000 { supportsCloseOnExec = true } }
- FreeBSDプラットフォーム向けの
-
src/pkg/os/sys_nacl.go
(新規ファイル):- NaClプラットフォーム向けの
supportsCloseOnExec
変数の定義。
package os const supportsCloseOnExec = false
- NaClプラットフォーム向けの
-
src/pkg/os/sys_unix.go
(新規ファイル):- その他のUnix系プラットフォーム向けの
supportsCloseOnExec
変数の定義。
package os // +build dragonfly linux netbsd openbsd solaris const supportsCloseOnExec = true
- その他のUnix系プラットフォーム向けの
コアとなるコードの解説
このコミットの核心は、os.OpenFile
関数におけるFD_CLOEXEC
フラグの設定方法を、各OSのO_CLOEXEC
サポート状況に応じて最適化することです。
以前のos/file_unix.go
のOpenFile
関数では、syscall.O_CLOEXEC == 0
(O_CLOEXEC
が定義されていない、つまりサポートされていない場合)またはruntime.GOOS == "darwin"
(Darwinの場合)という条件で無条件にsyscall.CloseOnExec(r)
を呼び出していました。これは、古いDarwinバージョンでO_CLOEXEC
が機能しないという既知の問題に対応するためでしたが、新しいDarwinバージョンや他のO_CLOEXEC
をサポートするUnix系システムでは冗長であり、アトミックなファイルオープンを妨げる可能性がありました。
このコミットでは、この条件を!supportsCloseOnExec
というより汎用的なものに置き換えました。supportsCloseOnExec
は、各プラットフォーム固有のファイル(sys_darwin.go
, sys_freebsd.go
, sys_nacl.go
, sys_unix.go
)で定義され、そのプラットフォームがO_CLOEXEC
をサポートしているかどうかを示します。
sys_darwin.go
: Darwinでは、kern.osrelease
をsyscall.Sysctl
で取得し、OS X 10.7 (Darwin 11.0.0)以降であるかを動的に判断します。これにより、古いOS XバージョンではsupportsCloseOnExec
がfalse
となり、syscall.CloseOnExec
が呼び出されますが、新しいバージョンではtrue
となり、syscall.CloseOnExec
はスキップされます。sys_freebsd.go
: FreeBSDでは、kern.osreldate
をsyscall.SysctlUint32
で取得し、FreeBSD 8.3以降であるかを動的に判断します。同様に、古いFreeBSDバージョンではsyscall.CloseOnExec
が呼び出され、新しいバージョンではスキップされます。sys_nacl.go
: NaClはO_CLOEXEC
をサポートしないため、supportsCloseOnExec
は常にfalse
と定義されます。sys_unix.go
:dragonfly
,linux
,netbsd
,openbsd
,solaris
などの一般的なUnix系システムでは、O_CLOEXEC
が広くサポートされているため、supportsCloseOnExec
は常にtrue
と定義されます。
この設計により、Goは各プラットフォームの特性に合わせて最適なFD_CLOEXEC
設定戦略を適用できるようになりました。O_CLOEXEC
がサポートされているシステムでは、open()
システムコール自体がアトミックにFD_CLOEXEC
を設定するため、後からsyscall.CloseOnExec
を呼び出す必要がなくなり、競合状態のリスクが排除されます。一方、O_CLOEXEC
がサポートされていないシステムでは、引き続きsyscall.CloseOnExec
を呼び出すことで、ファイルディスクリプタが子プロセスに意図せず継承されるのを防ぎます。
この変更は、Goのos
パッケージの堅牢性と移植性を向上させ、特に子プロセスを起動するアプリケーションにおけるファイルディスクリプタの管理をより安全かつ効率的にします。
関連リンク
- Go Change-Id: https://golang.org/cl/64510043
- Go Issue #7187: https://github.com/golang/go/issues/7187
- Go Issue #7193: https://github.com/golang/go/issues/7193
参考にした情報源リンク
- Apple Support - OS X: About the kern.osrelease and kern.osversion sysctl values: https://support.apple.com/kb/HT1633 (このリンクは古い可能性があり、現在のAppleのドキュメントでは見つからないかもしれません。当時の情報源として記載)
- FreeBSD Handbook - Chapter 23. Ports and Packages: 23.2. FreeBSD Versions: http://www.freebsd.org/doc/en/books/porters-handbook/freebsd-versions.html
- man 2 open (Linux manual page):
O_CLOEXEC
フラグに関する情報 - man 2 fcntl (Linux manual page):
FD_CLOEXEC
フラグに関する情報 - The Linux Programming Interface by Michael Kerrisk:
fork
,exec
,close-on-exec
に関する詳細な解説