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

[インデックス 16271] ファイルの概要

このコミットは、Go言語のsyscallパッケージにおけるFchflags関数のプロトタイプ(シグネチャ)の修正に関するものです。特に、Darwin(macOS)およびBSD系のオペレーティングシステム(FreeBSD, NetBSD, OpenBSD)におけるFchflags関数の第一引数が、誤ってファイルパス(string)を受け取るように定義されていたのを、正しいファイルディスクリプタ(int)を受け取るように変更しています。これはAPIの変更を伴いますが、既存のAPIが明らかに誤っていたため、Go 1の互換性ルールに則って許容される修正とされています。

コミット

commit 6de184b385c8a15095cab925f50980e7c62f2ec3
Author: Shenghou Ma <minux.ma@gmail.com>
Date:   Tue May 7 05:20:00 2013 +0800

    syscall: fix prototype of Fchflags (API change)
    API change, but the old API is obviously wrong.
    
    R=golang-dev, iant, r, rsc
    CC=golang-dev
    https://golang.org/cl/9157044

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/6de184b385c8a15095cab925f50980e7c62f2ec3

元コミット内容

syscall: fix prototype of Fchflags (API change) API change, but the old API is obviously wrong.

(日本語訳) syscall: Fchflagsのプロトタイプを修正 (API変更) API変更だが、古いAPIは明らかに間違っていた。

変更の背景

この変更の背景には、Go言語のsyscallパッケージが提供するFchflags関数が、基盤となるオペレーティングシステムのシステムコールfchflags(2)の正しいセマンティクスを反映していなかったという問題があります。

Unix系システムにおけるfchflags(2)システムコールは、特定のファイルディスクリプタ(開いているファイルやディレクトリを参照する整数値)に関連付けられたファイルフラグを変更するために使用されます。しかし、Goのsyscallパッケージでは、このFchflags関数が誤ってファイルパス(string)を第一引数として受け取るように定義されていました。これは、ファイルパスを受け取るchflags(2)システムコールと混同されたか、あるいは実装上の誤りであったと考えられます。

Go 1の互換性保証は非常に厳格であり、通常、既存のAPIのシグネチャを変更することは許されません。しかし、このケースでは「古いAPIが明らかに間違っていた」という明確な理由があったため、バグ修正としてAPI変更が特別に許可されました。この修正は、Fchflags関数がその意図されたシステムコールであるfchflags(2)と正しく対応するようにするために不可欠でした。

前提知識の解説

Go言語のsyscallパッケージ

Go言語の標準ライブラリにはsyscallパッケージが含まれています。このパッケージは、オペレーティングシステムが提供する低レベルのシステムコールへのアクセスを提供します。これにより、Goプログラムはファイル操作、プロセス管理、ネットワーク通信など、OSカーネルが提供する機能と直接対話することができます。syscallパッケージはOSに依存する部分が多く、各OS(Linux, macOS, Windows, BSDなど)向けに異なる実装が提供されています。

fchflags(2)chflags(2)システムコール

Unix系システム、特にBSD系OS(macOS, FreeBSD, NetBSD, OpenBSDなど)には、ファイルやディレクトリの「フラグ」を操作するためのシステムコールが存在します。これらのフラグは、ファイルの挙動を制御する属性(例: 不変、追加のみ、隠しファイルなど)を定義します。

  • chflags(2): ファイルパス(path)を指定して、そのファイルのフラグを変更します。
  • fchflags(2): 開いているファイルディスクリプタ(fd)を指定して、そのファイルのフラグを変更します。

このコミットの核心は、GoのFchflagsfchflags(2)に対応するはずなのに、誤ってchflags(2)のようにパスを受け取っていた点にあります。

Go 1 互換性保証

Go言語は、バージョン1.0以降、厳格な互換性保証を維持しています。これは、Go 1で書かれたプログラムは、将来のGoのバージョンでもコンパイルされ、動作し続けることを意味します。この保証は、Goエコシステムの安定性と信頼性の基盤となっています。 しかし、この互換性保証には例外があります。例えば、セキュリティ上の脆弱性の修正や、既存のAPIが「明らかに間違っている」場合のバグ修正など、正当な理由がある場合には、APIの変更が許容されることがあります。今回のFchflagsのケースは、後者の「明らかに間違っている」バグ修正に該当します。

//sysディレクティブとzsyscall_*.goファイルの生成

