返回 DeepSeek-Reasonix
ntfile_windows.go
根目录 / internal / winaclresidue / ntfile_windows.go
1 //go:build windows
2
3 package winaclresidue
4
5 import (
6 "fmt"
7 "path/filepath"
8 "strings"
9 "unsafe"
10
11 "golang.org/x/sys/windows"
12 )
13
14 // openExact opens path with exactly the requested access rights. CreateFile
15 // silently adds SYNCHRONIZE and FILE_READ_ATTRIBUTES, both of which a legacy
16 // DENY RX entry removes, so a WRITE_DAC- or DELETE-only open has to go
17 // through NtCreateFile. Reparse points are opened as themselves, never
18 // followed.
19 func openExact(path string, access uint32) (windows.Handle, error) {
20 name, err := windows.NewNTUnicodeString(ntPath(path))
21 if err != nil {
22 return 0, err
23 }
24 oa := windows.OBJECT_ATTRIBUTES{ObjectName: name, Attributes: windows.OBJ_CASE_INSENSITIVE}
25 oa.Length = uint32(unsafe.Sizeof(oa))
26 var (
27 handle windows.Handle
28 iosb windows.IO_STATUS_BLOCK
29 )
30 err = windows.NtCreateFile(&handle, access, &oa, &iosb, nil, 0,
31 windows.FILE_SHARE_READ|windows.FILE_SHARE_WRITE|windows.FILE_SHARE_DELETE,
32 windows.FILE_OPEN, windows.FILE_NON_DIRECTORY_FILE|windows.FILE_OPEN_REPARSE_POINT, 0, 0)
33 if err != nil {
34 return 0, fmt.Errorf("open %q with access 0x%x: %w", path, access, err)
35 }
36 return handle, nil
37 }
38
39 // ntPath converts an absolute Win32 path to the NT object namespace.
40 func ntPath(path string) string {
41 clean := filepath.Clean(path)
42 if rest, ok := strings.CutPrefix(clean, `\\`); ok {
43 return `\??\UNC\` + rest
44 }
45 return `\??\` + clean
46 }
47
48 // fileRenameInformation mirrors FILE_RENAME_INFORMATION; the name is stored
49 // inline after the header, so callers allocate the struct plus the name.
50 type fileRenameInformation struct {
51 ReplaceIfExists uint8
52 RootDirectory windows.Handle
53 FileNameLength uint32
54 FileName [1]uint16
55 }
56
57 // RenameLockedFile moves path to target through a DELETE-only handle so a
58 // deny that removes read rights cannot block the move; MoveFileEx would also
59 // ask for SYNCHRONIZE and fail. The target must not exist.
60 func RenameLockedFile(path, target string) error {
61 handle, err := openExact(path, windows.DELETE)
62 if err != nil {
63 return err
64 }
65 defer windows.CloseHandle(handle)
66 name, err := windows.UTF16FromString(ntPath(target))
67 if err != nil {
68 return err
69 }
70 name = name[:len(name)-1]
71 header := unsafe.Offsetof(fileRenameInformation{}.FileName)
72 size := int(header) + len(name)*2
73 buffer := make([]uint64, (size+7)/8)
74 info := (*fileRenameInformation)(unsafe.Pointer(&buffer[0]))
75 info.FileNameLength = uint32(len(name) * 2)
76 copy(unsafe.Slice((*uint16)(unsafe.Add(unsafe.Pointer(&buffer[0]), header)), len(name)), name)
77 var iosb windows.IO_STATUS_BLOCK
78 if err := windows.NtSetInformationFile(handle, &iosb, (*byte)(unsafe.Pointer(&buffer[0])), uint32(size), windows.FileRenameInformation); err != nil {
79 return fmt.Errorf("rename %q to %q: %w", path, target, err)
80 }
81 return nil
82 }
83
83 lines GO