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

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

このコミットは、Go言語のsyscallパッケージにおけるWindows固有のWin32finddata構造体の定義を修正し、それに伴うファイル検索APIの変更を導入するものです。具体的には、既存のWin32finddata構造体がWindows APIの期待するレイアウトと異なっていたために発生していた問題を解決するため、Win32finddata1という新しい正しい構造体を導入し、これを使用するFindFirstFile1およびFindNextFile1関数を追加しています。これにより、GoプログラムがWindows上でファイルやディレクトリを正確に列挙できるようになります。

コミット

commit 8801402940aa983a318ba680b7b65b5070dd35ca
Author: Alex Brainman <alex.brainman@gmail.com>
Date:   Sun Jun 3 19:27:17 2012 +1000

    syscall: correct Win32finddata definition
    
    Fixes #3685.
    
    R=golang-dev, rsc
    CC=golang-dev
    https://golang.org/cl/6261053

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

https://github.com/golang/go/commit/8801402940aa983a318ba680b7b65b5070dd35ca

元コミット内容

このコミットの元のメッセージは以下の通りです。

syscall: correct Win32finddata definition

Fixes #3685.

R=golang-dev, rsc
CC=golang-dev
https://golang.org/cl/6261053

これは、Win32finddataの定義が正しくないという問題に対処し、Go issue #3685を修正することを明確に示しています。

変更の背景

この変更の背景には、Go言語のsyscallパッケージがWindows APIと連携する際に発生していたバグがあります。Windows APIには、ファイルやディレクトリの情報を取得するためのFindFirstFileおよびFindNextFileという関数群が存在し、これらの関数はWIN32_FIND_DATAという構造体を使用してファイル情報を返します。

Goのsyscallパッケージでは、これらのWindows APIを呼び出すために、対応するGoの構造体Win32finddataを定義していました。しかし、このWin32finddataの定義が、Windows APIが期待するWIN32_FIND_DATA構造体のメモリレイアウトと一致していなかったため、ファイル名などの情報が正しく取得できないという問題が発生していました。特に、ファイル名のフィールドのサイズがWindows APIの定義と異なっていたことが原因で、メモリ破壊や不正なファイル名の取得といった不具合が引き起こされていました。

この問題はGo issue #3685として報告されており、このコミットはその問題を修正するために行われました。修正の目的は、Win32finddataの定義をWindows APIのWIN32_FIND_DATAに完全に一致させることで、GoプログラムがWindowsのファイルシステムを正確に操作できるようにすることです。

前提知識の解説

Windows APIとWIN32_FIND_DATA構造体

Windows API (Application Programming Interface) は、Windowsオペレーティングシステムの機能にアクセスするための関数群です。ファイルシステム操作もその一部であり、特定のディレクトリ内のファイルやサブディレクトリを検索・列挙するためにFindFirstFileFindNextFileという関数が提供されています。

これらの関数は、検索結果としてWIN32_FIND_DATAという構造体にファイルの詳細情報を格納します。この構造体には、ファイル属性(読み取り専用、隠しファイルなど)、作成日時、最終アクセス日時、最終書き込み日時、ファイルサイズ、そしてファイル名(短いファイル名と長いファイル名)などの情報が含まれています。

WIN32_FIND_DATA構造体の定義は以下のようになっています(C言語風の表現):

typedef struct _WIN32_FIND_DATA {
  DWORD    dwFileAttributes;
  FILETIME ftCreationTime;
  FILETIME ftLastAccessTime;
  FILETIME ftLastWriteTime;
  DWORD    nFileSizeHigh;
  DWORD    nFileSizeLow;
  DWORD    dwReserved0;
  DWORD    dwReserved1;
  TCHAR    cFileName[MAX_PATH];
  TCHAR    cAlternateFileName[14];
} WIN32_FIND_DATA, *PWIN32_FIND_DATA;

ここで重要なのは、cFileNamecAlternateFileNameの配列サイズです。MAX_PATHは通常260(ワイド文字の場合は520バイト)であり、cAlternateFileNameは14(ワイド文字の場合は28バイト)です。これらの配列サイズがGoの構造体定義と一致しないと、メモリレイアウトの不一致が発生し、データが正しく読み書きされなくなります。

Go言語のsyscallパッケージ

