clonetool/device.go

79 lines
2.2 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
}
func statPath(path string) (PathInfo, error) {
// A raw disk handle (Windows \\.\PhysicalDrive0, \\.\C:) is not something
// os.Stat can describe, so ask the platform for its size directly.
if isDevicePath(path) {
size, err := blockDeviceSize(path)
if err != nil {
if os.IsNotExist(err) {
return PathInfo{Exists: false}, nil
}
return PathInfo{}, fmt.Errorf("stat device %s: %w", path, err)
}
return PathInfo{Exists: true, IsDevice: true, Size: size}, 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
}
if isDevicePath(path) && !info.Exists {
return fmt.Errorf("destination device %s not found", path)
}
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
}