Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions Package.resolved

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

22 changes: 21 additions & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ let package = Package(
.library(name: "ContainerizationOS", targets: ["ContainerizationOS"]),
.library(name: "ContainerizationExtras", targets: ["ContainerizationExtras"]),
.library(name: "ContainerizationArchive", targets: ["ContainerizationArchive"]),
.library(name: "_ContainerizationTar", targets: ["_ContainerizationTar"]),
.executable(name: "cctl", targets: ["cctl"]),
],
dependencies: [
Expand All @@ -42,7 +43,7 @@ let package = Package(
.package(url: "https://github.com/apple/swift-crypto.git", from: "3.0.0"),
.package(url: "https://github.com/grpc/grpc-swift.git", from: "1.26.0"),
.package(url: "https://github.com/apple/swift-protobuf.git", from: "1.29.0"),
.package(url: "https://github.com/apple/swift-nio.git", from: "2.80.0"),
.package(url: "https://github.com/apple/swift-nio.git", from: "2.92.2"),
.package(url: "https://github.com/swift-server/async-http-client.git", from: "1.20.1"),
.package(url: "https://github.com/apple/swift-system.git", from: "1.4.0"),
.package(url: "https://github.com/swiftlang/swift-docc-plugin", from: "1.1.0"),
Expand All @@ -64,6 +65,7 @@ let package = Package(
"ContainerizationOS",
"ContainerizationIO",
"ContainerizationExtras",
"_ContainerizationTar",
.target(name: "ContainerizationEXT4", condition: .when(platforms: [.macOS])),
],
exclude: [
Expand Down Expand Up @@ -259,5 +261,23 @@ let package = Package(
.target(
name: "CShim"
),
.target(
name: "_ContainerizationTar",
dependencies: [
.product(name: "SystemPackage", package: "swift-system"),
.product(name: "NIOCore", package: "swift-nio"),
.product(name: "_NIOFileSystem", package: "swift-nio"),
],
path: "Sources/ContainerizationTar"
),
.testTarget(
name: "ContainerizationTarTests",
dependencies: [
"_ContainerizationTar",
.product(name: "SystemPackage", package: "swift-system"),
.product(name: "NIOCore", package: "swift-nio"),
.product(name: "_NIOFileSystem", package: "swift-nio"),
]
),
]
)
48 changes: 48 additions & 0 deletions Sources/Containerization/LinuxContainer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1089,6 +1089,54 @@ extension LinuxContainer {
}
}
}

/// Copy a directory from the host into the container.
public func copyDirIn(
from source: URL,
to destination: URL,
createParents: Bool = true,
chunkSize: Int = defaultCopyChunkSize,
progress: ProgressHandler? = nil
) async throws {
try await self.state.withLock {
let state = try $0.startedState("copyDirIn")

let guestPath = URL(filePath: self.root).appending(path: destination.path)
try await state.vm.withAgent { agent in
try await agent.copyDirIn(
from: source,
to: guestPath,
createParents: createParents,
chunkSize: chunkSize,
progress: progress
)
}
}
}

/// Copy a directory from the container to the host.
public func copyDirOut(
from source: URL,
to destination: URL,
createParents: Bool = true,
chunkSize: Int = defaultCopyChunkSize,
progress: ProgressHandler? = nil
) async throws {
try await self.state.withLock {
let state = try $0.startedState("copyDirOut")

let guestPath = URL(filePath: self.root).appending(path: source.path)
try await state.vm.withAgent { agent in
try await agent.copyDirOut(
from: guestPath,
to: destination,
createParents: createParents,
chunkSize: chunkSize,
progress: progress
)
}
}
}
}

extension VirtualMachineInstance {
Expand Down
16 changes: 0 additions & 16 deletions Sources/Containerization/SandboxContext/SandboxContext.grpc.swift
Original file line number Diff line number Diff line change
@@ -1,19 +1,3 @@
//===----------------------------------------------------------------------===//
// Copyright © 2025-2026 Apple Inc. and the Containerization project authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//===----------------------------------------------------------------------===//

//
// DO NOT EDIT.
// swift-format-ignore-file
Expand Down
34 changes: 18 additions & 16 deletions Sources/Containerization/SandboxContext/SandboxContext.pb.swift
Original file line number Diff line number Diff line change
@@ -1,19 +1,3 @@
//===----------------------------------------------------------------------===//
// Copyright © 2025-2026 Apple Inc. and the Containerization project authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//===----------------------------------------------------------------------===//

// DO NOT EDIT.
// swift-format-ignore-file
// swiftlint:disable all
Expand Down Expand Up @@ -863,6 +847,9 @@ public struct Com_Apple_Containerization_Sandbox_V3_CopyInInit: Sendable {
/// Create parent directories if they don't exist.
public var createParents: Bool = false

/// If true, the payload is a TAR archive to extract at the destination.
public var isDirectory: Bool = false

public var unknownFields = SwiftProtobuf.UnknownStorage()

public init() {}
Expand All @@ -886,6 +873,9 @@ public struct Com_Apple_Containerization_Sandbox_V3_CopyOutRequest: Sendable {
/// Source path in the guest.
public var path: String = String()

/// If true, create a TAR archive of the directory and stream it.
public var isDirectory: Bool = false

public var unknownFields = SwiftProtobuf.UnknownStorage()

public init() {}
Expand Down Expand Up @@ -2879,6 +2869,7 @@ extension Com_Apple_Containerization_Sandbox_V3_CopyInInit: SwiftProtobuf.Messag
1: .same(proto: "path"),
2: .same(proto: "mode"),
3: .standard(proto: "create_parents"),
4: .standard(proto: "is_directory"),
]

public mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
Expand All @@ -2890,6 +2881,7 @@ extension Com_Apple_Containerization_Sandbox_V3_CopyInInit: SwiftProtobuf.Messag
case 1: try { try decoder.decodeSingularStringField(value: &self.path) }()
case 2: try { try decoder.decodeSingularUInt32Field(value: &self.mode) }()
case 3: try { try decoder.decodeSingularBoolField(value: &self.createParents) }()
case 4: try { try decoder.decodeSingularBoolField(value: &self.isDirectory) }()
default: break
}
}
Expand All @@ -2905,13 +2897,17 @@ extension Com_Apple_Containerization_Sandbox_V3_CopyInInit: SwiftProtobuf.Messag
if self.createParents != false {
try visitor.visitSingularBoolField(value: self.createParents, fieldNumber: 3)
}
if self.isDirectory != false {
try visitor.visitSingularBoolField(value: self.isDirectory, fieldNumber: 4)
}
try unknownFields.traverse(visitor: &visitor)
}

