Source file src/os/root_windows_test.go

     1  // Copyright 2024 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  //go:build windows
     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  // Verify that Root.Open rejects Windows reserved names.
    23  func TestRootWindowsDeviceNames(t *testing.T) {
    24  	r, err := os.OpenRoot(t.TempDir())
    25  	if err != nil {
    26  		t.Fatal(err)
    27  	}
    28  	defer r.Close()
    29  	if f, err := r.Open("NUL"); err == nil {
    30  		t.Errorf(`r.Open("NUL") succeeded; want error"`)
    31  		f.Close()
    32  	}
    33  }
    34  
    35  // Verify that Root.Open is case-insensitive.
    36  // (The wrong options to NtOpenFile could make operations case-sensitive,
    37  // so this is worth checking.)
    38  func TestRootWindowsCaseInsensitivity(t *testing.T) {
    39  	dir := t.TempDir()
    40  	if err := os.WriteFile(filepath.Join(dir, "file"), nil, 0666); err != nil {
    41  		t.Fatal(err)
    42  	}
    43  	r, err := os.OpenRoot(dir)
    44  	if err != nil {
    45  		t.Fatal(err)
    46  	}
    47  	defer r.Close()
    48  	f, err := r.Open("FILE")
    49  	if err != nil {
    50  		t.Fatal(err)
    51  	}
    52  	f.Close()
    53  	if err := r.Remove("FILE"); err != nil {
    54  		t.Fatal(err)
    55  	}
    56  	if _, err := os.Stat(filepath.Join(dir, "file")); !errors.Is(err, os.ErrNotExist) {
    57  		t.Fatalf("os.Stat(file) after deletion: %v, want ErrNotFound", err)
    58  	}
    59  }
    60  
    61  // TestRootSymlinkRelativity tests that symlinks created using Root.Symlink have the
    62  // same SYMLINK_FLAG_RELATIVE value as ones creates using os.Symlink.
    63  func TestRootSymlinkRelativity(t *testing.T) {
    64  	testenv.MustHaveSymlink(t)
    65  
    66  	dir := t.TempDir()
    67  	root, err := os.OpenRoot(dir)
    68  	if err != nil {
    69  		t.Fatal(err)
    70  	}
    71  	defer root.Close()
    72  
    73  	for i, test := range []struct {
    74  		name   string
    75  		target string
    76  	}{{
    77  		name:   "relative",
    78  		target: `foo`,
    79  	}, {
    80  		name:   "absolute",
    81  		target: `C:\foo`,
    82  	}, {
    83  		name:   "current working directory-relative",
    84  		target: `C:foo`,
    85  	}, {
    86  		name:   "root-relative",
    87  		target: `\foo`,
    88  	}, {
    89  		name:   "question prefix",
    90  		target: `\\?\foo`,
    91  	}, {
    92  		name:   "relative with dot dot",
    93  		target: `a\..\b`, // could be cleaned (but isn't)
    94  	}} {
    95  		t.Run(test.name, func(t *testing.T) {
    96  			name := fmt.Sprintf("symlink_%v", i)
    97  			if err := os.Symlink(test.target, filepath.Join(dir, name)); err != nil {
    98  				t.Fatal(err)
    99  			}
   100  			if err := root.Symlink(test.target, name+"_at"); err != nil {
   101  				t.Fatal(err)
   102  			}
   103  
   104  			osRDB, err := readSymlinkReparseData(filepath.Join(dir, name))
   105  			if err != nil {
   106  				t.Fatal(err)
   107  			}
   108  			rootRDB, err := readSymlinkReparseData(filepath.Join(dir, name+"_at"))
   109  			if err != nil {
   110  				t.Fatal(err)
   111  			}
   112  			if osRDB.Flags != rootRDB.Flags {
   113  				t.Errorf("symlink target %q: Symlink flags = %x, Root.Symlink flags = %x", test.target, osRDB.Flags, rootRDB.Flags)
   114  			}
   115  
   116  			// Compare the link target.
   117  			// os.Symlink converts current working directory-relative links
   118  			// such as c:foo into absolute links.
   119  			osTarget, err := os.Readlink(filepath.Join(dir, name))
   120  			if err != nil {
   121  				t.Fatal(err)
   122  			}
   123  			rootTarget, err := os.Readlink(filepath.Join(dir, name+"_at"))
   124  			if err != nil {
   125  				t.Fatal(err)
   126  			}
   127  			if osTarget != rootTarget {
   128  				t.Errorf("symlink created with target %q: Symlink target = %q, Root.Symlink target = %q", test.target, osTarget, rootTarget)
   129  			}
   130  		})
   131  	}
   132  }
   133  
   134  func readSymlinkReparseData(name string) (*windows.SymbolicLinkReparseBuffer, error) {
   135  	nameu16, err := syscall.UTF16FromString(name)
   136  	if err != nil {
   137  		return nil, err
   138  	}
   139  	h, err := syscall.CreateFile(&nameu16[0], syscall.GENERIC_READ, 0, nil, syscall.OPEN_EXISTING,
   140  		syscall.FILE_FLAG_OPEN_REPARSE_POINT|syscall.FILE_FLAG_BACKUP_SEMANTICS, 0)
   141  	if err != nil {
   142  		return nil, err
   143  	}
   144  	defer syscall.CloseHandle(h)
   145  
   146  	var rdbbuf [syscall.MAXIMUM_REPARSE_DATA_BUFFER_SIZE]byte
   147  	var bytesReturned uint32
   148  	err = syscall.DeviceIoControl(h, syscall.FSCTL_GET_REPARSE_POINT, nil, 0, &rdbbuf[0], uint32(len(rdbbuf)), &bytesReturned, nil)
   149  	if err != nil {
   150  		return nil, err
   151  	}
   152  
   153  	rdb := (*windows.REPARSE_DATA_BUFFER)(unsafe.Pointer(&rdbbuf[0]))
   154  	if rdb.ReparseTag != syscall.IO_REPARSE_TAG_SYMLINK {
   155  		return nil, fmt.Errorf("%q: not a symlink", name)
   156  	}
   157  
   158  	bufoff := unsafe.Offsetof(rdb.DUMMYUNIONNAME)
   159  	symlinkBuf := (*windows.SymbolicLinkReparseBuffer)(unsafe.Pointer(&rdbbuf[bufoff]))
   160  
   161  	return symlinkBuf, nil
   162  }
   163  
   164  // TestRootSymlinkToDirectory tests that Root.Symlink creates directory links
   165  // when the target is a directory contained within the root.
   166  func TestRootSymlinkToDirectory(t *testing.T) {
   167  	testenv.MustHaveSymlink(t)
   168  
   169  	dir := t.TempDir()
   170  	root, err := os.OpenRoot(dir)
   171  	if err != nil {
   172  		t.Fatal(err)
   173  	}
   174  	defer root.Close()
   175  
   176  	if err := os.Mkdir(filepath.Join(dir, "dir"), 0777); err != nil {
   177  		t.Fatal(err)
   178  	}
   179  	if err := os.WriteFile(filepath.Join(dir, "file"), nil, 0666); err != nil {
   180  		t.Fatal(err)
   181  	}
   182  
   183  	dir2 := t.TempDir()
   184  
   185  	for i, test := range []struct {
   186  		name    string
   187  		target  string
   188  		wantDir bool
   189  	}{{
   190  		name:    "directory outside root",
   191  		target:  dir2,
   192  		wantDir: false,
   193  	}, {
   194  		name:    "directory inside root",
   195  		target:  "dir",
   196  		wantDir: true,
   197  	}, {
   198  		name:    "file inside root",
   199  		target:  "file",
   200  		wantDir: false,
   201  	}, {
   202  		name:    "nonexistent inside root",
   203  		target:  "nonexistent",
   204  		wantDir: false,
   205  	}} {
   206  		t.Run(test.name, func(t *testing.T) {
   207  			name := fmt.Sprintf("symlink_%v", i)
   208  			if err := root.Symlink(test.target, name); err != nil {
   209  				t.Fatal(err)
   210  			}
   211  
   212  			// Lstat strips the directory mode bit from reparse points,
   213  			// so we need to use GetFileInformationByHandle directly to
   214  			// determine if this is a directory link.
   215  			nameu16, err := syscall.UTF16PtrFromString(filepath.Join(dir, name))
   216  			if err != nil {
   217  				t.Fatal(err)
   218  			}
   219  			h, err := syscall.CreateFile(nameu16, 0, 0, nil, syscall.OPEN_EXISTING,
   220  				syscall.FILE_FLAG_OPEN_REPARSE_POINT|syscall.FILE_FLAG_BACKUP_SEMANTICS, 0)
   221  			if err != nil {
   222  				t.Fatal(err)
   223  			}
   224  			defer syscall.CloseHandle(h)
   225  			var fi syscall.ByHandleFileInformation
   226  			if err := syscall.GetFileInformationByHandle(h, &fi); err != nil {
   227  				t.Fatal(err)
   228  			}
   229  			gotDir := fi.FileAttributes&syscall.FILE_ATTRIBUTE_DIRECTORY != 0
   230  
   231  			if got, want := gotDir, test.wantDir; got != want {
   232  				t.Errorf("link target %q: isDir = %v, want %v", test.target, got, want)
   233  			}
   234  		})
   235  	}
   236  }
   237  
   238  func TestRootOpenFileTruncateNamedPipe(t *testing.T) {
   239  	t.Parallel()
   240  	name := pipeName()
   241  	pipe := newBytePipe(t, name, false)
   242  	defer pipe.Close()
   243  
   244  	root, err := os.OpenRoot(filepath.Dir(name))
   245  	if err != nil {
   246  		t.Fatal(err)
   247  	}
   248  	defer root.Close()
   249  
   250  	f, err := root.OpenFile(filepath.Base(name), os.O_TRUNC|os.O_RDWR|os.O_CREATE, 0666)
   251  	if err != nil {
   252  		t.Fatal(err)
   253  	}
   254  	f.Close()
   255  }
   256  
   257  func TestRootOpenFileFlags(t *testing.T) {
   258  	t.Parallel()
   259  
   260  	dir := t.TempDir()
   261  	root, err := os.OpenRoot(dir)
   262  	if err != nil {
   263  		t.Fatal(err)
   264  	}
   265  	defer root.Close()
   266  
   267  	// The only way to retrieve some of the flags passed in CreateFile
   268  	// is using NtQueryInformationFile, which returns the file flags
   269  	// NT equivalent. Note that FILE_SYNCHRONOUS_IO_NONALERT is always
   270  	// set when FILE_FLAG_OVERLAPPED is not passed.
   271  	// The flags that can't be retrieved using NtQueryInformationFile won't
   272  	// be tested in here, but we at least know that the logic to handle them is correct.
   273  	tests := []struct {
   274  		flag     uint32
   275  		wantMode uint32
   276  	}{
   277  		{0, windows.FILE_SYNCHRONOUS_IO_NONALERT},
   278  		{windows.O_FILE_FLAG_OVERLAPPED, 0},
   279  		{windows.O_FILE_FLAG_NO_BUFFERING, windows.FILE_NO_INTERMEDIATE_BUFFERING | windows.FILE_SYNCHRONOUS_IO_NONALERT},
   280  		{windows.O_FILE_FLAG_NO_BUFFERING | windows.O_FILE_FLAG_OVERLAPPED, windows.FILE_NO_INTERMEDIATE_BUFFERING},
   281  		{windows.O_FILE_FLAG_SEQUENTIAL_SCAN, windows.FILE_SEQUENTIAL_ONLY | windows.FILE_SYNCHRONOUS_IO_NONALERT},
   282  		{windows.O_FILE_FLAG_WRITE_THROUGH, windows.FILE_WRITE_THROUGH | windows.FILE_SYNCHRONOUS_IO_NONALERT},
   283  	}
   284  	for i, tt := range tests {
   285  		t.Run(strconv.Itoa(i), func(t *testing.T) {
   286  			f, err := root.OpenFile(strconv.Itoa(i)+".txt", syscall.O_RDWR|syscall.O_CREAT|int(tt.flag), 0666)
   287  			if err != nil {
   288  				t.Fatal(err)
   289  			}
   290  			defer f.Close()
   291  			var info windows.FILE_MODE_INFORMATION
   292  			if err := windows.NtQueryInformationFile(syscall.Handle(f.Fd()), &windows.IO_STATUS_BLOCK{},
   293  				unsafe.Pointer(&info), uint32(unsafe.Sizeof(info)), windows.FileModeInformation); err != nil {
   294  				t.Fatal(err)
   295  			}
   296  			if info.Mode != tt.wantMode {
   297  				t.Errorf("file mode = 0x%x; want 0x%x", info.Mode, tt.wantMode)
   298  			}
   299  		})
   300  	}
   301  }
   302  
   303  func TestRootOpenFileDeleteOnClose(t *testing.T) {
   304  	t.Parallel()
   305  	dir := t.TempDir()
   306  	root, err := os.OpenRoot(dir)
   307  	if err != nil {
   308  		t.Fatal(err)
   309  	}
   310  	defer root.Close()
   311  	const name = "test.txt"
   312  	f, err := root.OpenFile(name, syscall.O_RDWR|syscall.O_CREAT|windows.O_FILE_FLAG_DELETE_ON_CLOSE, 0666)
   313  	if err != nil {
   314  		t.Fatal(err)
   315  	}
   316  	if err := f.Close(); err != nil {
   317  		t.Fatal(err)
   318  	}
   319  	// The file should be deleted after closing.
   320  	if _, err := os.Stat(filepath.Join(dir, name)); !errors.Is(err, os.ErrNotExist) {
   321  		t.Errorf("expected file to be deleted, got %v", err)
   322  	}
   323  }
   324  
   325  func TestRootOpenFileFlagInvalid(t *testing.T) {
   326  	t.Parallel()
   327  	dir := t.TempDir()
   328  	root, err := os.OpenRoot(dir)
   329  	if err != nil {
   330  		t.Fatal(err)
   331  	}
   332  	defer root.Close()
   333  	// invalidFileFlag is the only value in the file flag range that is not supported,
   334  	// as it is not defined in the Windows API.
   335  	const invalidFileFlag = 0x00400000
   336  	f, err := root.OpenFile("test.txt", syscall.O_RDWR|syscall.O_CREAT|invalidFileFlag, 0666)
   337  	if !errors.Is(err, os.ErrInvalid) {
   338  		t.Fatalf("expected os.ErrInvalid, got %v", err)
   339  	}
   340  	f.Close()
   341  }
   342  

View as plain text