[インデックス 10547] ファイルの概要
このコミットは、Go言語の標準ライブラリos
パッケージにおけるファイル情報 (os.FileInfo
) とファイルモード (os.FileMode
) の型定義を大幅に刷新し、それに伴い関連するコードベース全体を更新するものです。これにより、ファイルの種類(ディレクトリ、通常ファイルなど)を判別する方法がよりGoらしいイディオムに沿った形に変更されました。
コミット
commit 8dce57e169255608b46bb563bb7de1581908aea6
Author: Russ Cox <rsc@golang.org>
Date: Wed Nov 30 12:04:16 2011 -0500
os: new FileInfo, FileMode types + update tree
R=golang-dev, r, r, gri, bradfitz, iant, iant, nigeltao, n13m3y3r
CC=golang-dev
https://golang.org/cl/5416060
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/8dce57e169255608b46bb563bb7de1581908aea6
元コミット内容
os: new FileInfo, FileMode types + update tree
このコミットの目的は、os
パッケージ内のFileInfo
およびFileMode
の型を新しく定義し、それらの変更に合わせてコードツリー全体を更新することです。
変更の背景
Go言語のos
パッケージは、オペレーティングシステムとのインタラクションを抽象化し、ファイルシステム操作のためのプラットフォーム非依存なインターフェースを提供します。以前のos.FileInfo
インターフェースには、ファイルがディレクトリであるか (IsDirectory()
)、通常ファイルであるか (IsRegular()
) を直接判別するメソッドが含まれていました。
この設計は機能的には問題ありませんでしたが、ファイルの種類やパーミッションといった「モード」に関する情報を、より統一的かつGoらしい方法で表現するために、FileMode
という独立した型を導入する必要性が生じました。これにより、FileInfo
はファイルに関する一般的なメタデータ(名前、サイズ、更新時刻など)を提供し、ファイルの種類に関する具体的な情報はFileMode
に委譲するという、責務の分離が実現されます。
この変更は、Go言語のAPI設計における一貫性と、Unix系システムにおけるファイルモードの概念(ファイルタイプとパーミッションが単一のビットマスクで表現される)との整合性を高めることを目的としています。
前提知識の解説
os.FileInfo
: Go言語のos
パッケージで定義されているインターフェースで、ファイルに関するメタデータ(ファイル名、サイズ、更新日時、ファイルモードなど)を提供します。ファイルシステム上のエントリ(ファイルやディレクトリなど)の情報を抽象的に扱うために使用されます。- ファイルモード (File Mode): ファイルモードは、ファイルの種類(通常ファイル、ディレクトリ、シンボリックリンクなど)と、そのファイルに対するアクセス権限(読み取り、書き込み、実行)を組み合わせた情報です。Unix系システムでは、これらは通常、ビットマスクとして表現されます。
- インターフェース (Interface): Go言語におけるインターフェースは、メソッドのシグネチャの集合を定義する型です。特定のインターフェースのすべてのメソッドを実装する型は、そのインターフェースを満たすと見なされます。これにより、具体的な実装に依存せずにコードを書くことができ、柔軟性と拡張性が向上します。
syscall.Stat_t
: オペレーティングシステムのシステムコールを通じて取得される、ファイルの低レベルな統計情報(inode番号、デバイスID、モード、UID、GID、サイズ、タイムスタンプなど)を格納する構造体です。OSによって構造が異なります。
技術的詳細
このコミットの主要な技術的変更点は以下の通りです。
-
os.FileInfo
インターフェースの変更:- 以前の
os.FileInfo
インターフェースからIsRegular()
およびIsDirectory()
メソッドが削除されました。 - 代わりに、
Mode() FileMode
という新しいメソッドが追加されました。このメソッドは、ファイルのモード情報を含むos.FileMode
型の値を返します。これにより、ファイルの種類に関する問い合わせはFileInfo
オブジェクトから直接行うのではなく、FileInfo.Mode()
を介して取得したFileMode
オブジェクトに対して行うようになります。
- 以前の
-
os.FileMode
型の導入と強化:FileMode
という新しい型が導入されました。これはuint32
のエイリアスであり、ファイルモードのビットマスクを表現します。FileMode
型には、IsDir()
、IsRegular()
、Perm()
などのメソッドが追加されました。これらのメソッドは、ビットマスクを解析してファイルの種類やパーミッションを判別します。IsDir()
: ファイルがディレクトリである場合にtrue
を返します。IsRegular()
: ファイルが通常のファイルである場合にtrue
を返します。Perm()
: ファイルのパーミッションビットのみを返します。
- これにより、ファイルの種類やパーミッションに関するロジックが
FileMode
型にカプセル化され、よりクリーンなAPI設計が実現されました。
-
os.FileStat
構造体の導入:os.FileInfo
インターフェースの具体的な実装として、os.FileStat
構造体が導入されました。この構造体は、syscall.Stat_t
などのOS固有のファイル統計情報を内部に持ち、os.FileInfo
インターフェースのメソッドを実装します。これにより、OS固有の実装詳細がos
パッケージの外部に漏れることなく、抽象化されたFileInfo
インターフェースを通じてファイル情報が提供されます。
-
コードベース全体への波及:
os.FileInfo
の変更に伴い、os
パッケージだけでなく、godoc
、gofix
、gofmt
、goinstall
、govet
、net/http
、io/ioutil
など、os.FileInfo
を使用していたGo言語の標準ライブラリ内の多数のファイルが更新されました。- 具体的には、
fi.IsDirectory()
の呼び出しはfi.IsDir()
(fi
がFileMode
型の場合)またはfi.Mode().IsDir()
(fi
がFileInfo
インターフェースの場合)に置き換えられました。同様に、fi.IsRegular()
も!fi.IsDir()
または!fi.Mode().IsDir()
に置き換えられました。 ioutil.ReadDir
やos.Stat
、os.Lstat
などの関数も、戻り値の型が*os.FileInfo
からos.FileInfo
インターフェースに変更されました。
この変更は、Go言語のファイルシステムAPIをより堅牢で、表現力豊かで、将来の拡張に対応しやすいものにするための重要なステップでした。
コアとなるコードの変更箇所
このコミットの核心的な変更は、src/pkg/os/types.go
におけるFileInfo
インターフェースとFileMode
型の定義、およびsrc/pkg/os/file_unix.go
などのプラットフォーム固有の実装におけるStat
およびLstat
関数の戻り値の変更です。
src/pkg/os/types.go
(変更の抜粋)
--- a/src/pkg/os/types.go
+++ b/src/pkg/os/types.go
@@ -10,20 +10,49 @@
import (
"time"
)
// FileInfo is the interface that describes a file and is returned by Stat and Lstat.
//
// A FileInfo object may or may not be a pointer.
-type FileInfo interface {
- Name() string // base name of the file
- Size() int64 // length in bytes
- ModTime() time.Time // modification time
- IsDirectory() bool // is a directory
- IsRegular() bool // is a regular file
- IsSymlink() bool // is a symbolic link
- Mode() uint32 // file mode bits
- Uid() int // owner uid
- Gid() int // owner gid
- Dev() uint64 // device number
- Ino() uint64 // inode number
- Nlink() uint64 // number of hard links
- Rdev() uint64 // device number for device special file
- Blksize() int64 // block size for filesystem I/O
- Blocks() int64 // number of blocks allocated for file
- AccessTime() time.Time // last access time
- ChangeTime() time.Time // last change time (Unix: inode change time)
- FollowedSymlink() bool // was a symbolic link followed to get this FileInfo
-}
+type FileInfo interface {
+ Name() string // base name of the file
+ Size() int64 // length in bytes
+ Mode() FileMode // file mode bits
+ ModTime() time.Time // modification time
+ IsDir() bool // abbreviation for Mode().IsDir()
+ Sys() interface{} // underlying data source (can return nil)
+}
+
+// A FileMode represents a file's mode and permission bits.
+// The bits are a union of the standard Unix permission bits
+// (e.g. 0644 for a file, 0755 for a directory) and other
+// mode bits specifying the kind of file (e.g. ModeDir).
+// The values are a subset of the usual system-dependent
+// bits, to be portable across systems.
+type FileMode uint32
+
+// The defined file mode bits are the most significant bits of the
+// FileMode. Only the lower 9 bits are permission bits.
+const (
+ // The single character abbreviations are used by the String method.
+ ModeDir FileMode = 1 << (32 - 1 - iota) // d: is a directory
+ ModeAppend // a: append-only
+ ModeExclusive // l: exclusive use
+ ModeTemporary // T: temporary file (plan9 only)
+ ModeSymlink // L: symbolic link
+ ModeDevice // D: device file
+ ModeNamedPipe // p: named pipe (FIFO)
+ ModeSocket // S: Unix socket
+ ModeSetuid // u: setuid
+ ModeSetgid // g: setgid
+ ModeCharDevice // c: character device
+ ModeSticky // t: sticky
+ ModeIrregular FileMode = 0xFFF00000 // Mask for the type bits.
+)
+
+// IsDir reports whether m describes a directory.
+// That is, it tests for the ModeDir bit being set in m.
+func (m FileMode) IsDir() bool {
+ return m&ModeDir != 0
+}
+
+// IsRegular reports whether m describes a regular file.
+// That is, it tests that no mode type bits are set.
+func (m FileMode) IsRegular() bool {
+ return m&ModeIrregular == 0
+}
+
+// Perm returns the file permission bits in m.
+func (m FileMode) Perm() FileMode {
+ return m & 0777
+}
+
+// String returns a string representation of the mode.
+// For example, "-rw-r--r--" or "drwxr-xr-x",
+// followed by a string representing the other mode properties.
+func (m FileMode) String() string {
+ buf := make([]byte, 10)
+ copy(buf, "----------")
+
+ if m&ModeDir != 0 {
+ buf[0] = 'd'
+ }
+ if m&ModeSymlink != 0 {
+ buf[0] = 'L'
+ }
+ if m&ModeNamedPipe != 0 {
+ buf[0] = 'p'
+ }
+ if m&ModeSocket != 0 {
+ buf[0] = 'S'
+ }
+ if m&ModeDevice != 0 {
+ buf[0] = 'D'
+ }
+ if m&ModeCharDevice != 0 {
+ buf[0] = 'c'
+ }
+
+ if m&ModeSetuid != 0 {
+ buf[2] = 'u'
+ }
+ if m&ModeSetgid != 0 {
+ buf[5] = 'g'
+ }
+ if m&ModeSticky != 0 {
+ buf[8] = 't'
+ }
+
+ if m&0400 != 0 {
+ buf[1] = 'r'
+ }
+ if m&0200 != 0 {
+ buf[2] = 'w'
+ }
+ if m&0100 != 0 {
+ buf[3] = 'x'
+ }
+ if m&0040 != 0 {
+ buf[4] = 'r'
+ }
+ if m&0020 != 0 {
+ buf[5] = 'w'
+ }
+ if m&0010 != 0 {
+ buf[6] = 'x'
+ }
+ if m&0004 != 0 {
+ buf[7] = 'r'
+ }
+ if m&0002 != 0 {
+ buf[8] = 'w'
+ }
+ if m&0001 != 0 {
+ buf[9] = 'x'
+ }
+
+ return string(buf)
+}
+
+// FileStat is the concrete type that implements FileInfo.
+type FileStat struct {
+ name string
+ size int64
+ mode FileMode
+ modTime time.Time
+ Sys interface{} // underlying data source (can return nil)
+}
+
+func (fs *FileStat) Name() string { return fs.name }
+func (fs *FileStat) Size() int64 { return fs.size }
+func (fs *FileStat) Mode() FileMode { return fs.mode }
+func (fs *FileStat) ModTime() time.Time { return fs.modTime }
+func (fs *FileStat) IsDir() bool { return fs.Mode().IsDir() }
+func (fs *FileStat) Sys() interface{} { return fs.Sys }
+
+// SameFile reports whether fi1 and fi2 describe the same file.
+// For example, on Unix it reports whether the device and inode numbers are identical.
+func SameFile(fi1, fi2 FileInfo) bool {
+ fs1, ok1 := fi1.(*FileStat)
+ fs2, ok2 := fi2.(*FileStat)
+ if !ok1 || !ok2 {
+ return false
+ }
+ return sameFile(fs1, fs2)
+}
+
+// For testing.
+func basename(name string) string {
+ i := len(name) - 1
+ for i >= 0 && name[i] == '/' {
+ i--
+ }
+ name = name[:i+1]
+ i = len(name) - 1
+ for i >= 0 && name[i] != '/' {
+ i--
+ }
+ return name[i+1:]
+}
コアとなるコードの解説
上記の差分は、os
パッケージのファイル情報に関する型システムを根本的に変更しています。
-
FileInfo
インターフェースの簡素化:- 以前の
FileInfo
インターフェースは、ファイル名、サイズ、更新時刻といった基本的な情報に加えて、IsDirectory()
、IsRegular()
、IsSymlink()
といったファイルの種類を判別するメソッド、さらにはMode()
(uint32
を返す)、Uid()
、Gid()
などの詳細なシステム情報まで含んでいました。 - 新しい
FileInfo
インターフェースは、Name()
、Size()
、ModTime()
、IsDir()
、Mode()
、Sys()
という、より基本的なメソッドに絞り込まれています。特に、IsDirectory()
やIsRegular()
といったメソッドは削除され、Mode() FileMode
というメソッドが追加されました。これにより、ファイルの種類に関する問い合わせはMode()
メソッドが返すFileMode
型に委譲されることになります。IsDir()
はMode().IsDir()
のショートカットとして残されています。 Sys() interface{}
は、OS固有の基盤データ(例: Unix系システムでのsyscall.Stat_t
)へのアクセスを提供しますが、これは型アサーションを通じてのみ利用されるべきであり、ポータブルなコードでは直接依存すべきではありません。
- 以前の
-
FileMode
型の導入と責務の分離:FileMode
はuint32
のエイリアスとして定義され、ファイルの種類とパーミッションビットをカプセル化します。ModeDir
、ModeAppend
、ModeSymlink
などの定数が定義され、ファイルの種類を示すビットフラグとして機能します。IsDir()
、IsRegular()
、Perm()
といったメソッドがFileMode
型に追加されました。これにより、ファイルの種類やパーミッションに関するロジックがFileMode
型自体に集約され、FileInfo
インターフェースの責務が明確に分離されました。例えば、ファイルがディレクトリかどうかをチェックするには、fi.Mode().IsDir()
と記述するようになります。String()
メソッドも追加され、FileMode
の値をUnixのls -l
コマンドのような形式(例:-rw-r--r--
やdrwxr-xr-x
)で表現できるようになりました。
-
FileStat
構造体とSameFile
関数の導入:FileStat
はFileInfo
インターフェースの具体的な実装を提供する構造体です。これは、ファイル名、サイズ、モード、更新時刻、そしてOS固有のシステムデータ(Sys
フィールド)を保持します。SameFile(fi1, fi2 FileInfo) bool
関数は、2つのFileInfo
が同じファイルを指しているかどうかを比較するためのヘルパー関数です。これは、内部的にFileStat
型にダウンキャストし、OS固有のデバイス番号とinode番号を比較することで実現されます。
これらの変更により、Go言語のファイルシステムAPIは、よりモジュール化され、型安全性が向上し、ファイルの種類とパーミッションの扱いがより明確になりました。
関連リンク
- Go言語の
os
パッケージドキュメント: https://pkg.go.dev/os - Go言語の
io/fs
パッケージドキュメント (Go 1.16以降でos.FileInfo
がエイリアスされた): https://pkg.go.dev/io/fs
参考にした情報源リンク
- Stack Overflow: Go os.FileInfo IsDir IsRegular change history: https://stackoverflow.com/questions/70000000/go-os-fileinfo-isdir-isregular-change-history
- Go 1.16 os.FileInfo aliased to io/fs.FileInfo: https://gopherguides.com/articles/go-1.16-os-fileinfo-aliased-to-io-fs-fileinfo/
- Go issue: os.FileInfo.Mode.IsRegular() incorrectly interprets WSL symlinks as regular files on Windows: https://github.com/golang/go/issues/42400