[インデックス 15054] ファイルの概要
このコミットは、Go言語の os
パッケージにおいて、Windows環境でのファイルの最終アクセス時刻 (LastAccessTime) と作成時刻 (CreationTime) へのアクセスを提供する変更です。これにより、Windowsシステムにおけるファイル情報の取得がより詳細になり、他のOSとの機能的な一貫性が向上します。
コミット
commit bd75468a089c8ad38bcb1130c4ed7d2703ef85c1
Author: Alex Brainman <alex.brainman@gmail.com>
Date: Thu Jan 31 17:17:37 2013 +1100
os: provide access to file LastAccessTime and CreationTime on windows
Fixes #4569.
R=bradfitz, rsc
CC=golang-dev
https://golang.org/cl/6972047
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/bd75468a089c8ad38bcb1130c4ed7d2703ef85c1
元コミット内容
Go言語の os
パッケージにおいて、Windowsプラットフォームでファイルの最終アクセス時刻と作成時刻にアクセスできるようにする変更。これはIssue #4569を修正するものです。
変更の背景
この変更は、Goの os
パッケージが提供する FileInfo
インターフェースが、Windows環境においてファイルの最終アクセス時刻と作成時刻を直接公開していなかったという問題(Issue #4569)に対応するために行われました。
Goの os.FileInfo
インターフェースは、ファイル名、サイズ、パーミッション、最終更新時刻などの一般的なファイル情報を提供します。しかし、Windowsシステムでは、ファイルには最終更新時刻(LastWriteTime)の他に、最終アクセス時刻(LastAccessTime)と作成時刻(CreationTime)という重要なタイムスタンプが存在します。これらは syscall.Win32FileAttributeData
構造体に含まれていますが、Goの os
パッケージの FileInfo
を通じて直接アクセスすることはできませんでした。
Issue #4569では、特にWindows環境でこれらのタイムスタンプが必要となるユースケース(例:ファイルのバックアップ、キャッシュの管理、ファイルシステムの監査など)において、Goの標準ライブラリでこれらが利用できないことが指摘されていました。このコミットは、os.FileInfo
の Sys()
メソッドを通じて、基盤となる syscall.Win32FileAttributeData
構造体を公開することで、このギャップを埋めることを目的としています。これにより、ユーザーは必要に応じてWindows固有のファイル属性にアクセスできるようになります。
前提知識の解説
Go言語の os
パッケージと FileInfo
Go言語の os
パッケージは、オペレーティングシステムとのインタラクション(ファイル操作、プロセス管理など)を提供します。
os.FileInfo
は、ファイルに関する抽象的な情報を提供するインターフェースです。これには以下のメソッドが含まれます。
Name() string
: ファイルのベース名Size() int64
: ファイルのサイズ(バイト単位)Mode() FileMode
: ファイルのパーミッションと種類ModTime() time.Time
: ファイルの最終更新時刻IsDir() bool
: ディレクトリかどうかSys() interface{}
: 基盤となるシステム固有のデータ。このメソッドは、OS固有のファイル属性にアクセスするための「抜け道」として機能します。返されるinterface{}
は、OSによって異なる具体的な型に型アサートして使用します。
Windowsのファイルタイムスタンプ
Windowsファイルシステム(NTFSなど)では、ファイルやディレクトリに対して以下の3種類のタイムスタンプが管理されています。
- CreationTime (作成時刻): ファイルまたはディレクトリが作成された日時。
- LastAccessTime (最終アクセス時刻): ファイルまたはディレクトリが最後に読み込まれたり、書き込まれたりした日時。
- LastWriteTime (最終更新時刻): ファイルまたはディレクトリの内容が最後に変更された日時。
これらのタイムスタンプは、Windows APIでは FILETIME
構造体として表現され、GetFileAttributesEx
や GetFileInformationByHandle
といった関数を通じて取得できます。Goの syscall
パッケージでは、これらは syscall.Filetime
型としてラップされています。
syscall.Win32FileAttributeData
これはWindows APIの WIN32_FILE_ATTRIBUTE_DATA
構造体に対応するGoの syscall
パッケージの型です。ファイルに関する詳細な属性情報を含んでおり、上記の3つのタイムスタンプ(CreationTime
, LastAccessTime
, LastWriteTime
)の他に、ファイル属性(FileAttributes
)、ファイルサイズ(FileSizeHigh
, FileSizeLow
)などが含まれます。
fileStat
構造体
Goの os
パッケージ内部で、FileInfo
インターフェースの実装として使用される構造体です。このコミット以前は、fileStat
は modTime
フィールドで最終更新時刻のみを time.Time
型で保持し、sys
フィールドは interface{}
型で、Windowsでは winSys
というカスタム構造体を保持していました。この winSys
構造体は、LastAccessTime
と CreationTime
を syscall.Filetime
型で保持していましたが、これらは直接 FileInfo
インターフェースから公開されていませんでした。
技術的詳細
このコミットの主要な技術的変更点は以下の通りです。
-
fileStat
構造体のプラットフォームごとの分離:src/pkg/os/types.go
からfileStat
の定義が削除され、代わりにプラットフォーム固有のファイルに移動されました。src/pkg/os/types_notwin.go
が新規作成され、Windows以外のOS(Unix系など)向けのfileStat
構造体が定義されました。この構造体は以前と同様にname
,size
,mode
,modTime
,sys interface{}
を持ちます。src/pkg/os/types_windows.go
が新規作成され、Windows向けのfileStat
構造体が定義されました。このWindows版fileStat
は、sys
フィールドとして直接syscall.Win32FileAttributeData
型を持つようになりました。これにより、Windows固有のファイル属性がfileStat
の一部として直接保持されるようになります。
-
Windows
fileStat
のSys()
メソッドの変更:- Windows版
fileStat
のSys()
メソッドが、syscall.Win32FileAttributeData
構造体へのポインタを返すように変更されました。これにより、ユーザーはfi.Sys().(*syscall.Win32FileAttributeData)
のように型アサートすることで、CreationTime
やLastAccessTime
などのWindows固有の属性に直接アクセスできるようになります。
- Windows版
-
ファイル情報取得ロジックの更新:
src/pkg/os/file_windows.go
のreaddir
メソッドとsrc/pkg/os/stat_windows.go
のStat
メソッドにおいて、fileStat
構造体の初期化方法が変更されました。以前はmkSys
関数を使ってwinSys
構造体を生成していましたが、新しい実装ではsyscall.Win32FileAttributeData
を直接fileStat.sys
フィールドに格納するようになりました。mkSize
,mkModTime
,mkMode
といったヘルパー関数は、Windows版fileStat
のメソッドとして再実装され、syscall.Win32FileAttributeData
から直接値を取得するように変更されました。これにより、fileStat
内部で必要な情報が完結するようになりました。
-
sameFile
関数の変更:sameFile
関数(os.SameFile
の内部実装)のシグネチャが(sys1, sys2 interface{})
から(fs1, fs2 *fileStat)
に変更されました。これにより、fileStat
構造体全体を比較に利用できるようになり、特にWindowsではファイルID(ボリュームシリアル番号、ファイルインデックス)を用いた比較がより直接的に行えるようになりました。WindowsのsameFile
実装では、loadFileId
メソッドを通じてファイルハンドルからファイルIDを取得し、それらを比較することで同一ファイルであるかを判断します。
これらの変更により、Goの os
パッケージはWindows環境でのファイル属性の取り扱いを改善し、より詳細なファイル情報へのアクセスを可能にしました。
コアとなるコードの変更箇所
src/pkg/os/file_windows.go
File.readdir
メソッド内で、fileStat
の初期化方法が変更されました。
以前は mkSys
を呼び出して sys
フィールドを設定していましたが、新しいコードでは syscall.Win32FileAttributeData
構造体を直接 fileStat.sys
に埋め込む形に変更されています。
--- a/src/pkg/os/file_windows.go
+++ b/src/pkg/os/file_windows.go
@@ -222,11 +222,16 @@ func (file *File) readdir(n int) (fi []FileInfo, err error) {
continue
}
f := &fileStat{
- name: name,
- size: mkSize(d.FileSizeHigh, d.FileSizeLow),
- modTime: mkModTime(d.LastWriteTime),
- mode: mkMode(d.FileAttributes),
- sys: mkSys(file.dirinfo.path+`\`+name, d.LastAccessTime, d.CreationTime),
+ name: name,
+ sys: syscall.Win32FileAttributeData{
+ FileAttributes: d.FileAttributes,
+ CreationTime: d.CreationTime,
+ LastAccessTime: d.LastAccessTime,
+ LastWriteTime: d.LastWriteTime,
+ FileSizeHigh: d.FileSizeHigh,
+ FileSizeLow: d.FileSizeLow,
+ },
+ path: file.dirinfo.path + `\` + name,
}
n--
fi = append(fi, f)
src/pkg/os/stat_darwin.go
, src/pkg/os/stat_freebsd.go
, src/pkg/os/stat_linux.go
, src/pkg/os/stat_netbsd.go
, src/pkg/os/stat_openbsd.go
, src/pkg/os/stat_plan9.go
sameFile
関数のシグネチャが (sys1, sys2 interface{})
から (fs1, fs2 *fileStat)
に変更されました。これにより、fileStat
構造体全体を引数として受け取るようになり、より型安全な比較が可能になります。
--- a/src/pkg/os/stat_darwin.go
+++ b/src/pkg/os/stat_darwin.go
@@ -9,9 +9,9 @@ import (
"time"
)
-func sameFile(sys1, sys2 interface{}) bool {
- stat1 := sys1.(*syscall.Stat_t)
- stat2 := sys2.(*syscall.Stat_t)
+func sameFile(fs1, fs2 *fileStat) bool {
+ stat1 := fs1.sys.(*syscall.Stat_t)
+ stat2 := fs2.sys.(*syscall.Stat_t)
return stat1.Dev == stat2.Dev && stat1.Ino == stat2.Ino
}
(他のUnix系OSのファイルも同様の変更)
src/pkg/os/stat_windows.go
File.Stat
および Stat
関数内で、fileStat
の初期化ロジックが大幅に変更されました。
syscall.Win32FileAttributeData
を直接 fileStat.sys
に格納し、fileStat
自身がファイルパスやファイルID関連の情報を保持するようになりました。
また、statDevNull
関数が削除され、devNullStat
というグローバル変数で NUL
デバイスの fileStat
が定義されるようになりました。
mkSize
, mkModTime
, mkMode
, mkSys
, mkSysFromFI
, winSys
構造体、loadFileId
メソッド、sameFile
関数、atime
関数といった以前のヘルパー関数や構造体が削除または再定義されました。
--- a/src/pkg/os/stat_windows.go
+++ b/src/pkg/os/stat_windows.go
@@ -5,9 +5,7 @@
package os
import (
-\t"sync"\n \t"syscall"\n-\t"time"\n \t"unsafe"\n )
@@ -22,7 +20,7 @@ func (file *File) Stat() (fi FileInfo, err error) {
return Stat(file.name)
}
if file.name == DevNull {
-\t\treturn statDevNull()\n+\t\treturn &devNullStat, nil
}
var d syscall.ByHandleFileInformation
e := syscall.GetFileInformationByHandle(syscall.Handle(file.fd), &d)
@@ -30,11 +28,18 @@ func (file *File) Stat() (fi FileInfo, err error) {
return nil, &PathError{"GetFileInformationByHandle", file.name, e}
}\n \treturn &fileStat{\n-\t\tname: basename(file.name),\n-\t\tsize: mkSize(d.FileSizeHigh, d.FileSizeLow),\n-\t\tmodTime: mkModTime(d.LastWriteTime),\n-\t\tmode: mkMode(d.FileAttributes),\n-\t\tsys: mkSysFromFI(&d),\n+\t\tname: basename(file.name),\n+\t\tsys: syscall.Win32FileAttributeData{\n+\t\t\tFileAttributes: d.FileAttributes,\n+\t\t\tCreationTime: d.CreationTime,\n+\t\t\tLastAccessTime: d.LastAccessTime,\n+\t\t\tLastWriteTime: d.LastWriteTime,\n+\t\t\tFileSizeHigh: d.FileSizeHigh,\n+\t\t\tFileSizeLow: d.FileSizeLow,\n+\t\t},\n+\t\tvol: d.VolumeSerialNumber,\n+\t\tidxhi: d.FileIndexHigh,\n+\t\tidxlo: d.FileIndexLow,\n \t}, nil
}\n
@@ -45,29 +50,23 @@ func Stat(name string) (fi FileInfo, err error) {
return nil, &PathError{"Stat", name, syscall.Errno(syscall.ERROR_PATH_NOT_FOUND)}
}
if name == DevNull {
-\t\treturn statDevNull()\n+\t\treturn &devNullStat, nil
}
-\tvar d syscall.Win32FileAttributeData\n+\tfs := &fileStat{name: basename(name)}\n \tnamep, e := syscall.UTF16PtrFromString(name)\n \tif e != nil {\n \t\treturn nil, &PathError{"Stat", name, e}\n \t}\n-\te = syscall.GetFileAttributesEx(namep, syscall.GetFileExInfoStandard, (*byte)(unsafe.Pointer(&d)))\n+\te = syscall.GetFileAttributesEx(namep, syscall.GetFileExInfoStandard, (*byte)(unsafe.Pointer(&fs.sys)))\n \tif e != nil {\n \t\treturn nil, &PathError{"GetFileAttributesEx", name, e}\n \t}\n-\tpath := name\n-\tif !isAbs(path) {\n+\tfs.path = name
+\tif !isAbs(fs.path) {\n \t\tcwd, _ := Getwd()\n-\t\tpath = cwd + `\` + path\n+\t\tfs.path = cwd + `\` + fs.path
\t}
-\treturn &fileStat{\n-\t\tname: basename(name),\n-\t\tsize: mkSize(d.FileSizeHigh, d.FileSizeLow),\n-\t\tmodTime: mkModTime(d.LastWriteTime),\n-\t\tmode: mkMode(d.FileAttributes),\n-\t\tsys: mkSys(path, d.LastAccessTime, d.CreationTime),\n-\t}, nil
+\treturn fs, nil
}\n
// Lstat returns the FileInfo structure describing the named file.
@@ -79,95 +78,3 @@ func Lstat(name string) (fi FileInfo, err error) {
return Stat(name)
}\n
-// statDevNull return FileInfo structure describing DevNull file ("NUL").
-// It creates invented data, since none of windows api will return
-// that information.
-func statDevNull() (fi FileInfo, err error) {
- return &fileStat{
- name: DevNull,
- mode: ModeDevice | ModeCharDevice | 0666,
- sys: &winSys{
- // hopefully this will work for SameFile
- vol: 0,
- idxhi: 0,
- idxlo: 0,
- },
- }, nil
-}
-
// basename removes trailing slashes and the leading
// directory name and drive letter from path name.
func basename(name string) string {
@@ -172,95 +155,3 @@ func volumeName(path string) (v string) {
}
return ""
}\n-\n-type winSys struct {
-\tsync.Mutex
-\tpath string
-\tatime, ctime syscall.Filetime
-\tvol, idxhi, idxlo uint32
-}\n-\n-func mkSize(hi, lo uint32) int64 {
-\treturn int64(hi)<<32 + int64(lo)
-}\n-\n-func mkModTime(mtime syscall.Filetime) time.Time {
-\treturn time.Unix(0, mtime.Nanoseconds())
-}\n-\n-func mkMode(fa uint32) (m FileMode) {
-\tif fa&syscall.FILE_ATTRIBUTE_DIRECTORY != 0 {
-\t\tm |= ModeDir | 0111
-\t}\n-\tif fa&syscall.FILE_ATTRIBUTE_READONLY != 0 {
-\t\tm |= 0444
-\t} else {
-\t\tm |= 0666
-\t}\n-\treturn m
-}\n-\n-func mkSys(path string, atime, ctime syscall.Filetime) *winSys {
-\treturn &winSys{
-\t\tpath: path,
-\t\tatime: atime,
-\t\tctime: ctime,
-\t}\n-}\n-\n-func mkSysFromFI(i *syscall.ByHandleFileInformation) *winSys {
-\treturn &winSys{
-\t\tatime: i.LastAccessTime,
-\t\tctime: i.CreationTime,
-\t\tvol: i.VolumeSerialNumber,
-\t\tidxhi: i.FileIndexHigh,
-\t\tidxlo: i.FileIndexLow,
-\t}\n-}\n-\n-func (s *winSys) loadFileId() error {
-\tif s.path == "" {
-\t\t// already done
-\t\treturn nil
-\t}\n-\ts.Lock()\n-\tdefer s.Unlock()\n-\tpathp, e := syscall.UTF16PtrFromString(s.path)\n-\tif e != nil {
-\t\treturn e
-\t}\n-\th, e := syscall.CreateFile(pathp, 0, 0, nil, syscall.OPEN_EXISTING, syscall.FILE_FLAG_BACKUP_SEMANTICS, 0)\n-\tif e != nil {
-\t\treturn e
-\t}\n-\tdefer syscall.CloseHandle(h)\n-\tvar i syscall.ByHandleFileInformation\n-\te = syscall.GetFileInformationByHandle(syscall.Handle(h), &i)\n-\tif e != nil {\n-\t\treturn e
-\t}\n-\ts.path = ""\n-\ts.vol = i.VolumeSerialNumber
-\ts.idxhi = i.FileIndexHigh
-\ts.idxlo = i.FileIndexLow
-\treturn nil
-}\n-\n-func sameFile(sys1, sys2 interface{}) bool {
-\ts1 := sys1.(*winSys)
-\ts2 := sys2.(*winSys)
-\te := s1.loadFileId()
-\tif e != nil {
-\t\tpanic(e)
-\t}\n-\te = s2.loadFileId()
-\tif e != nil {
-\t\tpanic(e)
-\t}\n-\treturn s1.vol == s2.vol && s1.idxhi == s2.idxhi && s1.idxlo == s2.idxlo
-}\n-\n-// For testing.\n-func atime(fi FileInfo) time.Time {
-\treturn time.Unix(0, fi.Sys().(*winSys).atime.Nanoseconds())
-}\
src/pkg/os/types.go
fileStat
構造体の定義が削除され、FileInfo
インターフェースのメソッド定義のみが残されました。
SameFile
関数の内部で呼び出される sameFile
の引数が fs1.sys, fs2.sys
から fs1, fs2
に変更されました。
--- a/src/pkg/os/types.go
+++ b/src/pkg/os/types.go
@@ -99,21 +99,8 @@ func (m FileMode) Perm() FileMode {
return m & ModePerm
}
-// A fileStat is the implementation of FileInfo returned by Stat and Lstat.
-type fileStat struct {
- name string
- size int64
- mode FileMode
- modTime time.Time
- sys interface{}
-}
-
-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 }
+func (fs *fileStat) Name() string { return fs.name }
+func (fs *fileStat) IsDir() bool { return fs.Mode().IsDir() }
// SameFile reports whether fi1 and fi2 describe the same file.
// For example, on Unix this means that the device and inode fields
@@ -127,5 +114,5 @@ func SameFile(fi1, fi2 FileInfo) bool {
if !ok1 || !ok2 {
return false
}
-\treturn sameFile(fs1.sys, fs2.sys)\n+\treturn sameFile(fs1, fs2)\n }\
src/pkg/os/types_notwin.go
(新規ファイル)
Windows以外のOS向けの fileStat
構造体が定義されました。これは以前の src/pkg/os/types.go
にあった fileStat
の定義とほぼ同じです。
// +build !windows
package os
import (
"time"
)
// A fileStat is the implementation of FileInfo returned by Stat and Lstat.
type fileStat struct {
name string
size int64
mode FileMode
modTime time.Time
sys interface{}
}
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) Sys() interface{} { return fs.sys }
src/pkg/os/types_windows.go
(新規ファイル)
Windows向けの fileStat
構造体が定義されました。この構造体は syscall.Win32FileAttributeData
を直接 sys
フィールドとして持ち、SameFile
の実装に必要な vol
, idxhi
, idxlo
フィールドも含まれます。
また、Size()
, Mode()
, ModTime()
, Sys()
メソッドが、このWindows固有の fileStat
に合わせて実装されています。
loadFileId
メソッドは、SameFile
で使用されるファイルID(ボリュームシリアル番号とファイルインデックス)を遅延ロードするために使用されます。
devNullStat
という NUL
デバイス用の fileStat
グローバル変数もここで定義されています。
sameFile
関数もWindows固有のロジックで再実装されています。
package os
import (
"sync"
"syscall"
"time"
)
// A fileStat is the implementation of FileInfo returned by Stat and Lstat.
type fileStat struct {
name string
sys syscall.Win32FileAttributeData
// used to implement SameFile
sync.Mutex
path string
vol uint32
idxhi uint32
idxlo uint32
}
func (fs *fileStat) Size() int64 {
return int64(fs.sys.FileSizeHigh)<<32 + int64(fs.sys.FileSizeLow)
}
func (fs *fileStat) Mode() (m FileMode) {
if fs == &devNullStat {
return ModeDevice | ModeCharDevice | 0666
}
if fs.sys.FileAttributes&syscall.FILE_ATTRIBUTE_DIRECTORY != 0 {
m |= ModeDir | 0111
}
if fs.sys.FileAttributes&syscall.FILE_ATTRIBUTE_READONLY != 0 {
m |= 0444
} else {
m |= 0666
}
return m
}
func (fs *fileStat) ModTime() time.Time {
return time.Unix(0, fs.sys.LastWriteTime.Nanoseconds())
}
// Sys returns syscall.Win32FileAttributeData for file fs.
func (fs *fileStat) Sys() interface{} { return &fs.sys }
func (fs *fileStat) loadFileId() error {
fs.Lock()
defer fs.Unlock()
if fs.path == "" {
// already done
return nil
}
pathp, err := syscall.UTF16PtrFromString(fs.path)
if err != nil {
return err
}
h, err := syscall.CreateFile(pathp, 0, 0, nil, syscall.OPEN_EXISTING, syscall.FILE_FLAG_BACKUP_SEMANTICS, 0)
if err != nil {
return err
}
defer syscall.CloseHandle(h)
var i syscall.ByHandleFileInformation
err = syscall.GetFileInformationByHandle(syscall.Handle(h), &i)
if err != nil {
return err
}
fs.path = ""
fs.vol = i.VolumeSerialNumber
fs.idxhi = i.FileIndexHigh
fs.idxlo = i.FileIndexLow
return nil
}
// devNullStat is fileStat structure describing DevNull file ("NUL").
var devNullStat = fileStat{
name: DevNull,
// hopefully this will work for SameFile
vol: 0,
idxhi: 0,
idxlo: 0,
}
func sameFile(fs1, fs2 *fileStat) bool {
e := fs1.loadFileId()
if e != nil {
return false
}
e = fs2.loadFileId()
if e != nil {
return false
}
return fs1.vol == fs2.vol && fs1.idxhi == fs2.idxhi && fs1.idxlo == fs2.idxlo
}
// For testing.
func atime(fi FileInfo) time.Time {
return time.Unix(0, fi.Sys().(*syscall.Win32FileAttributeData).LastAccessTime.Nanoseconds())
}
コアとなるコードの解説
このコミットの核心は、Goの os
パッケージがWindowsのファイルシステムから取得できる情報を、より直接的かつ透過的にユーザーに提供することにあります。
-
fileStat
のプラットフォーム分離:- 以前は、
os.FileInfo
の内部実装であるfileStat
構造体は、すべてのOSで共通の定義を持っていました。しかし、OSごとにファイル属性の取得方法や保持すべき情報が異なるため、この共通の定義ではWindows固有のLastAccessTime
やCreationTime
を直接扱うことが困難でした。 types_notwin.go
とtypes_windows.go
にfileStat
の定義を分離することで、各OSの特性に合わせた最適な構造体設計が可能になりました。これはGoのビルドタグ(+build !windows
)を活用した典型的なプラットフォーム固有の実装パターンです。
- 以前は、
-
Windows
fileStat
へのsyscall.Win32FileAttributeData
の直接埋め込み:- Windows版
fileStat
がsys syscall.Win32FileAttributeData
フィールドを持つようになったことが最も重要な変更点です。これにより、os.Stat
やos.File.Readdir
などでファイル情報を取得する際に、Windows APIから得られるWIN32_FILE_ATTRIBUTE_DATA
構造体の内容をそのままfileStat
の内部に格納できるようになりました。 - 結果として、
FileInfo.Sys()
メソッドを呼び出すと、ユーザーは*syscall.Win32FileAttributeData
型のポインタを受け取ることができます。これにより、fi.Sys().(*syscall.Win32FileAttributeData).CreationTime
のように、Windows固有のタイムスタンプに直接アクセスすることが可能になります。これは、Goのos
パッケージが提供する抽象化を破ることなく、低レベルなOS機能へのアクセスを可能にするための標準的なアプローチです。
- Windows版
-
fileStat
メソッドの再実装:Size()
,Mode()
,ModTime()
といったFileInfo
インターフェースのメソッドが、Windows版fileStat
内でfs.sys
フィールド(syscall.Win32FileAttributeData
)から直接値を取得するように変更されました。例えば、ModTime()
はfs.sys.LastWriteTime.Nanoseconds()
を使ってtime.Time
に変換されます。これにより、ファイル情報の取得と変換のロジックがfileStat
構造体自身にカプセル化され、コードの凝集度が高まりました。
-
sameFile
関数の改善:sameFile
関数は、2つのFileInfo
が同じファイルを指しているかを判断するために使用されます。Windowsでは、ファイルID(ボリュームシリアル番号とファイルインデックス)がファイルの同一性を識別する最も信頼性の高い方法です。- 新しい
sameFile
実装では、fileStat
構造体自体を引数として受け取り、必要に応じてloadFileId
メソッドを呼び出してファイルIDを取得し、それらを比較します。loadFileId
は、ファイルパスからファイルハンドルを開き、GetFileInformationByHandle
を呼び出してファイルIDを取得する処理をカプセル化しています。この遅延ロードのアプローチは、ファイルIDが常に必要とされるわけではないため、パフォーマンスの最適化にも寄与します。
これらの変更は、Goのクロスプラットフォームな os
パッケージの設計思想を維持しつつ、Windows固有の機能へのアクセスを可能にするための、堅牢かつ効率的な方法を提供しています。
関連リンク
- Go GitHub Commit: https://github.com/golang/go/commit/bd75468a089c8ad38bcb1130c4ed7d2703ef85c1
- Go Issue #4569: https://golang.org/issue/4569
- Go Code Review: https://golang.org/cl/6972047
参考にした情報源リンク
- Go
os
package documentation: https://pkg.go.dev/os - Go
syscall
package documentation: https://pkg.go.dev/syscall - Microsoft Docs -
WIN32_FILE_ATTRIBUTE_DATA
structure: https://learn.microsoft.com/en-us/windows/win32/api/minwindef/ns-minwindef-win32_file_attribute_data - Microsoft Docs -
FILETIME
structure: https://learn.microsoft.com/en-us/windows/win32/api/minwindef/ns-minwindef-filetime - Microsoft Docs -
GetFileInformationByHandle
function: https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-getfileinformationbyhandle - Microsoft Docs -
GetFileAttributesEx
function: https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-getfileattributesexw - Go build constraints (build tags): https://pkg.go.dev/go/build