[インデックス 19090] ファイルの概要
このコミットは、Go言語のsyscall
パッケージにおけるBSD系OS(Darwin, Dragonfly, FreeBSD, OpenBSD)向けのGetfsstat()
システムコール実装のバグ修正に関するものです。具体的には、Getfsstat()
がファイルシステム統計情報を取得する際に、バッファのサイズを構造体の数ではなくバイト単位で正しく計算して渡すように修正されました。これにより、Getfsstat()
が期待通りに動作し、不正なバッファサイズによる問題が解消されました。
コミット
- コミットハッシュ:
4abbd4a468ded765fc23cf4a4de3ff48f32b354b
- 作者: Preetam Jinka pj@preet.am
- コミット日時: 2014年4月10日 木曜日 13:58:03 +1000
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/4abbd4a468ded765fc23cf4a4de3ff48f32b354b
元コミット内容
syscall: fix Getfsstat() for BSD
The buffer length should be the size in bytes
instead of the number of structs.
Fixes #6588.
LGTM=mikioh.mikioh
R=golang-codereviews, mikioh.mikioh, adg
CC=golang-codereviews
https://golang.org/cl/84830043
変更の背景
この変更は、Go言語のsyscall
パッケージがBSD系OS上でGetfsstat()
システムコールを呼び出す際に発生していたバグを修正するために行われました。元の実装では、Getfsstat()
に渡すバッファの長さが、ファイルシステム統計情報構造体(Statfs_t
)の「数」として誤って解釈されていました。しかし、実際のシステムコールはバッファの「バイトサイズ」を期待していました。この不一致により、システムコールが正しく機能せず、ファイルシステム情報の取得に失敗したり、予期せぬ動作を引き起こしたりする可能性がありました。
この問題はGoのIssue #6588として報告されており、このコミットはその問題を解決することを目的としています。
前提知識の解説
1. システムコール (System Call)
システムコールは、オペレーティングシステム(OS)のカーネルが提供するサービスを、ユーザー空間のプログラムが利用するためのインターフェースです。ファイル操作、メモリ管理、プロセス制御、ネットワーク通信など、OSの機能にアクセスするために使用されます。Go言語では、syscall
パッケージ(またはより新しいgolang.org/x/sys
パッケージ)を通じてシステムコールを呼び出すことができます。
2. Getfsstat()
Getfsstat()
は、BSD系OS(macOS/Darwin, FreeBSD, OpenBSD, Dragonfly BSDなど)で利用可能なシステムコールです。このシステムコールは、現在マウントされているすべてのファイルシステムに関する統計情報(空き容量、ブロックサイズ、inode数など)を取得するために使用されます。通常、このシステムコールは、ファイルシステム情報を格納するためのバッファと、そのバッファのサイズ(バイト単位)、および動作を制御するフラグを引数として取ります。
3. Statfs_t
構造体
Statfs_t
は、Go言語のsyscall
パッケージ内で定義されている構造体で、C言語のstruct statfs
に対応します。この構造体は、個々のファイルシステムに関する詳細な統計情報(例: f_bsize
(ブロックサイズ), f_blocks
(総ブロック数), f_bfree
(空きブロック数), f_fstypename
(ファイルシステムタイプ名) など)を保持します。Getfsstat()
システムコールは、このStatfs_t
構造体の配列をバッファとして使用し、各ファイルシステムの情報をこの配列に格納します。
4. unsafe.Pointer
と uintptr
Go言語は通常、厳格な型安全性とメモリ安全性を保証しますが、低レベルの操作やC言語との相互運用性のためには、これらの安全性を一時的にバイパスするメカニズムが必要です。それがunsafe
パッケージとuintptr
型です。
unsafe.Pointer
: 任意の型のポインタを保持できる特殊なポインタ型です。C言語のvoid*
に似ており、Goの型システムを迂回して、異なる型のポインタ間で変換することを可能にします。これにより、Goのガベージコレクタが管理するメモリ領域に直接アクセスしたり、C言語の関数にポインタを渡したりすることができます。uintptr
: ポインタのビットパターンを保持できる符号なし整数型です。これはメモリアドレスを数値として表現します。uintptr
はポインタではありません。つまり、uintptr
自体はガベージコレクタによって追跡されません。unsafe.Pointer
とuintptr
を組み合わせることで、ポインタ演算(メモリ上のオフセット計算など)を行うことができます。システムコールにメモリバッファを渡す際には、Goのポインタをunsafe.Pointer
経由でuintptr
に変換し、そのuintptr
値をシステムコールに渡すのが一般的なパターンです。
このコミットの文脈では、Getfsstat()
システムコールが期待するバッファサイズ(バイト単位)を計算するために、Statfs_t
構造体のサイズと、バッファ内の構造体の数を乗算する必要があります。この計算にはunsafe.Sizeof()
関数が使用され、結果はuintptr
型としてシステムコールに渡されます。
5. zsyscall_*.go
ファイル
Goのsyscall
パッケージには、zsyscall_darwin_amd64.go
のようなzsyscall_*.go
という命名規則のファイルが多数存在します。これらのファイルは手書きではなく、Goのビルドツール(特にmksyscall.go
)によって自動生成されます。これらのファイルは、各OSおよびアーキテクチャ固有のシステムコール番号、定数、およびシステムコールを呼び出すための低レベルのGo関数ラッパーを含んでいます。
元の実装では、Getfsstat()
のGoラッパーもこれらの自動生成ファイルに含まれていました。しかし、このコミットでは、バッファサイズの計算ロジックが複雑であるため、自動生成ではなく手動でsyscall_*.go
ファイルに実装されることになりました。そのため、zsyscall_*.go
ファイルからはGetfsstat()
の定義が削除されています。
技術的詳細
このコミットの核心は、Getfsstat()
システムコールへの引数の渡し方に関する誤解を修正することです。
Getfsstat()
システムコールは、通常、以下のシグネチャを持ちます(C言語の例):
int getfsstat(struct statfs *buf, int bufsize, int flags);
ここで、bufsize
はバッファの「バイト単位のサイズ」を期待します。
Goの元の実装では、Getfsstat()
のラッパー関数がbuf []Statfs_t
というスライスを受け取り、その長さ(len(buf)
)を直接uintptr
にキャストしてbufsize
引数としてシステムコールに渡していました。
// 修正前のコードの概念的な表現
func Getfsstat(buf []Statfs_t, flags int) (n int, err error) {
// ...
// bufsize = uintptr(len(buf)) // ここが問題
// ...
r0, _, e1 := Syscall(SYS_GETFSSTAT, uintptr(_p0), uintptr(len(buf)), uintptr(flags))
// ...
}
これは、システムコールがStatfs_t
構造体の「数」ではなく、「バイト数」を期待しているという事実と矛盾していました。例えば、Statfs_t
構造体が100バイトのサイズを持つ場合、10個の構造体を格納するバッファのサイズは10 * 100 = 1000バイトであるべきです。しかし、元のコードでは単に10(構造体の数)を渡していました。
このコミットでは、この問題を修正するために、bufsize
を以下のように計算するように変更しました。
bufsize = unsafe.Sizeof(Statfs_t{}) * uintptr(len(buf))
unsafe.Sizeof(Statfs_t{})
:Statfs_t
構造体1つのバイトサイズを取得します。uintptr(len(buf))
: バッファ内のStatfs_t
構造体の数をuintptr
型に変換します。
この2つの値を乗算することで、バッファ全体の正確なバイトサイズが計算され、システムコールに正しく渡されるようになりました。
また、この変更に伴い、Getfsstat()
のGoラッパー関数は、自動生成されるzsyscall_*.go
ファイルから、各OS固有のsyscall_*.go
ファイル(例: syscall_darwin.go
, syscall_freebsd.go
など)に手動で実装される形に変更されました。これは、unsafe.Sizeof()
を使った複雑なバッファサイズ計算ロジックを自動生成ツールが適切に扱えなかったためと考えられます。手動実装にすることで、より柔軟な制御が可能になりました。
コアとなるコードの変更箇所
このコミットによる主要なコード変更は以下のファイルに集中しています。
-
src/pkg/syscall/syscall_bsd_test.go
(新規追加)- BSD系OS向けの
Getfsstat
関数のテストケースが追加されました。これにより、修正が正しく機能することを確認できます。
- BSD系OS向けの
-
src/pkg/syscall/syscall_darwin.go
Getfsstat
関数の手動実装が追加されました。bufsize
の計算がunsafe.Sizeof(Statfs_t{}) * uintptr(len(buf))
に変更されました。- 元の自動生成された
Getfsstat
の//sys
ディレクティブがコメントアウトされました。
-
src/pkg/syscall/syscall_dragonfly.go
syscall_darwin.go
と同様に、Getfsstat
関数の手動実装が追加され、bufsize
の計算が修正されました。- 元の自動生成された
Getfsstat
の//sys
ディレクティブがコメントアウトされました。
-
src/pkg/syscall/syscall_freebsd.go
syscall_darwin.go
と同様に、Getfsstat
関数の手動実装が追加され、bufsize
の計算が修正されました。- 元の自動生成された
Getfsstat
の//sys
ディレクティブがコメントアウトされました。
-
src/pkg/syscall/syscall_openbsd.go
syscall_darwin.go
と同様に、Getfsstat
関数の手動実装が追加され、bufsize
の計算が修正されました。- 元の自動生成された
Getfsstat
の//sys
ディレクティブがコメントアウトされました。
-
src/pkg/syscall/zsyscall_darwin_386.go
-
src/pkg/syscall/zsyscall_darwin_amd64.go
-
src/pkg/syscall/zsyscall_dragonfly_386.go
-
src/pkg/syscall/zsyscall_dragonfly_amd64.go
-
src/pkg/syscall/zsyscall_freebsd_386.go
-
src/pkg/syscall/zsyscall_freebsd_amd64.go
-
src/pkg/syscall/zsyscall_freebsd_arm.go
-
src/pkg/syscall/zsyscall_openbsd_386.go
-
src/pkg/syscall/zsyscall_openbsd_amd64.go
- これらの自動生成ファイルから、
Getfsstat
関数の定義が削除されました。これは、上記syscall_*.go
ファイルで手動実装に切り替わったためです。
- これらの自動生成ファイルから、
全体として、94行の追加と157行の削除が行われ、主にGetfsstat
関数の実装が自動生成から手動実装に移行し、バッファサイズの計算ロジックが修正されたことを示しています。
コアとなるコードの解説
修正後のGetfsstat
関数の実装(例: src/pkg/syscall/syscall_darwin.go
)は以下のようになります。
func Getfsstat(buf []Statfs_t, flags int) (n int, err error) {
var _p0 unsafe.Pointer
var bufsize uintptr // バッファのバイトサイズを格納する変数
if len(buf) > 0 {
_p0 = unsafe.Pointer(&buf[0]) // スライスの最初の要素へのポインタ
// ここでバッファのバイトサイズを正確に計算
bufsize = unsafe.Sizeof(Statfs_t{}) * uintptr(len(buf))
}
// Syscall関数を呼び出す際に、計算されたbufsizeを渡す
r0, _, e1 := Syscall(SYS_GETFSSTAT64, uintptr(_p0), bufsize, uintptr(flags))
n = int(r0)
if e1 != 0 {
err = e1
}
return
}
このコードの重要な点は以下の通りです。
bufsize
変数の導入:bufsize
というuintptr
型の変数が導入され、バッファのバイトサイズを保持するようになりました。unsafe.Sizeof(Statfs_t{})
:Statfs_t
構造体1つあたりのメモリサイズ(バイト単位)を取得します。これはコンパイル時に決定される定数です。uintptr(len(buf))
:buf
スライスに含まれるStatfs_t
構造体の数をuintptr
型に変換します。unsafe.Sizeof(Statfs_t{}) * uintptr(len(buf))
: これら2つの値を乗算することで、buf
スライスが占めるメモリの総バイトサイズが正確に計算されます。Syscall
への引数:Syscall
関数(実際のシステムコールを呼び出すGoの内部関数)の第2引数として、この正確に計算されたbufsize
が渡されます。これにより、OSカーネルが期待する正しいバッファサイズが提供され、Getfsstat()
システムコールが正しく動作するようになります。
また、zsyscall_*.go
ファイルからGetfsstat
の定義が削除されたのは、この手動でのバッファサイズ計算ロジックが自動生成ツールでは表現しきれなかったためです。これにより、Getfsstat
のGoラッパーは、各BSD系OSのsyscall_*.go
ファイル内で明示的に定義されることになりました。
src/pkg/syscall/syscall_bsd_test.go
で追加されたテストケースは、Getfsstat
が空でないStatfs_t
構造体を返すことを確認しており、修正が正しく適用されたことを検証しています。
関連リンク
- Go Issue #6588: https://code.google.com/p/go/issues/detail?id=6588 (元のGoプロジェクトのIssueトラッカーへのリンクですが、現在はGitHubに移行しているため、直接アクセスできない可能性があります。)
- Go CL 84830043: https://golang.org/cl/84830043 (Goのコードレビューシステムへのリンク)
参考にした情報源リンク
getfsstat
man page (BSD系OS):- Go
unsafe.Pointer
anduintptr
explanation: - Go
syscall
package documentation: - Explanation of
zsyscall
files in Go:- Go: The zsyscall files
- Understanding Go's zsyscall files (Note: This is a general article, not specific to this commit.)
statfs
structure details:- FreeBSD
sys/mount.h
(Relevant section forstruct statfs
) - OpenBSD
sys/mount.h
(Relevant section forstruct statfs
) - NetBSD
sys/mount.h
(Relevant section forstruct statfs
)
- FreeBSD
golang.org/x/sys
(Recommended alternative tosyscall
for new code):