Skip to content

optimize image copy on windows #217

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open

optimize image copy on windows #217

wants to merge 2 commits into from

Conversation

lstocchi
Copy link
Collaborator

@lstocchi lstocchi commented Aug 4, 2025

this commit adds an optimization of the image copy process on Windows. It leverages robocopy, when available, which is a file replication tool built into Windows. By using it we reduce the time to copy of ~40%.

Robocopy is launched with flags:

  • /J: Copies using unbuffered I/O (recommended for large files)
  • /MT: Creates multi-threaded copies with n threads. Default value of n is 8
  • /R: Specifies the number of retries on failed copies. 0 in this patch

Robocopy also uses specific exit code numbers which does not follow classic values. E.g. exit code 1 means all copies has been completed successfully. All exit codes are listed at https://learn.microsoft.com/en-us/windows-server/administration/windows-commands/robocopy#exit-return-codes

If robocopy is not found on the Win system, it fallback to the old copyFile function.

it resolves #69

Summary by CodeRabbit

  • Bug Fixes

    • More reliable, consistent file copy behavior across platforms with clearer transfer errors and unified destination handling.
  • New Features

    • Windows: faster, resilient native copy path with automatic fallback for robust transfers.
    • macOS: maintains image conversion support where needed.
    • Unix: preserves sensible extension handling for source images.
  • Refactor

    • Streamlined transfer flow to a single, direct streaming copy for simpler, more maintainable transfers.

Copy link

coderabbitai bot commented Aug 4, 2025

Walkthrough

Streamlines the core copy path to a single streamed copy that creates the destination, and splits platform-specific behavior into build-tagged helpers: Windows uses Robocopy with a fallback, Darwin performs qcow2→RAW conversion when needed, and Unix delegates to the core stream copy and uses filepath-based extension detection.

Changes

Cohort / File(s) Change Summary
Core refactor
pkg/imagepullers/noop.go
Replaced multi-path copy and VM-type/image-format logic with a single copyFile(src *os.File, dest string) error that creates the destination and performs a buffered stream copy; removed qcow2/image conversion helpers and related imports.
Darwin helpers
pkg/imagepullers/noop_darwin.go
New darwin build-tag file: opens source via qcow2reader, dispatches by image type (raw→stream copy, qcow2→convert to RAW), implements doCopyFile and convertToRaw using qcow2reader and convert.
Unix helpers
pkg/imagepullers/noop_unix.go
New non-Windows/non-Darwin file: imageExtension returns filepath.Ext(sourceURI); doCopyFile delegates to core copyFile.
Windows helpers
pkg/imagepullers/noop_windows.go
New windows build-tag file: imageExtension preserves .wsl for WSLVirt, otherwise uses filepath.Ext; doCopyFile attempts Robocopy with specific flags, handles exit codes, falls back to copyFile, then renames result to destination.

Sequence Diagram(s)

sequenceDiagram
    participant Caller
    participant doCopyFile
    participant copyFile
    participant Robocopy
    participant qcow2Reader
    participant qcow2Converter

    Caller->>doCopyFile: src *os.File, dest path
    alt platform == windows
        doCopyFile->>Robocopy: run robocopy(srcDir, destDir, file, args)
        alt robocopy success (exit <8)
            Robocopy-->>doCopyFile: success
            doCopyFile->>doCopyFile: rename destDir/srcFile -> dest
        else robocopy unavailable/fails
            doCopyFile->>copyFile: fallback stream copy
        end
    else platform == darwin
        doCopyFile->>qcow2Reader: open src with qcow2reader
        alt src is raw
            qcow2Reader->>copyFile: stream copy raw
        else src is qcow2
            qcow2Reader->>qcow2Converter: convert to RAW -> dest
        end
    else (unix/linux)
        doCopyFile->>copyFile: stream copy (uses filepath.Ext for extension)
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Assessment against linked issues

