clonetool/device_windows.go
2026-09-25 10:32:17 +02:00

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 }