Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 15362] ファイルの概要

このコミットは、Go言語の標準ライブラリarchive/tarパッケージにおけるテストの修正に関するものです。具体的には、setgidビットが設定されたディレクトリを扱う際に、tarアーカイブのヘッダー情報が正しく検証されるようにテストコードが調整されました。これにより、特定のファイルパーミッションを持つディレクトリが原因で発生していたテストの失敗が解消されました。

コミット

  • コミットハッシュ: 4335e69af64b96cd69fa876c5157d6caebde23a6
  • 作者: Brad Fitzpatrick bradfitz@golang.org
  • コミット日時: 2013年2月21日 木曜日 14:00:03 -0800

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/4335e69af64b96cd69fa876c5157d6caebde23a6

元コミット内容

archive/tar: make test pass on setgid dirs

Fixes #4867

R=golang-dev, dsymonds
CC=golang-dev
https://golang.org/cl/7382045

変更の背景

このコミットは、Go言語のIssue #4867を修正するために行われました。Issue #4867は、archive/tarパッケージがsetgidビットが設定されたディレクトリを処理する際に、テストが失敗するという問題に関連しています。

Unix/Linuxシステムにおいて、ディレクトリにsetgidビットが設定されている場合、そのディレクトリ内に新しく作成されるファイルやサブディレクトリは、親ディレクトリのグループIDを継承します。archive/tarパッケージは、ファイルシステム上のファイルやディレクトリのメタデータ(パーミッションなど)をtarアーカイブのヘッダー情報に変換する役割を担っています。

問題は、FileInfoHeaderDirテスト関数が、ディレクトリのモード(パーミッション)を検証する際に、setgidビットの存在を適切に考慮していなかったことにありました。os.FileInfoから取得したモード情報にはsetgidビットが含まれる可能性がありますが、tarヘッダーのモードには通常、このビットは含まれないか、異なる扱いがされることがあります。この不一致がテストの失敗を引き起こしていました。

このコミットの目的は、setgidディレクトリのモードを比較する際に、setgidビットを無視することで、テストが正しくパスするようにすることです。

前提知識の解説

archive/tarパッケージ

Go言語の標準ライブラリの一部であり、tarアーカイブ(テープアーカイブ)の読み書きをサポートします。tarは、複数のファイルを一つのアーカイブファイルにまとめるための一般的なフォーマットです。このパッケージは、ファイルのメタデータ(パーミッション、所有者、タイムスタンプなど)を保持しながら、ファイルのアーカイブ化と展開を可能にします。

Unix/Linuxファイルパーミッションとsetgidビット

Unix/Linuxシステムでは、ファイルやディレクトリには所有者、グループ、その他のユーザーに対する読み取り(r)、書き込み(w)、実行(x)のパーミッションが設定されます。これらは通常、3桁の8進数(例: 755)で表現されます。

さらに、特殊なパーミッションビットとしてsetuidsetgidstickyビットが存在します。

  • setgid (Set Group ID) ビット:
    • ファイルに設定された場合: その実行可能ファイルが実行される際に、実行者の実効グループIDではなく、ファイルのグループIDで実行されます。
    • ディレクトリに設定された場合: そのディレクトリ内に新しく作成されるファイルやサブディレクトリは、親ディレクトリのグループIDを継承します。これは、複数のユーザーが共有ディレクトリで作業する際に、新しく作成されたファイルが常に正しいグループに属するようにするために非常に便利です。
    • 8進数表記では、パーミッションの先頭に2を追加して表現されます(例: 2755)。

c_ISDIR定数

archive/tarパッケージ内で定義されている定数で、tarヘッダーのモードフィールドにおいて、エントリがディレクトリであることを示すビットマスクです。

Go言語のテストフレームワーク

Go言語には、標準でtestingパッケージが提供されており、ユニットテストやベンチマークテストを記述できます。テスト関数はTestで始まり、*testing.T型の引数を取ります。t.Errorfなどのメソッドを使ってテストの失敗を報告します。

技術的詳細

