[インデックス 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
syscall
package: https://pkg.go.dev/syscall@go1.0 (これは現在の最新版ですが、当時のAPIの雰囲気を掴むのに役立ちます)
- Go 1.0
- UNIX/Linux
stat
manページ (概念理解のため): 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
エラーに関する一般的な情報源。