[インデックス 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