diff --git a/backuptar/tar.go b/backuptar/tar.go index cb461ca3..088a43c6 100644 --- a/backuptar/tar.go +++ b/backuptar/tar.go @@ -16,7 +16,6 @@ import ( "time" "github.com/Microsoft/go-winio" - "golang.org/x/sys/windows" ) const ( @@ -298,11 +297,11 @@ func FileInfoFromHeader(hdr *tar.Header) (name string, size int64, fileInfo *win size = hdr.Size } fileInfo = &winio.FileBasicInfo{ - LastAccessTime: windows.NsecToFiletime(hdr.AccessTime.UnixNano()), - LastWriteTime: windows.NsecToFiletime(hdr.ModTime.UnixNano()), - ChangeTime: windows.NsecToFiletime(hdr.ChangeTime.UnixNano()), + LastAccessTime: syscall.NsecToFiletime(hdr.AccessTime.UnixNano()), + LastWriteTime: syscall.NsecToFiletime(hdr.ModTime.UnixNano()), + ChangeTime: syscall.NsecToFiletime(hdr.ChangeTime.UnixNano()), // Default to ModTime, we'll pull hdrCreationTime below if present - CreationTime: windows.NsecToFiletime(hdr.ModTime.UnixNano()), + CreationTime: syscall.NsecToFiletime(hdr.ModTime.UnixNano()), } if attrStr, ok := hdr.PAXRecords[hdrFileAttributes]; ok { attr, err := strconv.ParseUint(attrStr, 10, 32) @@ -320,7 +319,7 @@ func FileInfoFromHeader(hdr *tar.Header) (name string, size int64, fileInfo *win if err != nil { return "", 0, nil, err } - fileInfo.CreationTime = windows.NsecToFiletime(creationTime.UnixNano()) + fileInfo.CreationTime = syscall.NsecToFiletime(creationTime.UnixNano()) } return } diff --git a/fileinfo.go b/fileinfo.go index 3ab6bff6..ada2fbab 100644 --- a/fileinfo.go +++ b/fileinfo.go @@ -5,14 +5,21 @@ 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 - "golang.org/x/sys/windows" +const ( + fileBasicInfo = 0 + fileIDInfo = 0x12 ) // FileBasicInfo contains file access time and file attributes information. type FileBasicInfo struct { - CreationTime, LastAccessTime, LastWriteTime, ChangeTime windows.Filetime + CreationTime, LastAccessTime, LastWriteTime, ChangeTime syscall.Filetime FileAttributes uint32 pad uint32 // padding } @@ -20,7 +27,7 @@ type FileBasicInfo struct { // GetFileBasicInfo retrieves times and attributes for a file. func GetFileBasicInfo(f *os.File) (*FileBasicInfo, error) { bi := &FileBasicInfo{} - if err := windows.GetFileInformationByHandleEx(windows.Handle(f.Fd()), windows.FileBasicInfo, (*byte)(unsafe.Pointer(bi)), uint32(unsafe.Sizeof(*bi))); err != nil { + if err := getFileInformationByHandleEx(syscall.Handle(f.Fd()), 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) @@ -29,32 +36,13 @@ 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 := windows.SetFileInformationByHandle(windows.Handle(f.Fd()), windows.FileBasicInfo, (*byte)(unsafe.Pointer(bi)), uint32(unsafe.Sizeof(*bi))); err != nil { + if err := setFileInformationByHandle(syscall.Handle(f.Fd()), 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 { @@ -65,7 +53,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 := windows.GetFileInformationByHandleEx(windows.Handle(f.Fd()), windows.FileIdInfo, (*byte)(unsafe.Pointer(fileID)), uint32(unsafe.Sizeof(*fileID))); err != nil { + if err := getFileInformationByHandleEx(syscall.Handle(f.Fd()), 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) diff --git a/fileinfo_test.go b/fileinfo_test.go deleted file mode 100644 index e79700ba..00000000 --- a/fileinfo_test.go +++ /dev/null @@ -1,134 +0,0 @@ -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) -} diff --git a/zsyscall_windows.go b/zsyscall_windows.go index 176ff75e..4579c745 100644 --- a/zsyscall_windows.go +++ b/zsyscall_windows.go @@ -63,12 +63,14 @@ var ( procCreateIoCompletionPort = modkernel32.NewProc("CreateIoCompletionPort") procCreateNamedPipeW = modkernel32.NewProc("CreateNamedPipeW") procGetCurrentThread = modkernel32.NewProc("GetCurrentThread") + procGetFileInformationByHandleEx = modkernel32.NewProc("GetFileInformationByHandleEx") procGetNamedPipeHandleStateW = modkernel32.NewProc("GetNamedPipeHandleStateW") procGetNamedPipeInfo = modkernel32.NewProc("GetNamedPipeInfo") procGetQueuedCompletionStatus = modkernel32.NewProc("GetQueuedCompletionStatus") procLocalAlloc = modkernel32.NewProc("LocalAlloc") procLocalFree = modkernel32.NewProc("LocalFree") procSetFileCompletionNotificationModes = modkernel32.NewProc("SetFileCompletionNotificationModes") + procSetFileInformationByHandle = modkernel32.NewProc("SetFileInformationByHandle") procNtCreateNamedPipeFile = modntdll.NewProc("NtCreateNamedPipeFile") procRtlDefaultNpAcl = modntdll.NewProc("RtlDefaultNpAcl") procRtlDosPathNameToNtPathName_U = modntdll.NewProc("RtlDosPathNameToNtPathName_U") @@ -337,6 +339,14 @@ func getCurrentThread() (h syscall.Handle) { return } +func getFileInformationByHandleEx(h syscall.Handle, class uint32, buffer *byte, size uint32) (err error) { + r1, _, e1 := syscall.Syscall6(procGetFileInformationByHandleEx.Addr(), 4, uintptr(h), uintptr(class), uintptr(unsafe.Pointer(buffer)), uintptr(size), 0, 0) + if r1 == 0 { + err = errnoErr(e1) + } + return +} + func getNamedPipeHandleState(pipe syscall.Handle, state *uint32, curInstances *uint32, maxCollectionCount *uint32, collectDataTimeout *uint32, userName *uint16, maxUserNameSize uint32) (err error) { r1, _, e1 := syscall.Syscall9(procGetNamedPipeHandleStateW.Addr(), 7, uintptr(pipe), uintptr(unsafe.Pointer(state)), uintptr(unsafe.Pointer(curInstances)), uintptr(unsafe.Pointer(maxCollectionCount)), uintptr(unsafe.Pointer(collectDataTimeout)), uintptr(unsafe.Pointer(userName)), uintptr(maxUserNameSize), 0, 0) if r1 == 0 { @@ -380,6 +390,14 @@ func setFileCompletionNotificationModes(h syscall.Handle, flags uint8) (err erro return } +func setFileInformationByHandle(h syscall.Handle, class uint32, buffer *byte, size uint32) (err error) { + r1, _, e1 := syscall.Syscall6(procSetFileInformationByHandle.Addr(), 4, uintptr(h), uintptr(class), uintptr(unsafe.Pointer(buffer)), uintptr(size), 0, 0) + if r1 == 0 { + err = errnoErr(e1) + } + return +} + func ntCreateNamedPipeFile(pipe *syscall.Handle, access uint32, oa *objectAttributes, iosb *ioStatusBlock, share uint32, disposition uint32, options uint32, typ uint32, readMode uint32, completionMode uint32, maxInstances uint32, inboundQuota uint32, outputQuota uint32, timeout *int64) (status ntstatus) { r0, _, _ := syscall.Syscall15(procNtCreateNamedPipeFile.Addr(), 14, uintptr(unsafe.Pointer(pipe)), uintptr(access), uintptr(unsafe.Pointer(oa)), uintptr(unsafe.Pointer(iosb)), uintptr(share), uintptr(disposition), uintptr(options), uintptr(typ), uintptr(readMode), uintptr(completionMode), uintptr(maxInstances), uintptr(inboundQuota), uintptr(outputQuota), uintptr(unsafe.Pointer(timeout)), 0) status = ntstatus(r0)