Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Revert removal of "Implement winio.GetFileStandardInfo FileInfo" commits #209

Merged
merged 1 commit into from
Apr 22, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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)
}
18 changes: 0 additions & 18 deletions zsyscall_windows.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.