package main import ( "fmt" "os" ) // PathInfo describes a source/dest path as seen locally by whichever // process (control agent, sink, source-stream) actually opens it. type PathInfo struct { Exists bool IsDevice bool Size int64 } // alignmentFor returns the offset/length alignment a path's handle requires // for positioned reads and writes. On Linux this is always 1 (block devices // accept buffered pread/pwrite at any alignment); on Windows a raw disk handle // requires sector-aligned I/O, so deviceAlignment reports the sector size. func alignmentFor(path string) int64 { return deviceAlignment(path) } func statPath(path string) (PathInfo, error) { // Windows raw-disk paths (\\.\PhysicalDrive0) aren't reported as devices by // os.Stat, so recognize them by name first and size them via ioctl. if isRawDevice(path) { sz, err := blockDeviceSize(path) if err != nil { return PathInfo{}, fmt.Errorf("stat device %s: %w", path, err) } return PathInfo{Exists: true, IsDevice: true, Size: sz}, nil } fi, err := os.Stat(path) if err != nil { if os.IsNotExist(err) { return PathInfo{Exists: false}, nil } return PathInfo{}, err } mode := fi.Mode() isDevice := mode&os.ModeDevice != 0 && mode&os.ModeCharDevice == 0 size := fi.Size() if isDevice { sz, err := blockDeviceSize(path) if err != nil { return PathInfo{}, fmt.Errorf("stat block device %s: %w", path, err) } size = sz } return PathInfo{Exists: true, IsDevice: isDevice, Size: size}, nil } // prepareDest makes the destination ready to receive exactly targetSize // bytes: a regular file is created if missing and truncated (grown or // shrunk) to targetSize; a block device is only validated to be large // enough (it can't be resized) — the caller is expected to have already // capped targetSize at the device's own size when it is the limiting side. func prepareDest(path string, targetSize int64) error { info, err := statPath(path) if err != nil { return err } if info.Exists && info.IsDevice { if targetSize > info.Size { return fmt.Errorf("destination device %s is only %d bytes, need %d", path, info.Size, targetSize) } return nil } f, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY, 0644) if err != nil { return fmt.Errorf("open/create %s: %w", path, err) } defer f.Close() if err := f.Truncate(targetSize); err != nil { return fmt.Errorf("truncate %s to %d: %w", path, targetSize, err) } return nil }