Source file
src/os/file_windows.go
1
2
3
4
5 package os
6
7 import (
8 "errors"
9 "internal/filepathlite"
10 "internal/godebug"
11 "internal/poll"
12 "internal/syscall/windows"
13 "runtime"
14 "sync"
15 "sync/atomic"
16 "syscall"
17 "unsafe"
18 )
19
20
21 const _UTIME_OMIT = -1
22
23
24
25
26
27 type file struct {
28 pfd poll.FD
29 name string
30 dirinfo atomic.Pointer[dirInfo]
31 appendMode bool
32 cleanup runtime.Cleanup
33 }
34
35
36 func (file *File) fd() uintptr {
37 if file == nil {
38 return uintptr(syscall.InvalidHandle)
39 }
40
41
42
43
44 _ = file.pfd.DisassociateIOCP()
45 return uintptr(file.pfd.Sysfd)
46 }
47
48
49
50
51 func newFile(h syscall.Handle, name string, kind string, nonBlocking bool) *File {
52 if kind == "file" {
53 t, err := syscall.GetFileType(h)
54 if err != nil || t == syscall.FILE_TYPE_CHAR {
55 var m uint32
56 if syscall.GetConsoleMode(h, &m) == nil {
57 kind = "console"
58 }
59 } else if t == syscall.FILE_TYPE_PIPE {
60 kind = "pipe"
61 }
62 }
63
64 f := &File{&file{
65 pfd: poll.FD{
66 Sysfd: h,
67 IsStream: true,
68 ZeroReadIsEOF: true,
69 },
70 name: name,
71 }}
72 f.cleanup = runtime.AddCleanup(f, func(f *file) { f.close() }, f.file)
73
74
75
76 f.pfd.Init(kind, nonBlocking)
77 return f
78 }
79
80
81 func newConsoleFile(h syscall.Handle, name string) *File {
82 return newFile(h, name, "console", false)
83 }
84
85
86 func newFileFromNewFile(fd uintptr, name string) *File {
87 h := syscall.Handle(fd)
88 if h == syscall.InvalidHandle {
89 return nil
90 }
91 nonBlocking, _ := windows.IsNonblock(syscall.Handle(fd))
92 return newFile(h, name, "file", nonBlocking)
93 }
94
95
96
97
98
99
100 func net_newWindowsFile(h syscall.Handle, name string) *File {
101 if h == syscall.InvalidHandle {
102 panic("invalid FD")
103 }
104 return newFile(h, name, "file+net", true)
105 }
106
107 func epipecheck(file *File, e error) {
108 }
109
110
111
112 const DevNull = "NUL"
113
114
115 func openFileNolog(name string, flag int, perm FileMode) (*File, error) {
116 if name == "" {
117 return nil, &PathError{Op: "open", Path: name, Err: syscall.ENOENT}
118 }
119 path := fixLongPath(name)
120 r, err := syscall.Open(path, flag|syscall.O_CLOEXEC, syscallMode(perm))
121 if err != nil {
122 return nil, &PathError{Op: "open", Path: name, Err: err}
123 }
124
125 return newFile(r, name, "file", false), nil
126 }
127
128 func openDirNolog(name string) (*File, error) {
129 return openFileNolog(name, O_RDONLY, 0)
130 }
131
132 func (file *file) close() error {
133 if file == nil {
134 return syscall.EINVAL
135 }
136 if info := file.dirinfo.Swap(nil); info != nil {
137 info.close()
138 }
139 var err error
140 if e := file.pfd.Close(); e != nil {
141 if e == poll.ErrFileClosing {
142 e = ErrClosed
143 }
144 err = &PathError{Op: "close", Path: file.name, Err: e}
145 }
146
147
148
149 file.cleanup.Stop()
150 return err
151 }
152
153
154
155
156
157 func (f *File) seek(offset int64, whence int) (ret int64, err error) {
158 if info := f.dirinfo.Swap(nil); info != nil {
159
160
161 info.close()
162 }
163 ret, err = f.pfd.Seek(offset, whence)
164 runtime.KeepAlive(f)
165 return ret, err
166 }
167
168
169
170 func Truncate(name string, size int64) error {
171 f, e := OpenFile(name, O_WRONLY, 0666)
172 if e != nil {
173 return e
174 }
175 defer f.Close()
176 e1 := f.Truncate(size)
177 if e1 != nil {
178 return e1
179 }
180 return nil
181 }
182
183
184
185 func Remove(name string) error {
186 p, e := syscall.UTF16PtrFromString(fixLongPath(name))
187 if e != nil {
188 return &PathError{Op: "remove", Path: name, Err: e}
189 }
190
191
192
193 e = syscall.DeleteFile(p)
194 if e == nil {
195 return nil
196 }
197 e1 := syscall.RemoveDirectory(p)
198 if e1 == nil {
199 return nil
200 }
201
202
203 if e1 != e {
204 a, e2 := syscall.GetFileAttributes(p)
205 if e2 != nil {
206 e = e2
207 } else {
208 if a&syscall.FILE_ATTRIBUTE_DIRECTORY != 0 {
209 e = e1
210 } else if a&syscall.FILE_ATTRIBUTE_READONLY != 0 {
211 if e1 = syscall.SetFileAttributes(p, a&^syscall.FILE_ATTRIBUTE_READONLY); e1 == nil {
212 if e = syscall.DeleteFile(p); e == nil {
213 return nil
214 }
215 }
216 }
217 }
218 }
219 return &PathError{Op: "remove", Path: name, Err: e}
220 }
221
222 func rename(oldname, newname string) error {
223 e := windows.Rename(fixLongPath(oldname), fixLongPath(newname))
224 if e != nil {
225 return &LinkError{"rename", oldname, newname, e}
226 }
227 return nil
228 }
229
230
231
232
233 func Pipe() (r *File, w *File, err error) {
234 var p [2]syscall.Handle
235 e := syscall.Pipe(p[:])
236 if e != nil {
237 return nil, nil, NewSyscallError("pipe", e)
238 }
239
240 return newFile(p[0], "|0", "pipe", false), newFile(p[1], "|1", "pipe", false), nil
241 }
242
243 var useGetTempPath2 = sync.OnceValue(func() bool {
244 return windows.ErrorLoadingGetTempPath2() == nil
245 })
246
247 func tempDir() string {
248 getTempPath := syscall.GetTempPath
249 if useGetTempPath2() {
250 getTempPath = windows.GetTempPath2
251 }
252 n := uint32(syscall.MAX_PATH)
253 for {
254 b := make([]uint16, n)
255 n, _ = getTempPath(uint32(len(b)), &b[0])
256 if n > uint32(len(b)) {
257 continue
258 }
259 if n == 3 && b[1] == ':' && b[2] == '\\' {
260
261 } else if n > 0 && b[n-1] == '\\' {
262
263 n--
264 }
265 return syscall.UTF16ToString(b[:n])
266 }
267 }
268
269
270
271 func Link(oldname, newname string) error {
272 n, err := syscall.UTF16PtrFromString(fixLongPath(newname))
273 if err != nil {
274 return &LinkError{"link", oldname, newname, err}
275 }
276 o, err := syscall.UTF16PtrFromString(fixLongPath(oldname))
277 if err != nil {
278 return &LinkError{"link", oldname, newname, err}
279 }
280 err = syscall.CreateHardLink(n, o, 0)
281 if err != nil {
282 return &LinkError{"link", oldname, newname, err}
283 }
284 return nil
285 }
286
287
288
289
290
291 func Symlink(oldname, newname string) error {
292
293 oldname = filepathlite.FromSlash(oldname)
294
295
296 destpath := oldname
297 if v := filepathlite.VolumeName(oldname); v == "" {
298 if len(oldname) > 0 && IsPathSeparator(oldname[0]) {
299
300 if v = filepathlite.VolumeName(newname); v != "" {
301
302
303 destpath = v + oldname
304 }
305 } else {
306
307 destpath = dirname(newname) + `\` + oldname
308 }
309 }
310
311 fi, err := Stat(destpath)
312 isdir := err == nil && fi.IsDir()
313
314 n, err := syscall.UTF16PtrFromString(fixLongPath(newname))
315 if err != nil {
316 return &LinkError{"symlink", oldname, newname, err}
317 }
318 var o *uint16
319 if filepathlite.IsAbs(oldname) {
320 o, err = syscall.UTF16PtrFromString(fixLongPath(oldname))
321 } else {
322
323
324
325
326
327
328 o, err = syscall.UTF16PtrFromString(oldname)
329 }
330 if err != nil {
331 return &LinkError{"symlink", oldname, newname, err}
332 }
333
334 var flags uint32 = windows.SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE
335 if isdir {
336 flags |= syscall.SYMBOLIC_LINK_FLAG_DIRECTORY
337 }
338 err = syscall.CreateSymbolicLink(n, o, flags)
339 if err != nil {
340
341
342 flags &^= windows.SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE
343 err = syscall.CreateSymbolicLink(n, o, flags)
344 if err != nil {
345 return &LinkError{"symlink", oldname, newname, err}
346 }
347 }
348 return nil
349 }
350
351
352
353
354 func openSymlink(path string) (syscall.Handle, error) {
355 p, err := syscall.UTF16PtrFromString(path)
356 if err != nil {
357 return 0, err
358 }
359 attrs := uint32(syscall.FILE_FLAG_BACKUP_SEMANTICS)
360
361
362 attrs |= syscall.FILE_FLAG_OPEN_REPARSE_POINT
363 h, err := syscall.CreateFile(p, 0, 0, nil, syscall.OPEN_EXISTING, attrs, 0)
364 if err != nil {
365 return 0, err
366 }
367 return h, nil
368 }
369
370 var winreadlinkvolume = godebug.New("winreadlinkvolume")
371
372
373
374
375
376
377
378
379
380 func normaliseLinkPath(path string) (string, error) {
381 if len(path) < 4 || path[:4] != `\??\` {
382
383 return path, nil
384 }
385
386 s := path[4:]
387 switch {
388 case len(s) >= 2 && s[1] == ':':
389 return s, nil
390 case len(s) >= 4 && s[:4] == `UNC\`:
391 return `\\` + s[4:], nil
392 }
393
394
395 if winreadlinkvolume.Value() != "0" {
396 return `\\?\` + path[4:], nil
397 }
398 winreadlinkvolume.IncNonDefault()
399
400 h, err := openSymlink(path)
401 if err != nil {
402 return "", err
403 }
404 defer syscall.CloseHandle(h)
405
406 buf := make([]uint16, 100)
407 for {
408 n, err := windows.GetFinalPathNameByHandle(h, &buf[0], uint32(len(buf)), windows.VOLUME_NAME_DOS)
409 if err != nil {
410 return "", err
411 }
412 if n < uint32(len(buf)) {
413 break
414 }
415 buf = make([]uint16, n)
416 }
417 s = syscall.UTF16ToString(buf)
418 if len(s) > 4 && s[:4] == `\\?\` {
419 s = s[4:]
420 if len(s) > 3 && s[:3] == `UNC` {
421
422 return `\` + s[3:], nil
423 }
424 return s, nil
425 }
426 return "", errors.New("GetFinalPathNameByHandle returned unexpected path: " + s)
427 }
428
429 func readReparseLink(path string) (string, error) {
430 h, err := openSymlink(path)
431 if err != nil {
432 return "", err
433 }
434 defer syscall.CloseHandle(h)
435 return readReparseLinkHandle(h)
436 }
437
438 func readReparseLinkHandle(h syscall.Handle) (string, error) {
439 rdbbuf := make([]byte, syscall.MAXIMUM_REPARSE_DATA_BUFFER_SIZE)
440 var bytesReturned uint32
441 err := syscall.DeviceIoControl(h, syscall.FSCTL_GET_REPARSE_POINT, nil, 0, &rdbbuf[0], uint32(len(rdbbuf)), &bytesReturned, nil)
442 if err != nil {
443 return "", err
444 }
445
446 rdb := (*windows.REPARSE_DATA_BUFFER)(unsafe.Pointer(&rdbbuf[0]))
447 switch rdb.ReparseTag {
448 case syscall.IO_REPARSE_TAG_SYMLINK:
449 rb := (*windows.SymbolicLinkReparseBuffer)(unsafe.Pointer(&rdb.DUMMYUNIONNAME))
450 s := rb.Path()
451 if rb.Flags&windows.SYMLINK_FLAG_RELATIVE != 0 {
452 return s, nil
453 }
454 return normaliseLinkPath(s)
455 case windows.IO_REPARSE_TAG_MOUNT_POINT:
456 return normaliseLinkPath((*windows.MountPointReparseBuffer)(unsafe.Pointer(&rdb.DUMMYUNIONNAME)).Path())
457 default:
458
459
460 return "", syscall.ENOENT
461 }
462 }
463
464 func readlink(name string) (string, error) {
465 s, err := readReparseLink(fixLongPath(name))
466 if err != nil {
467 return "", &PathError{Op: "readlink", Path: name, Err: err}
468 }
469 return s, nil
470 }
471
View as plain text