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

[インデックス 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関数の追加です。

  1. 既存コメントの整理:

    • 以前の汎用的な「すべてのUnixesで基本的なシステムコールが一貫していることをテストする」というコメントが、より具体的な「以下の関数、構造体、定数がすべてのUnix系システムで一貫していることをテストする」という記述に更新されました。
    • Setpriority/Getpriorityおよびtermios関連の定数に関するコメントも、それぞれのセクションの前に移動され、より明確になりました。
  2. Flock_t構造体とfcntl定数の明示的な型チェック:

    • Flock_t構造体の定義と、F_GETLK, F_SETLK, F_SETLKWといったfcntl関連の定数が、無名関数func _() {}ブロック内で明示的に参照されるようになりました。これは、これらの型や定数がコンパイル時に存在し、期待される型であることを保証するためのGoの慣用的な方法です。以前はFlock_tのみが別の無名関数ブロックで参照されていましたが、fcntl関連の定数と合わせて一箇所にまとめられました。
  3. TestFcntlFlock関数の追加:

    • この関数は、実際にfcntlシステムコールを呼び出し、Flock_t構造体がカーネルの期待する形式と一致するかどうかを検証します。
    • 一時ファイルの作成: os.TempDir()filepath.Joinを使用して、テスト用の一時ファイルを作成します。これにより、テストがシステムに副作用を与えないようにします。
    • ファイルのオープン: syscall.Openを使用して、O_CREAT(ファイルが存在しない場合は作成)、O_RDWR(読み書きモード)、O_CLOEXECexec時にファイルディスクリプタをクローズ)フラグを指定してファイルを開きます。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: 1SEEK_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ファイルにおいて、以下の変更が行われました。

  1. コメントの変更と整理:

    • ファイルの冒頭のコメントがより具体的になりました。
    • Setpriority/Getprioritytermiosfcntl関連の定数と構造体に関するコメントが、それぞれの定義の直前に移動されました。
  2. Flock_t構造体とfcntl定数の型チェックの統合:

    • 以前は別の無名関数ブロックにあったFlock_tの型チェックが、fcntl関連の定数(F_GETLK, F_SETLK, F_SETLKW)の型チェックと同じ無名関数ブロック内に統合されました。
  3. 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言語の公式ドキュメント
  • Unix/Linuxのmanページ (fcntl(2), flock(2))
  • Go言語のソースコード (src/pkg/syscall/syscall_unix_test.go)
  • Go言語のコードレビューシステム (Gerrit) の変更リスト: https://golang.org/cl/58660044