Go言語のsyscallパッケージは、オペレーティングシステムが提供する低レベルなシステムコールにアクセスするための機能を提供します。これにより、GoプログラムはOS固有の機能(ファイルシステム操作、ネットワーク通信、プロセス管理など)を直接利用できます。

Windowsの場合、syscallパッケージはWindows APIの関数をGoの関数としてラップし、対応するC言語の構造体をGoの構造体として定義します。この際、C言語の構造体とGoの構造体のメモリレイアウトが完全に一致していることが非常に重要です。もし一致しない場合、API呼び出し時に渡されるデータや返されるデータが破損し、予期せぬ動作やクラッシュを引き起こす可能性があります。

メモリレイアウトとパディング

C言語の構造体は、コンパイラによってメモリ上に配置される際に、アライメント(メモリ境界への配置)のためにパディング(詰め物)が挿入されることがあります。これは、CPUが特定のデータ型を効率的にアクセスするために、そのデータ型が特定のメモリアドレスに配置されていることを要求するためです。

Go言語の構造体も同様にメモリレイアウトを持ちますが、C言語の構造体をGoで再現する際には、GoのコンパイラがC言語のコンパイラと同じパディングを挿入するとは限りません。そのため、C言語の構造体をGoで定義する際には、フィールドの順序や型、そして明示的なパディングの追加などによって、C言語の構造体と完全に同じメモリレイアウトになるように注意深く設計する必要があります。

今回の問題は、Win32finddata構造体のAlternateFileNameフィールドの配列サイズが、Windows APIのWIN32_FIND_DATAcAlternateFileNameフィールドのサイズと異なっていたために発生しました。これにより、構造体全体のサイズとフィールドのオフセットがずれ、後続のデータが正しく読み取れなくなっていました。

技術的詳細

このコミットの技術的な核心は、GoのsyscallパッケージにおけるWin32finddata構造体の定義が、Windows APIのWIN32_FIND_DATA構造体のメモリレイアウトと一致していなかったという点にあります。

元のWin32finddata構造体は、AlternateFileNameフィールドの配列サイズが[13]uint16と定義されていました。しかし、Windows APIのWIN32_FIND_DATA構造体では、対応するcAlternateFileNameフィールドは[14]のサイズを持つTCHAR(ワイド文字の場合はWCHAR、つまりuint16)の配列です。この1要素の差が、構造体全体のサイズと、その後に続くメモリ領域の解釈に影響を与え、ファイル名などの情報が正しく読み取れない原因となっていました。

このコミットでは、この問題を解決するために以下の変更が行われました。

  1. Win32finddata1構造体の導入: src/pkg/syscall/ztypes_windows.goに、Win32finddata1という新しい構造体が定義されました。この構造体は、AlternateFileNameフィールドのサイズを[14]uint16に修正し、Windows APIのWIN32_FIND_DATAと完全に一致するようにしました。また、FileNameフィールドも[MAX_PATH]uint16として定義され、これもWindows APIの定義に合わせられています。 元のWin32finddataは後方互換性のために残されていますが、コメントで「不正な構造体定義であり、Win32finddata1を使用すべき」と明記されています。

  2. FindFirstFile1およびFindNextFile1関数の導入: src/pkg/syscall/syscall_windows.goおよびsrc/pkg/syscall/zsyscall_windows_386.gosrc/pkg/syscall/zsyscall_windows_amd64.goにおいて、Win32finddata1構造体を使用する新しいAPIラッパー関数FindFirstFile1FindNextFile1が導入されました。これらの関数は、Windows APIのFindFirstFileWおよびFindNextFileWを正しく呼び出すためのものです。

  3. 既存関数の無効化とリダイレクト: src/pkg/syscall/syscall_windows.goでは、既存のFindFirstFileおよびFindNextFile関数が、Win32finddata構造体を使用しているため、エラーを返すように変更されました。これにより、開発者が誤って古い、壊れた関数を使用することを防ぎ、新しいFindFirstFile1およびFindNextFile1への移行を促しています。

  4. osパッケージの更新: src/pkg/os/file_windows.goでは、ファイルシステム操作を行うosパッケージが、内部的にsyscall.Win32finddataではなくsyscall.Win32finddata1を使用するように変更されました。これにより、osパッケージを介したファイルやディレクトリの列挙が正しく行われるようになります。

  5. テストの追加: src/pkg/syscall/syscall_windows_test.goに新しいテストファイルが追加されました。このテストは、Win32finddata1構造体とFindFirstFile1関数が正しく動作し、メモリ破壊が発生しないことを検証します。具体的には、一時ディレクトリにファイルを作成し、FindFirstFile1でその情報を取得した後、構造体内のパディング領域が意図せず変更されていないことを確認することで、メモリレイアウトの正確性を検証しています。

