100 lines
3.0 KiB
Go
100 lines
3.0 KiB
Go
//go:build windows
|
|
|
|
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"strings"
|
|
"syscall"
|
|
"unsafe"
|
|
)
|
|
|
|
// isDevicePath recognises the Win32 device namespaces clonetool can sync
|
|
// against by string form: \\.\PhysicalDrive0 (a whole disk) and \\.\C: (a
|
|
// volume). Both the \\.\ and \\?\ prefixes, and their forward-slash
|
|
// spellings, are accepted. os.Stat can't describe these paths, so statPath
|
|
// keys off this instead of mode bits on Windows.
|
|
func isDevicePath(path string) bool {
|
|
p := strings.ReplaceAll(path, "/", `\`)
|
|
return strings.HasPrefix(p, `\\.\`) || strings.HasPrefix(p, `\\?\`)
|
|
}
|
|
|
|
// canElevate is false on Windows: there is no `sudo` equivalent to re-exec
|
|
// under, so a permission failure opening a raw disk is reported with a hint
|
|
// to run from an elevated console instead of being retried.
|
|
func canElevate() bool { return false }
|
|
|
|
const (
|
|
ioctlDiskGetLengthInfo = 0x0007405C // IOCTL_DISK_GET_LENGTH_INFO
|
|
ioctlDiskGetDriveGeometry = 0x00070000 // IOCTL_DISK_GET_DRIVE_GEOMETRY
|
|
)
|
|
|
|
// diskGeometry mirrors DISK_GEOMETRY (24 bytes; Cylinders is a LARGE_INTEGER).
|
|
type diskGeometry struct {
|
|
Cylinders int64
|
|
MediaType uint32
|
|
TracksPerCylinder uint32
|
|
SectorsPerTrack uint32
|
|
BytesPerSector uint32
|
|
}
|
|
|
|
// openDeviceHandle opens path for a metadata ioctl only: zero access rights
|
|
// (which need no privilege and don't require the volume to be unlocked) and
|
|
// shared read/write so it doesn't disturb a mounted filesystem.
|
|
func openDeviceHandle(path string) (syscall.Handle, error) {
|
|
p, err := syscall.UTF16PtrFromString(path)
|
|
if err != nil {
|
|
return syscall.InvalidHandle, err
|
|
}
|
|
return syscall.CreateFile(p, 0,
|
|
syscall.FILE_SHARE_READ|syscall.FILE_SHARE_WRITE, nil,
|
|
syscall.OPEN_EXISTING, 0, 0)
|
|
}
|
|
|
|
// blockDeviceSize returns the byte length of a physical drive or volume via
|
|
// DeviceIoControl(IOCTL_DISK_GET_LENGTH_INFO).
|
|
func blockDeviceSize(path string) (int64, error) {
|
|
h, err := openDeviceHandle(path)
|
|
if err != nil {
|
|
return 0, &os.PathError{Op: "open", Path: path, Err: err}
|
|
}
|
|
defer syscall.CloseHandle(h)
|
|
|
|
var length int64 // GET_LENGTH_INFORMATION is a single LARGE_INTEGER
|
|
var ret uint32
|
|
err = syscall.DeviceIoControl(h, ioctlDiskGetLengthInfo,
|
|
nil, 0,
|
|
(*byte)(unsafe.Pointer(&length)), uint32(unsafe.Sizeof(length)),
|
|
&ret, nil)
|
|
if err != nil {
|
|
return 0, fmt.Errorf("IOCTL_DISK_GET_LENGTH_INFO %s: %w", path, err)
|
|
}
|
|
return length, nil
|
|
}
|
|
|
|
// alignmentFor returns the sector size a raw disk handle's positioned reads
|
|
// and writes must be aligned to; 1 for an ordinary file path. It falls back
|
|
// to 512 if the geometry query fails.
|
|
func alignmentFor(path string) int64 {
|
|
if !isDevicePath(path) {
|
|
return 1
|
|
}
|
|
h, err := openDeviceHandle(path)
|
|
if err != nil {
|
|
return 512
|
|
}
|
|
defer syscall.CloseHandle(h)
|
|
|
|
var g diskGeometry
|
|
var ret uint32
|
|
err = syscall.DeviceIoControl(h, ioctlDiskGetDriveGeometry,
|
|
nil, 0,
|
|
(*byte)(unsafe.Pointer(&g)), uint32(unsafe.Sizeof(g)),
|
|
&ret, nil)
|
|
if err != nil || g.BytesPerSector == 0 {
|
|
return 512
|
|
}
|
|
return int64(g.BytesPerSector)
|
|
}
|