//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) }