[インデックス 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
構造体のフィールド名(Atim
vsAtimespec
)の違いを吸収するために存在します。これにより、各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.FileInfo
documentation: https://pkg.go.dev/os#FileInfo - Go
archive/tar
documentation: https://pkg.go.dev/archive/tar - Go
syscall
documentation: 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