@@ -2,36 +2,95 @@ package archive
22
33import (
44 "os"
5+ "path/filepath"
56 "time"
7+ "unsafe"
68
79 "golang.org/x/sys/windows"
810)
911
1012// chtimes changes the access and modification time of a file at the given
11- // path.
13+ // path relative to root .
1214//
1315// Callers must use boundTime to ensure timestamps are within the range
1416// supported by os.Chtimes.
15- func chtimes (name string , atime time.Time , mtime time.Time ) error {
16- if err := os .Chtimes (name , atime , mtime ); err != nil {
17+ func chtimes (root * os.Root , name string , atime , mtime time.Time ) error {
18+ parent , err := root .OpenFile (filepath .Dir (name ), os .O_RDONLY , 0 )
19+ if err != nil {
1720 return err
1821 }
22+ defer parent .Close ()
23+
24+ return chtimesAt (parent , filepath .Base (name ), atime , mtime , false )
25+ }
1926
20- pathp , err := windows .UTF16PtrFromString (name )
27+ func lchtimes (root * os.Root , name string , atime time.Time , mtime time.Time ) error {
28+ return nil
29+ }
30+
31+ func chtimesAt (parent * os.File , name string , atime , mtime time.Time , noFollow bool ) error {
32+ h , err := openForWriteAttributesAt (windows .Handle (parent .Fd ()), name , noFollow )
2133 if err != nil {
2234 return err
2335 }
24- h , err := windows .CreateFile (pathp ,
25- windows .FILE_WRITE_ATTRIBUTES , windows .FILE_SHARE_WRITE , nil ,
26- windows .OPEN_EXISTING , windows .FILE_FLAG_BACKUP_SEMANTICS , 0 )
36+ defer func () { _ = windows .Close (h ) }()
37+
38+ var (
39+ creationTime = windows .NsecToFiletime (mtime .UnixNano ())
40+ accessTime = windows .NsecToFiletime (atime .UnixNano ())
41+ modificationTime = windows .NsecToFiletime (mtime .UnixNano ())
42+ )
43+ return windows .SetFileTime (h , & creationTime , & accessTime , & modificationTime )
44+ }
45+
46+ // openForWriteAttributesAt opens name relative to parent with permission to
47+ // modify its file attributes. If noFollow is true, it does not follow reparse
48+ // points.
49+ //
50+ // This implementation is based on Go's internal Windows Openat support:
51+ //
52+ // https://github.com/golang/go/blob/go1.26.0/src/internal/syscall/windows/at_windows.go
53+ //
54+ // It is used by os.Root's Windows implementation for root-relative filesystem
55+ // operations:
56+ //
57+ // https://github.com/golang/go/blob/go1.26.0/src/os/root_windows.go
58+ //
59+ // Keep this implementation aligned with the upstream code until an equivalent
60+ // operation is available from golang.org/x/sys/windows.
61+ func openForWriteAttributesAt (parent windows.Handle , name string , noFollow bool ) (windows.Handle , error ) {
62+ name16 , err := windows .UTF16FromString (name )
2763 if err != nil {
28- return err
64+ return windows . InvalidHandle , err
2965 }
30- defer windows .Close (h )
31- c := windows .NsecToFiletime (mtime .UnixNano ())
32- return windows .SetFileTime (h , & c , nil , nil )
33- }
3466
35- func lchtimes (root * os.Root , name string , atime time.Time , mtime time.Time ) error {
36- return nil
67+ attrs := uint32 (windows .OBJ_CASE_INSENSITIVE )
68+ if noFollow {
69+ attrs |= windows .OBJ_DONT_REPARSE
70+ }
71+
72+ var handle windows.Handle
73+ err = windows .NtCreateFile (
74+ & handle ,
75+ windows .SYNCHRONIZE | windows .FILE_WRITE_ATTRIBUTES ,
76+ & windows.OBJECT_ATTRIBUTES {
77+ Length : uint32 (unsafe .Sizeof (windows.OBJECT_ATTRIBUTES {})),
78+ RootDirectory : parent ,
79+ ObjectName : & windows.NTUnicodeString {
80+ Length : uint16 ((len (name16 ) - 1 ) * 2 ), // #nosec G115 -- Length is USHORT by definition. A Windows path component cannot exceed uint16 bytes.
81+ MaximumLength : uint16 (len (name16 ) * 2 ), // #nosec G115 -- MaximumLength is USHORT by definition. A Windows path component cannot exceed uint16 bytes.
82+ Buffer : & name16 [0 ],
83+ },
84+ Attributes : attrs ,
85+ },
86+ & windows.IO_STATUS_BLOCK {},
87+ nil ,
88+ windows .FILE_ATTRIBUTE_NORMAL ,
89+ windows .FILE_SHARE_READ | windows .FILE_SHARE_WRITE | windows .FILE_SHARE_DELETE ,
90+ windows .FILE_OPEN ,
91+ windows .FILE_OPEN_FOR_BACKUP_INTENT | windows .FILE_SYNCHRONOUS_IO_NONALERT ,
92+ 0 , // EA buffer
93+ 0 , // EA length
94+ )
95+ return handle , err
3796}
0 commit comments