[インデックス 17848] ファイルの概要
このコミットは、Go言語の os
パッケージにおける os.File
の Readdir
メソッドの挙動を修正するものです。具体的には、Readdir
がディレクトリ内のエントリの Lstat
呼び出しで発生したエラーをどのように処理するかについて、Go 1.1.2 のセマンティクスを復元することを目的としています。
コミット
commit ef805fe3a06c251903b3d634d1e6dd3d15d3245f
Author: Russ Cox <rsc@golang.org>
Date: Tue Oct 29 11:50:40 2013 -0400
os: do not return Lstat errors from Readdir
This CL restores the Go 1.1.2 semantics for os.File's Readdir method.
The code in Go 1.1.2 was rewritten mainly because it looked buggy.
This new version attempts to be clearer but still provide the 1.1.2 results.
The important diff is not this CL's version against tip but this CL's version
against Go 1.1.2.
Go 1.1.2:
names, err := f.Readdirnames(n)
fi = make([]FileInfo, len(names))
for i, filename := range names {
fip, err := Lstat(dirname + filename)
if err == nil {
fi[i] = fip
} else {
fi[i] = &fileStat{name: filename}
}\n }\n return fi, err
This CL:
names, err := f.Readdirnames(n)
fi = make([]FileInfo, len(names))
for i, filename := range names {
fip, lerr := lstat(dirname + filename)
if lerr != nil {
fi[i] = &fileStat{name: filename}
continue
}
fi[i] = fip
}
return fi, err
The changes from Go 1.1.2 are stylistic, not semantic:
1. Use lstat instead of Lstat, for testing (done before this CL).
2. Make error handling in loop body look more like an error case.
3. Use separate error variable name in loop body, to be clear
we are not trying to influence the final return result.
Fixes #6656.
Fixes #6680.
R=golang-dev, bradfitz
CC=golang-dev
https://golang.org/cl/18870043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/ef805fe3a06c251903b3d634d1e6dd3d15d3245f
元コミット内容
このコミットは、os.File
の Readdir
メソッドが、ディレクトリ内の各エントリに対して Lstat
を呼び出す際に発生するエラーの処理方法を修正しています。以前のバージョンでは、Lstat
がエラーを返した場合、そのエラーが Readdir
メソッド全体の戻り値 err
に影響を与える可能性がありました。このコミットは、Lstat
のエラーを個々の FileInfo
エントリに限定し、Readdir
メソッド自体は Lstat
エラーによって中断されないように、Go 1.1.2 のセマンティクスを復元します。
変更の背景
Go 1.1.2 の os.File.Readdir
メソッドのコードは、その後のバージョンで「バグがあるように見えた」という理由で書き直されました。しかし、その書き換えによって、Readdir
がディレクトリ内の個々のエントリの Lstat
呼び出しでエラーが発生した場合に、そのエラーを Readdir
メソッドの最終的な戻り値 err
に伝播させてしまうという、意図しないセマンティクスの変更が生じていました。
ファイルシステムは動的な環境であり、Readdir
が実行されている間にファイルが削除されたり、アクセス権が変更されたりすることは珍しくありません。このような状況で Lstat
がエラーを返した場合、Go 1.1.2 ではそのエントリの FileInfo
はエラー情報を含まないダミーの fileStat
オブジェクトで置き換えられ、Readdir
メソッド自体は成功として完了し、他の有効なエントリの FileInfo
を返していました。しかし、その後の変更では、Lstat
エラーが発生すると Readdir
メソッド全体がエラーを返す可能性があり、これはユーザーが期待する挙動と異なる場合がありました。
このコミットは、この意図しないセマンティクスの変更を元に戻し、Go 1.1.2 の挙動、すなわち Lstat
エラーが発生しても Readdir
メソッド自体はエラーを返さず、問題のあるエントリはダミーの FileInfo
で表現されるという挙動を復元します。これにより、部分的に読み取り可能なディレクトリであっても、可能な限り多くの情報を取得できるようになります。
コミットメッセージで言及されている Fixes #6656
と Fixes #6680
は、この変更によって解決されるバグトラッキングシステムの課題番号ですが、Goの公開イシュートラッカーではこれらの番号に直接関連する情報は見つかりませんでした。これは、内部的な課題番号であるか、または非常に古い課題である可能性があります。
前提知識の解説
このコミットを理解するためには、以下のGo言語の概念とファイルシステム操作に関する知識が必要です。
os.File.Readdir(n int) ([]FileInfo, error)
: このメソッドは、ディレクトリの内容を読み取り、最大n
個のFileInfo
オブジェクトのスライスを返します。n
が0より大きい場合、最大n
個のエントリが返されます。n
が0以下の場合、すべてのエントリが返されます。このメソッドは、ディレクトリの読み取り中にエラーが発生した場合にerror
を返します。os.FileInfo
インターフェース: このインターフェースは、ファイルに関する情報(名前、サイズ、パーミッション、最終更新時刻など)を提供します。Readdir
メソッドは、ディレクトリ内の各エントリに対してこのインターフェースを実装したオブジェクトを返します。os.Lstat(name string) (FileInfo, error)
: この関数は、指定されたパスname
のファイルまたはディレクトリのFileInfo
を返します。Stat
とは異なり、シンボリックリンクの場合、リンク自体に関する情報を返し、リンクが指すファイルの情報は返しません。ファイルが存在しない、またはアクセスできない場合など、エラーが発生する可能性があります。- エラーハンドリング:
Go言語では、関数は通常、最後の戻り値として
error
型の値を返します。nil
はエラーがないことを意味し、非nil
の値はエラーが発生したことを示します。このコミットでは、ループ内で発生するLstat
エラーが、外側のReaddir
メソッドの最終的な戻り値err
に影響を与えないように、エラー変数のスコープと処理が重要になります。 continue
キーワード: Go言語のfor
ループ内でcontinue
キーワードが使用されると、現在のイテレーションの残りの部分がスキップされ、次のイテレーションが開始されます。このコミットでは、Lstat
エラーが発生した場合に、そのエントリの処理をスキップし、次のエントリの処理に進むために使用されています。
技術的詳細
このコミットの核心は、os.File.Readdir
メソッドの内部実装における Lstat
エラーの処理ロジックの変更です。
元のGo 1.1.2のコードは以下のようになっていました。
names, err := f.Readdirnames(n)
fi = make([]FileInfo, len(names))
for i, filename := range names {
fip, err := Lstat(dirname + filename) // ここで新しい 'err' 変数が宣言される
if err == nil {
fi[i] = fip
} else {
fi[i] = &fileStat{name: filename}
}
}
return fi, err // ここで返される 'err' は Readdirnames からのもの
このコードでは、for
ループ内で fip, err := Lstat(...)
とすることで、新しい err
変数が宣言され、Lstat
のエラーはその新しい err
に格納されます。この err
はループのスコープ内でのみ有効であり、Readdirnames
から返された外側の err
変数とは別物です。したがって、Lstat
がエラーを返しても、外側の err
には影響を与えません。エラーが発生した場合、fi[i]
にはダミーの fileStat
が設定されます。
しかし、Go 1.1.2 以降のバージョンでは、このロジックが変更され、Lstat
エラーが Readdir
メソッドの最終的な戻り値 err
に伝播する可能性がありました。コミットメッセージにはその具体的なコードは示されていませんが、おそらく Lstat
エラーが発生した場合に、外側の err
変数に Lstat
のエラーを代入するようなロジックが含まれていたと推測されます。
このコミットで導入されたコードは以下のようになります。
names, err := f.Readdirnames(n)
fi = make([]FileInfo, len(names))
for i, filename := range names {
fip, lerr := lstat(dirname + filename) // 'lerr' という新しい変数名を使用
if lerr != nil { // エラーが発生した場合
fi[i] = &fileStat{name: filename} // ダミーの FileInfo を設定
continue // 次のイテレーションへ進む
}
fi[i] = fip // エラーがなければ通常の FileInfo を設定
}
return fi, err // Readdirnames からの 'err' を返す
この変更のポイントは以下の通りです。
lstat
の使用:Lstat
の代わりにlstat
が使用されています。コミットメッセージによると、これはテストのために以前に行われた変更であり、このコミットのセマンティクスには影響しません。- エラー変数の明確化:
Lstat
の戻り値のエラー変数にlerr
という新しい名前が付けられています。これにより、このエラーがReaddir
メソッドの最終的な戻り値err
とは別のものであることが明確になります。 - エラー処理の明確化:
if lerr != nil
という条件でエラーケースを明示的に処理しています。 continue
の使用:Lstat
がエラーを返した場合、fi[i]
にダミーのfileStat
を設定した後、continue
を使用してループの次のイテレーションに直接進みます。これにより、Lstat
エラーがReaddir
メソッドの最終的な戻り値err
に影響を与えることなく、ディレクトリの読み取り処理が続行されます。
結果として、この変更により、Readdir
メソッドは Readdirnames
からのエラー(ディレクトリ自体を読み取れない場合など)のみを返し、個々の Lstat
呼び出しで発生したエラーは、そのエントリの FileInfo
をダミーで置き換えることで吸収されるようになります。これは、Go 1.1.2 の挙動と一致し、部分的にアクセスできないディレクトリでも Readdir
が成功する可能性を高めます。
テストファイル src/pkg/os/os_unix_test.go
の変更も重要です。TestReaddirWithBadLstat
関数は、Lstat
がエラーを返す状況をシミュレートし、Readdir
が期待通りにエラーを返さないことを検証しています。
変更前:
if err != ErrInvalid {
t.Fatalf("Expected Readdir to return ErrInvalid, got %v", err)
}
変更後:
if err != nil {
t.Fatalf("Expected Readdir to return no error, got %v", err)
}
このテストの変更は、Readdir
が Lstat
エラーを返さないという新しい(復元された)セマンティクスを反映しています。以前は ErrInvalid
を期待していましたが、変更後は nil
エラーを期待するようになっています。
コアとなるコードの変更箇所
src/pkg/os/file_unix.go
の readdir
関数内のループ部分が変更されています。
--- a/src/pkg/os/file_unix.go
+++ b/src/pkg/os/file_unix.go
@@ -165,14 +165,11 @@ func (f *File) readdir(n int) (fi []FileInfo, err error) {
fi = make([]FileInfo, len(names))\n for i, filename := range names {
\tfip, lerr := lstat(dirname + filename)
-\t\tif lerr == nil {
-\t\t\tfi[i] = fip
-\t\t} else {
+\t\tif lerr != nil {
\t\t\tfi[i] = &fileStat{name: filename}
-\t\t\tif err == nil {
-\t\t\t\terr = lerr
-\t\t\t}\n+\t\t\tcontinue
\t\t}
+\t\tfi[i] = fip
\t}
\treturn fi, err
}
また、src/pkg/os/os_unix_test.go
の TestReaddirWithBadLstat
関数も変更されています。
--- a/src/pkg/os/os_unix_test.go
+++ b/src/pkg/os/os_unix_test.go
@@ -92,8 +92,8 @@ func TestReaddirWithBadLstat(t *testing.T) {
defer func() { *LstatP = Lstat }()
dirs, err := handle.Readdir(-1)
-\tif err != ErrInvalid {
-\t\tt.Fatalf("Expected Readdir to return ErrInvalid, got %v", err)
+\tif err != nil {
+\t\tt.Fatalf("Expected Readdir to return no error, got %v", err)
}
foundfail := false
for _, dir := range dirs {
コアとなるコードの解説
src/pkg/os/file_unix.go
の変更は、readdir
関数(Readdir
メソッドの実体)内で、ディレクトリ内の各エントリに対して lstat
を呼び出すループのロジックを修正しています。
変更前は、lerr == nil
の場合に fi[i] = fip
を実行し、else
ブロックで lerr != nil
の場合に fi[i] = &fileStat{name: filename}
を設定していました。さらに、lerr
が nil
でない場合に、外側の err
変数に lerr
を代入しようとするロジック(if err == nil { err = lerr }
)がありました。このロジックは、Readdirnames
からのエラーがまだ err
に設定されていない場合にのみ lerr
を err
に伝播させることを意図していた可能性がありますが、Go 1.1.2 のセマンティクスとは異なっていました。
変更後は、if lerr != nil
という条件でエラーケースを直接処理します。エラーが発生した場合、fi[i]
にはファイル名のみを持つダミーの fileStat
オブジェクトが設定され、continue
キーワードによってループの次のイテレーションにスキップされます。これにより、lstat
エラーが readdir
関数の最終的な戻り値 err
に影響を与えることがなくなります。最終的に readdir
関数は、Readdirnames
から返された err
をそのまま返します。
src/pkg/os/os_unix_test.go
の変更は、このセマンティクスの変更をテストに反映させたものです。TestReaddirWithBadLstat
は、Lstat
がエラーを返すようにモックされた状況で Readdir
を呼び出します。変更前は、Readdir
が ErrInvalid
を返すことを期待していましたが、これは Lstat
エラーが伝播する挙動をテストしていました。変更後は、Readdir
が nil
エラーを返すことを期待するようになり、これは Lstat
エラーが吸収される新しい(復元された)セマンティクスを正確にテストしています。
関連リンク
- Go言語の
os
パッケージに関する公式ドキュメント: https://pkg.go.dev/os - Go言語の
os.File.Readdir
メソッドに関するドキュメント: https://pkg.go.dev/os#File.Readdir - Go言語の
os.Lstat
関数に関するドキュメント: https://pkg.go.dev/os#Lstat
コミットメッセージで言及されている Fixes #6656
および Fixes #6680
については、Goの公開イシュートラッカーでは直接関連する情報を見つけることができませんでした。
参考にした情報源リンク
- コミットメッセージ自体
- Go言語の公式ドキュメント (pkg.go.dev)
- Go言語のソースコード (GitHub)