Skip to content

Add container copy/cp command for host-container file transfer#1190

Open
simone-panico wants to merge 8 commits intoapple:mainfrom
simone-panico:main
Open

Add container copy/cp command for host-container file transfer#1190
simone-panico wants to merge 8 commits intoapple:mainfrom
simone-panico:main

Conversation

@simone-panico
Copy link

@simone-panico simone-panico commented Feb 10, 2026

Type of Change

  • Bug fix
  • New feature
  • Breaking change
  • Documentation update

Motivation and Context

Adds the container copy (aliased as cp) command to copy files between a running container and the local filesystem.

I saw #1023 and the feedback from @dcantah — the previous attempt relied on tar being installed inside the container.
This implementation takes the recommended approach:
file transfers go through the guest agent via the existing copyIn/copyOut methods on the core Containerization, with no dependency on container tooling.

Testing

  • Tested locally
  • Added/updated tests
  • Added/updated docs

@simone-panico simone-panico marked this pull request as ready for review February 10, 2026 21:22
@JaewonHur
Copy link
Contributor

Related to #232

@jglogan jglogan requested a review from dcantah February 11, 2026 23:20
@jglogan jglogan added storage issues and features associated with storage. labels Feb 11, 2026
@jglogan jglogan requested review from JaewonHur February 11, 2026 23:21
@jglogan jglogan modified the milestone: 2026-02 Feb 11, 2026
let request = XPCMessage(route: SandboxRoutes.copyIn.rawValue)
request.set(key: SandboxKeys.sourcePath.rawValue, value: source)
request.set(key: SandboxKeys.destinationPath.rawValue, value: destination)
request.set(key: SandboxKeys.fileMode.rawValue, value: UInt64(mode))
Copy link
Contributor

Choose a reason for hiding this comment

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

Why UInt64 here?

Copy link
Author

Choose a reason for hiding this comment

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

Because the set Method in XPCMessage.swift expects a UInt64

Copy link
Author

Choose a reason for hiding this comment

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

Do you want to handle it differently? I looked at the code again and couldn't find a better option

@JaewonHur
Copy link
Contributor

JaewonHur commented Feb 17, 2026

CopyOut/CopyIn Scenarios: Current vs Docker Behavior

Scenario 1: src without /, dst without / (src must exist)

  • File, Directory → Non-existing:

    • Docker (desired): save as dst
    • Current: File is saved as dst, Directory is not supported.
  • File → File

    • Docker (desired): replace dst
    • Current: replace dst
  • Directory → File

    • Docker (desired): error
    • Current: Directory is not supported
  • File, Directory → Directory

    • Docker (desired): save under dst
    • Current: File returns error, Directory is not supported.

Scenario 2: src without /, dst with / (dst must be a directory if existing)

  • File → Non-existing:

    • Docker (desired): error no such directory
    • Current: dst is created and src file is saved under dst.
  • Directory → Non-existing:

    • Docker (desired): save as dst
    • Current: Directory is not supported.
  • File, Directory → Directory

    • Docker (desired): save under dst
    • Current: File is saved under dst. Directory is not supported.

Scenario 3: src with /, dst without / (src must be an existing directory)

  • Directory → Non-existing:

    • Docker (desired): save as dst
    • Current: Directory is not supported.
  • Directory → File

    • Docker (desired): error
    • Current: Directory is not supported.
  • Directory → Directory

    • Docker (desired): save under dst
    • Current: Directory is not supported.

Scenario 4: src with /, dst with / (src must be an existing directory & dst must be a directory if existing)

  • Directory → Non-existing:

    • Docker (desired): save as dst
    • Current: Directory is not supported.
  • Directory → Directory

    • Docker (desired): save under dst
    • Current: Directory is not supported.

@JaewonHur
Copy link
Contributor

JaewonHur commented Feb 18, 2026

Hi @simone-panico
Above I summarized the desired behavior and current behavior.

The bold italic phrases are what is different from desired behavior (i.e., docker) now, and can be addressed.

Below is general comments.

  1. Copying directory is not supported yet, we need to handle it later.
  2. If dst/, it would be better to check in command line if dst is directory if it exists.


static func parsePathRef(_ ref: String) -> PathRef {
let parts = ref.components(separatedBy: ":")
if parts.count == 2 {
Copy link
Contributor

Choose a reason for hiding this comment

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

How about this?

let parts = ref.components(separatedBy: ":")
switch parts.count {
case 1:
    return .local(ref)
case 2 where !parts[0].isEmpty && parts[1].starts(with: "/"):
    return .container(id: parts[0], path: parts[1])
default:
    throw ContainerizationError(.invalidArgument, message: "invalid path given: \(ref)")
}

let srcRef = Self.parsePathRef(source)
let dstRef = Self.parsePathRef(destination)

switch (srcRef, dstRef) {
Copy link
Contributor

@JaewonHur JaewonHur Feb 18, 2026

Choose a reason for hiding this comment

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

We need a sanitization logic here to check local path.

  1. If localPath is source
    a. Check if file or directory at localPath exists.
    b. If localPath ends with /, check it's directory.

  2. If localPath is dest
    a. If localPath ends with /, need to check if it's not existing or a directory.

Then, we need to extend destURL correctly following the comment I left.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

storage issues and features associated with storage.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants