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

[インデックス 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.OpenFileO_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パッケージにある関数で、指定されたファイルディスクリプタfdFD_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の呼び出しが不要な場合にスキップされ、アトミックなファイルオープン処理が可能なシステムではその利点を最大限に活用できるようになります。

具体的には、以下の変更が行われました。

  1. supportsCloseOnExec変数の導入:

    • 各プラットフォーム固有のosパッケージ(sys_darwin.go, sys_freebsd.go, sys_nacl.go, sys_unix.go)にsupportsCloseOnExecというグローバル変数が導入されました。この変数は、そのプラットフォームがO_CLOEXECフラグをサポートしているかどうかを示すブール値です。
  2. プラットフォームごとの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)以降であればsupportsCloseOnExectrueに設定します。これは、O_CLOEXECがOS X 10.7で導入されたためです。
    • FreeBSD (sys_freebsd.go):
      • init()関数内でsyscall.SysctlUint32("kern.osreldate")を呼び出し、OSのリリース日を示す整数値を取得します。
      • 取得した値が803000(FreeBSD 8.3)以上であればsupportsCloseOnExectrueに設定します。これは、O_CLOEXECがFreeBSD 8.3で導入されたためです。
    • NaCl (sys_nacl.go):
      • supportsCloseOnExecconst falseとして定義します。NaCl (Native Client)はO_CLOEXECをサポートしないためです。
    • その他のUnix系システム (sys_unix.go):
      • dragonfly, linux, netbsd, openbsd, solarisなどのシステムでは、supportsCloseOnExecconst trueとして定義します。これらのシステムでは一般的にO_CLOEXECがサポートされているためです。
  3. 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サポート状況に柔軟に対応できるようになり、ファイルディスクリプタの継承に関する競合状態のリスクを低減しつつ、不要なシステムコール呼び出しを避けることができます。

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

このコミットにおけるコアとなるコードの変更箇所は以下のファイルです。

  1. 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)
     	}
    
  2. 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
    	}
    }
    
  3. 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
    	}
    }
    
  4. src/pkg/os/sys_nacl.go (新規ファイル):

    • NaClプラットフォーム向けのsupportsCloseOnExec変数の定義。
    package os
    
    const supportsCloseOnExec = false
    
  5. src/pkg/os/sys_unix.go (新規ファイル):

    • その他のUnix系プラットフォーム向けのsupportsCloseOnExec変数の定義。
    package os
    
    // +build dragonfly linux netbsd openbsd solaris
    
    const supportsCloseOnExec = true
    

コアとなるコードの解説

このコミットの核心は、os.OpenFile関数におけるFD_CLOEXECフラグの設定方法を、各OSのO_CLOEXECサポート状況に応じて最適化することです。

以前のos/file_unix.goOpenFile関数では、syscall.O_CLOEXEC == 0O_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.osreleasesyscall.Sysctlで取得し、OS X 10.7 (Darwin 11.0.0)以降であるかを動的に判断します。これにより、古いOS XバージョンではsupportsCloseOnExecfalseとなり、syscall.CloseOnExecが呼び出されますが、新しいバージョンではtrueとなり、syscall.CloseOnExecはスキップされます。
  • sys_freebsd.go: FreeBSDでは、kern.osreldatesyscall.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パッケージの堅牢性と移植性を向上させ、特に子プロセスを起動するアプリケーションにおけるファイルディスクリプタの管理をより安全かつ効率的にします。

関連リンク

参考にした情報源リンク

  • 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.OpenFileO_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パッケージにある関数で、指定されたファイルディスクリプタfdFD_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の呼び出しが不要な場合にスキップされ、アトミックなファイルオープン処理が可能なシステムではその利点を最大限に活用できるようになります。

具体的には、以下の変更が行われました。

  1. supportsCloseOnExec変数の導入:

    • 各プラットフォーム固有のosパッケージ(sys_darwin.go, sys_freebsd.go, sys_nacl.go, sys_unix.go)にsupportsCloseOnExecというグローバル変数が導入されました。この変数は、そのプラットフォームがO_CLOEXECフラグをサポートしているかどうかを示すブール値です。
  2. プラットフォームごとの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)以降であればsupportsCloseOnExectrueに設定します。これは、O_CLOEXECがOS X 10.7で導入されたためです。
    • FreeBSD (sys_freebsd.go):
      • init()関数内でsyscall.SysctlUint32("kern.osreldate")を呼び出し、OSのリリース日を示す整数値を取得します。
      • 取得した値が803000(FreeBSD 8.3)以上であればsupportsCloseOnExectrueに設定します。これは、O_CLOEXECがFreeBSD 8.3で導入されたためです。
    • NaCl (sys_nacl.go):
      • supportsCloseOnExecconst falseとして定義します。NaCl (Native Client)はO_CLOEXECをサポートしないためです。
    • その他のUnix系システム (sys_unix.go):
      • dragonfly, linux, netbsd, openbsd, solarisなどのシステムでは、supportsCloseOnExecconst trueとして定義します。これらのシステムでは一般的にO_CLOEXECがサポートされているためです。
  3. 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サポート状況に柔軟に対応できるようになり、ファイルディスクリプタの継承に関する競合状態のリスクを低減しつつ、不要なシステムコール呼び出しを避けることができます。

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

このコミットにおけるコアとなるコードの変更箇所は以下のファイルです。

  1. 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)
     	}
    
  2. 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
    	}
    }
    
  3. 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
    	}
    }
    
  4. src/pkg/os/sys_nacl.go (新規ファイル):

    • NaClプラットフォーム向けのsupportsCloseOnExec変数の定義。
    package os
    
    const supportsCloseOnExec = false
    
  5. src/pkg/os/sys_unix.go (新規ファイル):

    • その他のUnix系プラットフォーム向けのsupportsCloseOnExec変数の定義。
    package os
    
    // +build dragonfly linux netbsd openbsd solaris
    
    const supportsCloseOnExec = true
    

コアとなるコードの解説

このコミットの核心は、os.OpenFile関数におけるFD_CLOEXECフラグの設定方法を、各OSのO_CLOEXECサポート状況に応じて最適化することです。

以前のos/file_unix.goOpenFile関数では、syscall.O_CLOEXEC == 0O_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.osreleasesyscall.Sysctlで取得し、OS X 10.7 (Darwin 11.0.0)以降であるかを動的に判断します。これにより、古いOS XバージョンではsupportsCloseOnExecfalseとなり、syscall.CloseOnExecが呼び出されますが、新しいバージョンではtrueとなり、syscall.CloseOnExecはスキップされます。
  • sys_freebsd.go: FreeBSDでは、kern.osreldatesyscall.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パッケージの堅牢性と移植性を向上させ、特に子プロセスを起動するアプリケーションにおけるファイルディスクリプタの管理をより安全かつ効率的にします。

関連リンク

参考にした情報源リンク

  • 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に関する詳細な解説