[インデックス 12606] ファイルの概要
このコミットは、Go言語のmisc/dist
パッケージにおけるarchive/tar
ヘッダーの生成方法を改善するものです。具体的には、tar.Header
のフィールドをより適切に、かつプラットフォーム固有の情報を考慮して設定するための変更が加えられています。これにより、生成されるtarアーカイブのメタデータがより正確になります。
コミット
commit c405b58f3fa7988d42b5e5e46910344f342c5b45
Author: Brad Fitzpatrick <bradfitz@golang.org>
Date: Mon Mar 12 21:49:43 2012 -0700
misc/dist: better archive/tar Headers
This should live in archive/tar later (CL 5796073) but we
can always do that after Go 1 and stick it here for now.
R=golang-dev, adg
CC=golang-dev
https://golang.org/cl/5754096
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/c405b58f3fa7988d42b5e5e46910344f342c5b45
元コミット内容
misc/dist: better archive/tar Headers
この変更は、archive/tar
パッケージのヘッダーをより良くするためのものです。将来的にはarchive/tar
パッケージ自体に組み込まれるべき変更(CL 5796073)ですが、Go 1リリース後でも可能であるため、当面はmisc/dist
に配置されます。
変更の背景
Go言語のディストリビューションツール(misc/dist
)は、Goのバイナリ配布物を作成する際にtarアーカイブを使用します。このアーカイブに含まれるファイルのメタデータ(パーミッション、所有者、タイムスタンプなど)は、archive/tar
パッケージのtar.Header
構造体によって表現されます。
以前の実装では、tar.Header
のフィールド、特にファイルモードやタイプフラグの設定が限定的でした。例えば、シンボリックリンクやデバイスファイルなどの特殊なファイルタイプが適切に扱われていませんでした。また、ファイルのUID(ユーザーID)やGID(グループID)、アクセス時刻、変更時刻といったシステム固有の情報が、os.FileInfo
から直接取得できる情報だけでは不十分でした。
このコミットの背景には、生成されるtarアーカイブの互換性と正確性を向上させるという目的があります。特に、異なるオペレーティングシステム(Darwin, Linuxなど)でアーカイブを作成する際に、それぞれのシステムコールから得られる詳細なファイル情報をtar.Header
に反映させる必要がありました。コミットメッセージにある「This should live in archive/tar later」という記述から、この機能が将来的にはGo標準ライブラリのarchive/tar
パッケージに統合されるべき、より汎用的な改善であることが示唆されています。
前提知識の解説
archive/tar
パッケージ
Go言語の標準ライブラリに含まれるarchive/tar
パッケージは、TAR(Tape Archive)形式のファイルを読み書きするための機能を提供します。TARファイルは、複数のファイルを一つのアーカイブにまとめるための一般的な形式であり、Unix系システムで広く利用されています。
tar.Header
: TARアーカイブ内の各ファイルまたはディレクトリのメタデータを表す構造体です。ファイル名、サイズ、パーミッション、変更時刻、ファイルタイプ(通常ファイル、ディレクトリ、シンボリックリンクなど)、所有者情報(UID, GID, Uname, Gname)などが含まれます。tar.TypeReg
: 通常のファイルを意味するtar.Header
のTypeflag
定数です。tar.TypeDir
: ディレクトリを意味するtar.Header
のTypeflag
定数です。tar.TypeSymlink
: シンボリックリンクを意味するtar.Header
のTypeflag
定数です。
os.FileInfo
インターフェース
os.FileInfo
は、ファイルに関する情報(名前、サイズ、モード、変更時刻、ディレクトリかどうかなど)を提供するインターフェースです。os.Stat
やos.Lstat
関数を呼び出すことで、このインターフェースを実装した値を取得できます。
fi.Mode()
: ファイルのモードとパーミッションを返します。これには、ファイルタイプ(通常ファイル、ディレクトリ、シンボリックリンクなど)と、読み書き実行のパーミッションが含まれます。fi.Mode().Perm()
: ファイルのパーミッションビット(例:0755
)のみを返します。fi.Mode() & os.ModeType
: ファイルタイプを示すビットマスクです。これを使って、ファイルが通常ファイル、ディレクトリ、シンボリックリンクなどのどのタイプであるかを判別できます。fi.Sys()
: 基盤となるシステムコールから得られるファイル情報(syscall.Stat_t
など)を返します。これはinterface{}
型であり、具体的な型はOSによって異なります。
syscall.Stat_t
構造体
syscall
パッケージは、GoプログラムからOSのシステムコールに直接アクセスするための機能を提供します。syscall.Stat_t
は、stat
システムコールによって返されるファイルステータス情報を表す構造体です。この構造体には、ファイルのUID、GID、アクセス時刻、変更時刻、作成時刻、デバイス番号など、os.FileInfo
では直接提供されない詳細な情報が含まれています。
sys.Uid
: ファイルの所有者ユーザーID。sys.Gid
: ファイルの所有者グループID。sys.Atimespec
(Darwin) /sys.Atim
(Linux): ファイルの最終アクセス時刻。sys.Ctimespec
(Darwin) /sys.Ctim
(Linux): ファイルの最終ステータス変更時刻(inode変更時刻)。
ビルドタグ(Build Tags)
Go言語では、ソースファイルの先頭に// +build tag
のようなコメントを追加することで、特定のビルド条件を満たす場合にのみそのファイルをコンパイルするように指定できます。これをビルドタグと呼びます。
このコミットでは、stat_darwin.go
には// +build darwin
が、stat_linux.go
には// +build linux
がそれぞれ追加されています。これは、Darwin(macOS)システムでビルドされる場合はstat_darwin.go
が、Linuxシステムでビルドされる場合はstat_linux.go
がそれぞれコンパイルされ、他のOSでは無視されることを意味します。これにより、OS固有のシステムコールや構造体を利用したコードを、プラットフォームごとに分離して記述することが可能になります。
技術的詳細
このコミットの主要な変更点は、misc/dist/bindist.go
内のmakeTar
関数におけるtar.Header
の生成ロジックの改善と、プラットフォーム固有のファイル情報取得のための新しいファイルの導入です。
-
tarFileInfoHeader
関数の導入:- 以前は
makeTar
関数内で直接tar.Header
を初期化し、os.FileInfo
から情報を手動でコピーしていました。 - 新しい
tarFileInfoHeader
関数は、os.FileInfo
とファイルパス(シンボリックリンクの場合にos.Readlink
で使用)を受け取り、tar.Header
の基本的なフィールド(Name
,ModTime
,Mode
,Typeflag
,Size
,Linkname
)を初期化します。 - ファイルタイプ(通常ファイル、ディレクトリ、シンボリックリンク、デバイスファイルなど)に応じて、
tar.Header
のMode
フィールドにTAR仕様で定義されている対応する定数(c_ISREG
,c_ISDIR
,c_ISLNK
など)をOR演算で追加します。これにより、TARヘッダーのモード情報がより正確になります。 - シンボリックリンクの場合、
os.Readlink
を使用してリンク先のパスを取得し、h.Linkname
に設定します。
- 以前は
-
sysStat
変数の導入とプラットフォーム固有の実装:tarFileInfoHeader
関数内で、グローバル変数sysStat
がnil
でない場合に呼び出されるロジックが追加されました。sysStat
はfunc(fi os.FileInfo, h *tar.Header) error
型の関数ポインタです。- この
sysStat
変数は、misc/dist/stat_darwin.go
とmisc/dist/stat_linux.go
という新しいファイルで、それぞれのOS固有のinit
関数内で初期化されます。 stat_darwin.go
: Darwin(macOS)システムの場合、sysStat
はos.FileInfo
のSys()
メソッドから*syscall.Stat_t
を取得し、そこからUid
,Gid
,AccessTime
,ChangeTime
をtar.Header
に設定します。stat_linux.go
: Linuxシステムの場合も同様に、sysStat
は*syscall.Stat_t
からUid
,Gid
,AccessTime
,ChangeTime
をtar.Header
に設定します。Linuxのsyscall.Stat_t
ではタイムスタンプフィールドの名前がAtim
,Ctim
となっています。- このメカニズムにより、
tarFileInfoHeader
は汎用的なヘッダー生成ロジックを提供しつつ、sysStat
を通じてOS固有の詳細なファイルメタデータ(UID, GID, アクセス/変更時刻など)をtar.Header
に注入できるようになります。
-
bindist.go
の変更点:syscall
パッケージのインポートが削除されました。これは、syscall.Stat_t
への直接アクセスがsysStat
関数にカプセル化されたためです。makeTar
関数内で、ファイルの処理時にtarFileInfoHeader
を呼び出してtar.Header
を取得するように変更されました。tar.Header
のUname
とGname
は引き続き"root"に設定され、Uid
とGid
は明示的に0
に設定されます。これは、Goの配布物を作成する際に、アーカイブ内のファイルの所有者を一貫して"root"として扱うためのものです。ただし、sysStat
が設定されている場合は、OS固有のUID/GIDが優先されます。
これらの変更により、Goのディストリビューションが生成するtarアーカイブは、より正確で互換性の高いファイルメタデータを含むようになります。特に、UID/GIDやタイムスタンプといったシステム固有の情報が、各OSのシステムコールを通じて適切に反映されるようになりました。
コアとなるコードの変更箇所
misc/dist/bindist.go
--- a/misc/dist/bindist.go
+++ b/misc/dist/bindist.go
@@ -26,7 +26,6 @@ import (
"path/filepath"
"runtime"
"strings"
- "syscall"
)
var (
@@ -527,22 +526,16 @@ func makeTar(targ, workdir string) error {
if fi.IsDir() {
return nil
}
- var typeFlag byte
- switch {
- case fi.Mode()&os.ModeType == 0:
- typeFlag = tar.TypeReg
- default:
- log.Fatalf("makeTar: unknown file for file %q", name)
- }
- hdr := &tar.Header{
- Name: name,
- Mode: int64(fi.Sys().(*syscall.Stat_t).Mode),
- Size: fi.Size(),
- ModTime: fi.ModTime(),
- Typeflag: typeFlag,
- Uname: "root",
- Gname: "root",
+ hdr, err := tarFileInfoHeader(fi, path)
+ if err != nil {
+ return err
}
+ hdr.Name = name
+ hdr.Uname = "root"
+ hdr.Gname = "root"
+ hdr.Uid = 0
+ hdr.Gid = 0
+
err = tw.WriteHeader(hdr)
if err != nil {
return fmt.Errorf("Error writing file %q: %v", name, err)
@@ -686,3 +679,64 @@ func lookPath(prog string) (absPath string, err error) {
}
return
}
+
+// sysStat, if non-nil, populates h from system-dependent fields of fi.
+var sysStat func(fi os.FileInfo, h *tar.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
+)
+
+// tarFileInfoHeader creates a partially-populated Header from an os.FileInfo.
+// The filename parameter is used only in the case of symlinks, to call os.Readlink.
+// If fi is a symlink but filename is empty, an error is returned.
+func tarFileInfoHeader(fi os.FileInfo, filename string) (*tar.Header, error) {
+ h := &tar.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 = tar.TypeReg
+ h.Size = fi.Size()
+ case fi.IsDir():
+ h.Typeflag = tar.TypeDir
+ h.Mode |= c_ISDIR
+ case fi.Mode()&os.ModeSymlink != 0:
+ h.Typeflag = tar.TypeSymlink
+ h.Mode |= c_ISLNK
+ if filename == "" {
+ return h, fmt.Errorf("archive/tar: unable to populate Header.Linkname of symlinks")
+ }
+ targ, err := os.Readlink(filename)
+ if err != nil {
+ return h, err
+ }
+ h.Linkname = targ
+ case fi.Mode()&os.ModeDevice != 0:
+ if fi.Mode()&os.ModeCharDevice != 0 {
+ h.Mode |= c_ISCHR
+ h.Typeflag = tar.TypeChar
+ } else {
+ h.Mode |= c_ISBLK
+ h.Typeflag = tar.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
+}
misc/dist/stat_darwin.go
(新規ファイル)
--- /dev/null
+++ b/misc/dist/stat_darwin.go
@@ -0,0 +1,32 @@
+// 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.
+
+// +build darwin
+
+package main
+
+import (
+ "archive/tar"
+ "os"
+ "syscall"
+ "time"
+)
+
+func init() {
+ sysStat = func(fi os.FileInfo, h *tar.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 = time.Unix(sys.Atimespec.Unix())
+ h.ChangeTime = time.Unix(sys.Ctimespec.Unix())
+ // TODO(bradfitz): major/minor device numbers?
+ return nil
+ }
+}
misc/dist/stat_linux.go
(新規ファイル)
--- /dev/null
+++ b/misc/dist/stat_linux.go
@@ -0,0 +1,32 @@
+// 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.
+
+// +build linux
+
+package main
+
+import (
+ "archive/tar"
+ "os"
+ "syscall"
+ "time"
+)
+
+func init() {
+ sysStat = func(fi os.FileInfo, h *tar.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 = time.Unix(sys.Atim.Unix())
+ h.ChangeTime = time.Unix(sys.Ctim.Unix())
+ // TODO(bradfitz): major/minor device numbers?
+ return nil
+ }
+}
コアとなるコードの解説
misc/dist/bindist.go
import "syscall"
の削除: 以前はos.FileInfo
のSys()
メソッドから直接syscall.Stat_t
に型アサーションして利用していましたが、このロジックがsysStat
関数にカプセル化されたため、bindist.go
からはsyscall
パッケージの直接的なインポートが不要になりました。makeTar
関数の変更:- ファイルの
tar.Header
を生成する部分が、新しく導入されたtarFileInfoHeader
関数を呼び出す形に変更されました。これにより、ヘッダー生成ロジックがモジュール化され、可読性と保守性が向上しました。 hdr.Uid = 0
とhdr.Gid = 0
が明示的に設定されています。これは、Goの配布物に含まれるファイルの所有者情報を一貫させるための措置です。ただし、後述のsysStat
が設定されている場合は、OS固有のUID/GIDがこれらの値を上書きする可能性があります。
- ファイルの
sysStat
変数の宣言:var sysStat func(fi os.FileInfo, h *tar.Header) error
として宣言されています。この関数ポインタは、OS固有のファイル情報をtar.Header
に設定するためのフックとして機能します。デフォルトではnil
ですが、プラットフォーム固有のinit
関数によって設定されます。- TARモード定数:
c_ISDIR
,c_ISFIFO
,c_ISREG
などの定数が定義されています。これらはTAR仕様で定義されているファイルモードのビットマスクであり、tarFileInfoHeader
関数内でtar.Header
のMode
フィールドにファイルタイプに応じた適切な値を設定するために使用されます。 tarFileInfoHeader
関数の実装:- この関数は、
os.FileInfo
からtar.Header
の基本的なフィールド(Name
,ModTime
,Mode
,Typeflag
,Size
)を初期化します。 fi.Mode()
とos.ModeType
のビット演算によってファイルタイプを判別し、それに応じてh.Typeflag
とh.Mode
を設定します。- シンボリックリンク(
os.ModeSymlink
)の場合、os.Readlink
を呼び出してリンク先のパスを取得し、h.Linkname
に設定します。この際、ファイルパスが提供されていない場合はエラーを返します。 - デバイスファイル(
os.ModeDevice
)やソケットファイル(os.ModeSocket
)も適切に処理し、対応するTARモード定数とタイプフラグを設定します。 - 最後に、
sysStat
がnil
でない場合、つまりプラットフォーム固有のsysStat
関数が定義されている場合にそれを呼び出し、tar.Header
にOS固有の詳細情報を追加します。
- この関数は、
misc/dist/stat_darwin.go
および misc/dist/stat_linux.go
- ビルドタグ: それぞれのファイルの先頭に
// +build darwin
と// +build linux
というビルドタグが記述されています。これにより、GoコンパイラはターゲットOSに応じて適切なファイルのみをコンパイルします。 init
関数の実装:- これらのファイルには
init
関数が定義されています。Goプログラムでは、パッケージの初期化時にinit
関数が自動的に実行されます。 init
関数内で、グローバル変数sysStat
に、そのOS固有のファイル情報取得ロジックを実装した匿名関数が代入されます。- この匿名関数は、
os.FileInfo
のSys()
メソッドから*syscall.Stat_t
を取得し、その中のUid
,Gid
,Atimespec
/Atim
(アクセス時刻)、Ctimespec
/Ctim
(変更時刻)といったフィールドをtar.Header
にコピーします。 - これにより、
tarFileInfoHeader
関数が呼び出された際に、sysStat
を通じてプラットフォーム固有の正確なファイルメタデータがtar.Header
に反映されるようになります。コメントにあるTODO
は、ユーザー名やグループ名の解決、およびデバイスのメジャー/マイナー番号の取得が将来的な改善点であることを示しています。
- これらのファイルには
これらの変更は、Goのarchive/tar
パッケージが生成するアーカイブの品質と互換性を向上させるための重要なステップであり、特にクロスプラットフォームでのアーカイブ作成において、より正確なファイルメタデータを提供することを可能にしています。
関連リンク
- Go CL 5754096: https://golang.org/cl/5754096
- Go CL 5796073 (言及されているが、このコミットでは実装されていない): https://golang.org/cl/5796073
参考にした情報源リンク
- Go
archive/tar
パッケージのドキュメント: https://pkg.go.dev/archive/tar - Go
os
パッケージのドキュメント: https://pkg.go.dev/os - Go
syscall
パッケージのドキュメント: https://pkg.go.dev/syscall - Go ビルド制約(Build Constraints)に関するドキュメント: https://pkg.go.dev/cmd/go#hdr-Build_constraints
- TARファイル形式の仕様(IEEE Std 1003.1-2001 (POSIX.1)): https://pubs.opengroup.org/onlinepubs/007904975/utilities/pax.html#tag_04_13_06_04 (TARヘッダーのモード定数に関する情報)