Source file
src/os/root_windows_test.go
1
2
3
4
5
6
7 package os_test
8
9 import (
10 "errors"
11 "fmt"
12 "internal/strconv"
13 "internal/syscall/windows"
14 "internal/testenv"
15 "os"
16 "path/filepath"
17 "syscall"
18 "testing"
19 "unsafe"
20 )
21
22 func init() {
23 createJunction = func(t *testing.T, link, target string) {
24 if !filepath.IsAbs(target) {
25 target = filepath.Dir(link) + `\` + target
26 }
27 target, err := syscall.FullPath(target)
28 if err != nil {
29 t.Fatal(err)
30 }
31 var rd reparseData
32 rd.addSubstituteName(`\??\` + target)
33 rd.addPrintName(target)
34 if err := createMountPoint(link, &rd); err != nil {
35 t.Fatal(err)
36 }
37 }
38 }
39
40 func TestRootOpenatFallback(t *testing.T) {
41 windows.TestOpenatFallback = true
42 t.Cleanup(func() { windows.TestOpenatFallback = false })
43
44
45
46 t.Run("OpenFile", TestRootOpen_File)
47 t.Run("OpenDirectory", TestRootOpen_Directory)
48 t.Run("Create", TestRootCreate)
49 t.Run("Stat", TestRootStat)
50 t.Run("Lstat", TestRootLstat)
51 t.Run("RemoveAll", TestRootRemoveAll)
52 t.Run("RemoveAllNoRoot", TestRemoveAll)
53 t.Run("DeleteOnClose", testRootOpenFileDeleteOnClose)
54 t.Run("LegacyDelete", func(t *testing.T) {
55 windows.TestDeleteatFallback = true
56 t.Cleanup(func() { windows.TestDeleteatFallback = false })
57 t.Run("RemoveAll", TestRootRemoveAll)
58 t.Run("RemoveAllNoRoot", TestRemoveAll)
59 })
60 }
61
62
63 func TestRootWindowsDeviceNames(t *testing.T) {
64 r, err := os.OpenRoot(t.TempDir())
65 if err != nil {
66 t.Fatal(err)
67 }
68 defer r.Close()
69 if f, err := r.Open("NUL"); err == nil {
70 t.Errorf(`r.Open("NUL") succeeded; want error"`)
71 f.Close()
72 }
73 }
74
75
76
77
78 func TestRootWindowsCaseInsensitivity(t *testing.T) {
79 dir := t.TempDir()
80 if err := os.WriteFile(filepath.Join(dir, "file"), nil, 0666); err != nil {
81 t.Fatal(err)
82 }
83 r, err := os.OpenRoot(dir)
84 if err != nil {
85 t.Fatal(err)
86 }
87 defer r.Close()
88 f, err := r.Open("FILE")
89 if err != nil {
90 t.Fatal(err)
91 }
92 f.Close()
93 if err := r.Remove("FILE"); err != nil {
94 t.Fatal(err)
95 }
96 if _, err := os.Stat(filepath.Join(dir, "file")); !errors.Is(err, os.ErrNotExist) {
97 t.Fatalf("os.Stat(file) after deletion: %v, want ErrNotFound", err)
98 }
99 }
100
101
102
103 func TestRootSymlinkRelativity(t *testing.T) {
104 testenv.MustHaveSymlink(t)
105
106 dir := t.TempDir()
107 root, err := os.OpenRoot(dir)
108 if err != nil {
109 t.Fatal(err)
110 }
111 defer root.Close()
112
113 for i, test := range []struct {
114 name string
115 target string
116 }{{
117 name: "relative",
118 target: `foo`,
119 }, {
120 name: "absolute",
121 target: `C:\foo`,
122 }, {
123 name: "current working directory-relative",
124 target: `C:foo`,
125 }, {
126 name: "root-relative",
127 target: `\foo`,
128 }, {
129 name: "question prefix",
130 target: `\\?\foo`,
131 }, {
132 name: "relative with dot dot",
133 target: `a\..\b`,
134 }} {
135 t.Run(test.name, func(t *testing.T) {
136 name := fmt.Sprintf("symlink_%v", i)
137 if err := os.Symlink(test.target, filepath.Join(dir, name)); err != nil {
138 t.Fatal(err)
139 }
140 if err := root.Symlink(test.target, name+"_at"); err != nil {
141 t.Fatal(err)
142 }
143
144 osRDB, err := readSymlinkReparseData(filepath.Join(dir, name))
145 if err != nil {
146 t.Fatal(err)
147 }
148 rootRDB, err := readSymlinkReparseData(filepath.Join(dir, name+"_at"))
149 if err != nil {
150 t.Fatal(err)
151 }
152 if osRDB.Flags != rootRDB.Flags {
153 t.Errorf("symlink target %q: Symlink flags = %x, Root.Symlink flags = %x", test.target, osRDB.Flags, rootRDB.Flags)
154 }
155
156
157
158
159 osTarget, err := os.Readlink(filepath.Join(dir, name))
160 if err != nil {
161 t.Fatal(err)
162 }
163 rootTarget, err := os.Readlink(filepath.Join(dir, name+"_at"))
164 if err != nil {
165 t.Fatal(err)
166 }
167 if osTarget != rootTarget {
168 t.Errorf("symlink created with target %q: Symlink target = %q, Root.Symlink target = %q", test.target, osTarget, rootTarget)
169 }
170 })
171 }
172 }
173
174 func readSymlinkReparseData(name string) (*windows.SymbolicLinkReparseBuffer, error) {
175 nameu16, err := syscall.UTF16FromString(name)
176 if err != nil {
177 return nil, err
178 }
179 h, err := syscall.CreateFile(&nameu16[0], syscall.GENERIC_READ, 0, nil, syscall.OPEN_EXISTING,
180 syscall.FILE_FLAG_OPEN_REPARSE_POINT|syscall.FILE_FLAG_BACKUP_SEMANTICS, 0)
181 if err != nil {
182 return nil, err
183 }
184 defer syscall.CloseHandle(h)
185
186 var rdbbuf [syscall.MAXIMUM_REPARSE_DATA_BUFFER_SIZE]byte
187 var bytesReturned uint32
188 err = syscall.DeviceIoControl(h, syscall.FSCTL_GET_REPARSE_POINT, nil, 0, &rdbbuf[0], uint32(len(rdbbuf)), &bytesReturned, nil)
189 if err != nil {
190 return nil, err
191 }
192
193 rdb := (*windows.REPARSE_DATA_BUFFER)(unsafe.Pointer(&rdbbuf[0]))
194 if rdb.ReparseTag != syscall.IO_REPARSE_TAG_SYMLINK {
195 return nil, fmt.Errorf("%q: not a symlink", name)
196 }
197
198 bufoff := unsafe.Offsetof(rdb.DUMMYUNIONNAME)
199 symlinkBuf := (*windows.SymbolicLinkReparseBuffer)(unsafe.Pointer(&rdbbuf[bufoff]))
200
201 return symlinkBuf, nil
202 }
203
204
205
206 func TestRootSymlinkToDirectory(t *testing.T) {
207 testenv.MustHaveSymlink(t)
208
209 dir := t.TempDir()
210 root, err := os.OpenRoot(dir)
211 if err != nil {
212 t.Fatal(err)
213 }
214 defer root.Close()
215
216 if err := os.Mkdir(filepath.Join(dir, "dir"), 0777); err != nil {
217 t.Fatal(err)
218 }
219 if err := os.WriteFile(filepath.Join(dir, "file"), nil, 0666); err != nil {
220 t.Fatal(err)
221 }
222
223 dir2 := t.TempDir()
224
225 for i, test := range []struct {
226 name string
227 target string
228 wantDir bool
229 }{{
230 name: "directory outside root",
231 target: dir2,
232 wantDir: false,
233 }, {
234 name: "directory inside root",
235 target: "dir",
236 wantDir: true,
237 }, {
238 name: "file inside root",
239 target: "file",
240 wantDir: false,
241 }, {
242 name: "nonexistent inside root",
243 target: "nonexistent",
244 wantDir: false,
245 }} {
246 t.Run(test.name, func(t *testing.T) {
247 name := fmt.Sprintf("symlink_%v", i)
248 if err := root.Symlink(test.target, name); err != nil {
249 t.Fatal(err)
250 }
251
252
253
254
255 nameu16, err := syscall.UTF16PtrFromString(filepath.Join(dir, name))
256 if err != nil {
257 t.Fatal(err)
258 }
259 h, err := syscall.CreateFile(nameu16, 0, 0, nil, syscall.OPEN_EXISTING,
260 syscall.FILE_FLAG_OPEN_REPARSE_POINT|syscall.FILE_FLAG_BACKUP_SEMANTICS, 0)
261 if err != nil {
262 t.Fatal(err)
263 }
264 defer syscall.CloseHandle(h)
265 var fi syscall.ByHandleFileInformation
266 if err := syscall.GetFileInformationByHandle(h, &fi); err != nil {
267 t.Fatal(err)
268 }
269 gotDir := fi.FileAttributes&syscall.FILE_ATTRIBUTE_DIRECTORY != 0
270
271 if got, want := gotDir, test.wantDir; got != want {
272 t.Errorf("link target %q: isDir = %v, want %v", test.target, got, want)
273 }
274 })
275 }
276 }
277
278 func TestRootSymlinkNormalization(t *testing.T) {
279 if !testenv.HasSymlink() {
280 t.Skip("skipping test; no symlink support")
281 }
282 const content = "dir/target"
283 dir := makefs(t, []string{
284 "dir/target",
285 })
286 root, err := os.OpenRoot(dir)
287 if err != nil {
288 t.Fatal(err)
289 }
290 defer root.Close()
291 if err := root.Symlink("dir/target", "link"); err != nil {
292 t.Fatal(err)
293 }
294 got, err := os.ReadFile(dir + "/link")
295 if err != nil {
296 t.Fatal(err)
297 }
298 if string(got) != content {
299 t.Fatalf("read link contents %q, want %q", got, content)
300 }
301 }
302
303 func TestRootOpenFileTruncateNamedPipe(t *testing.T) {
304 t.Parallel()
305 name := pipeName()
306 pipe := newBytePipe(t, name, false)
307 defer pipe.Close()
308
309 root, err := os.OpenRoot(filepath.Dir(name))
310 if err != nil {
311 t.Fatal(err)
312 }
313 defer root.Close()
314
315 f, err := root.OpenFile(filepath.Base(name), os.O_TRUNC|os.O_RDWR|os.O_CREATE, 0666)
316 if err != nil {
317 t.Fatal(err)
318 }
319 f.Close()
320 }
321
322 func TestRootOpenFileFlags(t *testing.T) {
323 t.Parallel()
324
325 dir := t.TempDir()
326 root, err := os.OpenRoot(dir)
327 if err != nil {
328 t.Fatal(err)
329 }
330 defer root.Close()
331
332
333
334
335
336
337
338 tests := []struct {
339 flag uint32
340 wantMode uint32
341 }{
342 {0, windows.FILE_SYNCHRONOUS_IO_NONALERT},
343 {windows.O_FILE_FLAG_OVERLAPPED, 0},
344 {windows.O_FILE_FLAG_NO_BUFFERING, windows.FILE_NO_INTERMEDIATE_BUFFERING | windows.FILE_SYNCHRONOUS_IO_NONALERT},
345 {windows.O_FILE_FLAG_NO_BUFFERING | windows.O_FILE_FLAG_OVERLAPPED, windows.FILE_NO_INTERMEDIATE_BUFFERING},
346 {windows.O_FILE_FLAG_SEQUENTIAL_SCAN, windows.FILE_SEQUENTIAL_ONLY | windows.FILE_SYNCHRONOUS_IO_NONALERT},
347 {windows.O_FILE_FLAG_WRITE_THROUGH, windows.FILE_WRITE_THROUGH | windows.FILE_SYNCHRONOUS_IO_NONALERT},
348 }
349 for i, tt := range tests {
350 t.Run(strconv.Itoa(i), func(t *testing.T) {
351 f, err := root.OpenFile(strconv.Itoa(i)+".txt", syscall.O_RDWR|syscall.O_CREAT|int(tt.flag), 0666)
352 if err != nil {
353 t.Fatal(err)
354 }
355 defer f.Close()
356 var info windows.FILE_MODE_INFORMATION
357 if err := windows.NtQueryInformationFile(syscall.Handle(f.Fd()), &windows.IO_STATUS_BLOCK{},
358 unsafe.Pointer(&info), uint32(unsafe.Sizeof(info)), windows.FileModeInformation); err != nil {
359 t.Fatal(err)
360 }
361 if info.Mode != tt.wantMode {
362 t.Errorf("file mode = 0x%x; want 0x%x", info.Mode, tt.wantMode)
363 }
364 })
365 }
366 }
367
368 func TestRootOpenFileDeleteOnClose(t *testing.T) {
369 t.Parallel()
370 testRootOpenFileDeleteOnClose(t)
371 }
372
373 func testRootOpenFileDeleteOnClose(t *testing.T) {
374 dir := t.TempDir()
375 root, err := os.OpenRoot(dir)
376 if err != nil {
377 t.Fatal(err)
378 }
379 defer root.Close()
380 const name = "test.txt"
381 f, err := root.OpenFile(name, syscall.O_RDWR|syscall.O_CREAT|windows.O_FILE_FLAG_DELETE_ON_CLOSE, 0666)
382 if err != nil {
383 t.Fatal(err)
384 }
385 if err := f.Close(); err != nil {
386 t.Fatal(err)
387 }
388
389 if _, err := os.Stat(filepath.Join(dir, name)); !errors.Is(err, os.ErrNotExist) {
390 t.Errorf("expected file to be deleted, got %v", err)
391 }
392 }
393
394 func TestRootOpenFileFlagInvalid(t *testing.T) {
395 t.Parallel()
396 dir := t.TempDir()
397 root, err := os.OpenRoot(dir)
398 if err != nil {
399 t.Fatal(err)
400 }
401 defer root.Close()
402
403
404 const invalidFileFlag = 0x00400000
405 f, err := root.OpenFile("test.txt", syscall.O_RDWR|syscall.O_CREAT|invalidFileFlag, 0666)
406 if !errors.Is(err, os.ErrInvalid) {
407 t.Fatalf("expected os.ErrInvalid, got %v", err)
408 }
409 f.Close()
410 }
411
View as plain text