[インデックス 13158] ファイルの概要
このコミットは、Go言語の標準ライブラリ archive/tar パッケージに FileInfoHeader 関数を追加するものです。この関数は、os.FileInfo インターフェースから tar.Header 構造体を生成する機能を提供し、ファイルシステムの情報に基づいてTARアーカイブのエントリヘッダを簡単に作成できるようにします。これにより、TARアーカイブの作成プロセスがより柔軟かつ効率的になります。
コミット
- コミットハッシュ:
2b98401a83465214d0ca5f2d52ea9d890ec6fc81 - Author: Brad Fitzpatrick bradfitz@golang.org
- Date: Thu May 24 14:10:54 2012 -0700
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/2b98401a83465214d0ca5f2d52ea9d890ec6fc81
元コミット内容
archive/tar: add FileInfoHeader function
Fixes #3295
R=adg, rsc, mike.rosset
CC=golang-dev
https://golang.org/cl/5796073
変更の背景
この変更は、Go言語のIssue #3295「archive/tar: os.FileInfo から Header を作成する関数を追加する」に対応するものです。既存の archive/tar パッケージでは、ファイルシステム上のファイルやディレクトリのメタデータ(os.FileInfo)から直接TARヘッダを作成する便利な方法がありませんでした。
TARアーカイブを作成する際、通常はファイルシステムから取得したファイル情報(パーミッション、サイズ、更新日時など)をTARヘッダに変換する必要があります。この変換を手動で行うのは手間がかかり、エラーの原因にもなり得ます。特に、シンボリックリンクのような特殊なファイルタイプを扱う場合には、その処理も考慮に入れる必要があります。
FileInfoHeader 関数の追加は、このプロセスを自動化し、開発者がより簡単にTARアーカイブを作成できるようにすることを目的としています。これにより、archive/tar パッケージの使いやすさと機能性が向上します。
前提知識の解説
os.FileInfo インターフェース
Go言語の os パッケージには、ファイルやディレクトリのメタデータ(名前、サイズ、パーミッション、更新日時、タイプなど)を抽象化するための FileInfo インターフェースが定義されています。os.Stat() や os.Lstat() 関数は、この FileInfo インターフェースを実装した値を返します。
type FileInfo interface {
Name() string // base name of the file
Size() int64 // length in bytes for regular files; system-dependent for others
Mode() FileMode // file mode bits
ModTime() time.Time // modification time
IsDir() bool // abbreviation for Mode().IsDir()
Sys() interface{} // underlying data source (can return nil)
}
Sys() メソッドは、基となるシステム固有の情報を返すために使用されます。例えばUnix系システムでは *syscall.Stat_t 型の値を返すことがあり、これによりアクセス時刻 (atime) や変更時刻 (ctime)、UID、GIDなどの詳細な情報を取得できます。
TARアーカイブフォーマット
TAR (Tape Archive) は、複数のファイルを一つのアーカイブファイルにまとめるためのファイルフォーマットです。各ファイルは「ヘッダ」とそれに続く「データ」で構成されます。ヘッダには、ファイル名、サイズ、パーミッション、所有者、グループ、更新日時などのメタデータが含まれます。
archive/tar パッケージでは、このヘッダ情報を Header 構造体で表現します。
type Header struct {
Name string // name of file
Mode int64 // permission and mode bits
Uid int // user id of owner
Gid int // group id of owner
Size int64 // length in bytes
ModTime time.Time // modification time
Typeflag byte // type of header entry
Linkname string // target name of link (for TypeLink, TypeSymlink)
Uname string // user name of owner
Gname string // group name of owner
Devmajor int64 // major device number (for TypeChar, TypeBlock)
Devminor int64 // minor device number (for TypeChar, TypeBlock)
AccessTime time.Time // access time
ChangeTime time.Time // status change time
}
ビルドタグ (Build Tags)
Go言語では、ソースファイルに特別なコメント行(ビルドタグ)を追加することで、特定の環境(OS、アーキテクチャなど)でのみそのファイルをコンパイルするように制御できます。例えば、// +build linux openbsd というタグが付いたファイルは、LinuxまたはOpenBSDシステムでのみコンパイルされます。
このコミットでは、stat_atim.go、stat_atimespec.go、stat_unix.go といったファイルが追加されており、それぞれ異なるOS向けのビルドタグが設定されています。これにより、各OSのシステムコール(syscall パッケージ)を利用して、より詳細なファイル情報を取得できるようになっています。
syscall.Stat_t 構造体
Unix系システムでは、stat システムコールによってファイルの詳細なメタデータが取得されます。Go言語の syscall パッケージは、このシステムコールの結果を Stat_t 構造体として提供します。この構造体には、アクセス時刻 (Atim または Atimespec)、変更時刻 (Ctim または Ctimespec)、UID、GIDなどの情報が含まれています。これらのフィールドはOSによって若干異なる場合があります。
技術的詳細
このコミットの主要な変更点は、archive/tar パッケージに FileInfoHeader 関数が追加されたことです。
FileInfoHeader 関数の役割
FileInfoHeader 関数は、os.FileInfo インターフェースとシンボリックリンクのターゲットパス(link 引数)を受け取り、対応する *tar.Header 構造体を返します。
func FileInfoHeader(fi os.FileInfo, link string) (*Header, error) {
if fi == nil {
return nil, errors.New("tar: FileInfo is nil")
}
h := &Header{
Name: fi.Name(),
ModTime: fi.ModTime(),
Mode: int64(fi.Mode().Perm()), // or'd with c_IS* constants later
}
switch {
case fi.Mode()&os.ModeType == 0: // 通常ファイル
h.Mode |= c_ISREG
h.Typeflag = TypeReg
h.Size = fi.Size()
case fi.IsDir(): // ディレクトリ
h.Typeflag = TypeDir
h.Mode |= c_ISDIR
case fi.Mode()&os.ModeSymlink != 0: // シンボリックリンク
h.Typeflag = TypeSymlink
h.Mode |= c_ISLNK
h.Linkname = link
case fi.Mode()&os.ModeDevice != 0: // デバイスファイル
if fi.Mode()&os.ModeCharDevice != 0 { // キャラクターデバイス
h.Mode |= c_ISCHR
h.Typeflag = TypeChar
} else { // ブロックデバイス
h.Mode |= c_ISBLK
h.Typeflag = TypeBlock
}
case fi.Mode()&os.ModeSocket != 0: // ソケット
h.Mode |= c_ISSOCK
default:
return nil, fmt.Errorf("archive/tar: unknown file mode %v", fi.Mode())
}
// システム固有の情報を追加
if sysStat != nil {
return h, sysStat(fi, h)
}
return h, nil
}
この関数は、まず os.FileInfo から基本的な情報(名前、更新日時、パーミッション)を tar.Header にコピーします。次に、fi.Mode() を調べてファイルタイプを判別し、対応するTARのタイプフラグ (TypeReg, TypeDir, TypeSymlink など) とモード定数 (c_ISREG, c_ISDIR など) を設定します。シンボリックリンクの場合には、Linkname フィールドに link 引数の値が設定されます。
システム固有の情報の取得 (sysStat とプラットフォーム固有ファイル)
FileInfoHeader の重要な側面は、sysStat という関数ポインタを利用して、システム固有のファイル情報を tar.Header に追加する点です。
var sysStat func(fi os.FileInfo, h *Header) errorがcommon.goに宣言されています。src/pkg/archive/tar/stat_unix.goファイルでは、init関数内でsysStat = statUnixと設定されています。このファイルは// +build linux darwin freebsd openbsd netbsdというビルドタグを持ち、Unix系システムでのみコンパイルされます。statUnix関数は、fi.Sys().(*syscall.Stat_t)を使ってos.FileInfoからsyscall.Stat_t構造体を取得し、そこからUID、GID、アクセス時刻 (AccessTime)、変更時刻 (ChangeTime) をtar.Headerに設定します。
アクセス時刻と変更時刻の取得は、さらにプラットフォーム固有のファイルに委譲されています。
src/pkg/archive/tar/stat_atim.go(// +build linux openbsd)statAtime(st *syscall.Stat_t) time.Timeはst.Atim.Unix()を使用。statCtime(st *syscall.Stat_t) time.Timeはst.Ctim.Unix()を使用。
src/pkg/archive/tar/stat_atimespec.go(// +build darwin freebsd netbsd)statAtime(st *syscall.Stat_t) time.Timeはst.Atimespec.Unix()を使用。statCtime(st *syscall.Stat_t) time.Timeはst.Ctimespec.Unix()を使用。
このように、ビルドタグと関数ポインタを組み合わせることで、Goのクロスプラットフォーム性を維持しつつ、各OSのシステムコールを利用して詳細なファイルメタデータをTARヘッダに正確にマッピングしています。
テストの追加
src/pkg/archive/tar/tar_test.go には、FileInfoHeader 関数の動作を検証するためのテストが追加されています。
TestFileInfoHeader: 通常のファイル (testdata/small.txt) からヘッダが正しく生成されることを確認します。TestFileInfoHeaderSymlink: シンボリックリンクからヘッダが正しく生成され、Linknameフィールドが設定されることを確認します。
コアとなるコードの変更箇所
api/next.txt
Go APIの変更を記録するファイルに、新しい関数が追加されています。
--- a/api/next.txt
+++ b/api/next.txt
@@ -1,3 +1,4 @@
+pkg archive/tar, func FileInfoHeader(os.FileInfo, string) (*Header, error)
pkg crypto/tls, const TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA uint16
pkg crypto/tls, const TLS_RSA_WITH_AES_256_CBC_SHA uint16
pkg crypto/x509, const ECDSA PublicKeyAlgorithm
src/pkg/archive/tar/common.go
errors,fmt,osパッケージがインポートに追加されました。sysStatグローバル変数(関数ポインタ)が宣言されました。- TARモード定数 (
c_ISDIR,c_ISREGなど) が追加されました。 FileInfoHeader関数が追加されました。
--- a/src/pkg/archive/tar/common.go
+++ b/src/pkg/archive/tar/common.go
@@ -11,7 +11,12 @@
// http://www.gnu.org/software/tar/manual/html_node/Standard.html
package tar
-import "time"
+import (
+ "errors"
+ "fmt"
+ "os"
+ "time"
+)
const (
blockSize = 512
@@ -49,6 +54,62 @@ type Header struct {
ChangeTime time.Time // status change time
}
+// sysStat, if non-nil, populates h from system-dependent fields of fi.
+var sysStat func(fi os.FileInfo, h *Header) error
+
+// Mode constants from the tar spec.
+const (
+ c_ISDIR = 040000
+ c_ISFIFO = 010000
+ c_ISREG = 0100000
+ c_ISLNK = 0120000
+ c_ISBLK = 060000
+ c_ISCHR = 020000
+ c_ISSOCK = 0140000
+)
+
+// FileInfoHeader creates a partially-populated Header from fi.
+// If fi describes a symlink, FileInfoHeader records link as the link target.
+func FileInfoHeader(fi os.FileInfo, link string) (*Header, error) {
+ if fi == nil {
+ return nil, errors.New("tar: FileInfo is nil")
+ }
+ h := &Header{
+ Name: fi.Name(),
+ ModTime: fi.ModTime(),
+ Mode: int64(fi.Mode().Perm()), // or'd with c_IS* constants later
+ }
+ switch {
+ case fi.Mode()&os.ModeType == 0:
+ h.Mode |= c_ISREG
+ h.Typeflag = TypeReg
+ h.Size = fi.Size()
+ case fi.IsDir():
+ h.Typeflag = TypeDir
+ h.Mode |= c_ISDIR
+ case fi.Mode()&os.ModeSymlink != 0:
+ h.Typeflag = TypeSymlink
+ h.Mode |= c_ISLNK
+ h.Linkname = link
+ case fi.Mode()&os.ModeDevice != 0:
+ if fi.Mode()&os.ModeCharDevice != 0 {
+ h.Mode |= c_ISCHR
+ h.Typeflag = TypeChar
+ } else {
+ h.Mode |= c_ISBLK
+ h.Typeflag = TypeBlock
+ }
+ case fi.Mode()&os.ModeSocket != 0:
+ h.Mode |= c_ISSOCK
+ default:
+ return nil, fmt.Errorf("archive/tar: unknown file mode %v", fi.Mode())
+ }
+ if sysStat != nil {
+ return h, sysStat(fi, h)
+ }
+ return h, nil
+}
+
var zeroBlock = make([]byte, blockSize)
// POSIX specifies a sum of the unsigned byte values, but the Sun tar uses signed byte values.
src/pkg/archive/tar/stat_atim.go (新規ファイル)
LinuxおよびOpenBSD向けのアクセス時刻・変更時刻取得関数。
// +build linux openbsd
package tar
import (
"syscall"
"time"
)
func statAtime(st *syscall.Stat_t) time.Time {
return time.Unix(st.Atim.Unix())
}
func statCtime(st *syscall.Stat_t) time.Time {
return time.Unix(st.Ctim.Unix())
}
src/pkg/archive/tar/stat_atimespec.go (新規ファイル)
Darwin (macOS), FreeBSD, NetBSD向けのアクセス時刻・変更時刻取得関数。
// +build darwin freebsd netbsd
package tar
import (
"syscall"
"time"
)
func statAtime(st *syscall.Stat_t) time.Time {
return time.Unix(st.Atimespec.Unix())
}
func statCtime(st *syscall.Stat_t) time.Time {
return time.Unix(st.Ctimespec.Unix())
}
src/pkg/archive/tar/stat_unix.go (新規ファイル)
Unix系システム向けの sysStat 実装。
// +build linux darwin freebsd openbsd netbsd
package tar
import (
"os"
"syscall"
)
func init() {
sysStat = statUnix
}
func statUnix(fi os.FileInfo, h *Header) error {
sys, ok := fi.Sys().(*syscall.Stat_t)
if !ok {
return nil
}
h.Uid = int(sys.Uid)
h.Gid = int(sys.Gid)
// TODO(bradfitz): populate username & group. os/user
// doesn't cache LookupId lookups, and lacks group
// lookup functions.
h.AccessTime = statAtime(sys)
h.ChangeTime = statCtime(sys)
// TODO(bradfitz): major/minor device numbers?
return nil
}
src/pkg/archive/tar/tar_test.go (新規ファイル)
FileInfoHeader 関数のテストケース。
// Copyright 2012 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package tar
import (
"os"
"testing"
"time"
)
func TestFileInfoHeader(t *testing.T) {
fi, err := os.Lstat("testdata/small.txt")
if err != nil {
t.Fatal(err)
}
h, err := FileInfoHeader(fi, "")
if err != nil {
t.Fatalf("on small.txt: %v", err)
}
if g, e := h.Name, "small.txt"; g != e {
t.Errorf("Name = %q; want %q", g, e)
}
if g, e := h.Mode, int64(0644|c_ISREG); g != e {
t.Errorf("Mode = %#o; want %#o", g, e)
}
if g, e := h.Size, int64(5); g != e {
t.Errorf("Size = %v; want %v", g, e)
}
if g, e := h.ModTime, fi.ModTime(); !g.Equal(e) {
t.Errorf("ModTime = %v; want %v", g, e)
}
}
func TestFileInfoHeaderSymlink(t *testing.T) {
h, err := FileInfoHeader(symlink{}, "some-target")
if err != nil {
t.Fatal(err)
}
if g, e := h.Name, "some-symlink"; g != e {
t.Errorf("Name = %q; want %q", g, e)
}
if g, e := h.Linkname, "some-target"; g != e {
t.Errorf("Linkname = %q; want %q", g, e)
}
}
type symlink struct{}
func (symlink) Name() string { return "some-symlink" }
func (symlink) Size() int64 { return 0 }
func (symlink) Mode() os.FileMode { return os.ModeSymlink }
func (symlink) ModTime() time.Time { return time.Time{} }
func (symlink) IsDir() bool { return false }
func (symlink) Sys() interface{} { return nil }
src/pkg/go/build/deps_test.go
archive/tar パッケージの依存関係に syscall が追加されました。
--- a/src/pkg/go/build/deps_test.go
+++ b/src/pkg/go/build/deps_test.go
@@ -177,7 +177,7 @@ var pkgDeps = map[string][]string{\
},
// One of a kind.
- "archive/tar": {"L4", "OS"},
+ "archive/tar": {"L4", "OS", "syscall"},
"archive/zip": {"L4", "OS", "compress/flate"},
"compress/bzip2": {"L4"},
"compress/flate": {"L4"},
コアとなるコードの解説
FileInfoHeader 関数
この関数は、os.FileInfo から tar.Header を生成する中心的なロジックを担います。
- 基本的な情報のコピー:
fi.Name(),fi.ModTime(),fi.Mode().Perm()からHeaderのName,ModTime,Modeフィールドを初期化します。 - ファイルタイプの判別と設定:
fi.Mode()のos.ModeTypeビットマスクを使ってファイルタイプを判別し、対応するTARのタイプフラグ (TypeReg,TypeDir,TypeSymlinkなど) とモード定数 (c_ISREG,c_ISDIRなど) を設定します。- 通常ファイル (
os.ModeType == 0):TypeRegとc_ISREGを設定し、fi.Size()をSizeにコピーします。 - ディレクトリ (
fi.IsDir()):TypeDirとc_ISDIRを設定します。 - シンボリックリンク (
os.ModeSymlink):TypeSymlinkとc_ISLNKを設定し、link引数をLinknameにコピーします。 - デバイスファイル (
os.ModeDevice): キャラクターデバイス (os.ModeCharDevice) かブロックデバイスかを判別し、それぞれTypeChar/c_ISCHRまたはTypeBlock/c_ISBLKを設定します。 - ソケット (
os.ModeSocket):c_ISSOCKを設定します。 - その他の不明なファイルモードの場合はエラーを返します。
- 通常ファイル (
- システム固有の情報の追加:
sysStat関数ポインタがnilでない場合、その関数を呼び出して、UID、GID、アクセス時刻、変更時刻などのシステム固有の情報をHeaderに追加します。これにより、より完全なTARヘッダが作成されます。
sysStat とプラットフォーム固有の stat 関数
sysStat は、Goのクロスプラットフォーム設計の典型的な例です。
stat_unix.goは、Unix系OS(Linux, macOS, FreeBSD, OpenBSD, NetBSD)向けにコンパイルされ、init関数でsysStatにstatUnix関数を割り当てます。statUnix関数は、os.FileInfoのSys()メソッドから*syscall.Stat_tを取得し、そこからUID、GID、そしてプラットフォーム固有のstatAtimeとstatCtime関数を使ってアクセス時刻と変更時刻を取得します。stat_atim.goとstat_atimespec.goは、それぞれ異なるUnix系OSのsyscall.Stat_t構造体のフィールド名(AtimvsAtimespec)の違いを吸収するために存在します。これにより、各OSで正しいタイムスタンプフィールドが参照されるようになります。
これらの変更により、archive/tar パッケージは、ファイルシステムから取得した情報を基に、より正確で完全なTARヘッダを生成できるようになり、TARアーカイブの作成がより堅牢かつ容易になりました。
関連リンク
- Go CL: https://golang.org/cl/5796073
- Go Issue #3295: https://code.google.com/p/go/issues/detail?id=3295 (現在はGitHubに移行済み)
参考にした情報源リンク
- Go
os.FileInfodocumentation: https://pkg.go.dev/os#FileInfo - Go
archive/tardocumentation: https://pkg.go.dev/archive/tar - Go
syscalldocumentation: https://pkg.go.dev/syscall - Go Build Constraints (Build Tags): https://pkg.go.dev/cmd/go#hdr-Build_constraints
- TAR File Format (GNU Tar Manual): https://www.gnu.org/software/tar/manual/html_node/Standard.html