[インデックス 18604] ファイルの概要
このコミットは、Go言語のsyscall
パッケージ内のsyscall_unix_test.go
ファイルに対する変更です。このファイルは、Goがサポートする様々なUnix系オペレーティングシステムにおいて、システムコールが期待通りに一貫して動作するかどうかを検証するためのテストコードを含んでいます。具体的には、このコミットではfcntl
システムコール、特にファイルロック機能に関するテストが追加され、関連するドキュメント(コメント)が更新されています。
コミット
commit 699aa37d033b6f94f106152aaf17d05849fed2dd
Author: Mikio Hara <mikioh.mikioh@gmail.com>
Date: Mon Feb 24 20:35:01 2014 +0900
syscall: add fcntl test
Also updates documentation.
LGTM=minux.ma
R=iant, bradfitz, nightlyone, minux.ma
CC=golang-codereviews
https://golang.org/cl/58660044
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/699aa37d033b6f94f106152aaf17d05849fed2dd
元コミット内容
syscall: add fcntl test
Also updates documentation.
LGTM=minux.ma
R=iant, bradfitz, nightlyone, minux.ma
CC=golang-codereviews
https://golang.org/cl/58660044
変更の背景
このコミットの主な背景は、Go言語のsyscall
パッケージが提供するfcntl
システムコール、特にファイルロック機能のクロスプラットフォーム(Unix系OS間)での一貫性と正確性を保証することにあります。
Go言語は、様々なオペレーティングシステム上で動作するように設計されており、そのためにOS固有の機能にアクセスするためのsyscall
パッケージを提供しています。しかし、同じシステムコールであっても、異なるOS(Linux, FreeBSD, macOSなど)ではその動作や引数の解釈、関連するデータ構造のメモリレイアウトなどが微妙に異なる場合があります。
以前のコードでは、Flock_t
構造体やfcntl
関連の定数が存在することを確認するだけの「型チェック」のような記述はありましたが、実際にそれらがカーネルの呼び出し規約と一致し、期待通りに機能するかどうかを検証する具体的なテストケースが不足していました。
このコミットは、fcntl
システムコールを用いたファイルロックが、各Unix系カーネルの呼び出し規約に正しく適合しているかを検証するテストを追加することで、このギャップを埋め、Goプログラムが異なるUnix系環境でファイルロックを安全かつ確実に利用できるようにすることを目的としています。また、関連するコメントを更新し、テストの意図をより明確にしています。
前提知識の解説
1. syscall
パッケージ
Go言語の標準ライブラリの一部であり、低レベルのオペレーティングシステムプリミティブへのアクセスを提供します。ファイル操作、ネットワーク通信、プロセス管理など、OS固有の機能に直接アクセスするために使用されます。C言語のシステムコールに相当する機能を提供しますが、Goの型システムとエラーハンドリングに統合されています。
2. fcntl
システムコール
fcntl
(file control) は、Unix系オペレーティングシステムにおけるシステムコールの一つで、オープンされたファイルディスクリプタの様々な特性を操作するために使用されます。これには、ファイルディスクリプタのフラグの変更、ファイルステータスフラグの取得/設定、非同期I/Oの制御、そしてこのコミットで焦点となっているファイルロックの管理などが含まれます。
3. ファイルロック
ファイルロックは、複数のプロセスが同時に同じファイルにアクセスする際に、データの破損や競合状態を防ぐためのメカニズムです。fcntl
システムコールを通じて提供されるファイルロックには、主に以下の2種類があります。
- 共有ロック (Shared Lock / Read Lock): 複数のプロセスが同時にファイルに読み込みアクセスすることを許可しますが、書き込みアクセスはブロックします。
- 排他ロック (Exclusive Lock / Write Lock): 一度に一つのプロセスのみがファイルにアクセスすることを許可します。他のプロセスからの読み込みも書き込みもブロックします。
ファイルロックは、ファイル全体またはファイルの一部(バイト範囲ロック)に対して設定できます。
4. Flock_t
構造体
fcntl
システムコールでファイルロックを操作する際に使用されるデータ構造です。この構造体は、ロックの種類、ロックを適用するファイルのオフセット、長さ、そしてロックを保持しているプロセスのIDなどの情報を含みます。Unix系OSによってフィールドの順序やサイズが異なる場合があるため、Goのsyscall
パッケージでは各OSの規約に合わせてこの構造体を定義し、一貫したインターフェースを提供する必要があります。
主要なフィールドは以下の通りです。
Type
: ロックの種類(F_RDLCK
:読み込みロック、F_WRLCK
:書き込みロック、F_UNLCK
:ロック解除)。Whence
: ロック範囲の開始位置の基準(SEEK_SET
:ファイルの先頭から、SEEK_CUR
:現在のファイルポインタから、SEEK_END
:ファイルの末尾から)。Start
: ロック範囲の開始オフセット。Len
: ロック範囲の長さ。0はファイルの現在のオフセットからファイルの最後までを意味します。Pid
: ロックを保持しているプロセスのID(F_GETLK
でロック情報を取得する際に設定されます)。
5. F_GETLK
, F_SETLK
, F_SETLKW
これらはfcntl
システムコールで使用されるコマンド(操作コード)です。
F_GETLK
: 指定されたロックがファイルに設定されているかどうかをチェックします。競合するロックが存在する場合、そのロック情報(Flock_t
構造体)を返します。F_SETLK
: ロックを設定または解除します。ロックがすぐに取得できない場合、エラーを返します。F_SETLKW
: ロックを設定または解除します。ロックがすぐに取得できない場合、ロックが取得できるまでプロセスをブロックします(待機します)。
6. Goのテストフレームワーク (testing
パッケージ)
Go言語には、ユニットテストやベンチマークテストを記述するための組み込みのtesting
パッケージがあります。テスト関数はTest
で始まり、*testing.T
型の引数を取ります。t.Fatalf
はテストが失敗した場合にメッセージを出力し、テストを終了させます。
技術的詳細
このコミットは、syscall_unix_test.go
ファイルに焦点を当てています。このファイルは、Goのsyscall
パッケージがUnix系OS(FreeBSD, Dragonfly, Darwin, Linux, NetBSD, OpenBSD)で正しく動作することを保証するためのテストを含んでいます。
変更の核心は、fcntl
システムコールとFlock_t
構造体の整合性を検証するTestFcntlFlock
関数の追加です。
-
既存コメントの整理:
- 以前の汎用的な「すべてのUnixesで基本的なシステムコールが一貫していることをテストする」というコメントが、より具体的な「以下の関数、構造体、定数がすべてのUnix系システムで一貫していることをテストする」という記述に更新されました。
Setpriority
/Getpriority
およびtermios
関連の定数に関するコメントも、それぞれのセクションの前に移動され、より明確になりました。
-
Flock_t
構造体とfcntl
定数の明示的な型チェック:Flock_t
構造体の定義と、F_GETLK
,F_SETLK
,F_SETLKW
といったfcntl
関連の定数が、無名関数func _() {}
ブロック内で明示的に参照されるようになりました。これは、これらの型や定数がコンパイル時に存在し、期待される型であることを保証するためのGoの慣用的な方法です。以前はFlock_t
のみが別の無名関数ブロックで参照されていましたが、fcntl
関連の定数と合わせて一箇所にまとめられました。
-
TestFcntlFlock
関数の追加:- この関数は、実際に
fcntl
システムコールを呼び出し、Flock_t
構造体がカーネルの期待する形式と一致するかどうかを検証します。 - 一時ファイルの作成:
os.TempDir()
とfilepath.Join
を使用して、テスト用の一時ファイルを作成します。これにより、テストがシステムに副作用を与えないようにします。 - ファイルのオープン:
syscall.Open
を使用して、O_CREAT
(ファイルが存在しない場合は作成)、O_RDWR
(読み書きモード)、O_CLOEXEC
(exec
時にファイルディスクリプタをクローズ)フラグを指定してファイルを開きます。O_CLOEXEC
は、子プロセスにファイルディスクリプタが漏洩するのを防ぐための重要なフラグです。 - リソースのクリーンアップ:
defer syscall.Unlink(name)
とdefer syscall.Close(fd)
により、テスト終了時に作成した一時ファイルを削除し、ファイルディスクリプタを閉じることが保証されます。 Flock_t
の初期化:syscall.Flock_t
型の変数を初期化し、読み込みロック(syscall.F_RDLCK
)を要求する設定を行います。Start: 0, Len: 0, Whence: 1
は、ファイルの先頭からファイル全体に対してロックを適用しようとしていることを意味します(Whence: 1
はSEEK_CUR
に相当し、Start: 0
と組み合わせることで実質的にファイルの先頭からの相対オフセット0を意味します。Len: 0
はファイルの最後までを意味します)。syscall.FcntlFlock
の呼び出し:syscall.FcntlFlock
関数を呼び出します。この関数は、ファイルディスクリプタ、fcntl
コマンド(ここではsyscall.F_GETLK
)、およびFlock_t
構造体へのポインタを引数に取ります。F_GETLK
は、指定されたロック(ここでは読み込みロック)がファイルに設定されているかどうかをチェックし、競合するロックがあればその情報をflock
構造体に書き込みます。このテストの目的は、F_GETLK
がエラーなく実行できること、つまりFlock_t
構造体のレイアウトがカーネルの期待するものと一致していることを確認することです。- エラーハンドリング:
syscall.FcntlFlock
がエラーを返した場合、t.Fatalf
を呼び出してテストを失敗させます。これは、Flock_t
構造体の定義がOSの期待するものと異なっている可能性を示唆します。
- この関数は、実際に
このテストは、Goのsyscall
パッケージが提供するFlock_t
構造体とFcntlFlock
関数が、基盤となるUnix系OSのfcntl
システムコールの呼び出し規約(特にF_GETLK
コマンドとFlock_t
のメモリレイアウト)に正しく適合していることを保証するための重要な検証ステップです。
コアとなるコードの変更箇所
src/pkg/syscall/syscall_unix_test.go
ファイルにおいて、以下の変更が行われました。
-
コメントの変更と整理:
- ファイルの冒頭のコメントがより具体的になりました。
Setpriority
/Getpriority
、termios
、fcntl
関連の定数と構造体に関するコメントが、それぞれの定義の直前に移動されました。
-
Flock_t
構造体とfcntl
定数の型チェックの統合:- 以前は別の無名関数ブロックにあった
Flock_t
の型チェックが、fcntl
関連の定数(F_GETLK
,F_SETLK
,F_SETLKW
)の型チェックと同じ無名関数ブロック内に統合されました。
- 以前は別の無名関数ブロックにあった
-
TestFcntlFlock
関数の新規追加:- ファイルロック機能の実際の動作を検証するためのテスト関数が追加されました。
--- a/src/pkg/syscall/syscall_unix_test.go
+++ b/src/pkg/syscall/syscall_unix_test.go
@@ -4,9 +4,6 @@
// +build freebsd dragonfly darwin linux netbsd openbsd
-// This file tests that some basic syscalls are consistent across
-// all Unixes.
-
package syscall_test
import (
@@ -16,14 +13,17 @@ import (
"net"
"os"
"os/exec"
+ "path/filepath"
"runtime"
"syscall"
"testing"
"time"
)
-// {Set,Get}priority and needed constants for them
+// Tests that below functions, structures and constants are consistent
+// on all Unix-like systems.
func _() {
+ // program scheduling priority functions and constants
var (
_ func(int, int, int) error = syscall.Setpriority
_ func(int, int) (int, error) = syscall.Getpriority
@@ -33,24 +33,47 @@ func _() {
_ int = syscall.PRIO_PROCESS
_ int = syscall.PRIO_PGRP
)
-}
-// termios functions and constants
-func _() {
+ // termios constants
const (
_ int = syscall.TCIFLUSH
_ int = syscall.TCIOFLUSH
_ int = syscall.TCOFLUSH
)
+
+ // fcntl file locking structure and constants
+ var (
+ _ = syscall.Flock_t{
+ Type: int16(0),
+ Whence: int16(0),
+ Start: int64(0),
+ Len: int64(0),
+ Pid: int32(0),
+ }
+ )
+ const (
+ _ = syscall.F_GETLK
+ _ = syscall.F_SETLK
+ _ = syscall.F_SETLKW
+ )
}
-func _() {
- _ = syscall.Flock_t{
- Type: int16(0),
- Whence: int16(0),
- Start: int64(0),
- Len: int64(0),
- Pid: int32(0),
+// TestFcntlFlock tests whether the file locking structure matches
+// the calling convention of each kernel.
+func TestFcntlFlock(t *testing.T) {
+ name := filepath.Join(os.TempDir(), "TestFcntlFlock")
+ fd, err := syscall.Open(name, syscall.O_CREAT|syscall.O_RDWR|syscall.O_CLOEXEC, 0)
+ if err != nil {
+ t.Fatalf("Open failed: %v", err)
+ }
+ defer syscall.Unlink(name)
+ defer syscall.Close(fd)
+ flock := syscall.Flock_t{
+ Type: syscall.F_RDLCK,
+ Start: 0, Len: 0, Whence: 1,
}
+ if err := syscall.FcntlFlock(uintptr(fd), syscall.F_GETLK, &flock); err != nil {
+ t.Fatalf("FcntlFlock failed: %v", err)
+ }
}
コアとなるコードの解説
このコミットで追加された最も重要な部分は、TestFcntlFlock
関数です。
// TestFcntlFlock tests whether the file locking structure matches
// the calling convention of each kernel.
func TestFcntlFlock(t *testing.T) {
// 一時ファイルの名前を生成。os.TempDir()はOSの一時ディレクトリを返す。
// filepath.JoinはOSに応じたパス区切り文字でパスを結合する。
name := filepath.Join(os.TempDir(), "TestFcntlFlock")
// 一時ファイルをオープンする。
// syscall.O_CREAT: ファイルが存在しない場合は作成する。
// syscall.O_RDWR: 読み書きモードで開く。
// syscall.O_CLOEXEC: exec()システムコールが実行された際に、このファイルディスクリプタを自動的にクローズする。
// これにより、子プロセスにファイルディスクリプタが意図せず継承されるのを防ぐ。
// 0: ファイルのパーミッション(ここではデフォルト値を使用、またはumaskによって決定される)。
fd, err := syscall.Open(name, syscall.O_CREAT|syscall.O_RDWR|syscall.O_CLOEXEC, 0)
if err != nil {
// ファイルのオープンに失敗した場合、テストを致命的なエラーとして終了させる。
t.Fatalf("Open failed: %v", err)
}
// defer文により、関数が終了する際に以下の処理が実行されることを保証する。
// syscall.Unlink(name): 作成した一時ファイルを削除する。
defer syscall.Unlink(name)
// syscall.Close(fd): オープンしたファイルディスクリプタをクローズする。
defer syscall.Close(fd)
// Flock_t構造体を初期化する。
// Type: syscall.F_RDLCK は読み込みロック(共有ロック)を要求することを示す。
// Start: 0, Len: 0 は、ファイルの先頭からファイル全体に対してロックを適用することを示す。
// Whence: 1 は SEEK_CUR (現在のファイルポインタからの相対オフセット) を意味する。
// Start: 0 と組み合わせることで、実質的にファイルの先頭からのオフセット0を意味する。
flock := syscall.Flock_t{
Type: syscall.F_RDLCK,
Start: 0, Len: 0, Whence: 1,
}
// syscall.FcntlFlockを呼び出す。
// uintptr(fd): ファイルディスクリプタをuintptr型にキャストして渡す。
// syscall.F_GETLK: fcntlシステムコールに、指定されたロックがファイルに設定されているかチェックするよう指示する。
// &flock: Flock_t構造体へのポインタを渡す。F_GETLKは競合するロックがあればその情報をこの構造体に書き込む。
// このテストの目的は、F_GETLKがエラーなく実行できること、つまりFlock_t構造体のレイアウトが
// 各OSのカーネルの期待するものと一致していることを確認することにある。
if err := syscall.FcntlFlock(uintptr(fd), syscall.F_GETLK, &flock); err != nil {
// FcntlFlockの呼び出しがエラーを返した場合、テストを致命的なエラーとして終了させる。
// これは、Flock_t構造体の定義がOSの期待するものと異なっている可能性が高い。
t.Fatalf("FcntlFlock failed: %v", err)
}
}
このテストは、Goのsyscall
パッケージが提供するFlock_t
構造体のメモリレイアウトが、各Unix系OSのカーネルが期待するflock
構造体(C言語のstruct flock
)のレイアウトと正確に一致していることを検証します。もし一致していなければ、FcntlFlock
システムコールはエラーを返すか、予期せぬ動作をする可能性があります。
具体的には、F_GETLK
コマンドを使用して、ファイルに読み込みロックを試みます。このコマンドは、実際にロックを設定するのではなく、指定されたロックが既存のロックと競合するかどうかをチェックします。もし競合するロックが存在すれば、そのロックの情報がflock
構造体に書き込まれます。このテストでは、競合の有無自体よりも、FcntlFlock
がエラーなく呼び出せること、つまりGoのFlock_t
定義がOSのシステムコールインターフェースと互換性があることを確認しています。
関連リンク
- Go言語
syscall
パッケージのドキュメント: https://pkg.go.dev/syscall fcntl
システムコール (Linux man page): https://man7.org/linux/man-pages/man2/fcntl.2.htmlflock
構造体 (Linux man page): https://man7.org/linux/man-pages/man2/flock.2.html (注意:flock
システムコールとfcntl
のF_SETLK
/F_GETLK
は異なるインターフェースですが、概念は関連しています。)
参考にした情報源リンク
- Go言語の公式ドキュメント
- Unix/Linuxのmanページ (
fcntl(2)
,flock(2)
) - Go言語のソースコード (
src/pkg/syscall/syscall_unix_test.go
) - Go言語のコードレビューシステム (Gerrit) の変更リスト: https://golang.org/cl/58660044