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