clonetool/device.go

72 lines
2.1 KiB
Go

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
}