archive/tarパッケージのFileInfoHeaderDir関数は、os.FileInfoインターフェースからディレクトリのメタデータを読み取り、それをtar.Header構造体に変換します。この変換プロセスにおいて、ファイルパーミッション(モード)も適切にマッピングされる必要があります。

問題の核心は、os.FileInfo.Mode()が返すファイルモードにはsetgidビットが含まれる可能性があるのに対し、tar.Header.Modeに格納されるモードは、tarフォーマットの仕様や慣習により、このsetgidビットが考慮されない場合がある点です。

元のコードでは、h.Modetarヘッダーのモード)とint64(fi.Mode().Perm())|c_ISDIRos.FileInfoから取得したパーミッションにディレクトリビットをORしたもの)を直接比較していました。fi.Mode().Perm()は、os.FileModeからパーミッションビットのみを抽出するメソッドですが、setgidビットも含まれる可能性があります。

このコミットでは、setgidビット(8進数で02000)をsetsidという定数として定義し、h.Modeからこのsetsidビットをマスクアウト(クリア)してから比較を行うように変更しました。

h.Mode &^ setsidという式は、Go言語におけるビットクリア操作です。

  • &はビットごとのAND演算子です。
  • ^はビットごとのXOR演算子ですが、&^と組み合わせることで「AND NOT」として機能し、特定のビットをクリアする(0にする)ために使用されます。 つまり、h.Modeからsetsidで指定されたビットが立っている部分を0にし、それ以外のビットはそのまま保持します。これにより、setgidビットの有無に関わらず、純粋なパーミッションとディレクトリフラグのみを比較できるようになり、テストがsetgidディレクトリに対しても正しく動作するようになりました。

コアとなるコードの変更箇所

変更はsrc/pkg/archive/tar/tar_test.goファイルにあります。

--- a/src/pkg/archive/tar/tar_test.go
+++ b/src/pkg/archive/tar/tar_test.go
@@ -48,7 +48,8 @@ func TestFileInfoHeaderDir(t *testing.T) {
 	if g, e := h.Name, "testdata/"; g != e {
 		t.Errorf("Name = %q; want %q", g, e)
 	}
-	if g, e := h.Mode, int64(fi.Mode().Perm())|c_ISDIR; g != e {
+	const setsid = 02000 // see golang.org/issue/4867
+	if g, e := h.Mode&^setsid, int64(fi.Mode().Perm())|c_ISDIR; g != e {
 		t.Errorf("Mode = %#o; want %#o", g, e)
 	}
 	if g, e := h.Size, int64(0); g != e {

コアとなるコードの解説

変更された行は以下の部分です。

-	if g, e := h.Mode, int64(fi.Mode().Perm())|c_ISDIR; g != e {
+	const setsid = 02000 // see golang.org/issue/4867
+	if g, e := h.Mode&^setsid, int64(fi.Mode().Perm())|c_ISDIR; g != e {
  1. const setsid = 02000: setgidビットを表す8進数02000setsidという定数として定義しています。
  2. h.Mode&^setsid: ここが変更の核心です。tarヘッダーのモードh.Modeからsetsidビットをクリアしています。これにより、h.Modesetgidビットを含んでいるかどうかにかかわらず、比較対象となる値からsetgidビットが取り除かれます。
  3. int64(fi.Mode().Perm())|c_ISDIR: os.FileInfoから取得したパーミッションにc_ISDIR(ディレクトリを示すビット)をOR演算で結合しています。Perm()メソッドは、setuid, setgid, stickyビットを含む特殊なパーミッションビットを返します。

この変更により、h.Modefi.Mode().Perm()の比較において、setgidビットの有無による不一致が解消され、テストがsetgidディレクトリに対しても期待通りに動作するようになりました。

関連リンク

参考にした情報源リンク

  • Go言語のarchive/tarパッケージに関するドキュメント
  • Unix/Linuxファイルパーミッション、特にsetgidビットに関する一般的な情報
  • Go言語のビット演算子に関する情報
  • Go言語のtestingパッケージに関するドキュメント
  • Go言語のIssueトラッカーの慣習とコミットメッセージの形式