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

[インデックス 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つの問題がありました。

  1. fstat のシステムコール誤り: fstat はファイルディスクリプタ(開いているファイルの識別子)に基づいてファイルのステータス情報を取得するシステムコールですが、Goの実装では誤ったシステムコール番号が指定されていた可能性があります。これにより、正しいファイル情報が取得できない、あるいは予期せぬエラーが発生する可能性がありました。
  2. 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言語スタイルのバイト配列間の変換に焦点を当てています。

  1. fstat の修正:

    • 変更前: Syscall(SYS_FSTAT, ...)
    • 変更後: Syscall(SYS_FSTAT64, ...) この変更は、fstat が使用するシステムコールを SYS_FSTAT から SYS_FSTAT64 に変更しています。これは、Darwinシステムにおいて、ファイルディスクリプタからファイル情報を取得する際に、より新しい(そしておそらく64ビット対応の)システムコールを使用することで、潜在的な互換性問題やファイルサイズに関する制限を回避することを目的としています。特に、大きなファイルを扱う際に正確な情報を取得するために重要です。
  2. 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言語スタイルのヌル終端バイト配列を期待するためです。
      • ファイル名が長すぎる場合のハンドリング: StringToBytesfalse を返した場合(つまり、ファイル名が 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 を返した場合、それは namenameBufsize よりも長すぎて 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環境において、ファイル情報の取得をより正確かつ堅牢に行えるようになりました。

関連リンク

参考にした情報源リンク