99 lines
2.6 KiB
Go
99 lines
2.6 KiB
Go
//go:build windows
|
|
|
|
package main
|
|
|
|
import (
|
|
"encoding/binary"
|
|
"strings"
|
|
"syscall"
|
|
)
|
|
|
|
// IOCTL codes (winioctl.h):
|
|
//
|
|
// IOCTL_DISK_GET_LENGTH_INFO -> GET_LENGTH_INFORMATION { LARGE_INTEGER Length }
|
|
// IOCTL_DISK_GET_DRIVE_GEOMETRY -> DISK_GEOMETRY (BytesPerSector at offset 20)
|
|
const (
|
|
ioctlDiskGetLengthInfo = 0x0007405C
|
|
ioctlDiskGetDriveGeometry = 0x00070000
|
|
)
|
|
|
|
// isRawDevice recognizes the raw-disk paths that os.Stat won't report as
|
|
// devices: \\.\PhysicalDrive<N> and the volume form \\.\X:.
|
|
func isRawDevice(path string) bool {
|
|
l := strings.ToLower(path)
|
|
if strings.HasPrefix(l, `\\.\physicaldrive`) {
|
|
return true
|
|
}
|
|
// \\.\C: — a single drive letter behind the device namespace.
|
|
if strings.HasPrefix(path, `\\.\`) && len(path) == 6 && path[5] == ':' {
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
// openDevice opens a raw device handle with the sharing a live disk needs.
|
|
func openDevice(path string, write bool) (syscall.Handle, error) {
|
|
p, err := syscall.UTF16PtrFromString(path)
|
|
if err != nil {
|
|
return syscall.InvalidHandle, err
|
|
}
|
|
access := uint32(syscall.GENERIC_READ)
|
|
if write {
|
|
access |= syscall.GENERIC_WRITE
|
|
}
|
|
return syscall.CreateFile(p, access,
|
|
syscall.FILE_SHARE_READ|syscall.FILE_SHARE_WRITE, nil,
|
|
syscall.OPEN_EXISTING, 0, 0)
|
|
}
|
|
|
|
func blockDeviceSize(path string) (int64, error) {
|
|
h, err := openDevice(path, false)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
defer syscall.CloseHandle(h)
|
|
|
|
var out [8]byte
|
|
var ret uint32
|
|
if err := syscall.DeviceIoControl(h, ioctlDiskGetLengthInfo, nil, 0,
|
|
&out[0], uint32(len(out)), &ret, nil); err != nil {
|
|
return 0, err
|
|
}
|
|
return int64(binary.LittleEndian.Uint64(out[:])), nil
|
|
}
|
|
|
|
// deviceSectorSize queries the disk's physical sector size (bytes-per-sector),
|
|
// falling back to 512 if the geometry can't be read.
|
|
func deviceSectorSize(path string) int64 {
|
|
h, err := openDevice(path, false)
|
|
if err != nil {
|
|
return 512
|
|
}
|
|
defer syscall.CloseHandle(h)
|
|
|
|
var geom [24]byte
|
|
var ret uint32
|
|
if err := syscall.DeviceIoControl(h, ioctlDiskGetDriveGeometry, nil, 0,
|
|
&geom[0], uint32(len(geom)), &ret, nil); err != nil {
|
|
return 512
|
|
}
|
|
bps := binary.LittleEndian.Uint32(geom[20:24])
|
|
if bps == 0 {
|
|
return 512
|
|
}
|
|
return int64(bps)
|
|
}
|
|
|
|
// deviceAlignment reports the sector alignment a raw disk handle requires;
|
|
// regular files need none.
|
|
func deviceAlignment(path string) int64 {
|
|
if isRawDevice(path) {
|
|
return deviceSectorSize(path)
|
|
}
|
|
return 1
|
|
}
|
|
|
|
// canElevate is false on Windows: there is no sudo. Raw-disk access requires
|
|
// running clonetool as Administrator, which the operator arranges outside the tool.
|
|
func canElevate() bool { return false }
|