mirror of
https://github.com/golang/go.git
synced 2025-12-08 06:10:04 +00:00
These file flags are supported by os.OpenFile since CL 699415. Closes #73676 Change-Id: Ib37102a565f538d394d2a94bd605d6c6004f3028 Reviewed-on: https://go-review.googlesource.com/c/go/+/724621 Auto-Submit: Damien Neil <dneil@google.com> Reviewed-by: Damien Neil <dneil@google.com> LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com> Reviewed-by: Cherry Mui <cherryyz@google.com>
336 lines
9.2 KiB
Go
336 lines
9.2 KiB
Go
// Copyright 2024 The Go Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
//go:build windows
|
|
|
|
package os_test
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"internal/strconv"
|
|
"internal/syscall/windows"
|
|
"os"
|
|
"path/filepath"
|
|
"syscall"
|
|
"testing"
|
|
"unsafe"
|
|
)
|
|
|
|
// Verify that Root.Open rejects Windows reserved names.
|
|
func TestRootWindowsDeviceNames(t *testing.T) {
|
|
r, err := os.OpenRoot(t.TempDir())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer r.Close()
|
|
if f, err := r.Open("NUL"); err == nil {
|
|
t.Errorf(`r.Open("NUL") succeeded; want error"`)
|
|
f.Close()
|
|
}
|
|
}
|
|
|
|
// Verify that Root.Open is case-insensitive.
|
|
// (The wrong options to NtOpenFile could make operations case-sensitive,
|
|
// so this is worth checking.)
|
|
func TestRootWindowsCaseInsensitivity(t *testing.T) {
|
|
dir := t.TempDir()
|
|
if err := os.WriteFile(filepath.Join(dir, "file"), nil, 0666); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
r, err := os.OpenRoot(dir)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer r.Close()
|
|
f, err := r.Open("FILE")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
f.Close()
|
|
if err := r.Remove("FILE"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := os.Stat(filepath.Join(dir, "file")); !errors.Is(err, os.ErrNotExist) {
|
|
t.Fatalf("os.Stat(file) after deletion: %v, want ErrNotFound", err)
|
|
}
|
|
}
|
|
|
|
// TestRootSymlinkRelativity tests that symlinks created using Root.Symlink have the
|
|
// same SYMLINK_FLAG_RELATIVE value as ones creates using os.Symlink.
|
|
func TestRootSymlinkRelativity(t *testing.T) {
|
|
dir := t.TempDir()
|
|
root, err := os.OpenRoot(dir)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer root.Close()
|
|
|
|
for i, test := range []struct {
|
|
name string
|
|
target string
|
|
}{{
|
|
name: "relative",
|
|
target: `foo`,
|
|
}, {
|
|
name: "absolute",
|
|
target: `C:\foo`,
|
|
}, {
|
|
name: "current working directory-relative",
|
|
target: `C:foo`,
|
|
}, {
|
|
name: "root-relative",
|
|
target: `\foo`,
|
|
}, {
|
|
name: "question prefix",
|
|
target: `\\?\foo`,
|
|
}, {
|
|
name: "relative with dot dot",
|
|
target: `a\..\b`, // could be cleaned (but isn't)
|
|
}} {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
name := fmt.Sprintf("symlink_%v", i)
|
|
if err := os.Symlink(test.target, filepath.Join(dir, name)); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := root.Symlink(test.target, name+"_at"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
osRDB, err := readSymlinkReparseData(filepath.Join(dir, name))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
rootRDB, err := readSymlinkReparseData(filepath.Join(dir, name+"_at"))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if osRDB.Flags != rootRDB.Flags {
|
|
t.Errorf("symlink target %q: Symlink flags = %x, Root.Symlink flags = %x", test.target, osRDB.Flags, rootRDB.Flags)
|
|
}
|
|
|
|
// Compare the link target.
|
|
// os.Symlink converts current working directory-relative links
|
|
// such as c:foo into absolute links.
|
|
osTarget, err := os.Readlink(filepath.Join(dir, name))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
rootTarget, err := os.Readlink(filepath.Join(dir, name+"_at"))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if osTarget != rootTarget {
|
|
t.Errorf("symlink created with target %q: Symlink target = %q, Root.Symlink target = %q", test.target, osTarget, rootTarget)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func readSymlinkReparseData(name string) (*windows.SymbolicLinkReparseBuffer, error) {
|
|
nameu16, err := syscall.UTF16FromString(name)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
h, err := syscall.CreateFile(&nameu16[0], syscall.GENERIC_READ, 0, nil, syscall.OPEN_EXISTING,
|
|
syscall.FILE_FLAG_OPEN_REPARSE_POINT|syscall.FILE_FLAG_BACKUP_SEMANTICS, 0)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer syscall.CloseHandle(h)
|
|
|
|
var rdbbuf [syscall.MAXIMUM_REPARSE_DATA_BUFFER_SIZE]byte
|
|
var bytesReturned uint32
|
|
err = syscall.DeviceIoControl(h, syscall.FSCTL_GET_REPARSE_POINT, nil, 0, &rdbbuf[0], uint32(len(rdbbuf)), &bytesReturned, nil)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
rdb := (*windows.REPARSE_DATA_BUFFER)(unsafe.Pointer(&rdbbuf[0]))
|
|
if rdb.ReparseTag != syscall.IO_REPARSE_TAG_SYMLINK {
|
|
return nil, fmt.Errorf("%q: not a symlink", name)
|
|
}
|
|
|
|
bufoff := unsafe.Offsetof(rdb.DUMMYUNIONNAME)
|
|
symlinkBuf := (*windows.SymbolicLinkReparseBuffer)(unsafe.Pointer(&rdbbuf[bufoff]))
|
|
|
|
return symlinkBuf, nil
|
|
}
|
|
|
|
// TestRootSymlinkToDirectory tests that Root.Symlink creates directory links
|
|
// when the target is a directory contained within the root.
|
|
func TestRootSymlinkToDirectory(t *testing.T) {
|
|
dir := t.TempDir()
|
|
root, err := os.OpenRoot(dir)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer root.Close()
|
|
|
|
if err := os.Mkdir(filepath.Join(dir, "dir"), 0777); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := os.WriteFile(filepath.Join(dir, "file"), nil, 0666); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
dir2 := t.TempDir()
|
|
|
|
for i, test := range []struct {
|
|
name string
|
|
target string
|
|
wantDir bool
|
|
}{{
|
|
name: "directory outside root",
|
|
target: dir2,
|
|
wantDir: false,
|
|
}, {
|
|
name: "directory inside root",
|
|
target: "dir",
|
|
wantDir: true,
|
|
}, {
|
|
name: "file inside root",
|
|
target: "file",
|
|
wantDir: false,
|
|
}, {
|
|
name: "nonexistent inside root",
|
|
target: "nonexistent",
|
|
wantDir: false,
|
|
}} {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
name := fmt.Sprintf("symlink_%v", i)
|
|
if err := root.Symlink(test.target, name); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Lstat strips the directory mode bit from reparse points,
|
|
// so we need to use GetFileInformationByHandle directly to
|
|
// determine if this is a directory link.
|
|
nameu16, err := syscall.UTF16PtrFromString(filepath.Join(dir, name))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
h, err := syscall.CreateFile(nameu16, 0, 0, nil, syscall.OPEN_EXISTING,
|
|
syscall.FILE_FLAG_OPEN_REPARSE_POINT|syscall.FILE_FLAG_BACKUP_SEMANTICS, 0)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer syscall.CloseHandle(h)
|
|
var fi syscall.ByHandleFileInformation
|
|
if err := syscall.GetFileInformationByHandle(h, &fi); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
gotDir := fi.FileAttributes&syscall.FILE_ATTRIBUTE_DIRECTORY != 0
|
|
|
|
if got, want := gotDir, test.wantDir; got != want {
|
|
t.Errorf("link target %q: isDir = %v, want %v", test.target, got, want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestRootOpenFileTruncateNamedPipe(t *testing.T) {
|
|
t.Parallel()
|
|
name := pipeName()
|
|
pipe := newBytePipe(t, name, false)
|
|
defer pipe.Close()
|
|
|
|
root, err := os.OpenRoot(filepath.Dir(name))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer root.Close()
|
|
|
|
f, err := root.OpenFile(filepath.Base(name), os.O_TRUNC|os.O_RDWR|os.O_CREATE, 0666)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
f.Close()
|
|
}
|
|
|
|
func TestRootOpenFileFlags(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
dir := t.TempDir()
|
|
root, err := os.OpenRoot(dir)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer root.Close()
|
|
|
|
// The only way to retrieve some of the flags passed in CreateFile
|
|
// is using NtQueryInformationFile, which returns the file flags
|
|
// NT equivalent. Note that FILE_SYNCHRONOUS_IO_NONALERT is always
|
|
// set when FILE_FLAG_OVERLAPPED is not passed.
|
|
// The flags that can't be retrieved using NtQueryInformationFile won't
|
|
// be tested in here, but we at least know that the logic to handle them is correct.
|
|
tests := []struct {
|
|
flag uint32
|
|
wantMode uint32
|
|
}{
|
|
{0, windows.FILE_SYNCHRONOUS_IO_NONALERT},
|
|
{windows.O_FILE_FLAG_OVERLAPPED, 0},
|
|
{windows.O_FILE_FLAG_NO_BUFFERING, windows.FILE_NO_INTERMEDIATE_BUFFERING | windows.FILE_SYNCHRONOUS_IO_NONALERT},
|
|
{windows.O_FILE_FLAG_NO_BUFFERING | windows.O_FILE_FLAG_OVERLAPPED, windows.FILE_NO_INTERMEDIATE_BUFFERING},
|
|
{windows.O_FILE_FLAG_SEQUENTIAL_SCAN, windows.FILE_SEQUENTIAL_ONLY | windows.FILE_SYNCHRONOUS_IO_NONALERT},
|
|
{windows.O_FILE_FLAG_WRITE_THROUGH, windows.FILE_WRITE_THROUGH | windows.FILE_SYNCHRONOUS_IO_NONALERT},
|
|
}
|
|
for i, tt := range tests {
|
|
t.Run(strconv.Itoa(i), func(t *testing.T) {
|
|
f, err := root.OpenFile(strconv.Itoa(i)+".txt", syscall.O_RDWR|syscall.O_CREAT|int(tt.flag), 0666)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer f.Close()
|
|
var info windows.FILE_MODE_INFORMATION
|
|
if err := windows.NtQueryInformationFile(syscall.Handle(f.Fd()), &windows.IO_STATUS_BLOCK{},
|
|
unsafe.Pointer(&info), uint32(unsafe.Sizeof(info)), windows.FileModeInformation); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if info.Mode != tt.wantMode {
|
|
t.Errorf("file mode = 0x%x; want 0x%x", info.Mode, tt.wantMode)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestRootOpenFileDeleteOnClose(t *testing.T) {
|
|
t.Parallel()
|
|
dir := t.TempDir()
|
|
root, err := os.OpenRoot(dir)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer root.Close()
|
|
const name = "test.txt"
|
|
f, err := root.OpenFile(name, syscall.O_RDWR|syscall.O_CREAT|windows.O_FILE_FLAG_DELETE_ON_CLOSE, 0666)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := f.Close(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
// The file should be deleted after closing.
|
|
if _, err := os.Stat(filepath.Join(dir, name)); !errors.Is(err, os.ErrNotExist) {
|
|
t.Errorf("expected file to be deleted, got %v", err)
|
|
}
|
|
}
|
|
|
|
func TestRootOpenFileFlagInvalid(t *testing.T) {
|
|
t.Parallel()
|
|
dir := t.TempDir()
|
|
root, err := os.OpenRoot(dir)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer root.Close()
|
|
// invalidFileFlag is the only value in the file flag range that is not supported,
|
|
// as it is not defined in the Windows API.
|
|
const invalidFileFlag = 0x00400000
|
|
f, err := root.OpenFile("test.txt", syscall.O_RDWR|syscall.O_CREAT|invalidFileFlag, 0666)
|
|
if !errors.Is(err, os.ErrInvalid) {
|
|
t.Fatalf("expected os.ErrInvalid, got %v", err)
|
|
}
|
|
f.Close()
|
|
}
|