[インデックス 16523] ファイルの概要
このコミットは、Go言語の標準ライブラリ os
パッケージにおけるWindows環境でのファイル読み込み処理、特に ReadAt
メソッドの挙動に関する修正です。具体的には、ファイルの終端(EOF)に達した際に、Goの io.EOF
エラーを正しく返すように変更されています。
変更されたファイルは以下の通りです。
src/pkg/os/file_windows.go
: Windows環境におけるファイル操作の実装。File.pread
メソッドが修正されています。src/pkg/os/os_test.go
:os
パッケージのテストファイル。TestReadAtEOF
という新しいテストケースが追加されています。src/pkg/syscall/ztypes_windows.go
: Windowsシステムコールに関連する型定義ファイル。ERROR_HANDLE_EOF
の定義が追加されています。
コミット
commit bb6e265fed09754d2a71966f661be9ab084ef43a
Author: Alex Brainman <alex.brainman@gmail.com>
Date: Mon Jun 10 19:14:41 2013 +1000
os: return io.EOF from windows ReadAt as documented
Fixes #5619.
R=golang-dev, r, peter.armitage, go.peter.90
CC=golang-dev
https://golang.org/cl/9952044
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/bb6e265fed09754d2a71966f661be9ab084ef43a
元コミット内容
os: return io.EOF from windows ReadAt as documented
このコミットは、Windows環境における os
パッケージの ReadAt
メソッドが、ドキュメントに記載されている通り io.EOF
を返すように修正するものです。Issue #5619 を解決します。
変更の背景
Go言語の io
パッケージには、データの読み込みがファイルの終端に達したことを示すための標準エラー io.EOF
が定義されています。これは、ストリームやファイルからの読み込み操作において、これ以上読み込むデータがないことを呼び出し元に伝えるための重要なシグナルです。
しかし、Windows環境における os
パッケージの ReadAt
メソッド(内部的には File.pread
が使用される)は、ファイルの終端に達した場合に io.EOF
を返さず、代わりに読み込んだバイト数が0でエラーが nil
となるか、あるいはWindows固有のシステムエラーコードを直接返してしまう可能性がありました。これは、Goの io
パッケージのインターフェースの期待される挙動と異なり、クロスプラットフォームでの一貫性を損なう問題でした。
特に、Windows APIの ReadFile
関数は、ファイルの終端に達した場合にエラーを返さず、読み込んだバイト数(lpNumberOfBytesRead
)が要求されたバイト数よりも少ない値になることでEOFを示します。しかし、特定の条件下では ERROR_HANDLE_EOF
というエラーコードを返すこともあります。Goの os
パッケージは、このWindows固有の挙動をGoの標準的な io.EOF
に変換する必要がありました。
この不整合は、GoプログラムがWindows上でファイルI/Oを行う際に、EOFの検出ロジックがプラットフォーム間で異なってしまう原因となり、バグや予期せぬ挙動を引き起こす可能性がありました。このコミットは、この問題を修正し、Windows上でも ReadAt
が期待通り io.EOF
を返すようにすることで、GoのファイルI/Oの堅牢性と移植性を向上させることを目的としています。
前提知識の解説
io.EOF
io.EOF
は、Go言語の io
パッケージで定義されているエラー変数です。これは、入力ストリームの終端に達し、それ以上データが利用できないことを示すために使用されます。Read
や ReadAt
のような読み込み関数が、要求されたバイト数を読み込む前にストリームの終端に達した場合、読み込んだバイト数と io.EOF
エラーを返します。これは、ループでデータを読み込む際に、読み込みを終了する条件としてよく利用されます。
ReadAt
メソッド
io.ReaderAt
インターフェースの一部であり、os.File
が実装しています。ReadAt(b []byte, off int64) (n int, err error)
は、指定されたオフセット off
からファイルの内容をバイトスライス b
に読み込みます。読み込んだバイト数 n
とエラー err
を返します。このメソッドは、ファイルの現在のオフセットを変更しないという特性があります。
syscall.ReadFile
Windows APIの ReadFile
関数に対応するGoの syscall
パッケージの関数です。これは、指定されたファイルハンドルからデータを読み込むための低レベルなシステムコールです。
syscall.ReadFile(handle syscall.Handle, buf []byte, done *uint32, overlapped *syscall.Overlapped) (err error)
この関数は、読み込みが成功した場合は nil
を返し、エラーが発生した場合は syscall.Errno
型のエラーを返します。読み込んだバイト数は done
ポインタを通じて返されます。
syscall.ERROR_HANDLE_EOF
Windows APIで定義されているエラーコードの一つです。このエラーコードは、ファイルハンドルがファイルの終端に達したことを示します。通常、ReadFile
関数はEOFに達してもエラーを返さず、読み込んだバイト数が0になることでEOFを示しますが、特定の状況下(例えば、非同期I/Oやパイプの終端など)でこのエラーが返されることがあります。Goの os
パッケージは、このWindows固有のエラーをGoの標準的な io.EOF
にマッピングする必要があります。
GoにおけるファイルI/Oの抽象化
Go言語は、異なるオペレーティングシステム(OS)間で一貫したファイルI/Oインターフェースを提供するために、OS固有のシステムコールを抽象化しています。os
パッケージは、この抽象化層を提供し、開発者がOSの違いを意識することなくファイル操作を行えるようにします。しかし、この抽象化層の内部では、各OSのシステムコール(Windowsでは syscall
パッケージを介してアクセスされる)の挙動をGoの標準インターフェースに適合させるための変換ロジックが必要となります。今回のコミットは、この変換ロジックの改善にあたります。
技術的詳細
このコミットの核心は、Windows環境における os.File.ReadAt
メソッドの内部実装である File.pread
関数が、Windows APIの ReadFile
から返される可能性のある ERROR_HANDLE_EOF
を適切に処理し、Goの io.EOF
に変換することです。
従来の File.pread
の実装では、syscall.ReadFile
がエラーを返した場合、そのエラーをそのまま返していました。しかし、WindowsのファイルI/Oでは、ファイルの終端に達した場合に ReadFile
が ERROR_HANDLE_EOF
という特定のエラーコードを返すことがあります。Goの io
パッケージの規約では、ファイルの終端に達した場合は io.EOF
を返すことになっています。この不一致が問題でした。
コミット前のコードでは、syscall.ReadFile
が ERROR_HANDLE_EOF
を返した場合、それはGoの os
パッケージのユーザーにとっては予期せぬエラーとして扱われるか、あるいはEOFとして正しく認識されない可能性がありました。
このコミットでは、File.pread
関数内で syscall.ReadFile
の戻り値をチェックし、もしエラーが syscall.ERROR_HANDLE_EOF
であった場合、読み込んだバイト数 n
を0とし、エラーを nil
とすることで、呼び出し元が io.EOF
を期待するような挙動に近づけています。Goの io.Reader
や io.ReaderAt
の規約では、n < len(b)
かつ err == io.EOF
の場合にEOFと判断されるため、この修正は io.EOF
を直接返しているわけではありませんが、n=0, err=nil
のケースを適切に処理することで、io.EOF
が返されるべき状況でそれが起こるように間接的に修正しています。
また、syscall/ztypes_windows.go
に ERROR_HANDLE_EOF
の定数を追加することで、この特定のエラーコードをGoのコード内でシンボリックに参照できるようにしています。これにより、コードの可読性と保守性が向上します。
さらに、os/os_test.go
に追加された TestReadAtEOF
テストは、この修正が正しく機能することを確認するためのものです。このテストは、空のファイルに対して ReadAt
を呼び出し、その結果が io.EOF
であることを検証します。これにより、将来の変更でこの挙動が壊れないように保護されます。
コアとなるコードの変更箇所
src/pkg/os/file_windows.go
--- a/src/pkg/os/file_windows.go
+++ b/src/pkg/os/file_windows.go
@@ -312,6 +312,10 @@ func (f *File) pread(b []byte, off int64) (n int, err error) {
var done uint32
e = syscall.ReadFile(syscall.Handle(f.fd), b, &done, &o)
if e != nil {
+ if e == syscall.ERROR_HANDLE_EOF {
+ // end of file
+ return 0, nil
+ }
return 0, e
}
return int(done), nil
src/pkg/os/os_test.go
--- a/src/pkg/os/os_test.go
+++ b/src/pkg/os/os_test.go
@@ -1114,3 +1114,19 @@ func TestStatDirModeExec(t *testing.T) {
t.Errorf("Stat %q: mode %#o want %#o", path, dir.Mode()&mode, mode)
}
}
+
+func TestReadAtEOF(t *testing.T) {
+ f := newFile("TestReadAtEOF", t)
+ defer Remove(f.Name())
+ defer f.Close()
+
+ _, err := f.ReadAt(make([]byte, 10), 0)
+ switch err {
+ case io.EOF:
+ // all good
+ case nil:
+ t.Fatalf("ReadAt succeeded")
+ default:
+ t.Fatalf("ReadAt failed: %s", err)
+ }
+}
src/pkg/syscall/ztypes_windows.go
--- a/src/pkg/syscall/ztypes_windows.go
+++ b/src/pkg/syscall/ztypes_windows.go
@@ -10,6 +10,7 @@ const (
ERROR_PATH_NOT_FOUND Errno = 3
ERROR_ACCESS_DENIED Errno = 5
ERROR_NO_MORE_FILES Errno = 18
+\tERROR_HANDLE_EOF Errno = 38
ERROR_FILE_EXISTS Errno = 80
ERROR_BROKEN_PIPE Errno = 109
ERROR_BUFFER_OVERFLOW Errno = 111
コアとなるコードの解説
src/pkg/os/file_windows.go
の変更
File.pread
関数は、os.File
の ReadAt
メソッドが内部的に呼び出す関数です。この関数は、Windows APIの ReadFile
を使用して実際にファイルからデータを読み込みます。
変更前は、syscall.ReadFile
がエラーを返した場合、そのエラーがそのまま呼び出し元に伝播していました。
変更後では、syscall.ReadFile
がエラー e
を返した場合に、まずそのエラーが syscall.ERROR_HANDLE_EOF
であるかどうかをチェックしています。
もし e == syscall.ERROR_HANDLE_EOF
であれば、それはファイルの終端に達したことを意味するため、読み込んだバイト数 n
を 0
とし、エラー err
を nil
として返します。
Goの io.Reader
および io.ReaderAt
の規約では、読み込みバイト数が0でエラーが nil
の場合、それはEOFではないことを意味します。しかし、この文脈では、ReadAt
が0バイトを読み込み、かつエラーが io.EOF
であるべき状況で、Windowsの低レベルAPIが ERROR_HANDLE_EOF
を返すという特殊なケースを扱っています。この修正により、ReadAt
の呼び出し元は、この特定のWindowsエラーを直接処理する必要がなくなり、Goの標準的なEOF処理に依存できるようになります。
src/pkg/os/os_test.go
の変更
TestReadAtEOF
という新しいテスト関数が追加されました。
このテストは、まず一時的なファイルを作成し、そのファイルに対して ReadAt
を呼び出します。
空のファイルに対して ReadAt(make([]byte, 10), 0)
を呼び出すと、ファイルの終端であるため、何も読み込めずに io.EOF
エラーが返されることが期待されます。
テストは、ReadAt
から返されたエラーが io.EOF
であることを switch
文で確認します。
もしエラーが nil
であった場合(つまり、ReadAt
が成功してしまった場合)、それはテストの失敗とみなされます。
その他のエラーが返された場合も、テストの失敗とみなされます。
このテストの追加により、Windows環境での ReadAt
のEOF挙動が正しく修正されたことを検証し、将来のリグレッションを防ぐことができます。
src/pkg/syscall/ztypes_windows.go
の変更
このファイルは、Windowsのシステムコールで使用される定数や型を定義しています。
ERROR_HANDLE_EOF
という新しい定数が追加されました。その値は 38
です。
この定数の追加により、src/pkg/os/file_windows.go
の中で syscall.ERROR_HANDLE_EOF
を直接参照できるようになり、マジックナンバー(38
)を使用するよりもコードの意図が明確になります。これは、GoのコードがWindows APIのエラーコードをより安全かつ可読性の高い方法で扱うための改善です。
関連リンク
- Go CL 9952044: https://golang.org/cl/9952044
- Go Issue #5619: このコミットが修正した問題のトラッキング。
参考にした情報源リンク
- コミットメッセージと変更されたコード
- Go言語の
io
パッケージのドキュメント(io.EOF
およびio.ReaderAt
の規約) - Windows APIの
ReadFile
関数のドキュメント(ERROR_HANDLE_EOF
の挙動) - Go言語の
syscall
パッケージのドキュメント