package mount import ( "fmt" "os" "path/filepath" "strings" "metis/pkg/util" ) // LoopMount describes a mounted image with boot/root paths. type LoopMount struct { LoopDevice string // only set when losetup created it BootPath string RootPath string } // Setup attaches an image as a loop device with partitions (-P) OR mounts an existing /dev path // by assuming p1=boot, p2=root. Intended for Linux hosts only. func Setup(path string) (*LoopMount, error) { device := path loopDevice := "" if !strings.HasPrefix(path, "/dev/") { var err error device, err = createLoop(path) if err != nil { return nil, err } loopDevice = device } bootDir, err := os.MkdirTemp("", "metis-boot-") if err != nil { return nil, err } rootDir, err := os.MkdirTemp("", "metis-root-") if err != nil { return nil, err } // Assume p1=boot, p2=root (Raspberry Pi style images) if err := util.Run("mount", partitionPath(device, 1), bootDir); err != nil { _ = Teardown(&LoopMount{LoopDevice: loopDevice, BootPath: bootDir, RootPath: rootDir}) return nil, fmt.Errorf("mount boot: %w", err) } if err := util.Run("mount", partitionPath(device, 2), rootDir); err != nil { _ = util.Run("umount", bootDir) _ = Teardown(&LoopMount{LoopDevice: loopDevice, BootPath: bootDir, RootPath: rootDir}) return nil, fmt.Errorf("mount root: %w", err) } return &LoopMount{LoopDevice: loopDevice, BootPath: bootDir, RootPath: rootDir}, nil } // Teardown unmounts and detaches the loop device. func Teardown(m *LoopMount) error { if m == nil { return nil } if m.BootPath != "" { _ = util.Run("umount", m.BootPath) _ = os.RemoveAll(m.BootPath) } if m.RootPath != "" { _ = util.Run("umount", m.RootPath) _ = os.RemoveAll(m.RootPath) } if m.LoopDevice != "" { _ = util.Run("losetup", "-d", m.LoopDevice) } return nil } func partitionPath(base string, idx int) string { p := fmt.Sprintf("%sp%d", base, idx) if _, err := os.Stat(p); err == nil { return p } return fmt.Sprintf("%s%d", base, idx) } func createLoop(imagePath string) (string, error) { // losetup -Pf --show out, err := util.RunLogged("losetup", "-Pf", "--show", filepath.Clean(imagePath)) if err != nil { return "", err } return strings.TrimSpace(out), nil }