Skip to content

Commit ef753e6

Browse files
committed
Implement winio.GetFileStandardInfo
Signed-off-by: Paul "TBBle" Hampson <[email protected]>
1 parent bfd5468 commit ef753e6

2 files changed

Lines changed: 153 additions & 0 deletions

File tree

fileinfo.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,25 @@ func SetFileBasicInfo(f *os.File, bi *FileBasicInfo) error {
3636
return nil
3737
}
3838

39+
// FileStandardInfo contains extended information for the file.
40+
// FILE_STANDARD_INFO in WinBase.h
41+
// https://docs.microsoft.com/en-us/windows/win32/api/winbase/ns-winbase-file_standard_info
42+
type FileStandardInfo struct {
43+
AllocationSize, EndOfFile int64
44+
NumberOfLinks uint32
45+
DeletePending, Directory bool
46+
}
47+
48+
// GetFileStandardInfo retrieves ended information for the file.
49+
func GetFileStandardInfo(f *os.File) (*FileStandardInfo, error) {
50+
si := &FileStandardInfo{}
51+
if err := windows.GetFileInformationByHandleEx(windows.Handle(f.Fd()), windows.FileStandardInfo, (*byte)(unsafe.Pointer(si)), uint32(unsafe.Sizeof(*si))); err != nil {
52+
return nil, &os.PathError{Op: "GetFileInformationByHandleEx", Path: f.Name(), Err: err}
53+
}
54+
runtime.KeepAlive(f)
55+
return si, nil
56+
}
57+
3958
// FileIDInfo contains the volume serial number and file ID for a file. This pair should be
4059
// unique on a system.
4160
type FileIDInfo struct {

fileinfo_test.go

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
package winio
2+
3+
import (
4+
"io/ioutil"
5+
"os"
6+
"testing"
7+
8+
"golang.org/x/sys/windows"
9+
)
10+
11+
// Checks if current matches expected. Note that AllocationSize is filesystem-specific,
12+
// so we check that the current.AllocationSize is >= expected.AllocationSize.
13+
// https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-fscc/5afa7f66-619c-48f3-955f-68c4ece704ae
14+
func checkFileStandardInfo(t *testing.T, current, expected *FileStandardInfo) {
15+
if current.AllocationSize < expected.AllocationSize {
16+
t.Fatalf("FileStandardInfo unexpectedly had AllocationSize %d, expecting >=%d", current.AllocationSize, expected.AllocationSize)
17+
}
18+
19+
if current.EndOfFile != expected.EndOfFile {
20+
t.Fatalf("FileStandardInfo unexpectedly had EndOfFile %d, expecting %d", current.EndOfFile, expected.EndOfFile)
21+
}
22+
23+
if current.NumberOfLinks != expected.NumberOfLinks {
24+
t.Fatalf("FileStandardInfo unexpectedly had NumberOfLinks %d, expecting %d", current.NumberOfLinks, expected.NumberOfLinks)
25+
}
26+
27+
if current.DeletePending != expected.DeletePending {
28+
if current.DeletePending {
29+
t.Fatalf("FileStandardInfo unexpectedly DeletePending")
30+
} else {
31+
t.Fatalf("FileStandardInfo unexpectedly not DeletePending")
32+
}
33+
}
34+
35+
if current.Directory != expected.Directory {
36+
if current.Directory {
37+
t.Fatalf("FileStandardInfo unexpectedly Directory")
38+
} else {
39+
t.Fatalf("FileStandardInfo unexpectedly not Directory")
40+
}
41+
}
42+
}
43+
44+
func TestGetFileStandardInfo_File(t *testing.T) {
45+
f, err := ioutil.TempFile("", "tst")
46+
if err != nil {
47+
t.Fatal(err)
48+
}
49+
defer f.Close()
50+
defer os.Remove(f.Name())
51+
52+
expectedFileInfo := &FileStandardInfo{
53+
AllocationSize: 0,
54+
EndOfFile: 0,
55+
NumberOfLinks: 1,
56+
DeletePending: false,
57+
Directory: false,
58+
}
59+
60+
info, err := GetFileStandardInfo(f)
61+
if err != nil {
62+
t.Fatal(err)
63+
}
64+
checkFileStandardInfo(t, info, expectedFileInfo)
65+
66+
bytesWritten, err := f.Write([]byte("0123456789"))
67+
if err != nil {
68+
t.Fatal(err)
69+
}
70+
71+
expectedFileInfo.EndOfFile = int64(bytesWritten)
72+
expectedFileInfo.AllocationSize = int64(bytesWritten)
73+
74+
info, err = GetFileStandardInfo(f)
75+
if err != nil {
76+
t.Fatal(err)
77+
}
78+
checkFileStandardInfo(t, info, expectedFileInfo)
79+
80+
linkName := f.Name() + ".link"
81+
82+
if err = os.Link(f.Name(), linkName); err != nil {
83+
t.Fatal(err)
84+
}
85+
defer os.Remove(linkName)
86+
87+
expectedFileInfo.NumberOfLinks = 2
88+
89+
info, err = GetFileStandardInfo(f)
90+
if err != nil {
91+
t.Fatal(err)
92+
}
93+
checkFileStandardInfo(t, info, expectedFileInfo)
94+
95+
os.Remove(linkName)
96+
97+
expectedFileInfo.NumberOfLinks = 1
98+
99+
info, err = GetFileStandardInfo(f)
100+
if err != nil {
101+
t.Fatal(err)
102+
}
103+
checkFileStandardInfo(t, info, expectedFileInfo)
104+
}
105+
106+
func TestGetFileStandardInfo_Directory(t *testing.T) {
107+
tempDir, err := ioutil.TempDir("", "tst")
108+
if err != nil {
109+
t.Fatal(err)
110+
}
111+
defer os.RemoveAll(tempDir)
112+
113+
// os.Open returns the Search Handle, not the Directory Handle
114+
// See https://github.com/golang/go/issues/13738
115+
f, err := OpenForBackup(tempDir, windows.GENERIC_READ, 0, windows.OPEN_EXISTING)
116+
if err != nil {
117+
t.Fatal(err)
118+
}
119+
defer f.Close()
120+
121+
expectedFileInfo := &FileStandardInfo{
122+
AllocationSize: 0,
123+
EndOfFile: 0,
124+
NumberOfLinks: 1,
125+
DeletePending: false,
126+
Directory: true,
127+
}
128+
129+
info, err := GetFileStandardInfo(f)
130+
if err != nil {
131+
t.Fatal(err)
132+
}
133+
checkFileStandardInfo(t, info, expectedFileInfo)
134+
}

0 commit comments

Comments
 (0)