[インデックス 1638] ファイルの概要
このコミットは、Go言語の標準ライブラリの一部である src/lib/syscall/file_darwin.go ファイルに対する変更です。このファイルは、Darwin(macOS)オペレーティングシステム上でファイルシステム操作を行うためのシステムコール(stat, lstat, fstat など)をGo言語から呼び出すためのラッパー関数を提供しています。具体的には、ファイルのメタデータ(サイズ、パーミッション、タイムスタンプなど)を取得する機能に関連する部分です。
コミット
このコミットは、fstat 関数が誤ったシステムコールを使用していた問題と、lstat 関数がファイル名の型を誤って扱っていた問題を修正します。これにより、Darwin環境でのファイル情報の取得が正確に行われるようになります。
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/69c41d7f5f965ba8e4f6dea5b6cdbeb348f13ba1
元コミット内容
fstat used wrong system call, lstat used wrong type of name
R=rsc
DELTA=7 (4 added, 0 deleted, 3 changed)
OCL=24617
CL=24617
変更の背景
この変更の背景には、Go言語が初期段階にあった頃のシステムコールラッパーの実装におけるバグが存在しました。具体的には、以下の2つの問題がありました。
fstatのシステムコール誤り:fstatはファイルディスクリプタ(開いているファイルの識別子)に基づいてファイルのステータス情報を取得するシステムコールですが、Goの実装では誤ったシステムコール番号が指定されていた可能性があります。これにより、正しいファイル情報が取得できない、あるいは予期せぬエラーが発生する可能性がありました。lstatのファイル名処理誤り:lstatはシンボリックリンク自体(リンク先ではなく)のステータス情報を取得するシステムコールです。元の実装では、ファイル名を表す引数の型が不適切であったか、または文字列からバイト列への変換が正しく行われていなかったため、長いファイル名や特殊文字を含むファイル名で問題が発生する可能性がありました。
これらの問題は、GoプログラムがDarwin環境でファイルシステムと正確にやり取りする上で重要な障害となるため、修正が必要でした。
前提知識の解説
このコミットを理解するためには、以下の概念を把握しておく必要があります。
- システムコール (System Call): オペレーティングシステム(OS)のカーネルが提供するサービスを、ユーザー空間のプログラムから利用するためのインターフェースです。ファイル操作、メモリ管理、プロセス制御など、OSの基本的な機能はシステムコールを通じて提供されます。Go言語の
syscallパッケージは、これらのシステムコールをGoプログラムから直接呼び出すための機能を提供します。 stat,lstat,fstat: これらはUNIX系OSにおけるファイル情報の取得に関するシステムコールです。stat(path, buf): 指定されたパスpathのファイル(シンボリックリンクの場合はそのリンク先)のステータス情報をbufに格納します。lstat(path, buf):statと似ていますが、pathがシンボリックリンクである場合、リンク先のファイルではなく、シンボリックリンク自体のステータス情報をbufに格納します。fstat(fd, buf): 指定されたファイルディスクリプタfdに関連付けられたファイルのステータス情報をbufに格納します。
Stat_t構造体:stat,lstat,fstatシステムコールによって取得されるファイル情報を格納するための構造体です。ファイルのサイズ、パーミッション、所有者、最終アクセス時刻、最終更新時刻などのメタデータが含まれます。Syscall関数 (Go言語のsyscallパッケージ): Go言語のsyscallパッケージで提供される低レベル関数で、OSのシステムコールを直接呼び出すために使用されます。通常、システムコール番号と引数をuintptr型(ポインタを整数として扱う型)にキャストして渡します。unsafe.Pointer: Go言語のunsafeパッケージに含まれる型で、任意の型のポインタを保持できます。型安全性をバイパスして、異なる型のポインタ間で変換を行う際に使用されます。システムコールに構造体のアドレスを渡す際などに利用されますが、使用には細心の注意が必要です。uintptr: 整数型の一種で、ポインタの値を保持するのに十分な大きさがあります。unsafe.Pointerと組み合わせて、ポインタを整数として操作したり、システムコールに渡したりする際に使われます。SYS_LSTAT,SYS_FSTAT,SYS_LSTAT64,SYS_FSTAT64: これらは特定のシステムコールを表す定数です。OSによってシステムコール番号が異なります。_64が付いているものは、64ビット環境や大きなファイルサイズを扱うためのバージョンであることが多いです。Darwin(macOS)では、ファイルサイズが2GBを超えるような大きなファイルを扱うためにstat64,lstat64,fstat64といったシステムコールが提供されており、これらはそれぞれSYS_LSTAT64,SYS_FSTAT64といった定数に対応します。StringToBytes関数: Go言語の内部関数で、Goの文字列(string型)をC言語スタイルのヌル終端バイト配列([]byte)に変換するために使用されます。システムコールに文字列を渡す際には、通常この形式に変換する必要があります。ENAMETOOLONG: エラーコードの一つで、ファイル名が長すぎる場合に返されます。
技術的詳細
このコミットの技術的詳細は、Darwin環境におけるシステムコールの正確な利用と、Go言語の文字列とC言語スタイルのバイト配列間の変換に焦点を当てています。
-
fstatの修正:- 変更前:
Syscall(SYS_FSTAT, ...) - 変更後:
Syscall(SYS_FSTAT64, ...)この変更は、fstatが使用するシステムコールをSYS_FSTATからSYS_FSTAT64に変更しています。これは、Darwinシステムにおいて、ファイルディスクリプタからファイル情報を取得する際に、より新しい(そしておそらく64ビット対応の)システムコールを使用することで、潜在的な互換性問題やファイルサイズに関する制限を回避することを目的としています。特に、大きなファイルを扱う際に正確な情報を取得するために重要です。
- 変更前:
-
lstatの修正:- 変更前:
func Lstat(name *byte, buf *Stat_t)- 引数
nameが*byte型であり、これはC言語スタイルのヌル終端文字列を直接指すポインタを意図していたと考えられます。しかし、Goの文字列は内部的に長さ情報を持つため、この直接的なポインタ渡しはGoの文字列の扱いに適していませんでした。 - システムコールは
SYS_LSTATを使用していました。
- 引数
- 変更後:
func Lstat(name string, buf *Stat_t)- 引数
nameがGoのstring型に変更されました。これにより、Goの文字列として自然に扱えるようになります。 - 文字列からバイト配列への変換:
var namebuf [nameBufsize]byte;で固定サイズのバイト配列を宣言し、if !StringToBytes(namebuf, name)を使ってGoのstringをこのバイト配列に変換しています。これは、システムコールがC言語スタイルのヌル終端バイト配列を期待するためです。 - ファイル名が長すぎる場合のハンドリング:
StringToBytesがfalseを返した場合(つまり、ファイル名がnameBufsizeを超えて長すぎる場合)、return -1, ENAMETOOLONGを返してエラーを適切に処理しています。これにより、ファイル名が長すぎる場合にプログラムがクラッシュするのではなく、適切なエラーが返されるようになります。 - システムコールは
SYS_LSTAT64に変更されました。これはfstatと同様に、より新しい64ビット対応のシステムコールを使用することで、正確性と互換性を向上させています。また、unsafe.Pointer(&namebuf[0])を使用して、変換されたバイト配列の先頭アドレスをシステムコールに渡しています。
- 引数
- 変更前:
これらの変更は、Go言語がDarwin上でファイルシステムとより堅牢かつ正確に連携できるようにするための重要な修正です。特に、ファイル名の長さ制限の適切な処理と、64ビット対応のシステムコールの採用は、現代のファイルシステム環境において不可欠な要素です。
コアとなるコードの変更箇所
--- a/src/lib/syscall/file_darwin.go
+++ b/src/lib/syscall/file_darwin.go
@@ -65,13 +65,17 @@ func Stat(name string, buf *Stat_t) (ret int64, errno int64) {
return r1, err;
}
-func Lstat(name *byte, buf *Stat_t) (ret int64, errno int64) {
- r1, r2, err := Syscall(SYS_LSTAT, int64(uintptr(unsafe.Pointer(name))), int64(uintptr(unsafe.Pointer(buf))), 0);\n+func Lstat(name string, buf *Stat_t) (ret int64, errno int64) {
+ var namebuf [nameBufsize]byte;
+ if !StringToBytes(namebuf, name) {
+ return -1, ENAMETOOLONG
+ }
+ r1, r2, err := Syscall(SYS_LSTAT64, int64(uintptr(unsafe.Pointer(&namebuf[0]))), int64(uintptr(unsafe.Pointer(buf))), 0);\n return r1, err;\n }
func Fstat(fd int64, buf *Stat_t) (ret int64, errno int64) {
-\tr1, r2, err := Syscall(SYS_FSTAT, fd, int64(uintptr(unsafe.Pointer(buf))), 0);\n+\tr1, r2, err := Syscall(SYS_FSTAT64, fd, int64(uintptr(unsafe.Pointer(buf))), 0);\n \treturn r1, err;\n }
コアとなるコードの解説
Lstat 関数の変更
-func Lstat(name *byte, buf *Stat_t) (ret int64, errno int64) {
- r1, r2, err := Syscall(SYS_LSTAT, int64(uintptr(unsafe.Pointer(name))), int64(uintptr(unsafe.Pointer(buf))), 0);
+func Lstat(name string, buf *Stat_t) (ret int64, errno int64) {
+ var namebuf [nameBufsize]byte;
+ if !StringToBytes(namebuf, name) {
+ return -1, ENAMETOOLONG
+ }
+ r1, r2, err := Syscall(SYS_LSTAT64, int64(uintptr(unsafe.Pointer(&namebuf[0]))), int64(uintptr(unsafe.Pointer(buf))), 0);
func Lstat(name *byte, buf *Stat_t)からfunc Lstat(name string, buf *Stat_t)へ:name引数の型が*byte(バイトポインタ) からstring(Goの文字列型) に変更されました。これにより、Goの慣習に沿った文字列の受け渡しが可能になり、より安全で扱いやすくなりました。
var namebuf [nameBufsize]byte;:nameBufsizeは、ファイル名を格納するための固定サイズのバイト配列の最大長を定義する定数です。Goの文字列をシステムコールに渡すために、この一時的なバイト配列が宣言されます。
if !StringToBytes(namebuf, name) { return -1, ENAMETOOLONG }:StringToBytesは、Goのstring型のnameを、C言語スタイルのヌル終端バイト配列としてnamebufにコピーする内部ヘルパー関数です。- この関数が
falseを返した場合、それはnameがnameBufsizeよりも長すぎてnamebufに収まらないことを意味します。この場合、関数はエラーコードENAMETOOLONGと-1を返して、ファイル名が長すぎるというエラーを適切に通知します。
r1, r2, err := Syscall(SYS_LSTAT64, int64(uintptr(unsafe.Pointer(&namebuf[0]))), int64(uintptr(unsafe.Pointer(buf))), 0);:- システムコールが
SYS_LSTATからSYS_LSTAT64に変更されました。これは、Darwin環境で64ビット対応のlstatシステムコールを使用することを意味し、より堅牢なファイル情報取得を可能にします。 int64(uintptr(unsafe.Pointer(&namebuf[0])))は、namebufの先頭アドレスをuintptrにキャストし、さらにint64にキャストしてシステムコールの引数として渡しています。これにより、システムコールはヌル終端されたファイル名バイト配列を正しく受け取ることができます。
- システムコールが
Fstat 関数の変更
func Fstat(fd int64, buf *Stat_t) (ret int64, errno int64) {
- r1, r2, err := Syscall(SYS_FSTAT, fd, int64(uintptr(unsafe.Pointer(buf))), 0);
+ r1, r2, err := Syscall(SYS_FSTAT64, fd, int64(uintptr(unsafe.Pointer(buf))), 0);
return r1, err;
}
Syscall(SYS_FSTAT, ...)からSyscall(SYS_FSTAT64, ...)へ:fstat関数が呼び出すシステムコールがSYS_FSTATからSYS_FSTAT64に変更されました。これはlstatの変更と同様に、Darwin環境で64ビット対応のfstatシステムコールを使用することで、特に大きなファイルを扱う際の正確性と互換性を向上させるための修正です。
これらの変更により、Go言語の syscall パッケージはDarwin環境において、ファイル情報の取得をより正確かつ堅牢に行えるようになりました。
関連リンク
- Go言語の
syscallパッケージに関する公式ドキュメント (当時のバージョンに近いもの):- Go 1.0
syscallpackage: https://pkg.go.dev/syscall@go1.0 (これは現在の最新版ですが、当時のAPIの雰囲気を掴むのに役立ちます)
- Go 1.0
- UNIX/Linux
statmanページ (概念理解のため): https://man7.org/linux/man-pages/man2/stat.2.html - Darwin (macOS) のシステムコールに関する情報 (より詳細な技術情報):
sys/syscall.h(macOSのシステムコール定義): https://opensource.apple.com/source/xnu/xnu-1504.3.12/bsd/sys/syscall.h.auto.html (当時のバージョンとは異なる可能性がありますが、参考になります)
参考にした情報源リンク
- Go言語のソースコード (特に
src/lib/syscallディレクトリの歴史): https://github.com/golang/go/tree/master/src/syscall - Go言語の
unsafeパッケージに関するドキュメント: https://pkg.go.dev/unsafe - Go言語の
uintptrに関するドキュメント: https://pkg.go.dev/builtin#uintptr - UNIX系OSにおける
stat,lstat,fstatの違いに関する一般的な解説記事。 - Darwin (macOS) のシステムコールに関する一般的な情報源。
ENAMETOOLONGエラーに関する一般的な情報源。