[インデックス 11428] ファイルの概要
このコミットは、Go言語の archive/zip
パッケージに、os.FileInfo
と FileHeader
間で変換を行うための新しい関数を追加するものです。これにより、ZIPアーカイブ内のファイル情報をGoの標準的なファイル情報インターフェースと相互運用できるようになります。
コミット
commit b62a5099e4bf2e87525792dd562c20894fff878c
Author: Brad Fitzpatrick <bradfitz@golang.org>
Date: Thu Jan 26 15:31:09 2012 -0800
archive/zip: add functions to convert between os.FileInfo & FileHeader
Fixes #2186
R=golang-dev, gri, adg
CC=golang-dev
https://golang.org/cl/5579044
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/b62a5099e4bf2e87525792dd562c20894fff878c
元コミット内容
archive/zip: add functions to convert between os.FileInfo & FileHeader
Fixes #2186
R=golang-dev, gri, adg
CC=golang-dev
https://golang.org/cl/5579044
変更の背景
この変更の主な背景は、Goの標準ライブラリである archive/zip
パッケージと、ファイルシステム操作に関連する os
パッケージとの間の相互運用性を向上させることにあります。具体的には、ZIPアーカイブ内のエントリを表す FileHeader
構造体と、Goの一般的なファイル情報インターフェースである os.FileInfo
との間で、容易に変換できるようにすることが目的です。
コミットメッセージにある "Fixes #2186" は、この変更が特定の課題を解決することを示唆していますが、このコミットが2012年のものであるため、当時のGoのIssueトラッカーにおける #2186 の具体的な内容は、現在の公開情報からは特定が困難です。しかし、一般的にこのような変換機能の追加は、以下のようなニーズから発生します。
- APIの一貫性: Goの他のファイル操作API(例:
os.Stat
)がos.FileInfo
を返すため、archive/zip
パッケージも同様のインターフェースを提供することで、開発者が一貫した方法でファイル情報を扱えるようになります。 - 利便性:
os.FileInfo
はファイル名、サイズ、更新時刻、パーミッションなどの一般的なファイル属性を抽象化しており、このインターフェースを介してZIPエントリの情報を取得できると、既存のos.FileInfo
を受け入れる関数やライブラリと容易に連携できるようになります。 - コードの簡素化:
FileHeader
からos.FileInfo
への変換、またはその逆の変換を手動で行う必要がなくなるため、開発者のコードが簡素化され、エラーの可能性が減少します。
前提知識の解説
このコミットを理解するためには、以下のGo言語の概念とパッケージに関する知識が必要です。
archive/zip
パッケージ: Goの標準ライブラリの一部で、ZIPアーカイブの読み書きをサポートします。FileHeader
構造体: ZIPアーカイブ内の個々のファイル(エントリ)のメタデータ(ファイル名、圧縮・非圧縮サイズ、更新日時、パーミッションなど)を保持する構造体です。ZIPファイルのセントラルディレクトリレコードに対応します。File
構造体: ZIPアーカイブ内の個々のエントリを表す構造体で、FileHeader
を含み、エントリのデータへのアクセスを提供します。
os
パッケージ: Goの標準ライブラリの一部で、オペレーティングシステムとのインタラクション(ファイルシステム操作、プロセス管理など)を提供します。os.FileInfo
インターフェース: ファイルに関する抽象的な情報(ファイル名、サイズ、更新時刻、ファイルモード(パーミッションと種類)、ディレクトリかどうかなど)を提供するインターフェースです。os.Stat
関数などがこのインターフェースを実装した値を返します。os.FileMode
型: ファイルのパーミッションビットとファイルの種類(ディレクトリ、シンボリックリンクなど)を表す型です。
- インターフェースの実装: Goでは、ある型がインターフェースのすべてのメソッドを実装していれば、そのインターフェースを「実装している」とみなされます。明示的な宣言は不要です。
技術的詳細
このコミットでは、主に以下の2つの新しい機能が archive/zip
パッケージに追加されています。
-
FileHeader.FileInfo() os.FileInfo
メソッドの追加:FileHeader
構造体にFileInfo()
メソッドが追加されました。このメソッドは、FileHeader
の情報に基づいてos.FileInfo
インターフェースを実装する新しい型headerFileInfo
のインスタンスを返します。headerFileInfo
は内部的にFileHeader
へのポインタを持ち、os.FileInfo
インターフェースが要求するName()
,Size()
,IsDir()
,ModTime()
,Mode()
メソッドを実装しています。これにより、FileHeader
の情報をos.FileInfo
として透過的に扱えるようになります。IsDir()
メソッドは、FileHeader
のMode()
メソッドの結果に基づいてディレクトリかどうかを判断します。Size()
メソッドは、FileHeader
のUncompressedSize
をint64
にキャストして返します。
-
FileInfoHeader(fi os.FileInfo) (*FileHeader, error)
関数の追加:os.FileInfo
インターフェースを引数に取り、それに対応するFileHeader
構造体のポインタを返す新しいトップレベル関数です。- この関数は、
os.FileInfo
からファイル名、非圧縮サイズ、更新時刻、ファイルモードなどの情報を抽出し、新しいFileHeader
インスタンスに設定します。 - 特に、ファイルサイズがZIPフォーマットで許容される最大値(約4GB - 1バイト)を超える場合、エラーを返すようになっています。これは、ZIPフォーマットの制限(
UncompressedSize
がuint32
であるため)に対応するためです。 SetModTime()
とSetMode()
メソッドは、FileHeader
の内部表現(DOS日付/時刻形式や外部属性)に変換して設定するために使用されます。
また、既存の FileHeader.Mode()
メソッドも変更されています。
- 変更前は
(mode os.FileMode, err error)
のようにエラーを返していましたが、変更後は(mode os.FileMode)
となり、エラーを返さなくなりました。これは、モード情報の取得が常に成功すると見なされるようになったためです。 - これに伴い、
reader_test.go
内のtestFileMode
関数も、f.Mode()
がエラーを返さなくなったことに合わせて修正されています。
コアとなるコードの変更箇所
src/pkg/archive/zip/struct.go
--- a/src/pkg/archive/zip/struct.go
+++ b/src/pkg/archive/zip/struct.go
@@ -12,6 +12,7 @@ This package does not support ZIP64 or disk spanning.
package zip
import (
+ "errors"
"os"
"time"
)
@@ -55,6 +56,38 @@ type FileHeader struct {
Comment string
}
+// FileInfo returns an os.FileInfo for the FileHeader.
+func (fh *FileHeader) FileInfo() os.FileInfo {
+ return headerFileInfo{fh}
+}
+
+// headerFileInfo implements os.FileInfo.
+type headerFileInfo struct {
+ fh *FileHeader
+}
+
+func (fi headerFileInfo) Name() string { return fi.fh.Name }
+func (fi headerFileInfo) Size() int64 { return int64(fi.fh.UncompressedSize) }
+func (fi headerFileInfo) IsDir() bool { return fi.Mode().IsDir() }
+func (fi headerFileInfo) ModTime() time.Time { return fi.fh.ModTime() }
+func (fi headerFileInfo) Mode() os.FileMode { return fi.fh.Mode() }
+
+// FileInfoHeader creates a partially-populated FileHeader from an
+// os.FileInfo.
+func FileInfoHeader(fi os.FileInfo) (*FileHeader, error) {
+ size := fi.Size()
+ if size > (1<<32 - 1) {
+ return nil, errors.New("zip: file over 4GB")
+ }
+ fh := &FileHeader{
+ Name: fi.Name(),
+ UncompressedSize: uint32(size),
+ }
+ fh.SetModTime(fi.ModTime())
+ fh.SetMode(fi.Mode())
+ return fh, nil
+}
+
type directoryEnd struct {
diskNbr uint16 // unused
dirDiskNbr uint16 // unused
@@ -131,8 +164,7 @@ const (
)
// Mode returns the permission and mode bits for the FileHeader.
-// An error is returned in case the information is not available.
-func (h *FileHeader) Mode() (mode os.FileMode, err error) {
+func (h *FileHeader) Mode() (mode os.FileMode) {
switch h.CreatorVersion >> 8 {
case creatorUnix, creatorMacOSX:
mode = unixModeToFileMode(h.ExternalAttrs >> 16)
@@ -142,7 +174,7 @@ func (h *FileHeader) Mode() (mode os.FileMode, err error) {
if len(h.Name) > 0 && h.Name[len(h.Name)-1] == '/' {
mode |= os.ModeDir
}
- return mode, nil
+ return mode
}
// SetMode changes the permission and mode bits for the FileHeader.
src/pkg/archive/zip/reader_test.go
testFileMode
関数の変更。f.Mode()
がエラーを返さなくなったため、エラーハンドリングが削除されました。
--- a/src/pkg/archive/zip/reader_test.go
+++ b/src/pkg/archive/zip/reader_test.go
@@ -250,13 +250,9 @@ func readTestFile(t *testing.T, ft ZipTestFile, f *File) {
}
func testFileMode(t *testing.T, f *File, want os.FileMode) {
- mode, err := f.Mode()
+ mode := f.Mode()
if want == 0 {
- if err == nil {
- t.Errorf("%s mode: got %v, want none", f.Name, mode)
- }
- } else if err != nil {
- t.Errorf("%s mode: %s", f.Name, err)
+ t.Errorf("%s mode: got %v, want none", f.Name, mode)
} else if mode != want {
t.Errorf("%s mode: want %v, got %v", f.Name, want, mode)
}
src/pkg/archive/zip/zip_test.go
新しい TestFileHeaderRoundTrip
テストの追加。
--- a/src/pkg/archive/zip/zip_test.go
+++ b/src/pkg/archive/zip/zip_test.go
@@ -10,6 +10,7 @@ import (
"bytes"
"fmt"
"io"
+ "reflect"
"testing"
"time"
)
@@ -66,3 +67,22 @@ func TestModTime(t *testing.T) {
\tt.Errorf("times don't match: got %s, want %s", outTime, testTime)\n \t}\n }\n+\n+func TestFileHeaderRoundTrip(t *testing.T) {\n+\tfh := &FileHeader{\n+\t\tName: "foo.txt",\n+\t\tUncompressedSize: 987654321,\n+\t\tModifiedTime: 1234,\n+\t\tModifiedDate: 5678,\n+\t}\n+\tfi := fh.FileInfo()\n+\tfh2, err := FileInfoHeader(fi)\n+\n+\t// Ignore these fields:\n+\tfh2.CreatorVersion = 0\n+\tfh2.ExternalAttrs = 0\n+\n+\tif !reflect.DeepEqual(fh, fh2) {\n+\t\tt.Errorf("mismatch\\n input=%#v\\noutput=%#v\\nerr=%v", fh, fh2, err)\n+\t}\n+}\n```
## コアとなるコードの解説
### `FileHeader.FileInfo()` メソッド
このメソッドは、`FileHeader` のインスタンスから `os.FileInfo` インターフェースを実装する `headerFileInfo` 型の値を生成して返します。
```go
// FileInfo returns an os.FileInfo for the FileHeader.
func (fh *FileHeader) FileInfo() os.FileInfo {
return headerFileInfo{fh}
}
// headerFileInfo implements os.FileInfo.
type headerFileInfo struct {
fh *FileHeader
}
func (fi headerFileInfo) Name() string { return fi.fh.Name }
func (fi headerFileInfo) Size() int64 { return int64(fi.fh.UncompressedSize) }
func (fi headerFileInfo) IsDir() bool { return fi.Mode().IsDir() }
func (fi headerFileInfo) ModTime() time.Time { return fi.fh.ModTime() }
func (fi headerFileInfo) Mode() os.FileMode { return fi.fh.Mode() }
headerFileInfo
はFileHeader
へのポインタfh
を保持します。os.FileInfo
インターフェースの各メソッド(Name
,Size
,IsDir
,ModTime
,Mode
)は、内部のFileHeader
の対応するフィールドやメソッドを呼び出すことで実装されています。Size()
はUncompressedSize
(uint32) をint64
にキャストしています。IsDir()
はFileHeader
のMode()
メソッドの結果を利用して、それがディレクトリモードであるかをチェックします。
この実装により、FileHeader
を直接 os.FileInfo
を期待する関数に渡すことはできませんが、fh.FileInfo()
を呼び出すことで、os.FileInfo
として扱うことができるようになります。
FileInfoHeader(fi os.FileInfo) (*FileHeader, error)
関数
この関数は、既存の os.FileInfo
から新しい FileHeader
を作成します。
// FileInfoHeader creates a partially-populated FileHeader from an
// os.FileInfo.
func FileInfoHeader(fi os.FileInfo) (*FileHeader, error) {
size := fi.Size()
if size > (1<<32 - 1) { // ZIPフォーマットのサイズ制限チェック (約4GB)
return nil, errors.New("zip: file over 4GB")
}
fh := &FileHeader{
Name: fi.Name(),
UncompressedSize: uint32(size),
}
fh.SetModTime(fi.ModTime()) // os.FileInfoの更新時刻をFileHeaderに設定
fh.SetMode(fi.Mode()) // os.FileInfoのモードをFileHeaderに設定
return fh, nil
}
os.FileInfo
からName()
とSize()
を取得し、FileHeader
のName
とUncompressedSize
に設定します。- ZIPフォーマットの
UncompressedSize
フィールドがuint32
であるため、ファイルサイズが(1<<32 - 1)
(約4GB) を超える場合はエラーを返します。これはZIP64拡張をサポートしないこのパッケージの制約です。 SetModTime()
とSetMode()
は、FileHeader
の内部的な日付/時刻表現や外部属性に変換して設定するためのヘルパーメソッドです。
FileHeader.Mode()
メソッドの変更
既存の FileHeader.Mode()
メソッドは、エラーを返すシグネチャからエラーを返さないシグネチャに変更されました。
--- a/src/pkg/archive/zip/struct.go
+++ b/src/pkg/archive/zip/struct.go
@@ -131,8 +164,7 @@ const (
)
// Mode returns the permission and mode bits for the FileHeader.
-// An error is returned in case the information is not available.
-func (h *FileHeader) Mode() (mode os.FileMode, err error) {
+func (h *FileHeader) Mode() (mode os.FileMode) {
switch h.CreatorVersion >> 8 {
case creatorUnix, creatorMacOSX:
mode = unixModeToFileMode(h.ExternalAttrs >> 16)
@@ -142,7 +174,7 @@ func (h *FileHeader) Mode() (mode os.FileMode, err error) {
if len(h.Name) > 0 && h.Name[len(h.Name)-1] == '/' {
mode |= os.ModeDir
}
- return mode, nil
+ return mode
}
この変更は、FileHeader
からモード情報を取得する際に、常に有効な os.FileMode
が返されるという前提が確立されたことを示唆しています。これにより、呼び出し側でのエラーハンドリングが不要になり、コードが簡素化されます。
テストの変更
reader_test.go
のtestFileMode
関数は、FileHeader.Mode()
がエラーを返さなくなったことに合わせて修正されました。zip_test.go
にTestFileHeaderRoundTrip
という新しいテストが追加されました。このテストは、FileHeader
からos.FileInfo
を作成し、そのos.FileInfo
から再度FileHeader
を作成するという「ラウンドトリップ」の変換が正しく行われることを検証します。CreatorVersion
とExternalAttrs
フィールドは、変換プロセスで変更される可能性があるため、比較から除外されています。
これらの変更により、archive/zip
パッケージは os.FileInfo
との相互運用性が大幅に向上し、Goのファイルシステム関連APIとの連携がよりシームレスになりました。
関連リンク
- Go CL 5579044: https://golang.org/cl/5579044
参考にした情報源リンク
- Go言語の公式ドキュメント:
archive/zip
パッケージ,os
パッケージ - Go言語のソースコード (このコミットの差分)