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. Linux block devices accept ordinary // buffered pread/pwrite at any alignment, so there is nothing to round to. func alignmentFor(string) int64 { return 1 } // canElevate reports whether a permission failure opening a device is worth // retrying under `sudo` (see --sudo). func canElevate() bool { return true } func statPath(path string) (PathInfo, error) { 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 }