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