[インデックス 15236] ファイルの概要
このコミットは、Go言語の標準ライブラリ archive/tar
パッケージにおける機能拡張とバグ修正に関するものです。具体的には、tar.Header
構造体から os.FileInfo
インターフェースを実装したオブジェクトを取得するための FileInfo()
メソッドが追加され、FileInfoHeader
関数が FIFO (名前付きパイプ)、setuid、setgid、およびスティッキービットといった特殊なファイルモードを適切に処理できるよう改善されました。これにより、tarアーカイブのメタデータとGoのファイルシステム抽象化との間の相互運用性が向上し、より多様なファイルタイプやパーミッション属性を正確に表現できるようになります。
コミット
commit 44d38ae3c0f44449933a5e56ed4282fabb2ffb09
Author: Robin Eklind <r.eklind.87@gmail.com>
Date: Thu Feb 14 17:32:48 2013 +1100
archive/tar: add Header.FileInfo method. Add more cases to FileInfoHeader.
FileInfoHeader can now handle fifo, setuid, setgid and sticky bits.
Fixes #4695.
R=golang-dev, donovanhide, r.eklind.87, minux.ma, adg
CC=golang-dev
https://golang.org/cl/7305072
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/44d38ae3c0f44449933a5e56ed4282fabb2ffb09
元コミット内容
archive/tar
: Header.FileInfo
メソッドを追加。FileInfoHeader
により多くのケースを追加。
FileInfoHeader
は、FIFO、setuid、setgid、およびスティッキービットを処理できるようになりました。
Fixes #4695.
変更の背景
この変更の背景には、Go言語の archive/tar
パッケージが、tarアーカイブ内のファイルメタデータをより正確かつ包括的に表現する必要があったという点があります。
-
os.FileInfo
との相互運用性: Goの標準ライブラリには、ファイルシステムオブジェクトの情報を抽象化するためのos.FileInfo
インターフェースが存在します。archive/tar
パッケージが tar ヘッダー情報をos.FileInfo
として直接提供できるようになることで、Goの他のファイルシステム関連APIとの連携が容易になり、開発者は tar アーカイブ内のファイルを通常のファイルシステムオブジェクトと同じように扱えるようになります。これにより、コードの再利用性が高まり、tarアーカイブの操作がより直感的になります。 -
特殊なファイルモードのサポート不足: 以前の
FileInfoHeader
関数は、通常のファイル、ディレクトリ、シンボリックリンク、デバイスファイルなどの基本的なファイルタイプは処理できましたが、FIFO (名前付きパイプ)、setuid、setgid、スティッキービットといった特殊なパーミッションビットを適切に変換できませんでした。これらのビットはUnix系システムにおいて重要なセキュリティおよび動作特性を定義するため、tarアーカイブがこれらの属性を保持し、Goプログラムがそれらを正確に解釈できることは、アーカイブの完全性と互換性を保証する上で不可欠でした。特に、Fixes #4695
という記述から、この機能不足が具体的なバグとして報告されていたことが伺えます。
これらの課題を解決し、archive/tar
パッケージの堅牢性と実用性を向上させることが、このコミットの主な動機となっています。
前提知識の解説
このコミットを理解するためには、以下の概念について基本的な知識が必要です。
-
tarアーカイブ形式:
- 概要: tar (tape archive) は、複数のファイルを1つのアーカイブファイルにまとめるためのファイル形式です。主にUnix系システムでファイルのバックアップや配布に利用されます。
- ヘッダー (Header): tarアーカイブ内の各ファイルやディレクトリは、その内容の前に「ヘッダー」と呼ばれるメタデータブロックを持ちます。このヘッダーには、ファイル名、サイズ、パーミッション、所有者、更新日時などの情報が含まれます。
- ファイルモード (Mode): tarヘッダー内の「モード」フィールドは、Unixのファイルパーミッション(読み取り、書き込み、実行権限)だけでなく、ファイルの種類(通常ファイル、ディレクトリ、シンボリックリンク、デバイスファイルなど)や特殊なパーミッションビット(setuid, setgid, sticky bit)もエンコードします。これは通常、8進数で表現されます(例:
0755
)。
-
Go言語の
os
パッケージとos.FileInfo
:os.FileInfo
インターフェース: Go言語のos
パッケージは、オペレーティングシステムとのインタラクションを提供します。os.FileInfo
は、ファイルやディレクトリに関する情報を抽象化するためのインターフェースです。このインターフェースは、Name()
,Size()
,Mode()
,ModTime()
,IsDir()
,Sys()
などのメソッドを持ち、ファイルの種類、サイズ、パーミッション、最終更新日時などを取得できます。os.FileMode
:os.FileMode
は、ファイルのパーミッションとモードビットを表す型です。Unixのファイルモードに対応しており、os.ModeDir
(ディレクトリ),os.ModeSymlink
(シンボリックリンク),os.ModeDevice
(デバイスファイル),os.ModeNamedPipe
(FIFO),os.ModeSetuid
(setuidビット),os.ModeSetgid
(setgidビット),os.ModeSticky
(スティッキービット) などの定数が定義されています。
-
Unixの特殊なパーミッションビット:
- setuid (Set User ID) ビット (
04000
): 実行可能ファイルにこのビットが設定されていると、そのファイルを実行したユーザーのIDではなく、ファイルの所有者のIDでプロセスが実行されます。これにより、一般ユーザーが通常アクセスできないリソースに、一時的にファイルの所有者の権限でアクセスできるようになります(例:passwd
コマンド)。 - setgid (Set Group ID) ビット (
02000
): 実行可能ファイルに設定されている場合、setuidと同様に、ファイルのグループIDでプロセスが実行されます。ディレクトリに設定されている場合、そのディレクトリ内に作成される新しいファイルやサブディレクトリは、親ディレクトリのグループIDを継承します。 - スティッキービット (Sticky Bit) (
01000
): ディレクトリにこのビットが設定されていると、そのディレクトリ内のファイルは、ファイルの所有者、ディレクトリの所有者、またはrootユーザーのみが削除または名前変更できます。他のユーザーは、たとえ書き込み権限があっても、自分が所有していないファイルを削除できません。これは、/tmp
のような共有ディレクトリでよく使用され、ユーザーが互いのファイルを誤って削除するのを防ぎます。 - FIFO (First-In, First-Out) / 名前付きパイプ: ファイルシステム上に存在する特殊なファイルで、プロセス間通信 (IPC) の一種として機能します。一方のプロセスがFIFOに書き込んだデータは、もう一方のプロセスがFIFOから読み取ることができます。通常のファイルとは異なり、データは永続的に保存されず、読み取られると消滅します。
- setuid (Set User ID) ビット (
これらの概念を理解することで、コミットが archive/tar
パッケージの機能とGoのファイルシステム抽象化をどのように統合し、Unixのファイルシステム属性をより正確に扱うように改善したかが明確になります。
技術的詳細
このコミットは、archive/tar
パッケージにおける Header
構造体と os.FileInfo
インターフェース間の変換ロジックを強化しています。
Header.FileInfo()
メソッドの追加
- 目的:
tar.Header
構造体から直接os.FileInfo
インターフェースを実装するオブジェクトを取得できるようにします。これにより、tarアーカイブ内のファイルメタデータをGoの標準的なファイル情報インターフェースを通じて統一的に扱えるようになります。 - 実装:
headerFileInfo
という内部構造体が定義され、これがos.FileInfo
インターフェースを実装します。headerFileInfo
は*Header
を内部に持ち、os.FileInfo
の各メソッド(Name()
,Size()
,IsDir()
,ModTime()
,Mode()
,Sys()
)は、内部のHeader
の対応するフィールドやロジックに基づいて値を返します。- 特に
Mode()
メソッドは、tar.Header
のMode
フィールドとTypeflag
フィールドを組み合わせて、正確なos.FileMode
を構築します。これには、通常のパーミッションビット (Perm()
) に加えて、setuid, setgid, sticky ビット、そしてディレクトリ、FIFO、シンボリックリンク、ブロックデバイス、キャラクターデバイス、ソケットといったファイルタイプを示すビットが含まれます。
FileInfoHeader
関数の拡張
- 目的:
os.FileInfo
オブジェクトからtar.Header
構造体を生成するFileInfoHeader
関数が、より多くのos.FileMode
の種類を正確にtar.Header
に変換できるように拡張されました。 - 変更点:
- 以前は
os.ModeNamedPipe
(FIFO),os.ModeSetuid
,os.ModeSetgid
,os.ModeSticky
といった特殊なモードビットが適切に処理されていませんでした。 - このコミットにより、
FileInfoHeader
はos.FileMode
のこれらのビットを検出し、対応するtar.Header
のMode
フィールドにc_ISFIFO
,c_ISUID
,c_ISGID
,c_ISVTX
といった tar 固有のモード定数を適切に設定するようになりました。 - これにより、Goの
os.FileInfo
から生成されたtar.Header
が、元のファイルのすべての重要な属性(特にパーミッションとファイルタイプ)を正確に保持できるようになります。
- 以前は
c_IS*
定数の明確化
src/pkg/archive/tar/common.go
内で定義されているc_IS*
定数(例:c_ISDIR
,c_ISFIFO
)に、それぞれの意味(例:// Directory
,// FIFO
)がコメントとして追加され、コードの可読性が向上しました。また、setuid, setgid, sticky ビットに対応するc_ISUID
,c_ISGID
,c_ISVTX
定数が追加されました。
テストの追加
src/pkg/archive/tar/tar_test.go
にTestHeaderRoundTrip
という新しいテスト関数が追加されました。- このテストは、様々なファイルタイプとパーミッション(通常のファイル、ハードリンク、シンボリックリンク、キャラクターデバイス、ブロックデバイス、ディレクトリ、FIFO、setuid、setgid、スティッキービットを持つファイル)を持つ
tar.Header
オブジェクトを作成し、それらをFileInfo()
メソッドでos.FileInfo
に変換し、さらにFileInfoHeader()
関数で元のtar.Header
に戻すというラウンドトリップテストを行います。 - これにより、
Header.FileInfo()
とFileInfoHeader()
の両関数が、これらの特殊なファイルモードを正確に処理し、情報の損失なく変換できることが検証されます。
これらの変更により、archive/tar
パッケージは、より広範なファイルシステム属性をサポートし、Goのファイルシステム抽象化との連携を強化することで、より堅牢で互換性の高いtarアーカイブ処理を提供できるようになりました。
コアとなるコードの変更箇所
このコミットにおける主要なコード変更は、src/pkg/archive/tar/common.go
と src/pkg/archive/tar/tar_test.go
の2つのファイルに集中しています。
src/pkg/archive/tar/common.go
-
Header.FileInfo()
メソッドの追加:// FileInfo returns an os.FileInfo for the Header. func (h *Header) FileInfo() os.FileInfo { return headerFileInfo{h} } // headerFileInfo implements os.FileInfo. type headerFileInfo struct { h *Header } func (fi headerFileInfo) Size() int64 { return fi.h.Size } func (fi headerFileInfo) IsDir() bool { return fi.Mode().IsDir() } func (fi headerFileInfo) ModTime() time.Time { return fi.h.ModTime } func (fi headerFileInfo) Sys() interface{} { return fi.h } // Name returns the base name of the file. func (fi headerFileInfo) Name() string { if fi.IsDir() { return path.Clean(fi.h.Name) } return fi.h.Name } // Mode returns the permission and mode bits for the headerFileInfo. func (fi headerFileInfo) Mode() (mode os.FileMode) { // Set file permission bits. mode = os.FileMode(fi.h.Mode).Perm() // Set setuid, setgid and sticky bits. if fi.h.Mode&c_ISUID != 0 { // setuid mode |= os.ModeSetuid } if fi.h.Mode&c_ISGID != 0 { // setgid mode |= os.ModeSetgid } if fi.h.Mode&c_ISVTX != 0 { // sticky mode |= os.ModeSticky } // Set file mode bits. // clear perm, setuid, setgid and sticky bits. m := os.FileMode(fi.h.Mode) &^ 07777 if m == c_ISDIR { // directory mode |= os.ModeDir } if m == c_ISFIFO { // named pipe (FIFO) mode |= os.ModeNamedPipe } if m == c_ISLNK { // symbolic link mode |= os.ModeSymlink } if m == c_ISBLK { // device file mode |= os.ModeDevice } if m == c_ISCHR { // Unix character device mode |= os.ModeDevice mode |= os.ModeCharDevice } if m == c_ISSOCK { // Unix domain socket mode |= os.ModeSocket } switch fi.h.Typeflag { case TypeLink, TypeSymlink: // hard link, symbolic link mode |= os.ModeSymlink case TypeChar: // character device node mode |= os.ModeDevice mode |= os.ModeCharDevice case TypeBlock: // block device node mode |= os.ModeDevice case TypeDir: // directory mode |= os.ModeDir case TypeFifo: // fifo node mode |= os.ModeNamedPipe } return mode }
-
c_IS*
定数の追加とコメントの明確化:const ( c_ISUID = 04000 // Set uid c_ISGID = 02000 // Set gid c_ISVTX = 01000 // Save text (sticky bit) c_ISDIR = 040000 // Directory c_ISFIFO = 010000 // FIFO c_ISREG = 0100000 // Regular file c_ISLNK = 0120000 // Symbolic link c_ISBLK = 060000 // Block special file c_ISCHR = 020000 // Character special file c_ISSOCK = 0140000 // Socket )
-
FileInfoHeader
関数の拡張:func FileInfoHeader(fi os.FileInfo, link string) (*Header, error) { if fi == nil { return nil, errors.New("tar: FileInfo is nil") } fm := fi.Mode() // fi.Mode() を一度変数に格納 h := &Header{ Name: fi.Name(), ModTime: fi.ModTime(), Mode: int64(fm.Perm()), // fm を使用 } switch { case fm.IsRegular(): // fm を使用 h.Mode |= c_ISREG h.Typeflag = TypeReg h.Size = fi.Size() case fm.IsDir(): // fm を使用 h.Typeflag = TypeDir h.Mode |= c_ISDIR h.Name += "/" case fm&os.ModeSymlink != 0: // fm を使用 h.Typeflag = TypeSymlink h.Mode |= c_ISLNK h.Linkname = link case fm&os.ModeDevice != 0: // fm を使用 if fm&os.ModeCharDevice != 0 { // fm を使用 h.Mode |= c_ISCHR h.Typeflag = TypeChar } else { h.Mode |= c_ISBLK h.Typeflag = TypeBlock } case fm&os.ModeNamedPipe != 0: // 新規追加 h.Typeflag = TypeFifo h.Mode |= c_ISFIFO case fm&os.ModeSocket != 0: // fm を使用 h.Mode |= c_ISSOCK default: return nil, fmt.Errorf("archive/tar: unknown file mode %v", fm) // fm を使用 } // setuid, setgid, sticky ビットの処理を追加 if fm&os.ModeSetuid != 0 { h.Mode |= c_ISUID } if fm&os.ModeSetgid != 0 { h.Mode |= c_ISGID } if fm&os.ModeSticky != 0 { h.Mode |= c_ISVTX } if sysStat != nil { return h, sysStat(fi, h) } return h, nil }
src/pkg/archive/tar/tar_test.go
-
TestHeaderRoundTrip
テスト関数の追加: このテストは非常に長く、様々なファイルタイプとモード(通常のファイル、ハードリンク、シンボリックリンク、キャラクターデバイス、ブロックデバイス、ディレクトリ、FIFO、setuid、setgid、スティッキービット)を網羅したheaderRoundTripTest
のスライスgolden
を定義しています。各テストケースでは、Header
からFileInfo()
を呼び出してos.FileInfo
を取得し、さらにFileInfoHeader()
を呼び出してHeader
に戻し、元の値と比較することで、変換の正確性を検証しています。type headerRoundTripTest struct { h *Header fm os.FileMode } func TestHeaderRoundTrip(t *testing.T) { golden := []headerRoundTripTest{ // regular file. { h: &Header{ Name: "test.txt", Mode: 0644 | c_ISREG, Size: 12, ModTime: time.Unix(1360600916, 0), Typeflag: TypeReg, }, fm: 0644, }, // hard link. { h: &Header{ Name: "hard.txt", Mode: 0644 | c_ISLNK, Size: 0, ModTime: time.Unix(1360600916, 0), Typeflag: TypeLink, }, fm: 0644 | os.ModeSymlink, }, // symbolic link. { h: &Header{ Name: "link.txt", Mode: 0777 | c_ISLNK, Size: 0, ModTime: time.Unix(1360600852, 0), Typeflag: TypeSymlink, }, fm: 0777 | os.ModeSymlink, }, // character device node. { h: &Header{ Name: "dev/null", Mode: 0666 | c_ISCHR, Size: 0, ModTime: time.Unix(1360578951, 0), Typeflag: TypeChar, }, fm: 0666 | os.ModeDevice | os.ModeCharDevice, }, // block device node. { h: &Header{ Name: "dev/sda", Mode: 0660 | c_ISBLK, Size: 0, ModTime: time.Unix(1360578954, 0), Typeflag: TypeBlock, }, fm: 0660 | os.ModeDevice, }, // directory. { h: &Header{ Name: "dir/", Mode: 0755 | c_ISDIR, Size: 0, ModTime: time.Unix(1360601116, 0), Typeflag: TypeDir, }, fm: 0755 | os.ModeDir, }, // fifo node. { h: &Header{ Name: "dev/initctl", Mode: 0600 | c_ISFIFO, Size: 0, ModTime: time.Unix(1360578949, 0), Typeflag: TypeFifo, }, fm: 0600 | os.ModeNamedPipe, }, // setuid. { h: &Header{ Name: "bin/su", Mode: 0755 | c_ISREG | c_ISUID, Size: 23232, ModTime: time.Unix(1355405093, 0), Typeflag: TypeReg, }, fm: 0755 | os.ModeSetuid, }, // setguid. { h: &Header{ Name: "group.txt", Mode: 0750 | c_ISREG | c_ISGID, Size: 0, ModTime: time.Unix(1360602346, 0), Typeflag: TypeReg, }, fm: 0750 | os.ModeSetgid, }, // sticky. { h: &Header{ Name: "sticky.txt", Mode: 0600 | c_ISREG | c_ISVTX, Size: 7, ModTime: time.Unix(1360602540, 0), Typeflag: TypeReg, }, fm: 0600 | os.ModeSticky, }, } for i, g := range golden { fi := g.h.FileInfo() h2, err := FileInfoHeader(fi, "") if err != nil { t.Error(err) continue } if got, want := h2.Name, g.h.Name; got != want { t.Errorf("i=%d: Name: got %v, want %v", i, got, want) } if got, want := h2.Size, g.h.Size; got != want { t.Errorf("i=%d: Size: got %v, want %v", i, got, want) } if got, want := h2.Mode, g.h.Mode; got != want { t.Errorf("i=%d: Mode: got %o, want %o", i, got, want) } if got, want := fi.Mode(), g.fm; got != want { t.Errorf("i=%d: fi.Mode: got %o, want %o", i, got, want) } if got, want := h2.ModTime, g.h.ModTime; got != want { t.Errorf("i=%d: ModTime: got %v, want %v", i, got, want) } if sysh, ok := fi.Sys().(*Header); !ok || sysh != g.h { t.Errorf("i=%d: Sys didn't return original *Header", i) } } }
コアとなるコードの解説
Header.FileInfo()
メソッドと headerFileInfo
構造体
このコミットの最も重要な追加は、tar.Header
構造体に FileInfo()
メソッドが導入されたことです。このメソッドは、os.FileInfo
インターフェースを実装する headerFileInfo
型のインスタンスを返します。
headerFileInfo
の役割:headerFileInfo
は、tar.Header
のデータをos.FileInfo
が期待する形式に変換するアダプターとして機能します。これにより、Goの他のファイル操作関数がtar.Header
を直接os.FileInfo
として受け入れられるようになり、コードの汎用性と再利用性が向上します。Mode()
メソッドのロジック:headerFileInfo
のMode()
メソッドは、tar.Header
のMode
フィールドとTypeflag
フィールドを組み合わせて、完全なos.FileMode
を構築します。- まず、
os.FileMode(fi.h.Mode).Perm()
で基本的なパーミッションビット(読み取り、書き込み、実行)を取得します。 - 次に、
fi.h.Mode
とc_ISUID
,c_ISGID
,c_ISVTX
とのビットAND演算によって、setuid, setgid, sticky ビットが設定されているかをチェックし、対応するos.ModeSetuid
,os.ModeSetgid
,os.ModeSticky
をmode
にOR演算で追加します。 - さらに、
fi.h.Mode
からパーミッションビットと特殊ビットを除去した値 (m
) を使用して、ファイルの種類(ディレクトリ、FIFO、シンボリックリンク、ブロックデバイス、キャラクターデバイス、ソケット)を判断し、対応するos.FileMode
定数をmode
に追加します。 - 最後に、
fi.h.Typeflag
(tarヘッダーのファイルタイプを示すフィールド) に基づいて、再度ファイルの種類をチェックし、mode
に適切なos.FileMode
定数を追加します。これにより、Mode
フィールドとTypeflag
フィールドの両方からファイルモード情報を正確に抽出できます。
- まず、
FileInfoHeader
関数の拡張
FileInfoHeader
関数は、os.FileInfo
から tar.Header
を生成する逆の変換を行います。このコミットでは、この関数が os.FileMode
のより多くの種類を適切に tar.Header
にマッピングできるように拡張されました。
fm := fi.Mode()
:os.FileInfo
からos.FileMode
を一度取得し、fm
変数に格納することで、コードの重複を減らし、可読性を向上させています。- 新しい
case
の追加:switch
ステートメントにcase fm&os.ModeNamedPipe != 0:
が追加され、os.ModeNamedPipe
が設定されている場合にh.Typeflag
をTypeFifo
に、h.Mode
にc_ISFIFO
を設定するようになりました。 - 特殊パーミッションビットの処理:
switch
ステートメントの後に、fm&os.ModeSetuid != 0
,fm&os.ModeSetgid != 0
,fm&os.ModeSticky != 0
のチェックが追加されました。これにより、os.FileMode
にこれらのビットが設定されている場合、対応するc_ISUID
,c_ISGID
,c_ISVTX
をh.Mode
にOR演算で追加します。
これらの変更により、archive/tar
パッケージは、Goのファイルシステム抽象化とtarアーカイブ形式の間で、より完全で正確なファイルメタデータの変換をサポートするようになりました。特に、Unix系システムで重要な意味を持つ特殊なパーミッションビットやファイルタイプが、Goプログラム内で適切に扱えるようになった点が大きな改善です。
関連リンク
- Go言語
archive/tar
パッケージのドキュメント: https://pkg.go.dev/archive/tar - Go言語
os
パッケージのドキュメント: https://pkg.go.dev/os - Go言語
os.FileInfo
インターフェースのドキュメント: https://pkg.go.dev/os#FileInfo - Go言語
os.FileMode
型のドキュメント: https://pkg.go.dev/os#FileMode - GitHub Issue #4695:
archive/tar: FileInfoHeader should handle fifo, setuid, setgid and sticky bits
(このコミットによって修正された問題): https://github.com/golang/go/issues/4695 - Gerrit Change-Id:
7305072
(Goのコードレビューシステムにおけるこのコミットの変更セット): https://golang.org/cl/7305072
参考にした情報源リンク
- Unixのファイルパーミッション (Wikipedia): https://ja.wikipedia.org/wiki/%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB%E3%83%91%E3%83%BC%E3%83%9F%E3%83%83%E3%82%B7%E3%83%A7%E3%83%B3
- tar (ファイル形式) (Wikipedia): https://ja.wikipedia.org/wiki/Tar_(%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB%E5%BD%A2%E5%BC%8F)
- 名前付きパイプ (Wikipedia): https://ja.wikipedia.org/wiki/%E5%90%8D%E5%89%8D%E4%BB%98%E3%81%8D%E3%83%91%E3%82%A4%E3%83%97
- Setuid (Wikipedia): https://ja.wikipedia.org/wiki/Setuid
- Sticky bit (Wikipedia): https://ja.wikipedia.org/wiki/Sticky_bit