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
21 changes: 18 additions & 3 deletions Sources/Containerization/LinuxContainer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1057,7 +1057,8 @@ extension LinuxContainer {
to destination: URL,
mode: UInt32 = 0o644,
createParents: Bool = true,
chunkSize: Int = defaultCopyChunkSize
chunkSize: Int = defaultCopyChunkSize,
destinationIsDirectory: Bool = false
) async throws {
try await self.state.withLock {
let state = try $0.startedState("copyIn")
Expand All @@ -1084,7 +1085,9 @@ extension LinuxContainer {
vsockPort: port,
mode: mode,
createParents: createParents,
isArchive: isArchive
isArchive: isArchive,
sourceName: source.lastPathComponent,
destinationIsDirectory: destinationIsDirectory
)
}
}
Expand Down Expand Up @@ -1162,7 +1165,8 @@ extension LinuxContainer {
from source: URL,
to destination: URL,
createParents: Bool = true,
chunkSize: Int = defaultCopyChunkSize
chunkSize: Int = defaultCopyChunkSize,
destinationIsDirectory: Bool = false
) async throws {
try await self.state.withLock {
let state = try $0.startedState("copyOut")
Expand Down Expand Up @@ -1201,6 +1205,17 @@ extension LinuxContainer {
throw ContainerizationError(.internalError, message: "copyOut: no metadata received")
}

if destinationIsDirectory && !metadata.isArchive {
var isDir: ObjCBool = false
let exists = FileManager.default.fileExists(atPath: destination.path, isDirectory: &isDir)
if !exists || !isDir.boolValue {
throw ContainerizationError(
.invalidArgument,
message: "copyOut: destination is not a directory: '\(destination.path)'"
)
}
}

guard let conn = await listener.first(where: { _ in true }) else {
throw ContainerizationError(.internalError, message: "copyOut: vsock connection not established")
}
Expand Down
18 changes: 18 additions & 0 deletions Sources/Containerization/SandboxContext/SandboxContext.pb.swift
Original file line number Diff line number Diff line change
Expand Up @@ -834,6 +834,12 @@ public struct Com_Apple_Containerization_Sandbox_V3_CopyRequest: Sendable {
/// For COPY_IN: indicates the data arriving on vsock is a tar+gzip archive.
public var isArchive: Bool = false

/// Source filename
public var sourceName: String = String()

/// When true, the caller expects the destination to be a directory.
public var destinationIsDirectory: Bool = false

public var unknownFields = SwiftProtobuf.UnknownStorage()

public enum Direction: SwiftProtobuf.Enum, Swift.CaseIterable {
Expand Down Expand Up @@ -2812,6 +2818,8 @@ extension Com_Apple_Containerization_Sandbox_V3_CopyRequest: SwiftProtobuf.Messa
4: .standard(proto: "create_parents"),
5: .standard(proto: "vsock_port"),
6: .standard(proto: "is_archive"),
7: .standard(proto: "source_name"),
8: .standard(proto: "destination_is_directory"),
]

public mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
Expand All @@ -2826,6 +2834,8 @@ extension Com_Apple_Containerization_Sandbox_V3_CopyRequest: SwiftProtobuf.Messa
case 4: try { try decoder.decodeSingularBoolField(value: &self.createParents) }()
case 5: try { try decoder.decodeSingularUInt32Field(value: &self.vsockPort) }()
case 6: try { try decoder.decodeSingularBoolField(value: &self.isArchive) }()
case 7: try { try decoder.decodeSingularStringField(value: &self.sourceName) }()
case 8: try { try decoder.decodeSingularBoolField(value: &self.destinationIsDirectory) }()
default: break
}
}
Expand All @@ -2850,6 +2860,12 @@ extension Com_Apple_Containerization_Sandbox_V3_CopyRequest: SwiftProtobuf.Messa
if self.isArchive != false {
try visitor.visitSingularBoolField(value: self.isArchive, fieldNumber: 6)
}
if !self.sourceName.isEmpty {
try visitor.visitSingularStringField(value: self.sourceName, fieldNumber: 7)
}
if self.destinationIsDirectory != false {
try visitor.visitSingularBoolField(value: self.destinationIsDirectory, fieldNumber: 8)
}
try unknownFields.traverse(visitor: &visitor)
}

