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