Objective Addressed Explanation
Make image copy faster on Hyper-V? (#69)

Out-of-scope changes

Code Change Explanation
Add qcow2 conversion and Darwin-specific conversion logic (pkg/imagepullers/noop_darwin.go) Darwin qcow2→RAW conversion targets macOS/qcow2 handling and is unrelated to Hyper-V/Windows copy performance objective.
Introduce Unix extension logic (pkg/imagepullers/noop_unix.go) filepath.Ext-based extension handling addresses Unix path parsing, not Hyper-V/Windows copy performance objective.

Possibly related PRs

Suggested reviewers

  • cfergeau

Poem

"I hopped through bytes with nimble paws,
Streamed each file without a pause,
On Windows Robocopy sped the trail,
On Darwin qcow2 told its tale,
A rabbit's hop—clean paths at last!" 🐇

Tip

🔌 Remote MCP (Model Context Protocol) integration is now available!

Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats.

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
pkg/imagepullers/noop.go (1)

122-159: Excellent Windows optimization implementation with minor robustness suggestions.

The robocopy implementation is well-designed with proper fallback, correct exit code handling, and appropriate flags for large file copying. The ~40% performance improvement mentioned in the PR objectives should be achieved with this approach.

Consider these minor robustness improvements:

 func copyFileWin(srcF *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, srcF.Name(), dest)
 		return copyFile(srcF, dest)
 	}
 
 	srcDir, srcFile := filepath.Split(srcF.Name())
+	if srcFile == "" {
+		return fmt.Errorf("invalid source file path: %s", srcF.Name())
+	}
 	destDir, _ := filepath.Split(dest)
 
 	cmd := exec.Command(binary, "/J", "/MT", "/R:0", srcDir, destDir, srcFile)
 	if logrus.IsLevelEnabled(logrus.DebugLevel) {
 		cmd.Stdout = os.Stdout
 		cmd.Stderr = os.Stderr
 	}
 
 	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
 		if exitErr, ok := err.(*exec.ExitError); ok {
 			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)
 		}
 	}
 
+	tempPath := filepath.Join(destDir, srcFile)
 	err = os.Rename(filepath.Join(destDir, srcFile), dest)
 	if err != nil {
+		// Clean up temporary file on rename failure
+		os.Remove(tempPath)
 		return fmt.Errorf("failed to rename file: %w", err)
 	}
 
 	return nil
 }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f83d6c4 and 0f69ed3.

📒 Files selected for processing (1)
  • pkg/imagepullers/noop.go (3 hunks)
🔇 Additional comments (5)
pkg/imagepullers/noop.go (5)

8-8: LGTM! Import additions support the new functionality.

The added imports (os/exec for robocopy execution and logrus for logging) are appropriate and necessary for the Windows optimization feature.

Also applies to: 11-12


95-95: LGTM! Consistent signature updates across copy functions.

The updated function calls consistently pass destination paths as strings rather than file handles, enabling specialized copy implementations (particularly the Windows robocopy optimization) while maintaining a uniform interface.

Also applies to: 97-97, 99-99


103-103: LGTM! Signature update aligns with refactoring pattern.

The function signature change from *os.File to string for the destination parameter is consistent with the overall refactoring, allowing downstream functions to handle file creation internally.


161-172: LGTM! Consistent refactoring with proper file handling.

The function correctly implements the signature change pattern, creating the destination file internally with appropriate error handling and cleanup via defer.


175-187: LGTM! Proper refactoring maintains performance characteristics.

The function correctly implements the signature change pattern with internal file creation, proper cleanup, and maintains the buffered writing approach for good performance.

}
}

err = os.Rename(filepath.Join(destDir, srcFile), dest)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What happens if there was already an unrelated file named destDir/srcFile in the destination directory?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The existing file will be overwritten

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you sure about this? The exit code doc seems to say otherwise?

0 - No files were copied. No failure was encountered. No files were mismatched. The files already exist in the destination directory; therefore, the copy operation was skipped.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm gonna try but I have based my answer on the description of rename

Rename renames (moves) oldpath to newpath. If newpath already exists and is not a directory, Rename replaces it.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah oops, I misplaced my comment and was not explicit enough, I was asking about robocopy behaviour when the destDir/srcFile already exists.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

by reading around it seems that on win it behaves differently and it fails. I'll fix it

Copy link
Collaborator Author

@lstocchi lstocchi Aug 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated. Added the flag /IS which overwrite if the file exists. I also verified rename works as expected on Windows, and it does. I added a remove command as suggested by the AI btw

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