これらの変更により、Go言語のWindows版におけるファイルシステム操作の安定性と正確性が大幅に向上しました。

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

このコミットにおけるコアとなるコードの変更箇所は以下のファイルに集中しています。

  1. src/pkg/syscall/ztypes_windows.go:

    • Win32finddata1構造体の新規定義。AlternateFileNameのサイズが[14]uint16に修正され、FileName[MAX_PATH]uint16として定義されています。
    • 既存のWin32finddata構造体に対して、その定義が不正確であり、Win32finddata1を使用すべきである旨のコメントが追加されています。
    // Win32finddata is an incorrect struct definition, preserved for
    // backwards compatibility. Use Win32finddata1 and the
    // FindFirstFile1 and FindNextFile1 functions instead.
    type Win32finddata struct {
        // ... (既存のフィールド)
        AlternateFileName [13]uint16 // <-- ここが [14] に修正される
    }
    
    type Win32finddata1 struct {
        FileAttributes    uint32
        CreationTime      Filetime
        LastAccessTime    Filetime
        LastWriteTime     Filetime
        FileSizeHigh      uint32
        FileSizeLow       uint32
        Reserved0         uint32
        Reserved1         uint32
        FileName          [MAX_PATH]uint16 // <-- 新しく追加
        AlternateFileName [14]uint16       // <-- サイズが修正された
    }
    
  2. src/pkg/syscall/syscall_windows.go:

    • FindFirstFile1FindNextFile1//sysディレクティブによる宣言。
    • 既存のFindFirstFileFindNextFile関数が、エラーを返すように変更され、新しいFindFirstFile1/FindNextFile1の使用を促しています。
    //sys	FindFirstFile1(name *uint16, data *Win32finddata1) (handle Handle, err error) [failretval==InvalidHandle] = FindFirstFileW
    //sys	FindNextFile1(handle Handle, data *Win32finddata1) (err error) = FindNextFileW
    
    func FindFirstFile(name *uint16, data *Win32finddata) (handle Handle, err error) {
        return InvalidHandle, errorspkg.New("FindFirstFile is broken, use FindFirstFile1 instead")
    }
    
    func FindNextFile(handle Handle, data *Win32finddata) (err error) {
        return errorspkg.New("FindNextFile is broken, use FindNextFile1 instead")
    }
    
  3. src/pkg/os/file_windows.go:

    • dirInfo構造体のdataフィールドがsyscall.Win32finddataからsyscall.Win32finddata1に変更。
    • openDir関数とreaddir関数内で、syscall.FindFirstFilesyscall.FindNextFileの呼び出しが、それぞれsyscall.FindFirstFile1syscall.FindNextFile1に変更。
    type dirInfo struct {
        data     syscall.Win32finddata1 // <-- 変更点
        needdata bool
        path     string
    }
    
    func openDir(name string) (file *File, err error) {
        d := new(dirInfo)
        r, e := syscall.FindFirstFile1(syscall.StringToUTF16Ptr(name+`\*`), &d.data) // <-- 変更点
        // ...
    }
    
    func (file *File) readdir(n int) (fi []FileInfo, err error) {
        d := &file.dirinfo.data
        for n != 0 {
            if file.dirinfo.needdata {
                e := syscall.FindNextFile1(syscall.Handle(file.fd), d) // <-- 変更点
                // ...
            }
            // ...
        }
        // ...
    }
    
  4. src/pkg/syscall/zsyscall_windows_386.go および src/pkg/syscall/zsyscall_windows_amd64.go:

    • FindFirstFileFindNextFileの関数定義が、Win32finddataからWin32finddata1を使用するように変更され、関数名もFindFirstFile1FindNextFile1に修正されています。これらはgo generateによって自動生成されるファイルですが、このコミットでは手動で修正が加えられています。
    // zsyscall_windows_386.go (amd64も同様)
    func FindFirstFile1(name *uint16, data *Win32finddata1) (handle Handle, err error) { // <-- 変更点
        r0, _, e1 := Syscall(procFindFirstFileW.Addr(), 2, uintptr(unsafe.Pointer(name)), uintptr(unsafe.Pointer(data)), 0)
        // ...
    }
    
    func FindNextFile1(handle Handle, data *Win32finddata1) (err error) { // <-- 変更点
        r1, _, e1 := Syscall(procFindNextFileW.Addr(), 2, uintptr(handle), uintptr(unsafe.Pointer(data)), 0)
        // ...
    }
    
  5. src/pkg/syscall/syscall_windows_test.go:

    • TestWin32finddataという新しいテスト関数が追加され、Win32finddata1構造体とFindFirstFile1関数が正しく動作することを確認しています。特に、構造体内のパディング領域が意図せず変更されていないか(メモリ破壊がないか)を検証するロジックが含まれています。
    func TestWin32finddata(t *testing.T) {
        // ... (一時ファイルの作成)
    
        type X struct {
            fd  syscall.Win32finddata1
            got byte
            pad [10]byte // to protect ourselves
        }
        var want byte = 2
        x := X{got: want}
    
        h, err := syscall.FindFirstFile1(syscall.StringToUTF16Ptr(path), &(x.fd)) // <-- FindFirstFile1を使用
        // ...
    
        if x.got != want {
            t.Fatalf("memory corruption: want=%d got=%d", want, x.got) // <-- メモリ破壊のチェック
        }
    }
    

