storj/storage/filestore/dir_windows.go

155 lines
3.8 KiB
Go
Raw Normal View History

2019-01-24 20:15:10 +00:00
// Copyright (C) 2019 Storj Labs, Inc.
2018-09-28 07:59:27 +01:00
// See LICENSE for copying information.
//go:build windows
2018-09-28 07:59:27 +01:00
// +build windows
package filestore
import (
"errors"
2018-09-28 07:59:27 +01:00
"fmt"
"os"
2018-09-28 07:59:27 +01:00
"path/filepath"
"unsafe"
"golang.org/x/sys/windows"
)
var errSharingViolation = windows.Errno(32)
2018-09-28 07:59:27 +01:00
func isBusy(err error) bool {
err = underlyingError(err)
return errors.Is(err, errSharingViolation)
2018-09-28 07:59:27 +01:00
}
func diskInfoFromPath(path string) (info DiskInfo, err error) {
absPath, err := filepath.Abs(path)
if err != nil {
absPath = path
}
var filesystemID string
var availableSpace int64
availableSpace, err = getDiskFreeSpace(absPath)
if err != nil {
return DiskInfo{"", -1}, err
}
filesystemID, err = getVolumeSerialNumber(absPath)
if err != nil {
return DiskInfo{"", availableSpace}, err
}
return DiskInfo{filesystemID, availableSpace}, nil
}
var (
kernel32 = windows.MustLoadDLL("kernel32.dll")
2018-09-28 07:59:27 +01:00
procGetDiskFreeSpace = kernel32.MustFindProc("GetDiskFreeSpaceExW")
)
func getDiskFreeSpace(path string) (int64, error) {
path16, err := windows.UTF16PtrFromString(path)
2018-09-28 07:59:27 +01:00
if err != nil {
return -1, err
}
var bytes int64
_, _, err = procGetDiskFreeSpace.Call(uintptr(unsafe.Pointer(path16)), uintptr(unsafe.Pointer(&bytes)), 0, 0)
err = ignoreSuccess(err)
return bytes, err
}
func getVolumeSerialNumber(path string) (string, error) {
path16, err := windows.UTF16PtrFromString(path)
if err != nil {
return "", err
2018-09-28 07:59:27 +01:00
}
var volumePath [1024]uint16
err = windows.GetVolumePathName(path16, &volumePath[0], uint32(len(volumePath)))
if err != nil {
return "", err
}
var volumeSerial uint32
err = windows.GetVolumeInformation(
&volumePath[0],
nil, 0, // volume name buffer
&volumeSerial,
nil, // maximum component length
nil, // filesystem flags
nil, 0, // filesystem name buffer
)
err = ignoreSuccess(err)
if err != nil {
return "", err
}
return fmt.Sprintf("%08x", volumeSerial), nil
}
// windows api occasionally returns.
2018-09-28 07:59:27 +01:00
func ignoreSuccess(err error) error {
if errors.Is(err, windows.Errno(0)) {
2018-09-28 07:59:27 +01:00
return nil
}
return err
}
// Adds `\\?` prefix to ensure that API recognizes it as a long path.
// see https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx#maxpath
func tryFixLongPath(path string) string {
abspath, err := filepath.Abs(path)
if err != nil {
return path
}
return `\\?\` + abspath
}
// rename implements atomic file rename on windows.
func rename(oldpath, newpath string) error {
oldpathp, err := windows.UTF16PtrFromString(tryFixLongPath(oldpath))
if err != nil {
return &os.LinkError{Op: "replace", Old: oldpath, New: newpath, Err: err}
}
newpathp, err := windows.UTF16PtrFromString(tryFixLongPath(newpath))
if err != nil {
return &os.LinkError{Op: "replace", Old: oldpath, New: newpath, Err: err}
}
err = windows.MoveFileEx(oldpathp, newpathp, windows.MOVEFILE_REPLACE_EXISTING|windows.MOVEFILE_WRITE_THROUGH)
if err != nil {
return &os.LinkError{Op: "replace", Old: oldpath, New: newpath, Err: err}
}
return nil
}
// openFileReadOnly opens the file with read only.
// Custom implementation, because os.Open doesn't support specifying FILE_SHARE_DELETE.
func openFileReadOnly(path string, perm os.FileMode) (*os.File, error) {
pathp, err := windows.UTF16PtrFromString(tryFixLongPath(path))
if err != nil {
return nil, err
}
access := uint32(windows.GENERIC_READ)
sharemode := uint32(windows.FILE_SHARE_READ | windows.FILE_SHARE_WRITE | windows.FILE_SHARE_DELETE)
var sa windows.SecurityAttributes
sa.Length = uint32(unsafe.Sizeof(sa))
sa.InheritHandle = 1
createmode := uint32(windows.OPEN_EXISTING)
handle, err := windows.CreateFile(pathp, access, sharemode, &sa, createmode, windows.FILE_ATTRIBUTE_NORMAL, 0)
if err != nil {
return nil, err
}
return os.NewFile(uintptr(handle), path), nil
}