1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185
|
//go:build windows
// +build windows
package winio
import (
"os"
"testing"
"unsafe"
"golang.org/x/sys/windows"
)
// Checks if current matches expected. Note that AllocationSize is filesystem-specific,
// so we check that the current.AllocationSize is >= expected.AllocationSize.
// https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-fscc/5afa7f66-619c-48f3-955f-68c4ece704ae
func checkFileStandardInfo(t *testing.T, current, expected *FileStandardInfo) {
t.Helper()
if current.AllocationSize < expected.AllocationSize {
t.Fatalf("FileStandardInfo unexpectedly had AllocationSize %d, expecting >=%d", current.AllocationSize, expected.AllocationSize)
}
if current.EndOfFile != expected.EndOfFile {
t.Fatalf("FileStandardInfo unexpectedly had EndOfFile %d, expecting %d", current.EndOfFile, expected.EndOfFile)
}
if current.NumberOfLinks != expected.NumberOfLinks {
t.Fatalf("FileStandardInfo unexpectedly had NumberOfLinks %d, expecting %d", current.NumberOfLinks, expected.NumberOfLinks)
}
if current.DeletePending != expected.DeletePending {
if current.DeletePending {
t.Fatalf("FileStandardInfo unexpectedly DeletePending")
} else {
t.Fatalf("FileStandardInfo unexpectedly not DeletePending")
}
}
if current.Directory != expected.Directory {
if current.Directory {
t.Fatalf("FileStandardInfo unexpectedly Directory")
} else {
t.Fatalf("FileStandardInfo unexpectedly not Directory")
}
}
}
func TestGetFileStandardInfo_File(t *testing.T) {
f, err := os.CreateTemp("", "tst")
if err != nil {
t.Fatal(err)
}
defer f.Close()
defer os.Remove(f.Name())
expectedFileInfo := &FileStandardInfo{
AllocationSize: 0,
EndOfFile: 0,
NumberOfLinks: 1,
DeletePending: false,
Directory: false,
}
info, err := GetFileStandardInfo(f)
if err != nil {
t.Fatal(err)
}
checkFileStandardInfo(t, info, expectedFileInfo)
bytesWritten, err := f.Write([]byte("0123456789"))
if err != nil {
t.Fatal(err)
}
expectedFileInfo.EndOfFile = int64(bytesWritten)
expectedFileInfo.AllocationSize = int64(bytesWritten)
info, err = GetFileStandardInfo(f)
if err != nil {
t.Fatal(err)
}
checkFileStandardInfo(t, info, expectedFileInfo)
linkName := f.Name() + ".link"
if err = os.Link(f.Name(), linkName); err != nil {
t.Fatal(err)
}
defer os.Remove(linkName)
expectedFileInfo.NumberOfLinks = 2
info, err = GetFileStandardInfo(f)
if err != nil {
t.Fatal(err)
}
checkFileStandardInfo(t, info, expectedFileInfo)
os.Remove(linkName)
expectedFileInfo.NumberOfLinks = 1
info, err = GetFileStandardInfo(f)
if err != nil {
t.Fatal(err)
}
checkFileStandardInfo(t, info, expectedFileInfo)
}
func TestGetFileStandardInfo_Directory(t *testing.T) {
tempDir := t.TempDir()
// os.Open returns the Search Handle, not the Directory Handle
// See https://github.com/golang/go/issues/13738
f, err := OpenForBackup(tempDir, windows.GENERIC_READ, 0, windows.OPEN_EXISTING)
if err != nil {
t.Fatal(err)
}
defer f.Close()
expectedFileInfo := &FileStandardInfo{
AllocationSize: 0,
EndOfFile: 0,
NumberOfLinks: 1,
DeletePending: false,
Directory: true,
}
info, err := GetFileStandardInfo(f)
if err != nil {
t.Fatal(err)
}
checkFileStandardInfo(t, info, expectedFileInfo)
}
// TestFileInfoStructAlignment checks that the alignment of Go fileinfo structs
// match what is expected by the Windows API.
func TestFileInfoStructAlignment(t *testing.T) {
//nolint:revive // SNAKE_CASE is not idiomatic in Go, but aligned with Win32 API.
const (
// The alignment of various types, as named in the Windows APIs. When
// deciding on an expectedAlignment for a struct's test case, use the
// type of the largest field in the struct as written in the Windows
// docs. This is intended to help reviewers by allowing them to first
// check that a new align* const is correct, then independently check
// that the test case is correct, rather than all at once.
alignLARGE_INTEGER = unsafe.Alignof(uint64(0))
alignULONGLONG = unsafe.Alignof(uint64(0))
)
tests := []struct {
name string
actualAlign uintptr
actualSize uintptr
expectedAlignment uintptr
}{
{
// alignedFileBasicInfo is passed to the Windows API rather than FileBasicInfo.
"alignedFileBasicInfo", unsafe.Alignof(alignedFileBasicInfo{}), unsafe.Sizeof(alignedFileBasicInfo{}),
// https://learn.microsoft.com/en-us/windows/win32/api/winbase/ns-winbase-file_basic_info
alignLARGE_INTEGER,
},
{
"FileStandardInfo", unsafe.Alignof(FileStandardInfo{}), unsafe.Sizeof(FileStandardInfo{}),
// https://learn.microsoft.com/en-us/windows/win32/api/winbase/ns-winbase-file_standard_info
alignLARGE_INTEGER,
},
{
"FileIDInfo", unsafe.Alignof(FileIDInfo{}), unsafe.Sizeof(FileIDInfo{}),
// https://learn.microsoft.com/en-us/windows/win32/api/winbase/ns-winbase-file_id_info
alignULONGLONG,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if tt.actualAlign != tt.expectedAlignment {
t.Errorf("alignment mismatch: actual %d, expected %d", tt.actualAlign, tt.expectedAlignment)
}
if r := tt.actualSize % tt.expectedAlignment; r != 0 {
t.Errorf(
"size is not a multiple of alignment: size %% alignment (%d %% %d) is %d, expected 0",
tt.actualSize, tt.expectedAlignment, r)
}
})
}
}
|