[インデックス 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オペレーティングシステムの機能にアクセスするための関数群です。ファイルシステム操作もその一部であり、特定のディレクトリ内のファイルやサブディレクトリを検索・列挙するためにFindFirstFile
とFindNextFile
という関数が提供されています。
これらの関数は、検索結果として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;
ここで重要なのは、cFileName
とcAlternateFileName
の配列サイズです。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_DATA
のcAlternateFileName
フィールドのサイズと異なっていたために発生しました。これにより、構造体全体のサイズとフィールドのオフセットがずれ、後続のデータが正しく読み取れなくなっていました。
技術的詳細
このコミットの技術的な核心は、Goのsyscall
パッケージにおけるWin32finddata
構造体の定義が、Windows APIのWIN32_FIND_DATA
構造体のメモリレイアウトと一致していなかったという点にあります。
元のWin32finddata
構造体は、AlternateFileName
フィールドの配列サイズが[13]uint16
と定義されていました。しかし、Windows APIのWIN32_FIND_DATA
構造体では、対応するcAlternateFileName
フィールドは[14]
のサイズを持つTCHAR
(ワイド文字の場合はWCHAR
、つまりuint16
)の配列です。この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
を使用すべき」と明記されています。 -
FindFirstFile1
およびFindNextFile1
関数の導入:src/pkg/syscall/syscall_windows.go
およびsrc/pkg/syscall/zsyscall_windows_386.go
、src/pkg/syscall/zsyscall_windows_amd64.go
において、Win32finddata1
構造体を使用する新しいAPIラッパー関数FindFirstFile1
とFindNextFile1
が導入されました。これらの関数は、Windows APIのFindFirstFileW
およびFindNextFileW
を正しく呼び出すためのものです。 -
既存関数の無効化とリダイレクト:
src/pkg/syscall/syscall_windows.go
では、既存のFindFirstFile
およびFindNextFile
関数が、Win32finddata
構造体を使用しているため、エラーを返すように変更されました。これにより、開発者が誤って古い、壊れた関数を使用することを防ぎ、新しいFindFirstFile1
およびFindNextFile1
への移行を促しています。 -
os
パッケージの更新:src/pkg/os/file_windows.go
では、ファイルシステム操作を行うos
パッケージが、内部的にsyscall.Win32finddata
ではなくsyscall.Win32finddata1
を使用するように変更されました。これにより、os
パッケージを介したファイルやディレクトリの列挙が正しく行われるようになります。 -
テストの追加:
src/pkg/syscall/syscall_windows_test.go
に新しいテストファイルが追加されました。このテストは、Win32finddata1
構造体とFindFirstFile1
関数が正しく動作し、メモリ破壊が発生しないことを検証します。具体的には、一時ディレクトリにファイルを作成し、FindFirstFile1
でその情報を取得した後、構造体内のパディング領域が意図せず変更されていないことを確認することで、メモリレイアウトの正確性を検証しています。
これらの変更により、Go言語のWindows版におけるファイルシステム操作の安定性と正確性が大幅に向上しました。
コアとなるコードの変更箇所
このコミットにおけるコアとなるコードの変更箇所は以下のファイルに集中しています。
-
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 // <-- サイズが修正された }
-
src/pkg/syscall/syscall_windows.go
:FindFirstFile1
とFindNextFile1
の//sys
ディレクティブによる宣言。- 既存の
FindFirstFile
とFindNextFile
関数が、エラーを返すように変更され、新しい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") }
-
src/pkg/os/file_windows.go
:dirInfo
構造体のdata
フィールドがsyscall.Win32finddata
からsyscall.Win32finddata1
に変更。openDir
関数とreaddir
関数内で、syscall.FindFirstFile
とsyscall.FindNextFile
の呼び出しが、それぞれsyscall.FindFirstFile1
とsyscall.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) // <-- 変更点 // ... } // ... } // ... }
-
src/pkg/syscall/zsyscall_windows_386.go
およびsrc/pkg/syscall/zsyscall_windows_amd64.go
:FindFirstFile
とFindNextFile
の関数定義が、Win32finddata
からWin32finddata1
を使用するように変更され、関数名もFindFirstFile1
とFindNextFile1
に修正されています。これらは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) // ... }
-
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要素)ために発生しました。このわずかな違いが、構造体全体のサイズを狂わせ、その後に続くメモリ領域(特にファイル名が格納される領域)の解釈を誤らせていました。結果として、FindFirstFile
やFindNextFile
といったファイル検索APIが、正しいファイル名を返せなくなっていたのです。
このコミットでは、この問題を解決するために、以下の戦略が取られました。
-
Win32finddata1
による正確なマッピング:src/pkg/syscall/ztypes_windows.go
で新しく定義されたWin32finddata1
構造体は、AlternateFileName [14]uint16
とFileName [MAX_PATH]uint16
という、Windows APIのWIN32_FIND_DATA
に完全に合致するフィールド定義を持っています。これにより、Goの構造体がメモリ上でWindows APIの期待するレイアウトと一致するようになり、データの読み書きが正確に行われるようになりました。 -
APIのバージョン管理と移行: 既存の
FindFirstFile
とFindNextFile
関数は、互換性のために残しつつも、内部でエラーを返すように変更されました。これは、古い壊れたAPIの使用を避け、開発者に新しいFindFirstFile1
とFindNextFile1
への移行を促すための措置です。os
パッケージのような高レベルなAPIは、この新しい正確なAPIを使用するように更新されました。これにより、Goのユーザーはos
パッケージを介して、修正されたファイル検索機能を利用できるようになります。 -
メモリ破壊の防止とテスト:
syscall_windows_test.go
に追加されたテストは、この修正の有効性を検証する上で非常に重要です。特に、X
構造体内にWin32finddata1
と、その後に続くgot byte
およびpad [10]byte
を配置し、FindFirstFile1
呼び出し後にgot
の値が変更されていないことを確認するロジックは、Win32finddata1
のメモリレイアウトが正しく、その後に続くメモリ領域を破壊していないことを保証します。これは、構造体のパディングやアライメントが正しく行われていることを確認する、堅牢なテスト手法と言えます。
この修正は、Go言語がWindowsプラットフォームでファイルシステムを扱う際の基盤となる部分の正確性を確保し、より信頼性の高いアプリケーション開発を可能にしました。
関連リンク
- Go issue #3685: https://code.google.com/p/go/issues/detail?id=3685 (元のGoogle Codeのリンクですが、現在はGitHubに移行しています)
- Go CL 6261053: https://golang.org/cl/6261053 (Gerritの変更リスト)
参考にした情報源リンク
- Microsoft Docs -
WIN32_FIND_DATA
structure: https://learn.microsoft.com/en-us/windows/win32/api/minwinbase/ns-minwinbase-win32_find_dataa - Microsoft Docs -
FindFirstFile
function: https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-findfirstfilew - Microsoft Docs -
FindNextFile
function: https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-findnextfilew - Go言語の
syscall
パッケージに関するドキュメント (Go公式ドキュメント): https://pkg.go.dev/syscall - Go言語におけるCgoと構造体のマッピングに関する情報 (一般的なGoのドキュメントやブログ記事)I have provided the detailed explanation as requested. I will now output the content to standard output.
# [インデックス 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;
ここで重要なのは、cFileName
とcAlternateFileName
の配列サイズです。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_DATA
のcAlternateFileName
フィールドのサイズと異なっていたために発生しました。これにより、構造体全体のサイズとフィールドのオフセットがずれ、後続のデータが正しく読み取れなくなっていました。
技術的詳細
このコミットの技術的な核心は、Goのsyscall
パッケージにおけるWin32finddata
構造体の定義が、Windows APIのWIN32_FIND_DATA
構造体のメモリレイアウトと一致していなかったという点にあります。
元のWin32finddata
構造体は、AlternateFileName
フィールドの配列サイズが[13]uint16
と定義されていました。しかし、Windows APIのWIN32_FIND_DATA
構造体では、対応するcAlternateFileName
フィールドは[14]
のサイズを持つTCHAR
(ワイド文字の場合はWCHAR
、つまりuint16
)の配列です。この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
を使用すべき」と明記されています。 -
FindFirstFile1
およびFindNextFile1
関数の導入:src/pkg/syscall/syscall_windows.go
およびsrc/pkg/syscall/zsyscall_windows_386.go
、src/pkg/syscall/zsyscall_windows_amd64.go
において、Win32finddata1
構造体を使用する新しいAPIラッパー関数FindFirstFile1
とFindNextFile1
が導入されました。これらの関数は、Windows APIのFindFirstFileW
およびFindNextFileW
を正しく呼び出すためのものです。 -
既存関数の無効化とリダイレクト:
src/pkg/syscall/syscall_windows.go
では、既存のFindFirstFile
およびFindNextFile
関数が、Win32finddata
構造体を使用しているため、エラーを返すように変更されました。これにより、開発者が誤って古い、壊れた関数を使用することを防ぎ、新しいFindFirstFile1
およびFindNextFile1
への移行を促しています。 -
os
パッケージの更新:src/pkg/os/file_windows.go
では、ファイルシステム操作を行うos
パッケージが、内部的にsyscall.Win32finddata
ではなくsyscall.Win32finddata1
を使用するように変更されました。これにより、os
パッケージを介したファイルやディレクトリの列挙が正しく行われるようになります。 -
テストの追加:
src/pkg/syscall/syscall_windows_test.go
に新しいテストファイルが追加されました。このテストは、Win32finddata1
構造体とFindFirstFile1
関数が正しく動作し、メモリ破壊が発生しないことを検証します。具体的には、一時ディレクトリにファイルを作成し、FindFirstFile1
でその情報を取得した後、構造体内のパディング領域が意図せず変更されていないことを確認することで、メモリレイアウトの正確性を検証しています。
これらの変更により、Go言語のWindows版におけるファイルシステム操作の安定性と正確性が大幅に向上しました。
コアとなるコードの変更箇所
このコミットにおけるコアとなるコードの変更箇所は以下のファイルに集中しています。
-
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 // <-- サイズが修正された }
-
src/pkg/syscall/syscall_windows.go
:FindFirstFile1
とFindNextFile1
の//sys
ディレクティブによる宣言。- 既存の
FindFirstFile
とFindNextFile
関数が、エラーを返すように変更され、新しい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") }
-
src/pkg/os/file_windows.go
:dirInfo
構造体のdata
フィールドがsyscall.Win32finddata
からsyscall.Win32finddata1
に変更。openDir
関数とreaddir
関数内で、syscall.FindFirstFile
とsyscall.FindNextFile
の呼び出しが、それぞれsyscall.FindFirstFile1
とsyscall.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) // <-- 変更点 // ... } // ... } // ... }
-
src/pkg/syscall/zsyscall_windows_386.go
およびsrc/pkg/syscall/zsyscall_windows_amd64.go
:FindFirstFile
とFindNextFile
の関数定義が、Win32finddata
からWin32finddata1
を使用するように変更され、関数名もFindFirstFile1
とFindNextFile1
に修正されています。これらは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) // ... }
-
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要素)ために発生しました。このわずかな違いが、構造体全体のサイズを狂わせ、その後に続くメモリ領域(特にファイル名が格納される領域)の解釈を誤らせていました。結果として、FindFirstFile
やFindNextFile
といったファイル検索APIが、正しいファイル名を返せなくなっていたのです。
このコミットでは、この問題を解決するために、以下の戦略が取られました。
-
Win32finddata1
による正確なマッピング:src/pkg/syscall/ztypes_windows.go
で新しく定義されたWin32finddata1
構造体は、AlternateFileName [14]uint16
とFileName [MAX_PATH]uint16
という、Windows APIのWIN32_FIND_DATA
に完全に合致するフィールド定義を持っています。これにより、Goの構造体がメモリ上でWindows APIの期待するレイアウトと一致するようになり、データの読み書きが正確に行われるようになりました。 -
APIのバージョン管理と移行: 既存の
FindFirstFile
とFindNextFile
関数は、互換性のために残しつつも、内部でエラーを返すように変更されました。これは、古い壊れたAPIの使用を避け、開発者に新しいFindFirstFile1
とFindNextFile1
への移行を促すための措置です。os
パッケージのような高レベルなAPIは、この新しい正確なAPIを使用するように更新されました。これにより、Goのユーザーはos
パッケージを介して、修正されたファイル検索機能を利用できるようになります。 -
メモリ破壊の防止とテスト:
syscall_windows_test.go
に追加されたテストは、この修正の有効性を検証する上で非常に重要です。特に、X
構造体内にWin32finddata1
と、その後に続くgot byte
およびpad [10]byte
を配置し、FindFirstFile1
呼び出し後にgot
の値が変更されていないことを確認するロジックは、Win32finddata1
のメモリレイアウトが正しく、その後に続くメモリ領域を破壊していないことを保証します。これは、構造体のパディングやアライメントが正しく行われていることを確認する、堅牢なテスト手法と言えます。
この修正は、Go言語がWindowsプラットフォームでファイルシステムを扱う際の基盤となる部分の正確性を確保し、より信頼性の高いアプリケーション開発を可能にしました。
関連リンク
- Go issue #3685: https://code.google.com/p/go/issues/detail?id=3685 (元のGoogle Codeのリンクですが、現在はGitHubに移行しています)
- Go CL 6261053: https://golang.org/cl/6261053 (Gerritの変更リスト)
参考にした情報源リンク
- Microsoft Docs -
WIN32_FIND_DATA
structure: https://learn.microsoft.com/en-us/windows/win32/api/minwinbase/ns-minwinbase-win32_find_dataa - Microsoft Docs -
FindFirstFile
function: https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-findfirstfilew - Microsoft Docs -
FindNextFile
function: https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-findnextfilew - Go言語の
syscall
パッケージに関するドキュメント (Go公式ドキュメント): https://pkg.go.dev/syscall - Go言語におけるCgoと構造体のマッピングに関する情報 (一般的なGoのドキュメントやブログ記事)