コアとなるコードの解説

このコミットの核心は、GoのsyscallパッケージがWindows APIのWIN32_FIND_DATA構造体を正しくマッピングできていなかったという問題の解決です。

Goのsyscallパッケージは、C言語で定義されたWindows APIの構造体をGoの構造体として再現します。この際、Goの構造体のフィールドの順序、型、そして配列のサイズが、C言語の構造体のメモリレイアウトと完全に一致している必要があります。もし一致しない場合、GoからAPIを呼び出す際に渡されるデータや、APIから返されるデータが正しく解釈されず、バグやクラッシュの原因となります。

今回の問題は、Win32finddata構造体内のAlternateFileNameフィールドの配列サイズが、Windows APIのWIN32_FIND_DATAにおけるcAlternateFileNameフィールドのサイズ(14要素)よりも1要素少なかった(13要素)ために発生しました。このわずかな違いが、構造体全体のサイズを狂わせ、その後に続くメモリ領域(特にファイル名が格納される領域)の解釈を誤らせていました。結果として、FindFirstFileFindNextFileといったファイル検索APIが、正しいファイル名を返せなくなっていたのです。

このコミットでは、この問題を解決するために、以下の戦略が取られました。

  1. Win32finddata1による正確なマッピング: src/pkg/syscall/ztypes_windows.goで新しく定義されたWin32finddata1構造体は、AlternateFileName [14]uint16FileName [MAX_PATH]uint16という、Windows APIのWIN32_FIND_DATAに完全に合致するフィールド定義を持っています。これにより、Goの構造体がメモリ上でWindows APIの期待するレイアウトと一致するようになり、データの読み書きが正確に行われるようになりました。

  2. APIのバージョン管理と移行: 既存のFindFirstFileFindNextFile関数は、互換性のために残しつつも、内部でエラーを返すように変更されました。これは、古い壊れたAPIの使用を避け、開発者に新しいFindFirstFile1FindNextFile1への移行を促すための措置です。osパッケージのような高レベルなAPIは、この新しい正確なAPIを使用するように更新されました。これにより、Goのユーザーはosパッケージを介して、修正されたファイル検索機能を利用できるようになります。

  3. メモリ破壊の防止とテスト: syscall_windows_test.goに追加されたテストは、この修正の有効性を検証する上で非常に重要です。特に、X構造体内にWin32finddata1と、その後に続くgot byteおよびpad [10]byteを配置し、FindFirstFile1呼び出し後にgotの値が変更されていないことを確認するロジックは、Win32finddata1のメモリレイアウトが正しく、その後に続くメモリ領域を破壊していないことを保証します。これは、構造体のパディングやアライメントが正しく行われていることを確認する、堅牢なテスト手法と言えます。

この修正は、Go言語がWindowsプラットフォームでファイルシステムを扱う際の基盤となる部分の正確性を確保し、より信頼性の高いアプリケーション開発を可能にしました。

関連リンク

参考にした情報源リンク

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

