Skip to content
Open
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
55 changes: 54 additions & 1 deletion Sources/Containerization/LinuxContainer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import ContainerizationArchive
import ContainerizationError
import ContainerizationExtras
import ContainerizationOCI
import ContainerizationOS
import Foundation
import Logging
import Synchronization
Expand Down Expand Up @@ -1065,7 +1066,19 @@ extension LinuxContainer {
}
let isArchive = isDirectory.boolValue

let guestPath = URL(filePath: self.root).appending(path: destination.path)
let guestPath: URL = try await state.vm.withAgent { agent in
guard let vminitd = agent as? Vminitd else {
throw ContainerizationError(.unsupported, message: "copyIn requires Vminitd agent")
}

return try await self.resolveCopyInGuestPath(
from: source,
to: destination,
sourceIsDirectory: isArchive,
using: vminitd
)
}

let port = self.hostVsockPorts.wrappingAdd(1, ordering: .relaxed).oldValue
let listener = try state.vm.listen(port)

Expand Down Expand Up @@ -1150,6 +1163,46 @@ extension LinuxContainer {
}
}

private func resolveCopyInGuestPath(
from source: URL,
to destination: URL,
sourceIsDirectory: Bool,
using vminitd: Vminitd
) async throws -> URL {
let guestDestination = URL(filePath: self.root).appending(path: destination.path)

let stat: ContainerizationOS.Stat?
do {
stat = try await vminitd.stat(path: guestDestination)
} catch let error as ContainerizationError where error.code == .notFound {
stat = nil
}
// Any other error propagates so transport and permission failures are visible.

guard let stat else {
if destination.hasDirectoryPath && !sourceIsDirectory {
throw ContainerizationError(
.invalidArgument,
message: "destination directory does not exist: \(destination.path)"
)
}
return guestDestination
}

let destinationIsDirectory = (stat.mode & UInt32(S_IFMT)) == UInt32(S_IFDIR)
guard destinationIsDirectory else {
if sourceIsDirectory {
throw ContainerizationError(
.invalidArgument,
message: "cannot copy directory over existing file: \(destination.path)"
)
}
return guestDestination
}

return guestDestination.appendingPathComponent(source.lastPathComponent)
}

/// Copy a file or directory from the container to the host.
///
/// Data transfer happens over a dedicated vsock connection. For directories,
Expand Down
7 changes: 6 additions & 1 deletion Sources/Containerization/Vminitd.swift
Original file line number Diff line number Diff line change
Expand Up @@ -472,7 +472,12 @@ extension Vminitd {
$0.path = path.path
}

let response = try await client.stat(request)
let response: Com_Apple_Containerization_Sandbox_V3_StatResponse
do {
response = try await client.stat(request)
} catch let error as RPCError where error.code == .notFound {
throw ContainerizationError(.notFound, message: "stat: path not found '\(path.path)'", cause: error)
}
guard response.error.isEmpty else {
throw ContainerizationError(.internalError, message: "stat: \(response.error)")
}
Expand Down
7 changes: 7 additions & 0 deletions vminitd/Sources/VminitdCore/Server+GRPC.swift
Original file line number Diff line number Diff line change
Expand Up @@ -384,6 +384,13 @@ extension Initd: Com_Apple_Containerization_Sandbox_V3_SandboxContext.SimpleServ
let result = _stat(request.path, &s)
if result == -1 {
let error = swiftErrno("stat")
if error.code == .ENOENT {
throw RPCError(
code: .notFound,
message: "stat: path not found '\(request.path)'",
cause: error
)
}
return .with { $0.error = "\(error)" }
}
return .with {
Expand Down