Expand All @@ -2860,6 +2876,8 @@ extension Com_Apple_Containerization_Sandbox_V3_CopyRequest: SwiftProtobuf.Messa
if lhs.createParents != rhs.createParents {return false}
if lhs.vsockPort != rhs.vsockPort {return false}
if lhs.isArchive != rhs.isArchive {return false}
if lhs.sourceName != rhs.sourceName {return false}
if lhs.destinationIsDirectory != rhs.destinationIsDirectory {return false}
if lhs.unknownFields != rhs.unknownFields {return false}
return true
}
Expand Down
4 changes: 4 additions & 0 deletions Sources/Containerization/SandboxContext/SandboxContext.proto
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,10 @@ message CopyRequest {
uint32 vsock_port = 5;
// For COPY_IN: indicates the data arriving on vsock is a tar+gzip archive.
bool is_archive = 6;
// Source filename
string source_name = 7;
// When true, the caller expects the destination to be a directory.
bool destination_is_directory = 8;
}

message CopyResponse {
Expand Down
6 changes: 5 additions & 1 deletion Sources/Containerization/Vminitd.swift
Original file line number Diff line number Diff line change
Expand Up @@ -461,7 +461,9 @@ extension Vminitd {
mode: UInt32 = 0,
createParents: Bool = false,
isArchive: Bool = false,
onMetadata: @Sendable (CopyMetadata) -> Void = { _ in }
onMetadata: @Sendable (CopyMetadata) -> Void = { _ in },
sourceName: String = "",
destinationIsDirectory: Bool = false
) async throws {
let request = Com_Apple_Containerization_Sandbox_V3_CopyRequest.with {
$0.direction = direction
Expand All @@ -470,6 +472,8 @@ extension Vminitd {
$0.createParents = createParents
$0.vsockPort = vsockPort
$0.isArchive = isArchive
$0.sourceName = sourceName
$0.destinationIsDirectory = destinationIsDirectory
}

let stream = client.copy(request)
Expand Down
25 changes: 21 additions & 4 deletions vminitd/Sources/vminitd/Server+GRPC.swift
Original file line number Diff line number Diff line change
Expand Up @@ -406,14 +406,31 @@ extension Initd: Com_Apple_Containerization_Sandbox_V3_SandboxContextAsyncProvid
request: Com_Apple_Containerization_Sandbox_V3_CopyRequest,
responseStream: GRPCAsyncResponseStreamWriter<Com_Apple_Containerization_Sandbox_V3_CopyResponse>
) async throws {
let path = request.path
var path = request.path
let isArchive = request.isArchive

if !request.sourceName.isEmpty {
var isDir: ObjCBool = false
if FileManager.default.fileExists(atPath: path, isDirectory: &isDir), isDir.boolValue {
path = URL(fileURLWithPath: path).appendingPathComponent(request.sourceName).path
} else if request.destinationIsDirectory && !isArchive {
let errSock = try Socket(type: VsockType(port: request.vsockPort, cid: VsockType.hostCID), closeOnDeinit: true)
try errSock.connect()
try errSock.close()
throw GRPCStatus(
code: .failedPrecondition,
message: "copy: destination is not a directory: '\(path)'"
)
}
}

if request.createParents {
let parentDir = URL(fileURLWithPath: path).deletingLastPathComponent()
try FileManager.default.createDirectory(at: parentDir, withIntermediateDirectories: true)
}

let resolvedPath = path

// Connect to the host's vsock port for data transfer.
let vsockType = VsockType(port: request.vsockPort, cid: VsockType.hostCID)
let sock = try Socket(type: vsockType, closeOnDeinit: false)
Expand All @@ -426,11 +443,11 @@ extension Initd: Com_Apple_Containerization_Sandbox_V3_SandboxContextAsyncProvid

guard isArchive else {
let mode = request.mode > 0 ? mode_t(request.mode) : mode_t(0o644)
let fd = open(path, O_WRONLY | O_CREAT | O_TRUNC, mode)
let fd = open(resolvedPath, O_WRONLY | O_CREAT | O_TRUNC, mode)
guard fd != -1 else {
throw GRPCStatus(
code: .internalError,
message: "copy: failed to open file '\(path)': \(swiftErrno("open"))"
message: "copy: failed to open file '\(resolvedPath)': \(swiftErrno("open"))"
)
}
defer { close(fd) }
Expand Down Expand Up @@ -461,7 +478,7 @@ extension Initd: Com_Apple_Containerization_Sandbox_V3_SandboxContextAsyncProvid
}
return []
}
let destURL = URL(fileURLWithPath: path)
let destURL = URL(fileURLWithPath: resolvedPath)
try FileManager.default.createDirectory(at: destURL, withIntermediateDirectories: true)

let fileHandle = FileHandle(fileDescriptor: sockFd, closeOnDealloc: false)
Expand Down