[インデックス 18491] ファイルの概要
このコミットは、Go言語の標準ライブラリであるarchive/tar
パッケージに、拡張属性(Extended Attributes, xattrs)のサポートを追加するものです。具体的には、GNU tarやstarといったツールが生成するPAXヘッダ内のSCHILY.xattr
フィールドを介して、ファイルの拡張属性を読み書きできるようになります。これにより、Goのarchive/tar
パッケージがより広範なtarアーカイブ形式と互換性を持つようになり、特にLinuxなどのUnix系システムで利用されるファイルシステムの拡張属性を適切に扱えるようになります。
コミット
archive/tar: support extended attributes
This adds support for archives with the SCHILY.xattr field in the
pax header. This is what gnu tar and star generate.
Fixes #7154.
LGTM=dsymonds
R=golang-codereviews, gobot, dsymonds
CC=golang-codereviews
https://golang.org/cl/54570043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/87d58f44a1e53a61c7bd4b11a7f7aa616f6e373d
元コミット内容
archive/tar: support extended attributes
This adds support for archives with the SCHILY.xattr field in the
pax header. This is what gnu tar and star generate.
Fixes #7154.
LGTM=dsymonds
R=golang-codereviews, gobot, dsymonds
CC=golang-codereviews
https://golang.org/cl/54570043
変更の背景
この変更の主な背景は、Goのarchive/tar
パッケージが、GNU tarやstarなどの一般的なtar実装によって生成されたアーカイブに含まれる拡張属性を適切に処理できないという問題(Issue #7154)を解決することにありました。
従来のarchive/tar
パッケージは、ファイル名、サイズ、パーミッションなどの基本的なメタデータは扱えましたが、ファイルシステムが提供する拡張属性(例えば、SELinuxのセキュリティコンテキストやユーザー定義のメタデータなど)は無視していました。これにより、拡張属性が重要な役割を果たすシステム(例えば、SELinuxが有効なLinuxシステム)で作成されたtarアーカイブをGoで処理する際に、情報が失われる可能性がありました。
このコミットは、PAX拡張ヘッダのSCHILY.xattr
フィールドを認識し、これらの拡張属性をGoのtar.Header
構造体に取り込むことで、互換性と機能性を向上させることを目的としています。
前提知識の解説
Tarアーカイブ形式
Tar(Tape Archive)は、複数のファイルを一つのアーカイブファイルにまとめるためのファイル形式です。元々は磁気テープへのバックアップのために開発されましたが、現在ではファイルやディレクトリの配布、転送、バックアップなど、幅広い用途で利用されています。
基本的なtarアーカイブは、各ファイルのエントリがヘッダブロックとデータブロックで構成されます。ヘッダブロックにはファイル名、サイズ、パーミッション、所有者などのメタデータが含まれます。
PAX拡張ヘッダ (POSIX.1-2001/2008)
従来のtar形式には、ファイル名の長さ制限(100文字)、ファイルサイズの制限(8GB)、タイムスタンプの精度不足など、いくつかの制約がありました。これらの制約を克服し、より柔軟なメタデータをサポートするために、POSIX.1-2001(およびその後のPOSIX.1-2008)でPAX(Portable Archive eXchange)拡張ヘッダが導入されました。
PAX拡張ヘッダは、通常のtarヘッダの後に続く特別なヘッダブロックで、キーと値のペアの形式で追加のメタデータを格納します。これにより、長いファイル名、大きなファイルサイズ、高精度なタイムスタンプ、そして今回焦点となる拡張属性など、従来のtarヘッダでは表現できなかった情報を格納できるようになります。
拡張属性 (Extended Attributes, xattrs)
拡張属性(Extended Attributes, xattrs)は、ファイルシステムレベルでファイルやディレクトリに関連付けられるメタデータのことです。通常のファイルデータや標準的なファイルシステムメタデータ(パーミッション、タイムスタンプなど)とは別に、追加の情報を格納するために使用されます。
xattrsの一般的な用途には以下のようなものがあります。
- セキュリティコンテキスト: SELinuxやAppArmorなどの強制アクセス制御(MAC)システムが、ファイルにセキュリティラベルを付与するために使用します。
- アクセス制御リスト (ACL): 標準的なUnixパーミッションよりも詳細なアクセス制御を設定するために使用されます。
- ユーザー定義データ: アプリケーションがファイルに独自のメタデータを関連付けるために使用できます。例えば、写真ファイルに撮影場所やカメラモデルの情報を付与するなど。
xattrsはファイルシステムによってサポートが異なり、Linuxではext2/3/4、XFS、Btrfsなどがサポートしています。
SCHILY.xattr
フィールド
SCHILY.xattr
は、PAX拡張ヘッダ内で拡張属性を格納するために使用される特定のキーのプレフィックスです。これは、GNU tarやstarといった広く使われているtar実装が、ファイルシステムの拡張属性をアーカイブに含める際に採用しているデファクトスタンダードな形式です。
具体的には、SCHILY.xattr.attribute_name=attribute_value
という形式で、各拡張属性がPAXヘッダ内にキーと値のペアとして格納されます。例えば、SCHILY.xattr.security.selinux=unconfined_u:object_r:default_t:s0
のように記述されます。
Go言語のarchive/tar
パッケージ
Go言語の標準ライブラリには、archive/tar
パッケージが含まれており、tarアーカイブの作成(書き込み)と展開(読み込み)の機能を提供します。このパッケージは、tar.Reader
とtar.Writer
という主要な型を通じて、ストリームベースでtarアーカイブを処理します。
tar.Reader
:Next()
メソッドでアーカイブ内の次のファイルまたはディレクトリのヘッダを読み込み、Read()
メソッドでその内容を読み込みます。tar.Writer
:WriteHeader()
メソッドでファイルのヘッダを書き込み、Write()
メソッドでその内容を書き込みます。
tar.Header
構造体は、アーカイブ内の各エントリのメタデータを表現します。
技術的詳細
このコミットは、archive/tar
パッケージが拡張属性を透過的に扱えるように、以下の主要な変更を加えています。
-
tar.Header
構造体の拡張:src/pkg/archive/tar/common.go
において、Header
構造体にXattrs map[string]string
フィールドが追加されました。これにより、tarアーカイブから読み込まれた拡張属性がこのマップに格納され、また、このマップに設定された拡張属性がtarアーカイブに書き込まれるようになります。キーは拡張属性名(例:user.key
、security.selinux
)、値はその属性値です。 -
PAXヘッダからの拡張属性の読み込み:
src/pkg/archive/tar/reader.go
のmergePAX
関数が修正されました。この関数は、PAX拡張ヘッダから読み込まれたキーと値のペアをtar.Header
構造体にマージする役割を担っています。 変更後、mergePAX
は、キーがpaxXattr
(SCHILY.xattr.
)プレフィックスで始まるかどうかをチェックします。もしそうであれば、プレフィックスを除いた部分を拡張属性のキーとして、対応する値をhdr.Xattrs
マップに格納します。これにより、SCHILY.xattr
形式で格納された拡張属性が正しくパースされ、Header
構造体のXattrs
フィールドに読み込まれるようになります。 -
PAXヘッダへの拡張属性の書き込み:
src/pkg/archive/tar/writer.go
のwriteHeader
関数が修正されました。この関数は、tar.Header
構造体の内容をtarヘッダ(必要に応じてPAXヘッダも含む)として書き込む役割を担っています。 変更後、writeHeader
は、hdr.Xattrs
マップに拡張属性が設定されている場合、それらをPAXヘッダの形式(SCHILY.xattr.key=value
)に変換し、paxHeaders
マップに追加します。これにより、Header
構造体に設定された拡張属性が、PAX拡張ヘッダとしてtarアーカイブに書き込まれるようになります。 -
テストケースの追加と改善:
src/pkg/archive/tar/reader_test.go
には、xattrs.tar
という新しいテストデータファイルと、それに対応するuntarTest
ケースが追加されました。このテストケースは、拡張属性を含むtarアーカイブを読み込み、Header.Xattrs
フィールドが期待通りにパースされることを検証します。特に、security.selinux
のような特殊な属性(ヌル文字を含む場合がある)も正しく扱えることを確認しています。- ヘッダの比較には、
*hdr != *header
から!reflect.DeepEqual(*hdr, *header)
への変更が行われました。これは、Header
構造体にマップ型(Xattrs
)が追加されたため、単純な構造体比較ではマップの内容を正しく比較できないためです。reflect.DeepEqual
は、マップを含む複雑なデータ構造の深い比較を行うために必要です。 src/pkg/archive/tar/writer_test.go
には、TestPaxXattrs
という新しいテスト関数が追加されました。このテストは、Header.Xattrs
に拡張属性を設定してtarアーカイブを書き込み、その後そのアーカイブを読み込み直して、拡張属性が正しくラウンドトリップ(書き込みと読み込み)できることを検証します。
コアとなるコードの変更箇所
src/pkg/archive/tar/common.go
--- a/src/pkg/archive/tar/common.go
+++ b/src/pkg/archive/tar/common.go
@@ -57,6 +57,7 @@ type Header struct {
Devminor int64 // minor number of character or block device
AccessTime time.Time // access time
ChangeTime time.Time // status change time
+ Xattrs map[string]string
}
// File name constants from the tar spec.
@@ -189,6 +190,7 @@ const (
paxSize = "size"
paxUid = "uid"
paxUname = "uname"
+ paxXattr = "SCHILY.xattr."
paxNone = ""
)
src/pkg/archive/tar/reader.go
--- a/src/pkg/archive/tar/reader.go
+++ b/src/pkg/archive/tar/reader.go
@@ -139,8 +139,14 @@ func mergePAX(hdr *Header, headers map[string]string) error {
return err
}
hdr.Size = int64(size)
+ default:
+ if strings.HasPrefix(k, paxXattr) {
+ if hdr.Xattrs == nil {
+ hdr.Xattrs = make(map[string]string)
+ }
+ hdr.Xattrs[k[len(paxXattr):]] = v
+ }
}
-
}
return nil
}
src/pkg/archive/tar/writer.go
--- a/src/pkg/archive/tar/writer.go
+++ b/src/pkg/archive/tar/writer.go
@@ -236,6 +236,12 @@ func (tw *Writer) writeHeader(hdr *Header, allowPax bool) error {
return tw.err
}
+ if allowPax {
+ for k, v := range hdr.Xattrs {
+ paxHeaders[paxXattr+k] = v
+ }
+ }
+
if len(paxHeaders) > 0 {
if !allowPax {
return errInvalidHeader
src/pkg/archive/tar/reader_test.go
--- a/src/pkg/archive/tar/reader_test.go
+++ b/src/pkg/archive/tar/reader_test.go
@@ -180,7 +220,7 @@ testLoop:
f.Close()
continue testLoop
}
- if *hdr != *header {
+ if !reflect.DeepEqual(*hdr, *header) {
t.Errorf("test %d, entry %d: Incorrect header:\nhave %+v\nwant %+v",
i, j, *hdr, *header)
}
@@ -253,7 +293,7 @@ func TestIncrementalRead(t *testing.T) {
}
// check the header
- if *hdr != *headers[nread] {
+ if !reflect.DeepEqual(*hdr, *headers[nread]) {
t.Errorf("Incorrect header:\nhave %+v\nwant %+v",
*hdr, headers[nread])
}
コアとなるコードの解説
src/pkg/archive/tar/common.go
の変更
Header
構造体へのXattrs map[string]string
の追加は、拡張属性をGoのプログラム内で表現するための基盤を構築します。map[string]string
型は、拡張属性のキーと値を文字列として格納するのに適しています。paxXattr = "SCHILY.xattr."
定数の追加は、PAXヘッダ内で拡張属性を識別するためのプレフィックスを定義します。これにより、コード全体で一貫してこのプレフィックスを使用できるようになります。
src/pkg/archive/tar/reader.go
の変更
mergePAX
関数は、PAX拡張ヘッダから読み取られたキーと値のペアを処理します。
変更されたdefault
ケースでは、読み取られたキーk
がpaxXattr
("SCHILY.xattr."
)で始まるかどうかをstrings.HasPrefix
で確認します。
もしプレフィックスが一致すれば、それは拡張属性であると判断されます。
hdr.Xattrs == nil
のチェックは、Xattrs
マップがまだ初期化されていない場合にmake(map[string]string)
で初期化を行います。これにより、nilマップへの書き込みを防ぎます。hdr.Xattrs[k[len(paxXattr):]] = v
の行が、実際の拡張属性の格納を行います。k[len(paxXattr):]
は、キーから"SCHILY.xattr."
プレフィックスを取り除き、純粋な拡張属性名(例:"user.key"
)を取得します。その属性名と対応する値v
がXattrs
マップに格納されます。
src/pkg/archive/tar/writer.go
の変更
writeHeader
関数は、tar.Header
構造体の内容をtarアーカイブに書き込む際に、PAXヘッダを生成する必要がある場合に呼び出されます。
if allowPax
ブロック内で、hdr.Xattrs
マップがイテレートされます。paxHeaders[paxXattr+k] = v
の行は、Header
構造体のXattrs
マップに格納されている各拡張属性を、PAXヘッダの形式(SCHILY.xattr.key=value
)に変換してpaxHeaders
マップに追加します。このpaxHeaders
マップは、最終的にPAXヘッダとしてアーカイブに書き込まれます。
src/pkg/archive/tar/reader_test.go
の変更
テストコードにおける!reflect.DeepEqual(*hdr, *header)
への変更は非常に重要です。
Goにおいて、構造体の比較演算子==
は、構造体のすべてのフィールドが比較可能であり、かつそれらが等しい場合にtrue
を返します。しかし、マップ型は比較可能ではないため、構造体内にマップが含まれる場合、==
演算子では正しく比較できません。
reflect.DeepEqual
関数は、2つの値が「深く」等しいかどうかを再帰的にチェックします。これは、ポインタが指す値、配列、スライス、マップ、構造体など、複雑なデータ構造の内容を比較する際に特に有用です。
Header
構造体にXattrs
というマップフィールドが追加されたため、この変更はテストが正しく機能するために不可欠でした。
これらの変更により、Goのarchive/tar
パッケージは、拡張属性を含むtarアーカイブを完全にサポートし、より堅牢で互換性の高いアーカイブ処理を提供できるようになりました。
関連リンク
- Go Issue #7154: https://code.google.com/p/go/issues/detail?id=7154 (元のIssueトラッカーのリンクですが、現在はGitHubに移行しています)
- Go CL 54570043: https://golang.org/cl/54570043 (Goのコードレビューシステムにおける変更リストのリンク)
参考にした情報源リンク
- POSIX.1-2001/2008 PAX Format:
- Extended Attributes (Linux kernel documentation):
- GNU Tar Manual - Extended File Attributes:
- Go
reflect
package documentation: - Go
archive/tar
package documentation: - Tar (computing) - Wikipedia:
- Extended file attributes - Wikipedia:
- SELinux:
- ACL (Access Control List):