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

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

このコミットは、Go言語の標準ライブラリ archive/tar パッケージに FileInfoHeader 関数を追加するものです。この関数は、os.FileInfo インターフェースから tar.Header 構造体を生成する機能を提供し、ファイルシステムの情報に基づいてTARアーカイブのエントリヘッダを簡単に作成できるようにします。これにより、TARアーカイブの作成プロセスがより柔軟かつ効率的になります。

コミット

  • コミットハッシュ: 2b98401a83465214d0ca5f2d52ea9d890ec6fc81
  • Author: Brad Fitzpatrick bradfitz@golang.org
  • Date: Thu May 24 14:10:54 2012 -0700

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

https://github.com/golang/go/commit/2b98401a83465214d0ca5f2d52ea9d890ec6fc81

元コミット内容

    archive/tar: add FileInfoHeader function
    
    Fixes #3295
    
    R=adg, rsc, mike.rosset
    CC=golang-dev
    https://golang.org/cl/5796073

変更の背景

この変更は、Go言語のIssue #3295「archive/tar: os.FileInfo から Header を作成する関数を追加する」に対応するものです。既存の archive/tar パッケージでは、ファイルシステム上のファイルやディレクトリのメタデータ(os.FileInfo)から直接TARヘッダを作成する便利な方法がありませんでした。

TARアーカイブを作成する際、通常はファイルシステムから取得したファイル情報(パーミッション、サイズ、更新日時など)をTARヘッダに変換する必要があります。この変換を手動で行うのは手間がかかり、エラーの原因にもなり得ます。特に、シンボリックリンクのような特殊なファイルタイプを扱う場合には、その処理も考慮に入れる必要があります。

FileInfoHeader 関数の追加は、このプロセスを自動化し、開発者がより簡単にTARアーカイブを作成できるようにすることを目的としています。これにより、archive/tar パッケージの使いやすさと機能性が向上します。

前提知識の解説

os.FileInfo インターフェース

Go言語の os パッケージには、ファイルやディレクトリのメタデータ(名前、サイズ、パーミッション、更新日時、タイプなど)を抽象化するための FileInfo インターフェースが定義されています。os.Stat()os.Lstat() 関数は、この FileInfo インターフェースを実装した値を返します。

type FileInfo interface {
    Name() string       // base name of the file
    Size() int64        // length in bytes for regular files; system-dependent for others
    Mode() FileMode     // file mode bits
    ModTime() time.Time // modification time
    IsDir() bool        // abbreviation for Mode().IsDir()
    Sys() interface{}   // underlying data source (can return nil)
}

Sys() メソッドは、基となるシステム固有の情報を返すために使用されます。例えばUnix系システムでは *syscall.Stat_t 型の値を返すことがあり、これによりアクセス時刻 (atime) や変更時刻 (ctime)、UID、GIDなどの詳細な情報を取得できます。

TARアーカイブフォーマット

TAR (Tape Archive) は、複数のファイルを一つのアーカイブファイルにまとめるためのファイルフォーマットです。各ファイルは「ヘッダ」とそれに続く「データ」で構成されます。ヘッダには、ファイル名、サイズ、パーミッション、所有者、グループ、更新日時などのメタデータが含まれます。

archive/tar パッケージでは、このヘッダ情報を Header 構造体で表現します。

type Header struct {
    Name       string    // name of file
    Mode       int64     // permission and mode bits
    Uid        int       // user id of owner
    Gid        int       // group id of owner
    Size       int64     // length in bytes
    ModTime    time.Time // modification time
    Typeflag   byte      // type of header entry
    Linkname   string    // target name of link (for TypeLink, TypeSymlink)
    Uname      string    // user name of owner
    Gname      string    // group name of owner
    Devmajor   int64     // major device number (for TypeChar, TypeBlock)
    Devminor   int64     // minor device number (for TypeChar, TypeBlock)
    AccessTime time.Time // access time
    ChangeTime time.Time // status change time
}

ビルドタグ (Build Tags)

Go言語では、ソースファイルに特別なコメント行(ビルドタグ)を追加することで、特定の環境(OS、アーキテクチャなど)でのみそのファイルをコンパイルするように制御できます。例えば、// +build linux openbsd というタグが付いたファイルは、LinuxまたはOpenBSDシステムでのみコンパイルされます。

このコミットでは、stat_atim.gostat_atimespec.gostat_unix.go といったファイルが追加されており、それぞれ異なるOS向けのビルドタグが設定されています。これにより、各OSのシステムコール(syscall パッケージ)を利用して、より詳細なファイル情報を取得できるようになっています。

