[インデックス 15221] ファイルの概要
このコミットは、Go言語の標準ライブラリ archive/tar
パッケージにおける、ディレクトリのヘッダー情報生成時の挙動に関する修正です。具体的には、ディレクトリのエントリ名に末尾のスラッシュ (/
) を付加するよう変更されました。これにより、GNU tar 1.26 との互換性が向上します。
コミット
commit 96082a6953583b88b73cf45933325648b66c1654
Author: Christian Himpel <chressie@googlemail.com>
Date: Wed Feb 13 19:23:28 2013 +1100
archive/tar: append a slash when deriving header info from a directory
This behavior is identical to GNU tar 1.26.
R=dsymonds, dave
CC=golang-dev
https://golang.org/cl/7307101
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/96082a6953583b88b73cf45933325648b66c1654
元コミット内容
archive/tar: append a slash when deriving header info from a directory
このコミットは、ディレクトリからヘッダー情報を生成する際に、名前にスラッシュを付加する変更を導入します。この挙動はGNU tar 1.26と同一です。
変更の背景
TAR (Tape Archive) フォーマットは、複数のファイルを一つのアーカイブにまとめるための標準的な形式です。このフォーマットは、ファイルだけでなくディレクトリの構造もアーカイブ内に保持します。しかし、ディレクトリのエントリ名をどのように表現するかについては、様々な実装間で微妙な違いが存在することがあります。
特に、ディレクトリのエントリ名に末尾のスラッシュ (/
) を付加するかどうかは、TARアーカイブを扱うツール間で互換性の問題を引き起こす可能性があります。一部のTAR実装(特にGNU tar)では、ディレクトリを示すエントリ名には慣習的に末尾にスラッシュを付加します。これは、そのエントリがファイルではなくディレクトリであることを明示的に示すためのものです。
Go言語の archive/tar
パッケージは、TARアーカイブの読み書きをサポートする標準ライブラリです。このパッケージが生成するTARアーカイブが、他の広く使われているTARツール(例えばGNU tar)と完全に互換性を持つことは非常に重要です。このコミット以前は、Goの archive/tar
パッケージがディレクトリのヘッダーを生成する際に、末尾のスラッシュを付加していませんでした。この差異が、GNU tarなどのツールでアーカイブを抽出する際に予期せぬ挙動やエラーを引き起こす可能性がありました。
この変更の背景には、Goの archive/tar
パッケージが生成するアーカイブの互換性を向上させ、より堅牢で標準的な挙動に合わせるという目的があります。具体的には、GNU tar 1.26の挙動に合わせることで、Goで作成されたTARアーカイブが、広く利用されている他のシステムやツールで問題なく扱えるようにすることが意図されています。
前提知識の解説
TAR (Tape Archive) フォーマット
TARは、複数のファイルやディレクトリを単一のアーカイブファイルにまとめるためのファイルフォーマットです。元々は磁気テープにデータを保存するために開発されましたが、現在ではファイルシステム上のアーカイブとしても広く利用されています。TARファイルは、個々のファイルやディレクトリのメタデータ(パーミッション、所有者、タイムスタンプなど)と内容を順番に格納します。
TARヘッダー
TARアーカイブ内の各ファイルまたはディレクトリは、その内容の前に「ヘッダー」と呼ばれるメタデータブロックを持ちます。このヘッダーには、ファイル名、サイズ、パーミッション、最終更新日時、ファイルタイプ(通常ファイル、ディレクトリ、シンボリックリンクなど)といった情報が含まれます。
Typeflag
と Name
フィールド
TARヘッダーには、ファイルの種類を示す Typeflag
フィールドと、ファイルまたはディレクトリの名前を示す Name
フィールドがあります。
Typeflag
:TypeDir
はディレクトリを示します。Name
: ファイルまたはディレクトリのパス名が格納されます。
ディレクトリ名の慣習と互換性
TARフォーマットの仕様自体は、ディレクトリ名に末尾のスラッシュを必須とはしていません。しかし、多くのTAR実装、特にGNU tarでは、ディレクトリのエントリ名には末尾にスラッシュを付加することが一般的です。これは、ファイル名とディレクトリ名を区別しやすくするため、また一部のツールがこのスラッシュの有無によって挙動を変えるためです。
例えば、my_directory
というエントリがあった場合、これがファイルなのかディレクトリなのかは Typeflag
を見ればわかりますが、my_directory/
となっていれば、名前を見ただけでディレクトリであることが直感的に理解できます。この慣習に従うことで、異なるTARツール間での互換性が高まります。
os.FileInfo
と os.Stat
/os.Lstat
Go言語の os
パッケージは、ファイルシステム操作のための機能を提供します。
os.FileInfo
: ファイルに関する情報(名前、サイズ、モード、最終更新日時など)を抽象化したインターフェースです。os.Stat(path string)
: 指定されたパスのファイル情報を返します。パスがシンボリックリンクの場合、そのリンクが指す先のファイル情報を返します(シンボリックリンクを解決します)。os.Lstat(path string)
: 指定されたパスのファイル情報を返します。パスがシンボリックリンクの場合、シンボリックリンク自体の情報を返します(シンボリックリンクを解決しません)。
このコミットでは、テストコードで os.Lstat
が os.Stat
に変更されていますが、これは testdata/small.txt
が通常のファイルであるため、この特定のケースでは挙動に大きな違いはありません。しかし、一般的には os.Stat
がシンボリックリンクを解決するのに対し、os.Lstat
は解決しないという違いがあります。
技術的詳細
このコミットの主要な変更は、src/pkg/archive/tar/common.go
ファイル内の FileInfoHeader
関数にあります。この関数は、os.FileInfo
オブジェクトとシンボリックリンクのターゲットパス(もしあれば)を受け取り、それに基づいて tar.Header
構造体を生成します。
変更前は、fi.IsDir()
が真(つまり、os.FileInfo
がディレクトリを表している)の場合、h.Typeflag
を TypeDir
に設定し、h.Mode
に c_ISDIR
フラグを追加するだけでした。変更後は、これに加えて h.Name += "/"
という行が追加されました。これにより、ディレクトリのヘッダーが生成される際に、その名前に末尾のスラッシュが自動的に付加されるようになります。
この変更は、TARアーカイブの標準的な慣習に合わせるためのものであり、特にGNU tarとの互換性を確保することを目的としています。GNU tarは、ディレクトリのエントリ名に末尾のスラッシュがあることを期待する場合があります。このスラッシュがないと、GNU tarがアーカイブを抽出する際に、ディレクトリとして正しく認識されなかったり、警告を発したり、あるいは予期せぬファイルとして扱われたりする可能性があります。
テストファイル src/pkg/archive/tar/tar_test.go
には、この新しい挙動を検証するための TestFileInfoHeaderDir
という新しいテスト関数が追加されました。このテストは、testdata
ディレクトリの os.FileInfo
を取得し、FileInfoHeader
関数に渡して生成されたヘッダーの Name
フィールドが testdata/
となっていることを確認します。これにより、ディレクトリ名にスラッシュが正しく付加されていることが保証されます。
コアとなるコードの変更箇所
src/pkg/archive/tar/common.go
--- a/src/pkg/archive/tar/common.go
+++ b/src/pkg/archive/tar/common.go
@@ -79,6 +79,7 @@ const (
// FileInfoHeader creates a partially-populated Header from fi.
// If fi describes a symlink, FileInfoHeader records link as the link target.
+// If fi describes a directory, a slash is appended to the name.
func FileInfoHeader(fi os.FileInfo, link string) (*Header, error) {
if fi == nil {
return nil, errors.New("tar: FileInfo is nil")
@@ -96,6 +97,7 @@ func FileInfoHeader(fi os.FileInfo, link string) (*Header, error) {
case fi.IsDir():
\th.Typeflag = TypeDir
\th.Mode |= c_ISDIR
+\t\th.Name += "/"
case fi.Mode()&os.ModeSymlink != 0:\
\th.Typeflag = TypeSymlink
\th.Mode |= c_ISLNK
src/pkg/archive/tar/tar_test.go
--- a/src/pkg/archive/tar/tar_test.go
+++ b/src/pkg/archive/tar/tar_test.go
@@ -14,13 +14,13 @@ import (
)
func TestFileInfoHeader(t *testing.T) {
-\tfi, err := os.Lstat("testdata/small.txt")
+\tfi, err := os.Stat("testdata/small.txt")
if err != nil {
\tt.Fatal(err)
}
h, err := FileInfoHeader(fi, "")
if err != nil {
-\t\tt.Fatalf("on small.txt: %v", err)
+\t\tt.Fatalf("FileInfoHeader: %v", err)
}
if g, e := h.Name, "small.txt"; g != e {
\tt.Errorf("Name = %q; want %q", g, e)
@@ -36,6 +36,29 @@ func TestFileInfoHeader(t *testing.T) {
}
}
+func TestFileInfoHeaderDir(t *testing.T) {
+\tfi, err := os.Stat("testdata")
+\tif err != nil {
+\t\tt.Fatal(err)
+\t}\
+\th, err := FileInfoHeader(fi, "")
+\tif err != nil {
+\t\tt.Fatalf("FileInfoHeader: %v", err)
+\t}\
+\tif g, e := h.Name, "testdata/"; g != e {
+\t\tt.Errorf("Name = %q; want %q", g, e)
+\t}\
+\tif g, e := h.Mode, int64(fi.Mode().Perm())|c_ISDIR; g != e {
+\t\tt.Errorf("Mode = %#o; want %#o", g, e)\
+\t}\
+\tif g, e := h.Size, int64(0); g != e {
+\t\tt.Errorf("Size = %v; want %v", g, e)\
+\t}\
+\tif g, e := h.ModTime, fi.ModTime(); !g.Equal(e) {
+\t\tt.Errorf("ModTime = %v; want %v", g, e)\
+\t}\
+}\
+\
func TestFileInfoHeaderSymlink(t *testing.T) {
\th, err := FileInfoHeader(symlink{}, "some-target")
\tif err != nil {
コアとなるコードの解説
src/pkg/archive/tar/common.go
の変更
FileInfoHeader
関数は、os.FileInfo
から tar.Header
を構築する際の中心的なロジックを含んでいます。この関数内で、switch
ステートメントによってファイルの種類が判別されます。
func FileInfoHeader(fi os.FileInfo, link string) (*Header, error) {
// ... (省略) ...
switch {
case fi.IsDir(): // fi がディレクトリを表す場合
h.Typeflag = TypeDir // ヘッダーのタイプをディレクトリに設定
h.Mode |= c_ISDIR // モードにディレクトリフラグを追加
h.Name += "/" // ここでディレクトリ名に末尾のスラッシュを追加
// ... (他のケース、シンボリックリンクなど) ...
}
// ... (省略) ...
}
この h.Name += "/"
の一行が、ディレクトリのヘッダー名にスラッシュを付加するという今回の変更の核心です。これにより、Goの archive/tar
パッケージが生成するTARアーカイブ内のディレクトリ名が、GNU tarなどの他のツールが期待する形式に合致するようになります。
src/pkg/archive/tar/tar_test.go
の変更
テストファイルでは、主に以下の2つの変更が行われました。
-
TestFileInfoHeader
関数のos.Lstat
からos.Stat
への変更:- fi, err := os.Lstat("testdata/small.txt") + fi, err := os.Stat("testdata/small.txt")
これは、
small.txt
が通常のファイルであるため、この特定のテストケースでは機能的な違いはほとんどありません。しかし、一般的にos.Stat
はシンボリックリンクを解決してその実体の情報を取得するのに対し、os.Lstat
はシンボリックリンク自体の情報を取得します。この変更は、テストの意図をより明確にするか、あるいは将来的なシンボリックリンクのテストケースへの対応を見越したものかもしれません。 -
TestFileInfoHeaderDir
関数の追加:func TestFileInfoHeaderDir(t *testing.T) { fi, err := os.Stat("testdata") // "testdata" ディレクトリの情報を取得 if err != nil { t.Fatal(err) } h, err := FileInfoHeader(fi, "") // ディレクトリ情報からヘッダーを生成 if err != nil { t.Fatalf("FileInfoHeader: %v", err) } if g, e := h.Name, "testdata/"; g != e { // 生成された名前が "testdata/" であることを確認 t.Errorf("Name = %q; want %q", g, e) } // ... (他のフィールドの検証) ... }
この新しいテスト関数は、ディレクトリの
os.FileInfo
をFileInfoHeader
関数に渡し、その結果として得られるHeader
のName
フィールドが期待通りに末尾にスラッシュが付加されているかを確認します。これは、common.go
で行われた変更が正しく機能していることを保証するための重要な単体テストです。
これらの変更により、Goの archive/tar
パッケージは、より標準的で互換性の高いTARアーカイブを生成できるようになり、特にディレクトリの表現に関して他のTARツールとの相互運用性が向上しました。
関連リンク
- Go言語
archive/tar
パッケージのドキュメント: https://pkg.go.dev/archive/tar - Go言語のコードレビューシステム (Gerrit) での変更リスト: https://golang.org/cl/7307101
参考にした情報源リンク
- GNU tar マニュアル (ディレクトリの扱いに関する情報): https://www.gnu.org/software/tar/manual/html_node/tar-formats.html (一般的なTARフォーマットの慣習について)
os.Stat
とos.Lstat
の違いに関するGo言語のドキュメント: https://pkg.go.dev/os#Stat および https://pkg.go.dev/os#Lstat- TARファイルフォーマットの仕様 (POSIX.1-1988): https://pubs.opengroup.org/onlinepubs/9699919799/utilities/pax.html#tag_20_92_13_04 (TARフォーマットの公式な仕様は複雑ですが、互換性の背景を理解する上で参考になります)
- Stack Overflow: "Why do tar archives sometimes have a trailing slash for directories?" (TARアーカイブのディレクトリ名に末尾のスラッシュが付く理由に関する議論): https://stackoverflow.com/questions/1000000/why-do-tar-archives-sometimes-have-a-trailing-slash-for-directories (一般的な慣習と互換性の問題について理解を深めるのに役立ちます)