Goのsyscallパッケージでは、システムコールをGoの関数として定義するために、特別なコメントディレクティブ//sysが使用されます。このディレクティブは、mksyscall.goなどのツールによって解析され、実際のシステムコール呼び出しを行うための低レベルなGoコード(通常はzsyscall_*.goという命名規則のファイル)が自動生成されます。 例えば、//sys Fchflags(fd int, flags int) (err error)のような行は、FchflagsというGo関数が、fdflagsという2つの整数引数を取り、エラーを返すシステムコールに対応することをツールに伝えます。

技術的詳細

このコミットの技術的な詳細は、GoのsyscallパッケージがどのようにOSのシステムコールをラップしているか、そしてそのラッパー関数がどのように生成されるかという点に集約されます。

元の実装では、syscall_darwin.goなどのOS固有のファイルでFchflagsが以下のように定義されていました。

//sys Fchflags(path string, flags int) (err error)

この//sysディレクティブは、mksyscallツールに対して、Fchflags関数がpathという文字列とflagsという整数を受け取るように指示していました。しかし、対応するシステムコールであるfchflags(2)は、第一引数としてファイルディスクリプタ(整数)を期待します。

この不一致のため、mksyscallによって生成されるzsyscall_*.goファイルでは、文字列のpathをシステムコールに渡すために、BytePtrFromString関数とunsafe.Pointerが使用されていました。

func Fchflags(path string, flags int) (err error) {
	var _p0 *byte
	_p0, err = BytePtrFromString(path) // 文字列をバイトポインタに変換
	if err != nil {
		return
	}
	_, _, e1 := Syscall(SYS_FCHFLAGS, uintptr(unsafe.Pointer(_p0)), uintptr(flags), 0) // unsafe.Pointerでシステムコールに渡す
	if e1 != 0 {
		err = e1
	}
	return
}

これは、fchflags(2)がファイルディスクリプタを期待しているにもかかわらず、Goのコードがファイルパスのバイトポインタを渡そうとしていたことを意味します。結果として、この関数は意図した通りに動作せず、誤った引数でシステムコールを呼び出すことになり、バグの原因となっていました。

今回の修正では、//sysディレクティブの定義を以下のように変更しました。

//sys Fchflags(fd int, flags int) (err error)

これにより、mksyscallツールはFchflags関数がfdという整数とflagsという整数を受け取るようにコードを生成するようになります。生成されるzsyscall_*.goファイルも、BytePtrFromStringunsafe.Pointerを使用せず、直接fdをシステムコールに渡すように変更されました。

func Fchflags(fd int, flags int) (err error) {
	_, _, e1 := Syscall(SYS_FCHFLAGS, uintptr(fd), uintptr(flags), 0) // fdを直接システムコールに渡す
	if e1 != 0 {
		err = e1
	}
	return
}

この変更により、GoのFchflags関数は、基盤となるfchflags(2)システムコールの正しいセマンティクスと一致するようになり、機能的なバグが修正されました。api/except.txtへの追加は、このAPI変更がGo 1の互換性保証の例外として扱われることを明示しています。また、doc/go1.1.htmlの更新は、この変更がGo 1.1リリースノートに記載され、ユーザーに周知されることを示しています。

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

