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

[インデックス 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.Pointeruintptr

Go言語は通常、厳格な型安全性とメモリ安全性を保証しますが、低レベルの操作やC言語との相互運用性のためには、これらの安全性を一時的にバイパスするメカニズムが必要です。それがunsafeパッケージとuintptr型です。

  • unsafe.Pointer: 任意の型のポインタを保持できる特殊なポインタ型です。C言語のvoid*に似ており、Goの型システムを迂回して、異なる型のポインタ間で変換することを可能にします。これにより、Goのガベージコレクタが管理するメモリ領域に直接アクセスしたり、C言語の関数にポインタを渡したりすることができます。
  • uintptr: ポインタのビットパターンを保持できる符号なし整数型です。これはメモリアドレスを数値として表現します。uintptrはポインタではありません。つまり、uintptr自体はガベージコレクタによって追跡されません。unsafe.Pointeruintptrを組み合わせることで、ポインタ演算(メモリ上のオフセット計算など)を行うことができます。システムコールにメモリバッファを渡す際には、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()を使った複雑なバッファサイズ計算ロジックを自動生成ツールが適切に扱えなかったためと考えられます。手動実装にすることで、より柔軟な制御が可能になりました。

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

このコミットによる主要なコード変更は以下のファイルに集中しています。

  1. src/pkg/syscall/syscall_bsd_test.go (新規追加)

    • BSD系OS向けのGetfsstat関数のテストケースが追加されました。これにより、修正が正しく機能することを確認できます。
  2. src/pkg/syscall/syscall_darwin.go

    • Getfsstat関数の手動実装が追加されました。
    • bufsizeの計算がunsafe.Sizeof(Statfs_t{}) * uintptr(len(buf))に変更されました。
    • 元の自動生成されたGetfsstat//sysディレクティブがコメントアウトされました。
  3. src/pkg/syscall/syscall_dragonfly.go

    • syscall_darwin.goと同様に、Getfsstat関数の手動実装が追加され、bufsizeの計算が修正されました。
    • 元の自動生成されたGetfsstat//sysディレクティブがコメントアウトされました。
  4. src/pkg/syscall/syscall_freebsd.go

    • syscall_darwin.goと同様に、Getfsstat関数の手動実装が追加され、bufsizeの計算が修正されました。
    • 元の自動生成されたGetfsstat//sysディレクティブがコメントアウトされました。
  5. src/pkg/syscall/syscall_openbsd.go

    • syscall_darwin.goと同様に、Getfsstat関数の手動実装が追加され、bufsizeの計算が修正されました。
    • 元の自動生成されたGetfsstat//sysディレクティブがコメントアウトされました。
  6. src/pkg/syscall/zsyscall_darwin_386.go

  7. src/pkg/syscall/zsyscall_darwin_amd64.go

  8. src/pkg/syscall/zsyscall_dragonfly_386.go

  9. src/pkg/syscall/zsyscall_dragonfly_amd64.go

  10. src/pkg/syscall/zsyscall_freebsd_386.go

  11. src/pkg/syscall/zsyscall_freebsd_amd64.go

  12. src/pkg/syscall/zsyscall_freebsd_arm.go

  13. src/pkg/syscall/zsyscall_openbsd_386.go

  14. 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
}

このコードの重要な点は以下の通りです。

  1. bufsize変数の導入: bufsizeというuintptr型の変数が導入され、バッファのバイトサイズを保持するようになりました。
  2. unsafe.Sizeof(Statfs_t{}): Statfs_t構造体1つあたりのメモリサイズ(バイト単位)を取得します。これはコンパイル時に決定される定数です。
  3. uintptr(len(buf)): bufスライスに含まれるStatfs_t構造体の数をuintptr型に変換します。
  4. unsafe.Sizeof(Statfs_t{}) * uintptr(len(buf)): これら2つの値を乗算することで、bufスライスが占めるメモリの総バイトサイズが正確に計算されます。
  5. 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構造体を返すことを確認しており、修正が正しく適用されたことを検証しています。

関連リンク

参考にした情報源リンク