[インデックス 12435] ファイルの概要
このコミットは、Go言語の標準ライブラリos
パッケージにおけるWindows環境でのSameFile
関数の不具合修正に関するものです。具体的には、ディレクトリに対してos.SameFile
が正しく動作しない問題を解決しています。
変更されたファイルは以下の通りです。
src/pkg/os/stat_windows.go
:os.SameFile
の実装に関連するWindows固有のシステムコール呼び出しが修正されました。src/pkg/path/filepath/path_test.go
: Windows環境で無効化されていたテストが再度有効化されました。これは、今回の修正によってテストがパスするようになったためと考えられます。
コミット
- コミットハッシュ:
7a3c6c950bddf21d4c39289abe1173dc52f757a7
- 作者: Alex Brainman alex.brainman@gmail.com
- コミット日時: 2012年3月7日(水) 11:01:23 +1100
- コミットメッセージ:
os: fix SameFile to work for directories on windows R=golang-dev, r CC=golang-dev https://golang.org/cl/5756064
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/7a3c6c950bddf21d4c39289abe1173dc52f757a7
元コミット内容
os: fix SameFile to work for directories on windows
R=golang-dev, r
CC=golang-dev
https://golang.org/cl/5756064
変更の背景
Go言語のos
パッケージには、2つのos.FileInfo
インターフェースが同じファイルまたはディレクトリを参照しているかどうかを判断するためのSameFile
関数が存在します。Windows環境において、このSameFile
関数がディレクトリに対して正しく機能しないという問題がありました。
Windowsでは、ファイルやディレクトリの一意性を識別するために、ファイルID(File ID)などの内部的な識別子を使用します。これらの識別子を取得するためには、通常、Windows APIのCreateFile
関数を用いてファイルやディレクトリのハンドル(HFILE)を取得し、そのハンドルを使ってGetFileInformationByHandle
などの関数を呼び出します。
問題は、CreateFile
関数をディレクトリに対して呼び出す際に、特定のフラグが設定されていないと、ディレクトリのハンドルが正しく取得できず、結果としてファイルIDなどの情報も取得できないことにありました。これにより、SameFile
関数がディレクトリの比較において誤った結果を返す可能性がありました。
このコミットは、CreateFile
システムコールを呼び出す際のパラメータを修正することで、ディレクトリに対しても正しくハンドルが取得され、SameFile
が期待通りに動作するようにすることを目的としています。
前提知識の解説
os.SameFile
関数
os.SameFile(fi1, fi2 os.FileInfo) bool
は、Go言語のos
パッケージが提供する関数で、2つのos.FileInfo
インターフェースが同じファイルシステム上のエントリ(ファイルまたはディレクトリ)を参照しているかどうかを判定します。これは、シンボリックリンクの解決後や、異なるパスで同じファイルを参照している場合などに特に有用です。内部的には、ファイルシステムが提供する一意な識別子(Unix系ではinode番号とデバイスID、Windows系ではファイルIDなど)を比較することで実現されます。
Windows API CreateFile
関数
CreateFile
は、Windowsオペレーティングシステムが提供する非常に汎用的なAPI関数で、ファイル、ディレクトリ、パイプ、コンソール、通信ポートなどのオブジェクトを作成または開くために使用されます。この関数は多くのパラメータを持ち、その挙動を細かく制御できます。
主要なパラメータは以下の通りです。
lpFileName
: 開くファイルまたはディレクトリのパス。dwDesiredAccess
: オブジェクトへのアクセス権限を指定します。例としてGENERIC_READ
(読み取りアクセス)、GENERIC_WRITE
(書き込みアクセス)などがあります。dwShareMode
: オブジェクトの共有モードを指定します。他のプロセスが同時にオブジェクトを開くことを許可するかどうかを制御します。lpSecurityAttributes
: セキュリティ記述子へのポインタ。dwCreationDisposition
: ファイルが存在する場合、または存在しない場合の動作を指定します。例としてOPEN_EXISTING
(既存のファイルを開く)などがあります。dwFlagsAndAttributes
: ファイルの属性とフラグを指定します。このパラメータは今回の修正の核心部分です。hTemplateFile
: テンプレートファイルへのハンドル。
syscall.GENERIC_READ
とsyscall.FILE_FLAG_BACKUP_SEMANTICS
-
syscall.GENERIC_READ
: これはdwDesiredAccess
パラメータに指定されるフラグの一つで、オブジェクトに対する読み取りアクセス権を要求します。通常のファイルを開いて内容を読み取る際には一般的に使用されます。 -
syscall.FILE_FLAG_BACKUP_SEMANTICS
: これはdwFlagsAndAttributes
パラメータに指定される重要なフラグです。- ディレクトリのオープン:
CreateFile
関数でディレクトリを開く場合、このフラグを必ず指定する必要があります。このフラグがないと、CreateFile
はディレクトリのハンドルを正しく返さないか、エラーを返す可能性があります。これは、ディレクトリがファイルとは異なる特殊なオブジェクトとして扱われるためです。 - バックアップ操作: このフラグは元々、バックアップアプリケーションがファイルシステムを走査し、ファイルやディレクトリのセキュリティ記述子などの情報を取得するために設計されました。このフラグを使用すると、通常のアクセス権限チェックを一部バイパスして、ディレクトリの内容を列挙したり、ファイル情報を取得したりすることが可能になります。
SameFile
関数がファイルIDを取得するためにディレクトリのハンドルを必要とする場合、このフラグが不可欠となります。
- ディレクトリのオープン:
ファイルIDとファイルハンドル
- ファイルハンドル (HFILE): Windows APIでファイルやI/Oデバイスを操作するために使用される抽象的な参照です。
CreateFile
関数が成功すると、このハンドルが返されます。 - ファイルID (File ID): NTFSファイルシステムにおいて、ファイルやディレクトリを一意に識別するための64ビットの数値です。異なるパスから同じファイルを参照している場合でも、ファイルIDは同じになります。
os.SameFile
は、このファイルIDを比較することで、2つのパスが同じファイルシステム上のエントリを指しているかを判断します。ファイルIDは、ファイルハンドルからGetFileInformationByHandle
などの関数を使って取得されます。
技術的詳細
このコミットの核心は、Windows APIのCreateFile
関数を呼び出す際のパラメータの変更にあります。os.SameFile
関数がWindows上でディレクトリを比較する際、内部的にディレクトリのハンドルを取得し、そのハンドルからファイルIDを抽出する必要があります。
元のコードでは、CreateFile
を呼び出す際にdwDesiredAccess
パラメータにsyscall.GENERIC_READ
を指定し、dwFlagsAndAttributes
パラメータには0
(何もフラグを設定しない)を指定していました。
h, e := syscall.CreateFile(syscall.StringToUTF16Ptr(s.path), syscall.GENERIC_READ, syscall.FILE_SHARE_READ, nil, syscall.OPEN_EXISTING, 0, 0)
この設定は通常のファイルを開く場合には問題ありませんが、ディレクトリを開く場合には不適切です。Windowsのファイルシステムでは、ディレクトリは通常のファイルとは異なる特性を持ちます。特に、ディレクトリのハンドルを取得してそのメタデータ(ファイルIDなど)を問い合わせるためには、CreateFile
のdwFlagsAndAttributes
パラメータにFILE_FLAG_BACKUP_SEMANTICS
フラグを設定する必要があります。このフラグがない場合、CreateFile
はディレクトリに対して有効なハンドルを返さないか、アクセス拒否などのエラーを返すことがあります。
修正後のコードでは、dwDesiredAccess
を0
(アクセス権限を特に要求しない、または後で必要な権限を個別に設定する)に変更し、dwFlagsAndAttributes
にsyscall.FILE_FLAG_BACKUP_SEMANTICS
を追加しています。
h, e := syscall.CreateFile(syscall.StringToUTF16Ptr(s.path), 0, 0, nil, syscall.OPEN_EXISTING, syscall.FILE_FLAG_BACKUP_SEMANTICS, 0)
dwDesiredAccess
を0
に設定することは、ハンドルを取得する目的がデータの読み書きではなく、ファイルやディレクトリのメタデータ(この場合はファイルID)を取得することにある場合に適切です。FILE_FLAG_BACKUP_SEMANTICS
を組み合わせることで、ディレクトリのハンドルを正しく取得し、その後のGetFileInformationByHandle
などの呼び出しでファイルIDを確実に取得できるようになります。これにより、os.SameFile
がディレクトリに対しても正確な比較を行えるようになりました。
また、src/pkg/path/filepath/path_test.go
からWindowsでのTestAbs
のスキップロジックが削除されたのは、このos
パッケージの修正によって、TestAbs
が依存する可能性のあるファイルシステム操作が安定し、テストがパスするようになったためと考えられます。TestAbs
は絶対パスの取得に関するテストであり、直接SameFile
とは関係ありませんが、ファイルシステムとの低レベルな相互作用において、今回の修正が間接的に影響を与えた可能性があります。
コアとなるコードの変更箇所
src/pkg/os/stat_windows.go
--- a/src/pkg/os/stat_windows.go
+++ b/src/pkg/os/stat_windows.go
@@ -199,7 +199,7 @@ func (s *winSys) loadFileId() error {
}\n \ts.Lock()\n \tdefer s.Unlock()\n-\th, e := syscall.CreateFile(syscall.StringToUTF16Ptr(s.path), syscall.GENERIC_READ, syscall.FILE_SHARE_READ, nil, syscall.OPEN_EXISTING, 0, 0)\n+\th, e := syscall.CreateFile(syscall.StringToUTF16Ptr(s.path), 0, 0, nil, syscall.OPEN_EXISTING, syscall.FILE_FLAG_BACKUP_SEMANTICS, 0)\n \tif e != nil {\n \t\treturn e\n \t}\n```
### `src/pkg/path/filepath/path_test.go`
```diff
--- a/src/pkg/path/filepath/path_test.go
+++ b/src/pkg/path/filepath/path_test.go
@@ -666,10 +666,6 @@ var absTests = []string{\n }\n \n func TestAbs(t *testing.T) {\n-\tif runtime.GOOS == \"windows\" {\n-\t\tt.Log(\"TestAbs disabled on windows\")\n-\t\treturn\n-\t}\n \toldwd, err := os.Getwd()\n \tif err != nil {\n \t\tt.Fatal(\"Getwd failed: \", err)\n```
## コアとなるコードの解説
### `src/pkg/os/stat_windows.go`の変更
このファイルは、Windows環境におけるファイルシステム統計情報(`os.FileInfo`など)の取得に関連するシステムコールをラップしています。`loadFileId`関数は、ファイルまたはディレクトリの一意な識別子であるファイルIDをロードするために使用されます。
変更された行は、`syscall.CreateFile`関数の呼び出しです。
* **変更前**:
```go
h, e := syscall.CreateFile(syscall.StringToUTF16Ptr(s.path), syscall.GENERIC_READ, syscall.FILE_SHARE_READ, nil, syscall.OPEN_EXISTING, 0, 0)
```
ここでは、`dwDesiredAccess`に`syscall.GENERIC_READ`(読み取りアクセス)が、`dwFlagsAndAttributes`に`0`(追加フラグなし)が指定されていました。ディレクトリに対してこの設定で`CreateFile`を呼び出すと、有効なハンドルが取得できないことがありました。
* **変更後**:
```go
h, e := syscall.CreateFile(syscall.StringToUTF16Ptr(s.path), 0, 0, nil, syscall.OPEN_EXISTING, syscall.FILE_FLAG_BACKUP_SEMANTICS, 0)
```
この修正では、以下の2点が変更されています。
1. `dwDesiredAccess`が`syscall.GENERIC_READ`から`0`に変更されました。これは、ファイルの内容を読み取るのではなく、ファイルやディレクトリのメタデータ(ファイルID)を取得することが目的であるため、より適切な設定です。
2. `dwFlagsAndAttributes`が`0`から`syscall.FILE_FLAG_BACKUP_SEMANTICS`に変更されました。このフラグは、ディレクトリを開いてその情報を取得する際に必須です。これにより、`CreateFile`がディレクトリに対して正しくハンドルを返し、その後のファイルID取得処理が成功するようになります。
この変更により、`os.SameFile`が内部的に`loadFileId`を呼び出す際に、ディレクトリに対しても正確なファイルIDを取得できるようになり、結果として`SameFile`が期待通りに動作するようになりました。
### `src/pkg/path/filepath/path_test.go`の変更
このファイルは、`path/filepath`パッケージのテストコードを含んでいます。`TestAbs`関数は、パスを絶対パスに変換する`filepath.Abs`関数のテストです。
* **変更前**:
```go
if runtime.GOOS == "windows" {
t.Log("TestAbs disabled on windows")
return
}
```
このコードブロックは、`runtime.GOOS`が`"windows"`である場合に`TestAbs`をスキップしていました。これは、Windows環境でこのテストが何らかの理由で失敗していたため、一時的に無効化されていたことを示唆しています。
* **変更後**:
上記のコードブロックが削除されました。
これは、`os`パッケージにおける`CreateFile`の修正(またはそれに関連する他の修正)によって、`TestAbs`がWindows上でも正しく動作するようになったため、テストを再度有効化できるようになったことを意味します。`TestAbs`は直接`os.SameFile`とは関係ありませんが、ファイルシステムとの低レベルな相互作用が改善されたことで、間接的に影響を受けた可能性があります。
## 関連リンク
* Go Change List: [https://golang.org/cl/5756064](https://golang.org/cl/5756064)
## 参考にした情報源リンク
* Microsoft Learn - CreateFileW function: [https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilew](https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilew)
* Microsoft Learn - File Access Rights and Attributes: [https://learn.microsoft.com/en-us/windows/win32/fileio/file-access-rights-and-attributes](https://learn.microsoft.com/en-us/windows/win32/fileio/file-access-rights-and-attributes)
* Go Documentation - os.SameFile: [https://pkg.go.dev/os#SameFile](https://pkg.go.dev/os#SameFile)
* Go Documentation - syscall package: [https://pkg.go.dev/syscall](https://pkg.go.dev/syscall)
* Stack Overflow - CreateFile with FILE_FLAG_BACKUP_SEMANTICS: [https://stackoverflow.com/questions/13044562/createfile-with-file-flag-backup-semantics](https://stackoverflow.com/questions/13044562/createfile-with-file-flag-backup-semantics) (一般的な情報源として)