clonetool/clonedrive.go
2026-09-06 20:15:54 +02:00

118 lines
3.7 KiB
Go

package main
import (
"fmt"
"os"
)
// windowJob raw-copies a byte window [srcBase,srcBase+size) of the source
// disk onto [dstBase,dstBase+size) of the target, through the same
// push/pull block-diff engine `sync` uses. Used for boot regions and for
// partitions cloned with method "raw".
type windowJob struct {
srcPath, dstPath string
srcBase, dstBase int64
size int64
bothLocal bool
blockSize int64
cfg *SyncConfig
srcHost, srcUser string
dstHost, dstUser string
srcRemoteBin string
dstRemoteBin string
srcSudo, dstSudo bool
}
func runWindowTransfer(srcCtrl, dstCtrl *Controller, j windowJob) error {
pp := newProgressPrinter(j.blockSize)
cb := transferCallbacks{onProgress: pp.print}
pushReq := CtrlMsg{
Path: j.srcPath, Base: j.srcBase, PeerBase: j.dstBase, Size: j.size, BlockSize: j.blockSize,
PeerHost: j.dstHost, PeerUser: j.dstUser, PeerPath: j.dstPath, PeerLocal: j.bothLocal,
RemoteBin: j.dstRemoteBin, SSHBin: j.cfg.SSHBin, SSHOpts: j.cfg.SSHOpts,
ConnectTimeoutSec: j.cfg.ConnectTimeoutSec, Sudo: j.dstSudo,
}
ok, reason, err := srcCtrl.ConnectPush(pushReq, cb)
if err != nil {
pp.finish()
return err
}
if !ok {
pp.finish()
fmt.Fprintf(os.Stderr, " push not possible (%s); pulling instead ...\n", reason)
pullReq := CtrlMsg{
Path: j.dstPath, Base: j.dstBase, PeerBase: j.srcBase, Size: j.size, BlockSize: j.blockSize,
PeerHost: j.srcHost, PeerUser: j.srcUser, PeerPath: j.srcPath, PeerLocal: j.bothLocal,
RemoteBin: j.srcRemoteBin, SSHBin: j.cfg.SSHBin, SSHOpts: j.cfg.SSHOpts,
ConnectTimeoutSec: j.cfg.ConnectTimeoutSec, Sudo: j.srcSudo,
}
ok2, reason2, err2 := dstCtrl.ConnectPull(pullReq, cb)
if err2 != nil {
pp.finish()
return err2
}
if !ok2 {
pp.finish()
return fmt.Errorf("no direct connection either way (push: %s; pull: %s)", reason, reason2)
}
}
pp.finish()
return nil
}
// cloneCtx carries the transport context for an fs-image partition clone.
type cloneCtx struct {
srcPath, dstPath string
bothLocal bool
cfg *SyncConfig
srcHost, srcUser string
dstHost, dstUser string
srcRemoteBin string
dstRemoteBin string
srcSudo, dstSudo bool
}
// runFSImagePartition streams a filesystem image for one partition, trying
// push (source drives) then pull (dest drives).
func runFSImagePartition(srcCtrl, dstCtrl *Controller, pp plannedPart, c cloneCtx) error {
opts := fsCloneOpts{FSType: pp.FSType, Tool: pp.Tool, SizeBytes: pp.SrcSizeB, ShrinkToBytes: pp.ShrinkToB}
prog := newProgressPrinter(1 << 20)
cb := transferCallbacks{onProgress: prog.printBytes}
pushReq := CtrlMsg{
Method: "fs-image", PartIndex: pp.Num, Options: opts.encode(),
Path: c.srcPath, PeerPath: c.dstPath, PeerLocal: c.bothLocal,
PeerHost: c.dstHost, PeerUser: c.dstUser,
RemoteBin: c.dstRemoteBin, SSHBin: c.cfg.SSHBin, SSHOpts: c.cfg.SSHOpts,
ConnectTimeoutSec: c.cfg.ConnectTimeoutSec, Sudo: c.dstSudo,
}
ok, reason, err := srcCtrl.ClonePartition(pushReq, cb)
if err != nil {
prog.finish()
return err
}
if !ok {
prog.finish()
fmt.Fprintf(os.Stderr, " fs-image push not possible (%s); pulling instead ...\n", reason)
pullReq := CtrlMsg{
Method: "fs-image", Reason: "pull", PartIndex: pp.Num, Options: opts.encode(),
Path: c.dstPath, PeerPath: c.srcPath, PeerLocal: c.bothLocal,
PeerHost: c.srcHost, PeerUser: c.srcUser,
RemoteBin: c.srcRemoteBin, SSHBin: c.cfg.SSHBin, SSHOpts: c.cfg.SSHOpts,
ConnectTimeoutSec: c.cfg.ConnectTimeoutSec, Sudo: c.srcSudo,
}
ok2, reason2, err2 := dstCtrl.ClonePartition(pullReq, cb)
if err2 != nil {
prog.finish()
return err2
}
if !ok2 {
prog.finish()
return fmt.Errorf("fs-image failed both ways (push: %s; pull: %s)", reason, reason2)
}
}
prog.finish()
return nil
}