Rollback partial dm snapshot startup

Prevent partial VM startup failures from leaking loop devices and dm state on the host.

Move root snapshot setup into a rollback-safe helper that records loop and mapper handles incrementally, tears them down in reverse order on failure, and reuses the same dm/loop cleanup path during normal runtime teardown. Also switch the daemon runner field to a small command-runner interface so the snapshot path can be tested with injected failures.

Add failure-injection coverage for losetup, blockdev, dmsetup, partial teardown, and joined rollback errors. Validated with go test ./... and make build.
This commit is contained in:
Thales Maciel 2026-03-16 14:06:17 -03:00
parent 171009b30b
commit 375900cf65
No known key found for this signature in database
GPG key ID: 33112E6833C34679
5 changed files with 401 additions and 47 deletions

View file

@ -20,6 +20,11 @@ import (
type Runner struct{}
type CommandRunner interface {
Run(ctx context.Context, name string, args ...string) ([]byte, error)
RunSudo(ctx context.Context, args ...string) ([]byte, error)
}
func NewRunner() Runner {
return Runner{}
}
@ -162,7 +167,7 @@ func lastJSONLine(data []byte) []byte {
return last
}
func CopyDirContents(ctx context.Context, runner Runner, sourceDir, targetDir string, useSudo bool) error {
func CopyDirContents(ctx context.Context, runner CommandRunner, sourceDir, targetDir string, useSudo bool) error {
args := []string{"-a", filepath.Join(sourceDir, "."), targetDir + "/"}
var err error
if useSudo {
@ -173,7 +178,7 @@ func CopyDirContents(ctx context.Context, runner Runner, sourceDir, targetDir st
return err
}
func ResizeExt4Image(ctx context.Context, runner Runner, path string, bytes int64) error {
func ResizeExt4Image(ctx context.Context, runner CommandRunner, path string, bytes int64) error {
if _, err := runner.Run(ctx, "truncate", "-s", strconv.FormatInt(bytes, 10), path); err != nil {
return err
}
@ -184,7 +189,7 @@ func ResizeExt4Image(ctx context.Context, runner Runner, path string, bytes int6
return err
}
func ReadDebugFSText(ctx context.Context, runner Runner, imagePath, guestPath string) (string, error) {
func ReadDebugFSText(ctx context.Context, runner CommandRunner, imagePath, guestPath string) (string, error) {
out, err := runner.Run(ctx, "debugfs", "-R", "cat "+guestPath, imagePath)
if err != nil {
return "", err
@ -192,7 +197,7 @@ func ReadDebugFSText(ctx context.Context, runner Runner, imagePath, guestPath st
return string(out), nil
}
func WriteExt4File(ctx context.Context, runner Runner, imagePath, guestPath string, data []byte) error {
func WriteExt4File(ctx context.Context, runner CommandRunner, imagePath, guestPath string, data []byte) error {
tmp, err := os.CreateTemp("", "banger-ext4-*")
if err != nil {
return err
@ -210,7 +215,7 @@ func WriteExt4File(ctx context.Context, runner Runner, imagePath, guestPath stri
return err
}
func MountTempDir(ctx context.Context, runner Runner, source string, readOnly bool) (string, func() error, error) {
func MountTempDir(ctx context.Context, runner CommandRunner, source string, readOnly bool) (string, func() error, error) {
mountDir, err := os.MkdirTemp("", "banger-mnt-*")
if err != nil {
return "", nil, err