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

[インデックス 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_READsyscall.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など)を問い合わせるためには、CreateFiledwFlagsAndAttributesパラメータにFILE_FLAG_BACKUP_SEMANTICSフラグを設定する必要があります。このフラグがない場合、CreateFileはディレクトリに対して有効なハンドルを返さないか、アクセス拒否などのエラーを返すことがあります。

修正後のコードでは、dwDesiredAccess0(アクセス権限を特に要求しない、または後で必要な権限を個別に設定する)に変更し、dwFlagsAndAttributessyscall.FILE_FLAG_BACKUP_SEMANTICSを追加しています。

h, e := syscall.CreateFile(syscall.StringToUTF16Ptr(s.path), 0, 0, nil, syscall.OPEN_EXISTING, syscall.FILE_FLAG_BACKUP_SEMANTICS, 0)

dwDesiredAccess0に設定することは、ハンドルを取得する目的がデータの読み書きではなく、ファイルやディレクトリのメタデータ(この場合はファイル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) (一般的な情報源として)