♻️ Duplicate comments (1)
pkg/imagepullers/noop.go (1)

44-50: Cross-OS extension differences: confirm expectations

imageExtension is now OS-specific. On Unix it preserves multi-extensions (see my comment in noop_unix.go), on Windows it has WSL-specific rules. Please confirm downstream code does not rely on previous extension behavior for imports/detection.

🧹 Nitpick comments (5)
pkg/imagepullers/noop_darwin.go (2)

24-26: Add context to image open error for easier diagnosis

Including the source path makes troubleshooting much easier.

-  if err != nil {
-    return err
-  }
+  if err != nil {
+    return fmt.Errorf("open source image %q: %w", src.Name(), err)
+  }

39-51: Make conversion atomic and clean up on failures

Writing directly to dest can leave a partially written file on error. Write to a temp file in the destination directory and rename on success to ensure atomicity and automatic cleanup.

@@
-import (
-  "fmt"
-  "os"
+import (
+  "fmt"
+  "os"
+  "path/filepath"
@@
 func convertToRaw(srcImg image.Image, dest string) error {
-  destF, err := os.Create(dest)
+  // Write to a temp file and atomically move it into place on success.
+  destDir := filepath.Dir(dest)
+  tmp, err := os.CreateTemp(destDir, filepath.Base(dest)+".tmp-*")
   if err != nil {
     return err
   }
-  defer destF.Close()
+  tmpPath := tmp.Name()
+  // Ensure cleanup on failure paths.
+  defer func() {
+    tmp.Close()
+    // Ignore errors; tmpPath may have been renamed already.
+    _ = os.Remove(tmpPath)
+  }()
 
   if err := srcImg.Readable(); err != nil {
     return fmt.Errorf("source image is not readable: %w", err)
   }
 
-  return convert.Convert(destF, srcImg, convert.Options{})
+  if err := convert.Convert(tmp, srcImg, convert.Options{}); err != nil {
+    return err
+  }
+  if err := tmp.Close(); err != nil {
+    return err
+  }
+  // Move the completed file into place.
+  return os.Rename(tmpPath, dest)
 }

Also applies to: 5-16

pkg/imagepullers/noop_windows.go (2)

16-27: Handle multi-part extensions for WSL and general cases

Current logic returns ".tar.gz" unconditionally for WSL (unless ".wsl"). This can mislabel ".tar", ".tar.xz", etc. Suggest recognizing common multi-extensions, defaulting to ".tar.gz" only when unknown.

 import (
   "errors"
   "fmt"
+  "io"
   "os"
   "os/exec"
   "path/filepath"
+  "strings"
 
   "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)
-  }
+  if vmType == define.WSLVirt {
+    lower := strings.ToLower(sourceURI)
+    switch {
+    case strings.HasSuffix(lower, ".wsl"):
+      return ".wsl"
+    case strings.HasSuffix(lower, ".tar.gz"),
+      strings.HasSuffix(lower, ".tar.xz"),
+      strings.HasSuffix(lower, ".tar.zst"),
+      strings.HasSuffix(lower, ".tar.bz2"),
+      strings.HasSuffix(lower, ".tar"):
+      i := strings.LastIndex(lower, ".tar")
+      return lower[i:]
+    default:
+      return ".tar.gz"
+    }
+  }
+  lower := strings.ToLower(sourceURI)
+  switch {
+  case strings.HasSuffix(lower, ".tar.gz"),
+    strings.HasSuffix(lower, ".tar.xz"),
+    strings.HasSuffix(lower, ".tar.zst"),
+    strings.HasSuffix(lower, ".tar.bz2"),
+    strings.HasSuffix(lower, ".tar"):
+    i := strings.LastIndex(lower, ".tar")
+    return lower[i:]
+  default:
+    return filepath.Ext(sourceURI)
+  }
 }

Would you prefer limiting multi-extension handling only to WSL paths to minimize changes?

Also applies to: 5-14


32-33: Nit: logging tone and newline

The message is logged at Debug level but says "warning:" and includes an extra newline.

-  logrus.Debugf("warning: failed to get robocopy binary: %v. Falling back to default file copy between %s and %s\n", err, src.Name(), dest)
+  logrus.Debugf("robocopy not found (%v); falling back to default file copy between %s and %s", err, src.Name(), dest)
pkg/imagepullers/noop.go (1)

72-84: Avoid double buffering; write directly to dest (optionally fsync)

bufio.NewWriter plus io.Copy adds redundant buffering without benefit here. Writing directly to the file is simpler and often faster. Optionally fsync to reduce risk of partial data after a crash.

-import (
-  "bufio"
-  "fmt"
-  "io"
-  "os"
+import (
+  "fmt"
+  "io"
+  "os"
@@
-func copyFile(src *os.File, dest string) error {
-  destF, err := os.Create(dest)
+func copyFile(src *os.File, dest string) error {
+  destF, err := os.Create(dest)
   if err != nil {
     return err
   }
-  defer destF.Close()
+  defer destF.Close()
 
-  bufferedWriter := bufio.NewWriter(destF)
-  defer bufferedWriter.Flush()
-
-  _, err = io.Copy(bufferedWriter, src)
-  return err
+  if _, err = io.Copy(destF, src); err != nil {
+    return err
+  }
+  // Optional, uncomment if durability is desired over speed:
+  // return destF.Sync()
+  return nil
 }

Also applies to: 3-12

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these settings in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between fe965ad and 73864ae.

📒 Files selected for processing (4)
  • pkg/imagepullers/noop.go (1 hunks)
  • pkg/imagepullers/noop_darwin.go (1 hunks)
  • pkg/imagepullers/noop_unix.go (1 hunks)
  • pkg/imagepullers/noop_windows.go (1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (2)
pkg/imagepullers/noop_darwin.go (1)
vendor/github.com/lima-vm/go-qcow2reader/convert/convert.go (1)
  • Convert (139-225)
pkg/imagepullers/noop.go (3)
vendor/github.com/lima-vm/go-qcow2reader/image/raw/raw.go (1)
  • Open (55-57)
vendor/github.com/lima-vm/go-qcow2reader/qcow2reader.go (1)
  • Open (30-42)
vendor/github.com/lima-vm/go-qcow2reader/image/qcow2/qcow2.go (1)
  • Open (556-644)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build (oldstable)
🔇 Additional comments (4)
pkg/imagepullers/noop_darwin.go (1)

18-20: LGTM: extension determination matches VM image format on macOS

Using VMType.ImageFormat().Kind() keeps the filename aligned with the target runtime format.

pkg/imagepullers/noop_unix.go (1)

16-18: LGTM: straightforward delegation to copy path

This keeps Unix simple and leverages the shared implementation.

pkg/imagepullers/noop_windows.go (1)

45-56: LGTM: Robocopy exit code handling

Treating 0–7 as success and >=8 as failure matches Microsoft’s documented semantics. Fallback path for non-ExitError is correct.

pkg/imagepullers/noop.go (1)

63-70: LGTM: centralizing the copy path and deferring to OS-specific helpers

Opening the source once and delegating to doCopyFile aligns with the new design and keeps the flow clean.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
pkg/imagepullers/noop.go (1)

63-67: Wrap source open error with context for better diagnosability

Returning the raw error here makes troubleshooting harder. Add context with the source path.

-	src, err := os.Open(puller.sourceURI)
-	if err != nil {
-		return err
-	}
+	src, err := os.Open(puller.sourceURI)
+	if err != nil {
+		return fmt.Errorf("open source image %q: %w", puller.sourceURI, err)
+	}
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these settings in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 73864ae and 7320451.

📒 Files selected for processing (4)
  • pkg/imagepullers/noop.go (1 hunks)
  • pkg/imagepullers/noop_darwin.go (1 hunks)
  • pkg/imagepullers/noop_unix.go (1 hunks)
  • pkg/imagepullers/noop_windows.go (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • pkg/imagepullers/noop_darwin.go
  • pkg/imagepullers/noop_windows.go
  • pkg/imagepullers/noop_unix.go
🧰 Additional context used
🧬 Code Graph Analysis (1)
pkg/imagepullers/noop.go (9)
vendor/github.com/lima-vm/go-qcow2reader/image/parallels/parallels.go (1)
  • Open (13-18)
vendor/github.com/lima-vm/go-qcow2reader/image/raw/raw.go (1)
  • Open (55-57)
vendor/github.com/lima-vm/go-qcow2reader/image/vdi/vdi.go (1)
  • Open (15-25)
vendor/github.com/lima-vm/go-qcow2reader/image/vhdx/vhdx.go (1)
  • Open (13-15)
vendor/github.com/lima-vm/go-qcow2reader/image/vpc/vpc.go (1)
  • Open (13-15)
vendor/github.com/lima-vm/go-qcow2reader/image/vmdk/vmdk.go (1)
  • Open (13-19)
vendor/github.com/lima-vm/go-qcow2reader/qcow2reader.go (1)
  • Open (30-42)
vendor/github.com/lima-vm/go-qcow2reader/image/qcow2/qcow2.go (1)
  • Open (556-644)
vendor/github.com/containers/storage/pkg/pools/pools.go (1)
  • Copy (62-67)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build (oldstable)

this commit adds an optimization of the image copy process on Windows.
It leverages robocopy, when available, which is a file replication tool
built into Windows. By using it we reduce the time to copy of ~40%.

Robocopy is launched with flags:
- /J: Copies using unbuffered I/O (recommended for large files)
- /MT: Creates multi-threaded copies with n threads. Default value of n
  is 8
- /R:<n> Specifies the number of retries on failed copies. 0 in this
  patch
- /IS Includes Same files, which forces an overwrite even if the destination file appears identical to the source.

Robocopy also uses specific exit code numbers which does not follow
classic values. E.g. exit code 1 means all copies has been completed
successfully. All exit codes are listed at https://learn.microsoft.com/en-us/windows-server/administration/windows-commands/robocopy#exit-return-codes

If robocopy is not found on the Win system, it fallback to the old copyFile function.

Signed-off-by: lstocchi <lstocchi@redhat.com>
@lstocchi lstocchi force-pushed the i69 branch 2 times, most recently from df82944 to 9fa6017 Compare August 18, 2025 13:53
noop contains funcs that are specific to different OS. This commit creates OS specific noop file that implements their logic.
This cleans the noop file of all switches/if. This is a simple refactor, no logic is changed

Signed-off-by: Luca Stocchi <lstocchi@redhat.com>
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (3)
pkg/imagepullers/noop_windows.go (2)

36-37: Resolve relative src paths before invoking robocopy

When src.Name() is relative, filepath.Split returns an empty directory, which breaks robocopy’s expected “source directory” argument.

Apply this diff to normalize to an absolute path before splitting:

-srcDir, srcFile := filepath.Split(src.Name())
+srcPath := src.Name()
+if !filepath.IsAbs(srcPath) {
+	if abs, absErr := filepath.Abs(srcPath); absErr == nil {
+		srcPath = abs
+	}
+}
+srcDir, srcFile := filepath.Split(srcPath)

5-14: Fix build: import io for io.Discard

The Windows build fails because io.Discard is used without importing io.

Apply this diff to fix the import:

 import (
 	"errors"
 	"fmt"
+	"io"
 	"os"
 	"os/exec"
 	"path/filepath"
 
 	"github.com/containers/podman/v5/pkg/machine/define"
 	"github.com/sirupsen/logrus"
 )
pkg/imagepullers/noop.go (1)

72-84: Avoid bufio.Writer here; check Sync/Close errors and clean up partial files

io.Copy already buffers internally. Flushing errors are currently ignored, and durability isn’t ensured.

Apply this diff to simplify and harden copy semantics:

-func copyFile(src *os.File, dest string) error {
-	destF, err := os.Create(dest)
-	if err != nil {
-		return err
-	}
-	defer destF.Close()
-
-	bufferedWriter := bufio.NewWriter(destF)
-	defer bufferedWriter.Flush()
-
-	_, err = io.Copy(bufferedWriter, src)
-	return err
-}
+func copyFile(src *os.File, dest string) error {
+	destF, err := os.Create(dest)
+	if err != nil {
+		return err
+	}
+
+	// Copy with io.Copy (has its own internal buffer).
+	_, err = io.Copy(destF, src)
+	// Ensure bytes hit disk before returning success.
+	if syncErr := destF.Sync(); err == nil && syncErr != nil {
+		err = syncErr
+	}
+	// Propagate close error if no prior error occurred.
+	if closeErr := destF.Close(); err == nil && closeErr != nil {
+		err = closeErr
+	}
+	// Best-effort cleanup of partial file on error.
+	if err != nil {
+		_ = os.Remove(dest)
+	}
+	return err
+}
🧹 Nitpick comments (3)
pkg/imagepullers/noop_windows.go (2)

56-58: Clarify robocopy exit-code comment (0–7 success; >= 8 failure)

The code handles exit codes correctly, but the comment is misleading. Update it for accuracy.

-		// 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
+		// robocopy does not use classic exit codes like Linux commands.
+		// Per Microsoft docs, exit codes 0–7 indicate success or non-fatal conditions; codes >= 8 indicate failure.
+		// See https://learn.microsoft.com/en-us/windows-server/administration/windows-commands/robocopy#return-codes

32-33: Nit: remove trailing newline from Debugf

logrus handles line endings; embedding “\n” in the format string is unnecessary.

-		logrus.Debugf("warning: failed to get robocopy binary: %v. Falling back to default file copy between %s and %s\n", err, src.Name(), dest)
+		logrus.Debugf("warning: failed to get robocopy binary: %v. Falling back to default file copy between %s and %s", err, src.Name(), dest)
pkg/imagepullers/noop.go (1)

3-12: Remove unused bufio import after refactor

If you apply the copyFile refactor, bufio becomes unused.

 import (
-	"bufio"
 	"fmt"
 	"io"
 	"os"
 
 	"github.com/containers/podman/v5/pkg/machine/define"
 
 	"github.com/containers/podman/v5/pkg/machine/env"
 )
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 7320451 and df82944.

📒 Files selected for processing (4)
  • pkg/imagepullers/noop.go (1 hunks)
  • pkg/imagepullers/noop_darwin.go (1 hunks)
  • pkg/imagepullers/noop_unix.go (1 hunks)
  • pkg/imagepullers/noop_windows.go (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • pkg/imagepullers/noop_unix.go
  • pkg/imagepullers/noop_darwin.go
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-08-18T13:21:37.636Z
Learnt from: lstocchi
PR: crc-org/macadam#217
File: pkg/imagepullers/noop_windows.go:61-64
Timestamp: 2025-08-18T13:21:37.636Z
Learning: os.Rename works correctly on Windows even when the destination file exists, based on user testing in Windows environments.

Applied to files:

  • pkg/imagepullers/noop_windows.go
🧬 Code Graph Analysis (1)
pkg/imagepullers/noop.go (3)
vendor/github.com/lima-vm/go-qcow2reader/image/raw/raw.go (1)
  • Open (55-57)
vendor/github.com/lima-vm/go-qcow2reader/qcow2reader.go (1)
  • Open (30-42)
vendor/github.com/lima-vm/go-qcow2reader/image/qcow2/qcow2.go (1)
  • Open (556-644)
🪛 GitHub Check: build (stable)
pkg/imagepullers/noop_windows.go

[failure] 51-51:
undefined: io


[failure] 50-50:
undefined: io

🪛 GitHub Check: build (oldstable)
pkg/imagepullers/noop_windows.go

[failure] 51-51:
undefined: io


[failure] 50-50:
undefined: io

🪛 GitHub Actions: Build
pkg/imagepullers/noop_windows.go

[error] 50-50: Go build failed for Windows target: undefined: io in pkg/imagepullers/noop_windows.go:50:16. Step: 'go build' for Windows (part of 'make cross').

🔇 Additional comments (3)
pkg/imagepullers/noop_windows.go (2)

45-52: Silencing robocopy output by default looks good

Good practice: default to discarding stdout/stderr and only surface output when debug logging is enabled.


70-77: Overwrite semantics + final rename: LGTM

Pre-removing the destination and then renaming the copied file in-place provides predictable overwrite behavior and avoids cross-volume moves. Given the earlier validation that os.Rename behaves as expected on Windows, this is solid.

pkg/imagepullers/noop.go (1)

63-70: File lifecycle handling is correct

Opening the source here and deferring Close() at the caller while delegating the copy to doCopyFile aligns ownership and avoids premature closes.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (1)
pkg/imagepullers/noop.go (1)

72-84: Do not ignore flush errors; remove unnecessary buffering; fsync and clean up partial files on error

Buffered writer is unnecessary (io.Copy already buffers) and the deferred Flush error is ignored. Capture Sync/Close errors and remove partial files on failure. Also drop the now-unused bufio import.

Apply this diff:

-func copyFile(src *os.File, dest string) error {
-	destF, err := os.Create(dest)
-	if err != nil {
-		return err
-	}
-	defer destF.Close()
-
-	bufferedWriter := bufio.NewWriter(destF)
-	defer bufferedWriter.Flush()
-
-	_, err = io.Copy(bufferedWriter, src)
-	return err
-}
+func copyFile(src *os.File, dest string) error {
+	destF, err := os.Create(dest)
+	if err != nil {
+		return err
+	}
+
+	// Copy with io.Copy (has its own internal buffer).
+	_, err = io.Copy(destF, src)
+	// Ensure data hits disk before reporting success.
+	if syncErr := destF.Sync(); err == nil && syncErr != nil {
+		err = syncErr
+	}
+	// Close and propagate any close error if no prior error.
+	if closeErr := destF.Close(); err == nil && closeErr != nil {
+		err = closeErr
+	}
+	// Best-effort cleanup of partial file on error.
+	if err != nil {
+		_ = os.Remove(dest)
+	}
+	return err
+}

Follow-up:

  • Remove "bufio" from the import list in this file.
  • Optional performance tweak: use github.com/containers/storage/pkg/pools.Copy(destF, src) for fewer allocations.
🧹 Nitpick comments (1)
pkg/imagepullers/noop.go (1)

69-70: Preserve error context from doCopyFile

Wrap the error so callers/logs know where the copy was headed.

Apply this diff:

-	return doCopyFile(src, localPath.Path)
+	if err := doCopyFile(src, localPath.Path); err != nil {
+		return fmt.Errorf("copy image to %q: %w", localPath.Path, err)
+	}
+	return nil
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between df82944 and 9fa6017.

📒 Files selected for processing (4)
  • pkg/imagepullers/noop.go (1 hunks)
  • pkg/imagepullers/noop_darwin.go (1 hunks)
  • pkg/imagepullers/noop_unix.go (1 hunks)
  • pkg/imagepullers/noop_windows.go (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • pkg/imagepullers/noop_windows.go
  • pkg/imagepullers/noop_darwin.go
  • pkg/imagepullers/noop_unix.go
🧰 Additional context used
🧬 Code Graph Analysis (1)
pkg/imagepullers/noop.go (6)
vendor/github.com/lima-vm/go-qcow2reader/image/raw/raw.go (1)
  • Open (55-57)
vendor/github.com/lima-vm/go-qcow2reader/image/vdi/vdi.go (1)
  • Open (15-25)
vendor/github.com/lima-vm/go-qcow2reader/image/vmdk/vmdk.go (1)
  • Open (13-19)
vendor/github.com/lima-vm/go-qcow2reader/image/vpc/vpc.go (1)
  • Open (13-15)
vendor/github.com/lima-vm/go-qcow2reader/qcow2reader.go (1)
  • Open (30-42)
vendor/github.com/lima-vm/go-qcow2reader/image/qcow2/qcow2.go (1)
  • Open (556-644)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: e2e (ubuntu-24.04)
  • GitHub Check: test (windows-latest)
  • GitHub Check: lint
🔇 Additional comments (1)
pkg/imagepullers/noop.go (1)

63-67: Wrap os.Open error with context; ownership boundary remains clear

In pkg/imagepullers/noop.go (lines 63–67), update the error return:

-   src, err := os.Open(puller.sourceURI)
-   if err != nil {
-       return err
-   }
+   src, err := os.Open(puller.sourceURI)
+   if err != nil {
+       return fmt.Errorf("open source image %q: %w", puller.sourceURI, err)
+   }

• Verified that none of the platform-specific doCopyFile implementations (noop_windows.go, noop_unix.go, noop_darwin.go) invoke src.Close(), so the deferred close remains the sole owner of the file.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Make image copy faster on hyper-v?
2 participants