このコミットは、Go言語の`syscall`パッケージにおけるWindows固有の`Win32finddata`構造体の定義を修正し、それに伴うファイル検索APIの変更を導入するものです。具体的には、既存の`Win32finddata`構造体がWindows APIの期待するレイアウトと異なっていたために発生していた問題を解決するため、`Win32finddata1`という新しい正しい構造体を導入し、これを使用する`FindFirstFile1`および`FindNextFile1`関数を追加しています。これにより、GoプログラムがWindows上でファイルやディレクトリを正確に列挙できるようになります。

## コミット

commit 8801402940aa983a318ba680b7b65b5070dd35ca Author: Alex Brainman alex.brainman@gmail.com Date: Sun Jun 3 19:27:17 2012 +1000

syscall: correct Win32finddata definition

Fixes #3685.

R=golang-dev, rsc
CC=golang-dev
https://golang.org/cl/6261053

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

[https://github.com/golang/go/commit/8801402940aa983a318ba680b7b65b5070dd35ca](https://github.com/golang/go/commit/8801402940aa983a318ba680b7b65b5070dd35ca)

## 元コミット内容

このコミットの元のメッセージは以下の通りです。

syscall: correct Win32finddata definition

Fixes #3685.

R=golang-dev, rsc CC=golang-dev https://golang.org/cl/6261053


これは、`Win32finddata`の定義が正しくないという問題に対処し、Go issue #3685を修正することを明確に示しています。

## 変更の背景

この変更の背景には、Go言語の`syscall`パッケージがWindows APIと連携する際に発生していたバグがあります。Windows APIには、ファイルやディレクトリの情報を取得するための`FindFirstFile`および`FindNextFile`という関数群が存在し、これらの関数は`WIN32_FIND_DATA`という構造体を使用してファイル情報を返します。

Goの`syscall`パッケージでは、これらのWindows APIを呼び出すために、対応するGoの構造体`Win32finddata`を定義していました。しかし、この`Win32finddata`の定義が、Windows APIが期待する`WIN32_FIND_DATA`構造体のメモリレイアウトと一致していなかったため、ファイル名などの情報が正しく取得できないという問題が発生していました。特に、ファイル名のフィールドのサイズがWindows APIの定義と異なっていたことが原因で、メモリ破壊や不正なファイル名の取得といった不具合が引き起こされていました。

この問題はGo issue #3685として報告されており、このコミットはその問題を修正するために行われました。修正の目的は、`Win32finddata`の定義をWindows APIの`WIN32_FIND_DATA`に完全に一致させることで、GoプログラムがWindowsのファイルシステムを正確に操作できるようにすることです。

## 前提知識の解説

### Windows APIと`WIN32_FIND_DATA`構造体

Windows API (Application Programming Interface) は、Windowsオペレーティングシステムの機能にアクセスするための関数群です。ファイルシステム操作もその一部であり、特定のディレクトリ内のファイルやサブディレクトリを検索・列挙するために`FindFirstFile`と`FindNextFile`という関数が提供されています。

これらの関数は、検索結果として`WIN32_FIND_DATA`という構造体にファイルの詳細情報を格納します。この構造体には、ファイル属性(読み取り専用、隠しファイルなど)、作成日時、最終アクセス日時、最終書き込み日時、ファイルサイズ、そしてファイル名(短いファイル名と長いファイル名)などの情報が含まれています。

`WIN32_FIND_DATA`構造体の定義は以下のようになっています(C言語風の表現):

```c
typedef struct _WIN32_FIND_DATA {
  DWORD    dwFileAttributes;
  FILETIME ftCreationTime;
  FILETIME ftLastAccessTime;
  FILETIME ftLastWriteTime;
  DWORD    nFileSizeHigh;
  DWORD    nFileSizeLow;
  DWORD    dwReserved0;
  DWORD    dwReserved1;
  TCHAR    cFileName[MAX_PATH];
  TCHAR    cAlternateFileName[14];
} WIN32_FIND_DATA, *PWIN32_FIND_DATA;

ここで重要なのは、cFileNamecAlternateFileNameの配列サイズです。MAX_PATHは通常260(ワイド文字の場合は520バイト)であり、cAlternateFileNameは14(ワイド文字の場合は28バイト)です。これらの配列サイズがGoの構造体定義と一致しないと、メモリレイアウトの不一致が発生し、データが正しく読み書きされなくなります。

Go言語のsyscallパッケージ

Go言語のsyscallパッケージは、オペレーティングシステムが提供する低レベルなシステムコールにアクセスするための機能を提供します。これにより、GoプログラムはOS固有の機能(ファイルシステム操作、ネットワーク通信、プロセス管理など)を直接利用できます。

Windowsの場合、syscallパッケージはWindows APIの関数をGoの関数としてラップし、対応するC言語の構造体をGoの構造体として定義します。この際、C言語の構造体とGoの構造体のメモリレイアウトが完全に一致していることが非常に重要です。もし一致しない場合、API呼び出し時に渡されるデータや返されるデータが破損し、予期せぬ動作やクラッシュを引き起こす可能性があります。

メモリレイアウトとパディング

C言語の構造体は、コンパイラによってメモリ上に配置される際に、アライメント(メモリ境界への配置)のためにパディング(詰め物)が挿入されることがあります。これは、CPUが特定のデータ型を効率的にアクセスするために、そのデータ型が特定のメモリアドレスに配置されていることを要求するためです。

Go言語の構造体も同様にメモリレイアウトを持ちますが、C言語のコンパイラがGoのコンパイラと同じパディングを挿入するとは限りません。そのため、C言語の構造体をGoで定義する際には、フィールドの順序や型、そして明示的なパディングの追加などによって、C言語の構造体と完全に同じメモリレイアウトになるように注意深く設計する必要があります。

今回の問題は、Win32finddata構造体のAlternateFileNameフィールドの配列サイズが、Windows APIのWIN32_FIND_DATAcAlternateFileNameフィールドのサイズと異なっていたために発生しました。これにより、構造体全体のサイズとフィールドのオフセットがずれ、後続のデータが正しく読み取れなくなっていました。

技術的詳細

このコミットの技術的な核心は、GoのsyscallパッケージにおけるWin32finddata構造体の定義が、Windows APIのWIN32_FIND_DATA構造体のメモリレイアウトと一致していなかったという点にあります。

元のWin32finddata構造体は、AlternateFileNameフィールドの配列サイズが[13]uint16と定義されていました。しかし、Windows APIのWIN32_FIND_DATA構造体では、対応するcAlternateFileNameフィールドは[14]のサイズを持つTCHAR(ワイド文字の場合はWCHAR、つまりuint16)の配列です。この1要素の差が、構造体全体のサイズと、その後に続くメモリ領域の解釈に影響を与え、ファイル名などの情報が正しく読み取れない原因となっていました。

このコミットでは、この問題を解決するために以下の変更が行われました。

  1. Win32finddata1構造体の導入: src/pkg/syscall/ztypes_windows.goに、Win32finddata1という新しい構造体が定義されました。この構造体は、AlternateFileNameフィールドのサイズを[14]uint16に修正し、Windows APIのWIN32_FIND_DATAと完全に一致するようにしました。また、FileNameフィールドも[MAX_PATH]uint16として定義され、これもWindows APIの定義に合わせられています。 元のWin32finddataは後方互換性のために残されていますが、コメントで「不正な構造体定義であり、Win32finddata1を使用すべき」と明記されています。

  2. FindFirstFile1およびFindNextFile1関数の導入: src/pkg/syscall/syscall_windows.goおよびsrc/pkg/syscall/zsyscall_windows_386.gosrc/pkg/syscall/zsyscall_windows_amd64.goにおいて、Win32finddata1構造体を使用する新しいAPIラッパー関数FindFirstFile1FindNextFile1が導入されました。これらの関数は、Windows APIのFindFirstFileWおよびFindNextFileWを正しく呼び出すためのものです。

  3. 既存関数の無効化とリダイレクト: src/pkg/syscall/syscall_windows.goでは、既存のFindFirstFileおよびFindNextFile関数が、Win32finddata構造体を使用しているため、エラーを返すように変更されました。これにより、開発者が誤って古い、壊れた関数を使用することを防ぎ、新しいFindFirstFile1およびFindNextFile1への移行を促しています。

  4. osパッケージの更新: src/pkg/os/file_windows.goでは、ファイルシステム操作を行うosパッケージが、内部的にsyscall.Win32finddataではなくsyscall.Win32finddata1を使用するように変更されました。これにより、osパッケージを介したファイルやディレクトリの列挙が正しく行われるようになります。

  5. テストの追加: src/pkg/syscall/syscall_windows_test.goに新しいテストファイルが追加されました。このテストは、Win32finddata1構造体とFindFirstFile1関数が正しく動作し、メモリ破壊が発生しないことを検証します。具体的には、一時ディレクトリにファイルを作成し、FindFirstFile1でその情報を取得した後、構造体内のパディング領域が意図せず変更されていないことを確認することで、メモリレイアウトの正確性を検証しています。

これらの変更により、Go言語のWindows版におけるファイルシステム操作の安定性と正確性が大幅に向上しました。

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

このコミットにおけるコアとなるコードの変更箇所は以下のファイルに集中しています。

  1. src/pkg/syscall/ztypes_windows.go:

    • Win32finddata1構造体の新規定義。AlternateFileNameのサイズが[14]uint16に修正され、FileName[MAX_PATH]uint16として定義されています。
    • 既存のWin32finddata構造体に対して、その定義が不正確であり、Win32finddata1を使用すべきである旨のコメントが追加されています。
    // Win32finddata is an incorrect struct definition, preserved for
    // backwards compatibility. Use Win32finddata1 and the
    // FindFirstFile1 and FindNextFile1 functions instead.
    type Win32finddata struct {
        // ... (既存のフィールド)
        AlternateFileName [13]uint16 // <-- ここが [14] に修正される
    }
    
    type Win32finddata1 struct {
        FileAttributes    uint32
        CreationTime      Filetime
        LastAccessTime    Filetime
        LastWriteTime     Filetime
        FileSizeHigh      uint32
        FileSizeLow       uint32
        Reserved0         uint32
        Reserved1         uint32
        FileName          [MAX_PATH]uint16 // <-- 新しく追加
        AlternateFileName [14]uint16       // <-- サイズが修正された
    }
    
  2. src/pkg/syscall/syscall_windows.go:

    • FindFirstFile1FindNextFile1//sysディレクティブによる宣言。
    • 既存のFindFirstFileFindNextFile関数が、エラーを返すように変更され、新しいFindFirstFile1/FindNextFile1の使用を促しています。
    //sys	FindFirstFile1(name *uint16, data *Win32finddata1) (handle Handle, err error) [failretval==InvalidHandle] = FindFirstFileW
    //sys	FindNextFile1(handle Handle, data *Win32finddata1) (err error) = FindNextFileW
    
    func FindFirstFile(name *uint16, data *Win32finddata) (handle Handle, err error) {
        return InvalidHandle, errorspkg.New("FindFirstFile is broken, use FindFirstFile1 instead")
    }
    
    func FindNextFile(handle Handle, data *Win32finddata) (err error) {
        return errorspkg.New("FindNextFile is broken, use FindNextFile1 instead")
    }
    
  3. src/pkg/os/file_windows.go:

    • dirInfo構造体のdataフィールドがsyscall.Win32finddataからsyscall.Win32finddata1に変更。
    • openDir関数とreaddir関数内で、syscall.FindFirstFilesyscall.FindNextFileの呼び出しが、それぞれsyscall.FindFirstFile1syscall.FindNextFile1に変更。
    type dirInfo struct {
        data     syscall.Win32finddata1 // <-- 変更点
        needdata bool
        path     string
    }
    
    func openDir(name string) (file *File, err error) {
        d := new(dirInfo)
        r, e := syscall.FindFirstFile1(syscall.StringToUTF16Ptr(name+`\*`), &d.data) // <-- 変更点
        // ...
    }
    
    func (file *File) readdir(n int) (fi []FileInfo, err error) {
        d := &file.dirinfo.data
        for n != 0 {
            if file.dirinfo.needdata {
                e := syscall.FindNextFile1(syscall.Handle(file.fd), d) // <-- 変更点
                // ...
            }
            // ...
        }
        // ...
    }
    
  4. src/pkg/syscall/zsyscall_windows_386.go および src/pkg/syscall/zsyscall_windows_amd64.go:

    • FindFirstFileFindNextFileの関数定義が、Win32finddataからWin32finddata1を使用するように変更され、関数名もFindFirstFile1FindNextFile1に修正されています。これらはgo generateによって自動生成されるファイルですが、このコミットでは手動で修正が加えられています。
    // zsyscall_windows_386.go (amd64も同様)
    func FindFirstFile1(name *uint16, data *Win32finddata1) (handle Handle, err error) { // <-- 変更点
        r0, _, e1 := Syscall(procFindFirstFileW.Addr(), 2, uintptr(unsafe.Pointer(name)), uintptr(unsafe.Pointer(data)), 0)
        // ...
    }
    
    func FindNextFile1(handle Handle, data *Win32finddata1) (err error) { // <-- 変更点
        r1, _, e1 := Syscall(procFindNextFileW.Addr(), 2, uintptr(handle), uintptr(unsafe.Pointer(data)), 0)
        // ...
    }
    
  5. src/pkg/syscall/syscall_windows_test.go:

    • TestWin32finddataという新しいテスト関数が追加され、Win32finddata1構造体とFindFirstFile1関数が正しく動作することを確認しています。特に、構造体内のパディング領域が意図せず変更されていないか(メモリ破壊がないか)を検証するロジックが含まれています。
    func TestWin32finddata(t *testing.T) {
        // ... (一時ファイルの作成)
    
        type X struct {
            fd  syscall.Win32finddata1
            got byte
            pad [10]byte // to protect ourselves
        }
        var want byte = 2
        x := X{got: want}
    
        h, err := syscall.FindFirstFile1(syscall.StringToUTF16Ptr(path), &(x.fd)) // <-- FindFirstFile1を使用
        // ...
    
        if x.got != want {
            t.Fatalf("memory corruption: want=%d got=%d", want, x.got) // <-- メモリ破壊のチェック
        }
    }
    

コアとなるコードの解説

このコミットの核心は、GoのsyscallパッケージがWindows APIのWIN32_FIND_DATA構造体を正しくマッピングできていなかったという問題の解決です。

Goのsyscallパッケージは、C言語で定義されたWindows APIの構造体をGoの構造体として再現します。この際、Goの構造体のフィールドの順序、型、そして配列のサイズが、C言語の構造体のメモリレイアウトと完全に一致している必要があります。もし一致しない場合、GoからAPIを呼び出す際に渡されるデータや、APIから返されるデータが正しく解釈されず、バグやクラッシュの原因となります。

今回の問題は、Win32finddata構造体内のAlternateFileNameフィールドの配列サイズが、Windows APIのWIN32_FIND_DATAにおけるcAlternateFileNameフィールドのサイズ(14要素)よりも1要素少なかった(13要素)ために発生しました。このわずかな違いが、構造体全体のサイズを狂わせ、その後に続くメモリ領域(特にファイル名が格納される領域)の解釈を誤らせていました。結果として、FindFirstFileFindNextFileといったファイル検索APIが、正しいファイル名を返せなくなっていたのです。

このコミットでは、この問題を解決するために、以下の戦略が取られました。

  1. Win32finddata1による正確なマッピング: src/pkg/syscall/ztypes_windows.goで新しく定義されたWin32finddata1構造体は、AlternateFileName [14]uint16FileName [MAX_PATH]uint16という、Windows APIのWIN32_FIND_DATAに完全に合致するフィールド定義を持っています。これにより、Goの構造体がメモリ上でWindows APIの期待するレイアウトと一致するようになり、データの読み書きが正確に行われるようになりました。

  2. APIのバージョン管理と移行: 既存のFindFirstFileFindNextFile関数は、互換性のために残しつつも、内部でエラーを返すように変更されました。これは、古い壊れたAPIの使用を避け、開発者に新しいFindFirstFile1FindNextFile1への移行を促すための措置です。osパッケージのような高レベルなAPIは、この新しい正確なAPIを使用するように更新されました。これにより、Goのユーザーはosパッケージを介して、修正されたファイル検索機能を利用できるようになります。

  3. メモリ破壊の防止とテスト: syscall_windows_test.goに追加されたテストは、この修正の有効性を検証する上で非常に重要です。特に、X構造体内にWin32finddata1と、その後に続くgot byteおよびpad [10]byteを配置し、FindFirstFile1呼び出し後にgotの値が変更されていないことを確認するロジックは、Win32finddata1のメモリレイアウトが正しく、その後に続くメモリ領域を破壊していないことを保証します。これは、構造体のパディングやアライメントが正しく行われていることを確認する、堅牢なテスト手法と言えます。

この修正は、Go言語がWindowsプラットフォームでファイルシステムを扱う際の基盤となる部分の正確性を確保し、より信頼性の高いアプリケーション開発を可能にしました。

関連リンク

参考にした情報源リンク