Skip to content

Commit

Permalink
Merge pull request #185 from TBBle/GetFileStandardInfo
Browse files Browse the repository at this point in the history
Implement `winio.GetFileStandardInfo`
  • Loading branch information
dcantah authored Feb 11, 2021
2 parents 68cdd9b + ef753e6 commit 6eac466
Show file tree
Hide file tree
Showing 14 changed files with 686 additions and 587 deletions.
11 changes: 6 additions & 5 deletions backuptar/tar.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import (
"time"

"github.com/Microsoft/go-winio"
"golang.org/x/sys/windows"
)

const (
Expand Down Expand Up @@ -297,11 +298,11 @@ func FileInfoFromHeader(hdr *tar.Header) (name string, size int64, fileInfo *win
size = hdr.Size
}
fileInfo = &winio.FileBasicInfo{
LastAccessTime: syscall.NsecToFiletime(hdr.AccessTime.UnixNano()),
LastWriteTime: syscall.NsecToFiletime(hdr.ModTime.UnixNano()),
ChangeTime: syscall.NsecToFiletime(hdr.ChangeTime.UnixNano()),
LastAccessTime: windows.NsecToFiletime(hdr.AccessTime.UnixNano()),
LastWriteTime: windows.NsecToFiletime(hdr.ModTime.UnixNano()),
ChangeTime: windows.NsecToFiletime(hdr.ChangeTime.UnixNano()),
// Default to ModTime, we'll pull hdrCreationTime below if present
CreationTime: syscall.NsecToFiletime(hdr.ModTime.UnixNano()),
CreationTime: windows.NsecToFiletime(hdr.ModTime.UnixNano()),
}
if attrStr, ok := hdr.PAXRecords[hdrFileAttributes]; ok {
attr, err := strconv.ParseUint(attrStr, 10, 32)
Expand All @@ -319,7 +320,7 @@ func FileInfoFromHeader(hdr *tar.Header) (name string, size int64, fileInfo *win
if err != nil {
return "", 0, nil, err
}
fileInfo.CreationTime = syscall.NsecToFiletime(creationTime.UnixNano())
fileInfo.CreationTime = windows.NsecToFiletime(creationTime.UnixNano())
}
return
}
Expand Down
36 changes: 24 additions & 12 deletions fileinfo.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,29 +5,22 @@ package winio
import (
"os"
"runtime"
"syscall"
"unsafe"
)

//sys getFileInformationByHandleEx(h syscall.Handle, class uint32, buffer *byte, size uint32) (err error) = GetFileInformationByHandleEx
//sys setFileInformationByHandle(h syscall.Handle, class uint32, buffer *byte, size uint32) (err error) = SetFileInformationByHandle

const (
fileBasicInfo = 0
fileIDInfo = 0x12
"golang.org/x/sys/windows"
)

// FileBasicInfo contains file access time and file attributes information.
type FileBasicInfo struct {
CreationTime, LastAccessTime, LastWriteTime, ChangeTime syscall.Filetime
CreationTime, LastAccessTime, LastWriteTime, ChangeTime windows.Filetime
FileAttributes uint32
pad uint32 // padding
}

// GetFileBasicInfo retrieves times and attributes for a file.
func GetFileBasicInfo(f *os.File) (*FileBasicInfo, error) {
bi := &FileBasicInfo{}
if err := getFileInformationByHandleEx(syscall.Handle(f.Fd()), fileBasicInfo, (*byte)(unsafe.Pointer(bi)), uint32(unsafe.Sizeof(*bi))); err != nil {
if err := windows.GetFileInformationByHandleEx(windows.Handle(f.Fd()), windows.FileBasicInfo, (*byte)(unsafe.Pointer(bi)), uint32(unsafe.Sizeof(*bi))); err != nil {
return nil, &os.PathError{Op: "GetFileInformationByHandleEx", Path: f.Name(), Err: err}
}
runtime.KeepAlive(f)
Expand All @@ -36,13 +29,32 @@ func GetFileBasicInfo(f *os.File) (*FileBasicInfo, error) {

// SetFileBasicInfo sets times and attributes for a file.
func SetFileBasicInfo(f *os.File, bi *FileBasicInfo) error {
if err := setFileInformationByHandle(syscall.Handle(f.Fd()), fileBasicInfo, (*byte)(unsafe.Pointer(bi)), uint32(unsafe.Sizeof(*bi))); err != nil {
if err := windows.SetFileInformationByHandle(windows.Handle(f.Fd()), windows.FileBasicInfo, (*byte)(unsafe.Pointer(bi)), uint32(unsafe.Sizeof(*bi))); err != nil {
return &os.PathError{Op: "SetFileInformationByHandle", Path: f.Name(), Err: err}
}
runtime.KeepAlive(f)
return nil
}

// FileStandardInfo contains extended information for the file.
// FILE_STANDARD_INFO in WinBase.h
// https://docs.microsoft.com/en-us/windows/win32/api/winbase/ns-winbase-file_standard_info
type FileStandardInfo struct {
AllocationSize, EndOfFile int64
NumberOfLinks uint32
DeletePending, Directory bool
}

// GetFileStandardInfo retrieves ended information for the file.
func GetFileStandardInfo(f *os.File) (*FileStandardInfo, error) {
si := &FileStandardInfo{}
if err := windows.GetFileInformationByHandleEx(windows.Handle(f.Fd()), windows.FileStandardInfo, (*byte)(unsafe.Pointer(si)), uint32(unsafe.Sizeof(*si))); err != nil {
return nil, &os.PathError{Op: "GetFileInformationByHandleEx", Path: f.Name(), Err: err}
}
runtime.KeepAlive(f)
return si, nil
}

// FileIDInfo contains the volume serial number and file ID for a file. This pair should be
// unique on a system.
type FileIDInfo struct {
Expand All @@ -53,7 +65,7 @@ type FileIDInfo struct {
// GetFileID retrieves the unique (volume, file ID) pair for a file.
func GetFileID(f *os.File) (*FileIDInfo, error) {
fileID := &FileIDInfo{}
if err := getFileInformationByHandleEx(syscall.Handle(f.Fd()), fileIDInfo, (*byte)(unsafe.Pointer(fileID)), uint32(unsafe.Sizeof(*fileID))); err != nil {
if err := windows.GetFileInformationByHandleEx(windows.Handle(f.Fd()), windows.FileIdInfo, (*byte)(unsafe.Pointer(fileID)), uint32(unsafe.Sizeof(*fileID))); err != nil {
return nil, &os.PathError{Op: "GetFileInformationByHandleEx", Path: f.Name(), Err: err}
}
runtime.KeepAlive(f)
Expand Down
134 changes: 134 additions & 0 deletions fileinfo_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
package winio

import (
"io/ioutil"
"os"
"testing"

"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) {
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 := ioutil.TempFile("", "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, err := ioutil.TempDir("", "tst")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(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)
}
Loading

0 comments on commit 6eac466

Please sign in to comment.