public static func ==(lhs: Com_Apple_Containerization_Sandbox_V3_CopyInInit, rhs: Com_Apple_Containerization_Sandbox_V3_CopyInInit) -> Bool {
if lhs.path != rhs.path {return false}
if lhs.mode != rhs.mode {return false}
if lhs.createParents != rhs.createParents {return false}
if lhs.isDirectory != rhs.isDirectory {return false}
if lhs.unknownFields != rhs.unknownFields {return false}
return true
}
Expand Down Expand Up @@ -2940,6 +2936,7 @@ extension Com_Apple_Containerization_Sandbox_V3_CopyOutRequest: SwiftProtobuf.Me
public static let protoMessageName: String = _protobuf_package + ".CopyOutRequest"
public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
1: .same(proto: "path"),
2: .standard(proto: "is_directory"),
]

public mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
Expand All @@ -2949,6 +2946,7 @@ extension Com_Apple_Containerization_Sandbox_V3_CopyOutRequest: SwiftProtobuf.Me
// enabled. https://github.com/apple/swift-protobuf/issues/1034
switch fieldNumber {
case 1: try { try decoder.decodeSingularStringField(value: &self.path) }()
case 2: try { try decoder.decodeSingularBoolField(value: &self.isDirectory) }()
default: break
}
}
Expand All @@ -2958,11 +2956,15 @@ extension Com_Apple_Containerization_Sandbox_V3_CopyOutRequest: SwiftProtobuf.Me
if !self.path.isEmpty {
try visitor.visitSingularStringField(value: self.path, fieldNumber: 1)
}
if self.isDirectory != false {
try visitor.visitSingularBoolField(value: self.isDirectory, fieldNumber: 2)
}
try unknownFields.traverse(visitor: &visitor)
}

public static func ==(lhs: Com_Apple_Containerization_Sandbox_V3_CopyOutRequest, rhs: Com_Apple_Containerization_Sandbox_V3_CopyOutRequest) -> Bool {
if lhs.path != rhs.path {return false}
if lhs.isDirectory != rhs.isDirectory {return false}
if lhs.unknownFields != rhs.unknownFields {return false}
return true
}
Expand Down
5 changes: 4 additions & 1 deletion Sources/Containerization/SandboxContext/SandboxContext.proto
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ service SandboxContext {
rpc CopyIn(stream CopyInChunk) returns (CopyInResponse);
// Copy a file from the guest to the host.
rpc CopyOut(CopyOutRequest) returns (stream CopyOutChunk);

// Create a new process inside the container.
rpc CreateProcess(CreateProcessRequest) returns (CreateProcessResponse);
// Delete an existing process inside the container.
Expand Down Expand Up @@ -245,13 +244,17 @@ message CopyInInit {
uint32 mode = 2;
// Create parent directories if they don't exist.
bool create_parents = 3;
// If true, the payload is a TAR archive to extract at the destination.
bool is_directory = 4;
}

message CopyInResponse {}

message CopyOutRequest {
// Source path in the guest.
string path = 1;
// If true, create a TAR archive of the directory and stream it.
bool is_directory = 2;
}

message CopyOutChunk {
Expand Down
38 changes: 38 additions & 0 deletions Sources/Containerization/VirtualMachineAgent.swift
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,24 @@ public protocol VirtualMachineAgent: Sendable {
chunkSize: Int,
progress: ProgressHandler?
) async throws

/// Copy a dir from the host into the guest.
func copyDirIn(
from source: URL,
to destination: URL,
createParents: Bool,
chunkSize: Int,
progress: ProgressHandler?
) async throws

/// Copy a dir from the guest to the host.
func copyDirOut(
from source: URL,
to destination: URL,
createParents: Bool,
chunkSize: Int,
progress: ProgressHandler?
) async throws

// Process lifecycle
func createProcess(
Expand Down Expand Up @@ -139,4 +157,24 @@ extension VirtualMachineAgent {
) async throws {
throw ContainerizationError(.unsupported, message: "copyOut")
}

public func copyDirIn(
from source: URL,
to destination: URL,
createParents: Bool,
chunkSize: Int,
progress: ProgressHandler?
) async throws {
throw ContainerizationError(.unsupported, message: "copyDirIn")
}

public func copyDirOut(
from source: URL,
to destination: URL,
createParents: Bool,
chunkSize: Int,
progress: ProgressHandler?
) async throws {
throw ContainerizationError(.unsupported, message: "copyDirOut")
}
}
Loading