diff --git a/pkg/imagepullers/noop.go b/pkg/imagepullers/noop.go index 4cf5a311..261a08fc 100644 --- a/pkg/imagepullers/noop.go +++ b/pkg/imagepullers/noop.go @@ -5,17 +5,10 @@ import ( "fmt" "io" "os" - "path/filepath" "github.com/containers/podman/v5/pkg/machine/define" "github.com/containers/podman/v5/pkg/machine/env" - - "github.com/lima-vm/go-qcow2reader" - "github.com/lima-vm/go-qcow2reader/convert" - "github.com/lima-vm/go-qcow2reader/image" - "github.com/lima-vm/go-qcow2reader/image/qcow2" - "github.com/lima-vm/go-qcow2reader/image/raw" ) type NoopImagePuller struct { @@ -36,21 +29,6 @@ func (puller *NoopImagePuller) SetSourceURI(sourcePath string) { puller.sourceURI = sourcePath } -func imageExtension(vmType define.VMType, sourceURI string) string { - switch vmType { - case define.WSLVirt: - ext := filepath.Ext(sourceURI) - if ext == ".wsl" { - return ".wsl" - } - return ".tar.gz" - case define.QemuVirt, define.HyperVVirt: - return filepath.Ext(sourceURI) - default: - return "." + vmType.ImageFormat().Kind() - } -} - func (puller *NoopImagePuller) LocalPath() (*define.VMFile, error) { // if localPath has already been calculated returns it if puller.localPath != nil { @@ -81,61 +59,26 @@ func (puller *NoopImagePuller) Download() error { if err != nil { return err } - return doCopyFile(puller.sourceURI, localPath.Path, puller.vmType) -} -func doCopyFile(src, dest string, vmType define.VMType) error { - srcF, err := os.Open(src) + src, err := os.Open(puller.sourceURI) if err != nil { return err } - defer srcF.Close() - - destF, err := os.Create(dest) - if err != nil { - return err - } - defer destF.Close() + defer src.Close() - switch vmType { - case define.AppleHvVirt, define.LibKrun: - return copyFileMac(srcF, destF) - default: - return copyFile(srcF, destF) - } + return doCopyFile(src, localPath.Path) } -func copyFileMac(src, dest *os.File) error { - srcImg, err := qcow2reader.Open(src) +func copyFile(src *os.File, dest string) error { + destF, err := os.Create(dest) if err != nil { return err } - defer srcImg.Close() - - switch srcImg.Type() { - case raw.Type: - // if the image is raw it performs a simple copy - return copyFile(src, dest) - case qcow2.Type: - // if the image is qcow2 it performs a conversion to raw - return convertToRaw(srcImg, dest) - default: - return fmt.Errorf("%s format not supported for conversion to raw", srcImg.Type()) - } -} - -func convertToRaw(srcImg image.Image, dest *os.File) error { - if err := srcImg.Readable(); err != nil { - return fmt.Errorf("source image is not readable: %w", err) - } - - return convert.Convert(dest, srcImg, convert.Options{}) -} + defer destF.Close() -func copyFile(src, dst *os.File) error { - bufferedWriter := bufio.NewWriter(dst) + bufferedWriter := bufio.NewWriter(destF) defer bufferedWriter.Flush() - _, err := io.Copy(bufferedWriter, src) + _, err = io.Copy(bufferedWriter, src) return err } diff --git a/pkg/imagepullers/noop_darwin.go b/pkg/imagepullers/noop_darwin.go new file mode 100644 index 00000000..70d46819 --- /dev/null +++ b/pkg/imagepullers/noop_darwin.go @@ -0,0 +1,51 @@ +//go:build darwin + +package imagepullers + +import ( + "fmt" + "os" + + "github.com/containers/podman/v5/pkg/machine/define" + + "github.com/lima-vm/go-qcow2reader" + "github.com/lima-vm/go-qcow2reader/convert" + "github.com/lima-vm/go-qcow2reader/image" + "github.com/lima-vm/go-qcow2reader/image/qcow2" + "github.com/lima-vm/go-qcow2reader/image/raw" +) + +func imageExtension(vmType define.VMType, _ string) string { + return "." + vmType.ImageFormat().Kind() +} + +func doCopyFile(src *os.File, dest string) error { + srcImg, err := qcow2reader.Open(src) + if err != nil { + return err + } + defer srcImg.Close() + + switch srcImg.Type() { + case raw.Type: + return copyFile(src, dest) + case qcow2.Type: + return convertToRaw(srcImg, dest) + default: + return fmt.Errorf("%s format not supported for conversion to raw", srcImg.Type()) + } +} + +func convertToRaw(srcImg image.Image, dest string) error { + destF, err := os.Create(dest) + if err != nil { + return err + } + defer destF.Close() + + if err := srcImg.Readable(); err != nil { + return fmt.Errorf("source image is not readable: %w", err) + } + + return convert.Convert(destF, srcImg, convert.Options{}) +} diff --git a/pkg/imagepullers/noop_unix.go b/pkg/imagepullers/noop_unix.go new file mode 100644 index 00000000..60ebfca3 --- /dev/null +++ b/pkg/imagepullers/noop_unix.go @@ -0,0 +1,18 @@ +//go:build !windows && !darwin + +package imagepullers + +import ( + "os" + "path/filepath" + + "github.com/containers/podman/v5/pkg/machine/define" +) + +func imageExtension(_ define.VMType, sourceURI string) string { + return filepath.Ext(sourceURI) +} + +func doCopyFile(src *os.File, dest string) error { + return copyFile(src, dest) +} diff --git a/pkg/imagepullers/noop_windows.go b/pkg/imagepullers/noop_windows.go new file mode 100644 index 00000000..45cbd754 --- /dev/null +++ b/pkg/imagepullers/noop_windows.go @@ -0,0 +1,81 @@ +//go:build windows + +package imagepullers + +import ( + "errors" + "fmt" + "io" + "os" + "os/exec" + "path/filepath" + + "github.com/containers/podman/v5/pkg/machine/define" + "github.com/sirupsen/logrus" +) + +func imageExtension(vmType define.VMType, sourceURI string) string { + switch vmType { + case define.WSLVirt: + ext := filepath.Ext(sourceURI) + if ext == ".wsl" { + return ".wsl" + } + return ".tar.gz" + default: + return filepath.Ext(sourceURI) + } +} + +func doCopyFile(src *os.File, dest string) error { + binary, err := exec.LookPath("robocopy") + if err != nil { + logrus.Debugf("warning: failed to get robocopy binary: %v. Falling back to default file copy between %s and %s\n", err, src.Name(), dest) + return copyFile(src, dest) + } + + srcDir, srcFile := filepath.Split(src.Name()) + destDir := filepath.Dir(dest) + + // Executes the robocopy command with options optimized for a fast, single-file copy. + // /J: Copies using unbuffered I/O (better for large files). + // /MT: Enables multi-threaded copying for improved performance. + // /R:0: Sets retries on failed copies to 0 to avoid long waits. + // /IS: Includes Same files, which forces an overwrite even if the destination + // file appears identical to the source. + cmd := exec.Command(binary, "/J", "/MT", "/R:0", "/IS", srcDir, destDir, srcFile) + if logrus.IsLevelEnabled(logrus.DebugLevel) { + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + } else { + cmd.Stdout = io.Discard + cmd.Stderr = io.Discard + } + + err = cmd.Run() + if err != nil { + // robocopy does not use classic exit codes like linux commands, so we need to check for specific errors + // Only exit code 8 is considered an error, all other exit codes are considered successful with exceptions + // see https://learn.microsoft.com/en-us/windows-server/administration/windows-commands/robocopy + var exitErr *exec.ExitError + if errors.As(err, &exitErr) { + exitCode := exitErr.ExitCode() + if exitCode >= 8 { + return fmt.Errorf("failed to run robocopy: %w", err) + } + } else { + return fmt.Errorf("failed to run robocopy: %w", err) + } + } + + if err := os.Remove(dest); err != nil && !errors.Is(err, os.ErrNotExist) { + return fmt.Errorf("failed to remove existing destination file: %w", err) + } + + err = os.Rename(filepath.Join(destDir, srcFile), dest) + if err != nil { + return fmt.Errorf("failed to rename file: %w", err) + } + + return nil +}