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

[インデックス 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.FileInfoSys() メソッドを通じて、基盤となる 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種類のタイムスタンプが管理されています。

  1. CreationTime (作成時刻): ファイルまたはディレクトリが作成された日時。
  2. LastAccessTime (最終アクセス時刻): ファイルまたはディレクトリが最後に読み込まれたり、書き込まれたりした日時。
  3. LastWriteTime (最終更新時刻): ファイルまたはディレクトリの内容が最後に変更された日時。

これらのタイムスタンプは、Windows APIでは FILETIME 構造体として表現され、GetFileAttributesExGetFileInformationByHandle といった関数を通じて取得できます。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 インターフェースの実装として使用される構造体です。このコミット以前は、fileStatmodTime フィールドで最終更新時刻のみを time.Time 型で保持し、sys フィールドは interface{} 型で、Windowsでは winSys というカスタム構造体を保持していました。この winSys 構造体は、LastAccessTimeCreationTimesyscall.Filetime 型で保持していましたが、これらは直接 FileInfo インターフェースから公開されていませんでした。

技術的詳細

このコミットの主要な技術的変更点は以下の通りです。

  1. 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 の一部として直接保持されるようになります。
  2. Windows fileStatSys() メソッドの変更:

    • Windows版 fileStatSys() メソッドが、syscall.Win32FileAttributeData 構造体へのポインタを返すように変更されました。これにより、ユーザーは fi.Sys().(*syscall.Win32FileAttributeData) のように型アサートすることで、CreationTimeLastAccessTime などのWindows固有の属性に直接アクセスできるようになります。
  3. ファイル情報取得ロジックの更新:

    • src/pkg/os/file_windows.goreaddir メソッドと src/pkg/os/stat_windows.goStat メソッドにおいて、fileStat 構造体の初期化方法が変更されました。以前は mkSys 関数を使って winSys 構造体を生成していましたが、新しい実装では syscall.Win32FileAttributeData を直接 fileStat.sys フィールドに格納するようになりました。
    • mkSize, mkModTime, mkMode といったヘルパー関数は、Windows版 fileStat のメソッドとして再実装され、syscall.Win32FileAttributeData から直接値を取得するように変更されました。これにより、fileStat 内部で必要な情報が完結するようになりました。
  4. 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のファイルシステムから取得できる情報を、より直接的かつ透過的にユーザーに提供することにあります。

  1. fileStat のプラットフォーム分離:

    • 以前は、os.FileInfo の内部実装である fileStat 構造体は、すべてのOSで共通の定義を持っていました。しかし、OSごとにファイル属性の取得方法や保持すべき情報が異なるため、この共通の定義ではWindows固有の LastAccessTimeCreationTime を直接扱うことが困難でした。
    • types_notwin.gotypes_windows.gofileStat の定義を分離することで、各OSの特性に合わせた最適な構造体設計が可能になりました。これはGoのビルドタグ(+build !windows)を活用した典型的なプラットフォーム固有の実装パターンです。
  2. Windows fileStat への syscall.Win32FileAttributeData の直接埋め込み:

    • Windows版 fileStatsys syscall.Win32FileAttributeData フィールドを持つようになったことが最も重要な変更点です。これにより、os.Statos.File.Readdir などでファイル情報を取得する際に、Windows APIから得られる WIN32_FILE_ATTRIBUTE_DATA 構造体の内容をそのまま fileStat の内部に格納できるようになりました。
    • 結果として、FileInfo.Sys() メソッドを呼び出すと、ユーザーは *syscall.Win32FileAttributeData 型のポインタを受け取ることができます。これにより、fi.Sys().(*syscall.Win32FileAttributeData).CreationTime のように、Windows固有のタイムスタンプに直接アクセスすることが可能になります。これは、Goの os パッケージが提供する抽象化を破ることなく、低レベルなOS機能へのアクセスを可能にするための標準的なアプローチです。
  3. fileStat メソッドの再実装:

    • Size(), Mode(), ModTime() といった FileInfo インターフェースのメソッドが、Windows版 fileStat 内で fs.sys フィールド(syscall.Win32FileAttributeData)から直接値を取得するように変更されました。例えば、ModTime()fs.sys.LastWriteTime.Nanoseconds() を使って time.Time に変換されます。これにより、ファイル情報の取得と変換のロジックが fileStat 構造体自身にカプセル化され、コードの凝集度が高まりました。
  4. sameFile 関数の改善:

    • sameFile 関数は、2つの FileInfo が同じファイルを指しているかを判断するために使用されます。Windowsでは、ファイルID(ボリュームシリアル番号とファイルインデックス)がファイルの同一性を識別する最も信頼性の高い方法です。
    • 新しい sameFile 実装では、fileStat 構造体自体を引数として受け取り、必要に応じて loadFileId メソッドを呼び出してファイルIDを取得し、それらを比較します。loadFileId は、ファイルパスからファイルハンドルを開き、GetFileInformationByHandle を呼び出してファイルIDを取得する処理をカプセル化しています。この遅延ロードのアプローチは、ファイルIDが常に必要とされるわけではないため、パフォーマンスの最適化にも寄与します。

これらの変更は、Goのクロスプラットフォームな os パッケージの設計思想を維持しつつ、Windows固有の機能へのアクセスを可能にするための、堅牢かつ効率的な方法を提供しています。

関連リンク

参考にした情報源リンク