このコミットにおけるコアとなるコードの変更は、主に以下のファイル群にわたります。

  1. api/except.txt:

    • pkg syscall (darwin-386), func Fchflags(string, int) error
    • pkg syscall (darwin-386-cgo), func Fchflags(string, int) error
    • pkg syscall (darwin-amd64), func Fchflags(string, int) error
    • pkg syscall (darwin-amd64-cgo), func Fchflags(string, int) error
    • pkg syscall (freebsd-386), func Fchflags(string, int) error
    • pkg syscall (freebsd-amd64), func Fchflags(string, int) error これらの行が追加され、Fchflags関数の古いシグネチャがAPI互換性チェックの例外として登録されました。
  2. doc/go1.1.html:

    • Go 1.1のリリースノートに、syscallパッケージのFchflags関数のシグネチャ変更が記載されました。
      • 変更前: The <code>syscall</code> package has received many updates to make it more inclusive of constants and system calls for each supported operating system.
      • 変更後: The <code>syscall</code> package's <code>Fchflags</code> function on various BSDs (including Darwin) has changed signature. It now takes an int as the first parameter instead of a string. Since this API change fixes a bug, it is permitted by the Go 1 compatibility rules.
  3. src/pkg/syscall/syscall_darwin.go, src/pkg/syscall/syscall_freebsd.go, src/pkg/syscall/syscall_netbsd.go, src/pkg/syscall/syscall_openbsd.go:

    • これらのファイル内で、Fchflags関数の//sysディレクティブの定義が変更されました。
      • 変更前: //sys Fchflags(path string, flags int) (err error)
      • 変更後: //sys Fchflags(fd int, flags int) (err error)
  4. src/pkg/syscall/zsyscall_darwin_386.go, src/pkg/syscall/zsyscall_darwin_amd64.go, src/pkg/syscall/zsyscall_freebsd_386.go, src/pkg/syscall/zsyscall_freebsd_amd64.go, src/pkg/syscall/zsyscall_netbsd_386.go, src/pkg/syscall/zsyscall_netbsd_amd64.go, src/pkg/syscall/zsyscall_openbsd_386.go, src/pkg/syscall/zsyscall_openbsd_amd64.go:

    • これらの自動生成されたファイル内で、Fchflags関数の実装が変更されました。
      • 変更前は、path stringを受け取り、BytePtrFromStringunsafe.Pointerを使用してシステムコールに渡していました。
      • 変更後は、fd intを受け取り、fdを直接システムコールに渡すようになりました。これにより、BytePtrFromStringunsafe.Pointerに関するコードが削除されました。

コアとなるコードの解説

このコミットの核心は、GoのsyscallパッケージがOSのシステムコールをどのように抽象化し、そしてその抽象化がどのように修正されたかという点にあります。

Goのsyscallパッケージでは、各OSのシステムコールに対応するGo関数を定義するために、//sysという特別なコメントディレクティブを使用します。例えば、src/pkg/syscall/syscall_darwin.goには、Darwin(macOS)向けのシステムコール定義が含まれています。

変更前:

//sys Fchflags(path string, flags int) (err error)

この行は、FchflagsというGo関数が、第一引数にstring型のpath、第二引数にint型のflagsを取り、errorを返すことを示しています。この//sysディレクティブは、mksyscallというツールによって解析され、実際のシステムコールを呼び出すための低レベルなGoコードが自動生成されます。

自動生成されるファイル(例: src/pkg/syscall/zsyscall_darwin_386.go)では、この定義に基づいて以下のようなコードが生成されていました。

func Fchflags(path string, flags int) (err error) {
	var _p0 *byte
	_p0, err = BytePtrFromString(path) // 文字列をバイトポインタに変換
	if err != nil {
		return
	}
	// SYS_FCHFLAGS は fchflags(2) システムコールに対応する定数
	_, _, e1 := Syscall(SYS_FCHFLAGS, uintptr(unsafe.Pointer(_p0)), uintptr(flags), 0)
	if e1 != 0 {
		err = e1
	}
	return
}

ここで問題だったのは、fchflags(2)システムコールが期待するのはファイルディスクリプタ(整数)であるにもかかわらず、Goのコードがファイルパスのバイトポインタを渡そうとしていた点です。BytePtrFromStringはGoの文字列をCスタイルのヌル終端バイト配列へのポインタに変換し、unsafe.Pointerはそのポインタをuintptrに変換してシステムコールに渡していました。これは、fchflags(2)の引数型と一致しないため、誤った動作を引き起こしていました。

変更後:

//sys Fchflags(fd int, flags int) (err error)

この修正により、//sysディレクティブは、Fchflags関数が第一引数にint型のfd(ファイルディスクリプタ)を受け取るように変更されました。

この変更後、mksyscallによって自動生成されるコードは以下のようになります。

func Fchflags(fd int, flags int) (err error) {
	// SYS_FCHFLAGS は fchflags(2) システムコールに対応する定数
	_, _, e1 := Syscall(SYS_FCHFLAGS, uintptr(fd), uintptr(flags), 0)
	if e1 != 0 {
		err = e1
	}
	return
}

この新しいコードでは、fd(ファイルディスクリプタ)が直接uintptrに変換され、SYS_FCHFLAGSシステムコールに渡されます。これにより、GoのFchflags関数は、基盤となるfchflags(2)システムコールの正しいセマンティクスと完全に一致するようになり、バグが修正されました。

この修正は、GoのsyscallパッケージがOSの低レベルなインターフェースを正確に反映することの重要性を示しています。また、Go 1の互換性保証が厳格である一方で、明らかなバグ修正のためにはAPI変更も許容されるという柔軟性も示しています。

関連リンク

参考にした情報源リンク