syscall.Stat_t 構造体

Unix系システムでは、stat システムコールによってファイルの詳細なメタデータが取得されます。Go言語の syscall パッケージは、このシステムコールの結果を Stat_t 構造体として提供します。この構造体には、アクセス時刻 (Atim または Atimespec)、変更時刻 (Ctim または Ctimespec)、UID、GIDなどの情報が含まれています。これらのフィールドはOSによって若干異なる場合があります。

技術的詳細

このコミットの主要な変更点は、archive/tar パッケージに FileInfoHeader 関数が追加されたことです。

FileInfoHeader 関数の役割

FileInfoHeader 関数は、os.FileInfo インターフェースとシンボリックリンクのターゲットパス(link 引数)を受け取り、対応する *tar.Header 構造体を返します。

func FileInfoHeader(fi os.FileInfo, link string) (*Header, error) {
    if fi == nil {
        return nil, errors.New("tar: FileInfo is nil")
    }
    h := &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 = TypeReg
        h.Size = fi.Size()
    case fi.IsDir(): // ディレクトリ
        h.Typeflag = TypeDir
        h.Mode |= c_ISDIR
    case fi.Mode()&os.ModeSymlink != 0: // シンボリックリンク
        h.Typeflag = TypeSymlink
        h.Mode |= c_ISLNK
        h.Linkname = link
    case fi.Mode()&os.ModeDevice != 0: // デバイスファイル
        if fi.Mode()&os.ModeCharDevice != 0 { // キャラクターデバイス
            h.Mode |= c_ISCHR
            h.Typeflag = TypeChar
        } else { // ブロックデバイス
            h.Mode |= c_ISBLK
            h.Typeflag = 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
}

この関数は、まず os.FileInfo から基本的な情報(名前、更新日時、パーミッション)を tar.Header にコピーします。次に、fi.Mode() を調べてファイルタイプを判別し、対応するTARのタイプフラグ (TypeReg, TypeDir, TypeSymlink など) とモード定数 (c_ISREG, c_ISDIR など) を設定します。シンボリックリンクの場合には、Linkname フィールドに link 引数の値が設定されます。

システム固有の情報の取得 (sysStat とプラットフォーム固有ファイル)

FileInfoHeader の重要な側面は、sysStat という関数ポインタを利用して、システム固有のファイル情報を tar.Header に追加する点です。

  • var sysStat func(fi os.FileInfo, h *Header) errorcommon.go に宣言されています。
  • src/pkg/archive/tar/stat_unix.go ファイルでは、init 関数内で sysStat = statUnix と設定されています。このファイルは // +build linux darwin freebsd openbsd netbsd というビルドタグを持ち、Unix系システムでのみコンパイルされます。
  • statUnix 関数は、fi.Sys().(*syscall.Stat_t) を使って os.FileInfo から syscall.Stat_t 構造体を取得し、そこからUID、GID、アクセス時刻 (AccessTime)、変更時刻 (ChangeTime) を tar.Header に設定します。

アクセス時刻と変更時刻の取得は、さらにプラットフォーム固有のファイルに委譲されています。

  • src/pkg/archive/tar/stat_atim.go (// +build linux openbsd)
    • statAtime(st *syscall.Stat_t) time.Timest.Atim.Unix() を使用。
    • statCtime(st *syscall.Stat_t) time.Timest.Ctim.Unix() を使用。
  • src/pkg/archive/tar/stat_atimespec.go (// +build darwin freebsd netbsd)
    • statAtime(st *syscall.Stat_t) time.Timest.Atimespec.Unix() を使用。
    • statCtime(st *syscall.Stat_t) time.Timest.Ctimespec.Unix() を使用。

このように、ビルドタグと関数ポインタを組み合わせることで、Goのクロスプラットフォーム性を維持しつつ、各OSのシステムコールを利用して詳細なファイルメタデータをTARヘッダに正確にマッピングしています。

テストの追加

src/pkg/archive/tar/tar_test.go には、FileInfoHeader 関数の動作を検証するためのテストが追加されています。

  • TestFileInfoHeader: 通常のファイル (testdata/small.txt) からヘッダが正しく生成されることを確認します。
  • TestFileInfoHeaderSymlink: シンボリックリンクからヘッダが正しく生成され、Linkname フィールドが設定されることを確認します。

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

api/next.txt

Go APIの変更を記録するファイルに、新しい関数が追加されています。

--- a/api/next.txt
+++ b/api/next.txt
@@ -1,3 +1,4 @@
+pkg archive/tar, func FileInfoHeader(os.FileInfo, string) (*Header, error)
 pkg crypto/tls, const TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA uint16
 pkg crypto/tls, const TLS_RSA_WITH_AES_256_CBC_SHA uint16
 pkg crypto/x509, const ECDSA PublicKeyAlgorithm

src/pkg/archive/tar/common.go

  • errors, fmt, os パッケージがインポートに追加されました。
  • sysStat グローバル変数(関数ポインタ)が宣言されました。
  • TARモード定数 (c_ISDIR, c_ISREG など) が追加されました。
  • FileInfoHeader 関数が追加されました。
--- a/src/pkg/archive/tar/common.go
+++ b/src/pkg/archive/tar/common.go
@@ -11,7 +11,12 @@
 //   http://www.gnu.org/software/tar/manual/html_node/Standard.html
 package tar
 
-import "time"
+import (
+	"errors"
+	"fmt"
+	"os"
+	"time"
+)
 
 const (
 	blockSize = 512
@@ -49,6 +54,62 @@ type Header struct {
 	ChangeTime time.Time // status change time
 }
 
+// sysStat, if non-nil, populates h from system-dependent fields of fi.
+var sysStat func(fi os.FileInfo, h *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
+)
+
+// FileInfoHeader creates a partially-populated Header from fi.
+// If fi describes a symlink, FileInfoHeader records link as the link target.
+func FileInfoHeader(fi os.FileInfo, link string) (*Header, error) {
+	if fi == nil {
+		return nil, errors.New("tar: FileInfo is nil")
+	}
+	h := &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 = TypeReg
+		h.Size = fi.Size()
+	case fi.IsDir():
+		h.Typeflag = TypeDir
+		h.Mode |= c_ISDIR
+	case fi.Mode()&os.ModeSymlink != 0:
+		h.Typeflag = TypeSymlink
+		h.Mode |= c_ISLNK
+		h.Linkname = link
+	case fi.Mode()&os.ModeDevice != 0:
+		if fi.Mode()&os.ModeCharDevice != 0 {
+			h.Mode |= c_ISCHR
+			h.Typeflag = TypeChar
+		} else {
+			h.Mode |= c_ISBLK
+			h.Typeflag = 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
+}
+
 var zeroBlock = make([]byte, blockSize)
 
 // POSIX specifies a sum of the unsigned byte values, but the Sun tar uses signed byte values.

src/pkg/archive/tar/stat_atim.go (新規ファイル)

LinuxおよびOpenBSD向けのアクセス時刻・変更時刻取得関数。

// +build linux openbsd

package tar

import (
	"syscall"
	"time"
)

func statAtime(st *syscall.Stat_t) time.Time {
	return time.Unix(st.Atim.Unix())
}

func statCtime(st *syscall.Stat_t) time.Time {
	return time.Unix(st.Ctim.Unix())
}

src/pkg/archive/tar/stat_atimespec.go (新規ファイル)

Darwin (macOS), FreeBSD, NetBSD向けのアクセス時刻・変更時刻取得関数。

// +build darwin freebsd netbsd

package tar

import (
	"syscall"
	"time"
)

func statAtime(st *syscall.Stat_t) time.Time {
	return time.Unix(st.Atimespec.Unix())
}

func statCtime(st *syscall.Stat_t) time.Time {
	return time.Unix(st.Ctimespec.Unix())
}

src/pkg/archive/tar/stat_unix.go (新規ファイル)

Unix系システム向けの sysStat 実装。

// +build linux darwin freebsd openbsd netbsd

package tar

import (
	"os"
	"syscall"
)

func init() {
	sysStat = statUnix
}

func statUnix(fi os.FileInfo, h *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 = statAtime(sys)
	h.ChangeTime = statCtime(sys)
	// TODO(bradfitz): major/minor device numbers?
	return nil
}

src/pkg/archive/tar/tar_test.go (新規ファイル)

FileInfoHeader 関数のテストケース。

// 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.

package tar

import (
	"os"
	"testing"
	"time"
)

func TestFileInfoHeader(t *testing.T) {
	fi, err := os.Lstat("testdata/small.txt")
	if err != nil {
		t.Fatal(err)
	}
	h, err := FileInfoHeader(fi, "")
	if err != nil {
		t.Fatalf("on small.txt: %v", err)
	}
	if g, e := h.Name, "small.txt"; g != e {
		t.Errorf("Name = %q; want %q", g, e)
	}
	if g, e := h.Mode, int64(0644|c_ISREG); g != e {
		t.Errorf("Mode = %#o; want %#o", g, e)
	}
	if g, e := h.Size, int64(5); g != e {
		t.Errorf("Size = %v; want %v", g, e)
	}
	if g, e := h.ModTime, fi.ModTime(); !g.Equal(e) {
		t.Errorf("ModTime = %v; want %v", g, e)
	}
}

func TestFileInfoHeaderSymlink(t *testing.T) {
	h, err := FileInfoHeader(symlink{}, "some-target")
	if err != nil {
		t.Fatal(err)
	}
	if g, e := h.Name, "some-symlink"; g != e {
		t.Errorf("Name = %q; want %q", g, e)
	}
	if g, e := h.Linkname, "some-target"; g != e {
		t.Errorf("Linkname = %q; want %q", g, e)
	}
}

type symlink struct{}

func (symlink) Name() string       { return "some-symlink" }
func (symlink) Size() int64        { return 0 }
func (symlink) Mode() os.FileMode  { return os.ModeSymlink }
func (symlink) ModTime() time.Time { return time.Time{} }
func (symlink) IsDir() bool        { return false }
func (symlink) Sys() interface{}   { return nil }

src/pkg/go/build/deps_test.go

archive/tar パッケージの依存関係に syscall が追加されました。

--- a/src/pkg/go/build/deps_test.go
+++ b/src/pkg/go/build/deps_test.go
@@ -177,7 +177,7 @@ var pkgDeps = map[string][]string{\
 	},
 
 	// One of a kind.
-	"archive/tar":         {"L4", "OS"},
+	"archive/tar":         {"L4", "OS", "syscall"},
 	"archive/zip":         {"L4", "OS", "compress/flate"},
 	"compress/bzip2":      {"L4"},
 	"compress/flate":      {"L4"},

コアとなるコードの解説

FileInfoHeader 関数

この関数は、os.FileInfo から tar.Header を生成する中心的なロジックを担います。

  1. 基本的な情報のコピー: fi.Name(), fi.ModTime(), fi.Mode().Perm() から HeaderName, ModTime, Mode フィールドを初期化します。
  2. ファイルタイプの判別と設定: fi.Mode()os.ModeType ビットマスクを使ってファイルタイプを判別し、対応するTARのタイプフラグ (TypeReg, TypeDir, TypeSymlink など) とモード定数 (c_ISREG, c_ISDIR など) を設定します。
    • 通常ファイル (os.ModeType == 0): TypeRegc_ISREG を設定し、fi.Size()Size にコピーします。
    • ディレクトリ (fi.IsDir()): TypeDirc_ISDIR を設定します。
    • シンボリックリンク (os.ModeSymlink): TypeSymlinkc_ISLNK を設定し、link 引数を Linkname にコピーします。
    • デバイスファイル (os.ModeDevice): キャラクターデバイス (os.ModeCharDevice) かブロックデバイスかを判別し、それぞれ TypeChar/c_ISCHR または TypeBlock/c_ISBLK を設定します。
    • ソケット (os.ModeSocket): c_ISSOCK を設定します。
    • その他の不明なファイルモードの場合はエラーを返します。
  3. システム固有の情報の追加: sysStat 関数ポインタが nil でない場合、その関数を呼び出して、UID、GID、アクセス時刻、変更時刻などのシステム固有の情報を Header に追加します。これにより、より完全なTARヘッダが作成されます。

sysStat とプラットフォーム固有の stat 関数

sysStat は、Goのクロスプラットフォーム設計の典型的な例です。

  • stat_unix.go は、Unix系OS(Linux, macOS, FreeBSD, OpenBSD, NetBSD)向けにコンパイルされ、init 関数で sysStatstatUnix 関数を割り当てます。
  • statUnix 関数は、os.FileInfoSys() メソッドから *syscall.Stat_t を取得し、そこからUID、GID、そしてプラットフォーム固有の statAtimestatCtime 関数を使ってアクセス時刻と変更時刻を取得します。
  • stat_atim.gostat_atimespec.go は、それぞれ異なるUnix系OSの syscall.Stat_t 構造体のフィールド名(Atim vs Atimespec)の違いを吸収するために存在します。これにより、各OSで正しいタイムスタンプフィールドが参照されるようになります。

これらの変更により、archive/tar パッケージは、ファイルシステムから取得した情報を基に、より正確で完全なTARヘッダを生成できるようになり、TARアーカイブの作成がより堅牢かつ容易になりました。

関連リンク

参考にした情報源リンク