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
14 changes: 14 additions & 0 deletions Sources/Containerization/LinuxContainer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1043,6 +1043,20 @@ extension LinuxContainer {
}
}

// Perform filesystem operations in the container.
public func filesystemOperation(operation: FilesystemOperation, path: String) async throws -> UInt64? {
try await self.state.withLock {
let state = try $0.startedState("filesystemOperation")
return try await state.vm.withAgent { agent in
guard let vminitd = agent as? Vminitd else {
throw ContainerizationError(.unsupported, message: "filesystemOperation requires Vminitd agent")
}
let guestPath = URL(filePath: Self.guestRootfsPath(self.id)).appending(path: path).path
return try await vminitd.filesystemOperation(operation: operation, path: guestPath)
}
}
}

private func relayUnixSocket(
socket: UnixSocketConfiguration,
relayManager: UnixSocketRelayManager,
Expand Down
222 changes: 222 additions & 0 deletions Sources/Containerization/SandboxContext/SandboxContext.grpc.swift

Large diffs are not rendered by default.

361 changes: 361 additions & 0 deletions Sources/Containerization/SandboxContext/SandboxContext.pb.swift

Large diffs are not rendered by default.

29 changes: 29 additions & 0 deletions Sources/Containerization/SandboxContext/SandboxContext.proto
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ service SandboxContext {
rpc Copy(CopyRequest) returns (stream CopyResponse);
// Stat a path in the guest filesystem.
rpc Stat(StatRequest) returns (StatResponse);
// Perform a filesystem operation on a mounted filesystem.
rpc FilesystemOperation(FilesystemOperationRequest) returns (FilesystemOperationResponse);

// Create a new process inside the container.
rpc CreateProcess(CreateProcessRequest) returns (CreateProcessResponse);
Expand Down Expand Up @@ -292,6 +294,33 @@ message StatResponse {
string error = 2; // Non-empty if stat failed.
}

message FiTrimParams {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

For FITRIM I don't think we want a 1:1 mapping between the ioctl parameters and the vminit API parameters. At least for now, we want to trim the entire volume that path points to:

struct fstrim_range range = {
    .start = 0,
    .len = ULLONG_MAX,
    .minlen = 0,
};

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

What we want to build toward for this call is something where we define trim policies. For this PR, schedule could just contain OneShot, and we could add Recurring in a follow-up PR.

Once the one-shot operation is merged, you can implement a container clean command that runs one shot trim on the container root fs path, and the mount paths for each named volume.

Recurring would trim the named path periodically, with a duration of zero clearing a previously established schedule for the path.

Discussion topics once we get to the recurring trim policy include:

  • Should the call naively trim path, or should it infer the mount point such that callers don't inadvertently install multiple periodic trims that operate on the same mount?
  • Should there be a Params value that clears all previously established trim policies?
import "google/protobuf/duration.proto";

message FiTrimParams {
  oneof schedule {
    OneShot one_shot = 1;
    Recurring recurring = 2;
  }

  message OneShot {}

  message Recurring {
    google.protobuf.Duration interval = 1;
  }
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Will come back to this comment/PR when #685 is completed and merged.

uint64 start = 1;
uint64 len = 2;
uint64 minimum_len = 3;
}
message FiFreezeParams {}
message FiThawParams {}

message FiTrimResult {
uint64 trimmed_bytes = 1;
}

message FilesystemOperationRequest {
string path = 1;
oneof operation {
FiTrimParams trim = 2;
FiFreezeParams freeze = 3;
FiThawParams thaw = 4;
}
}

message FilesystemOperationResponse {
oneof result {
FiTrimResult trim = 1;
}
}

message IpLinkSetRequest {
string interface = 1;
bool up = 2;
Expand Down
8 changes: 8 additions & 0 deletions Sources/Containerization/VirtualMachineAgent.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,12 @@ public struct WriteFileFlags {
public var create = false
}

public enum FilesystemOperation: Sendable {
case freeze
case thaw
case trim
}

/// A protocol for the agent running inside a virtual machine. If an operation isn't
/// supported the implementation MUST return a ContainerizationError with a code of
/// `.unsupported`.
Expand All @@ -34,6 +40,8 @@ public protocol VirtualMachineAgent: Sendable {
func standardSetup() async throws
/// Close any resources held by the agent.
func close() async throws
// Perform a filesystem operation on the given path.
func filesystemOperation(operation: FilesystemOperation, path: String) async throws -> UInt64?

// POSIX-y
func getenv(key: String) async throws -> String
Expand Down
30 changes: 30 additions & 0 deletions Sources/Containerization/Vminitd.swift
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,22 @@ extension Vminitd: VirtualMachineAgent {
})
}

/// Perform a filesystem operation on a path inside the sandbox's environment.
public func filesystemOperation(operation: FilesystemOperation, path: String) async throws -> UInt64? {
let response = try await client.filesystemOperation(
.with {
$0.operation = operation.toProtoOperation()
$0.path = path
})

switch operation {
case .trim:
return response.trim.trimmedBytes
case .freeze, .thaw:
return nil
}
}

public func createProcess(
id: String,
containerID: String?,
Expand Down Expand Up @@ -596,3 +612,17 @@ extension StatCategory {
return categories
}
}

extension FilesystemOperation {
/// Convert FilesystemOperation to proto oneof value.
func toProtoOperation() -> Com_Apple_Containerization_Sandbox_V3_FilesystemOperationRequest.OneOf_Operation {
switch self {
case .freeze:
return .freeze(.init())
case .thaw:
return .thaw(.init())
case .trim:
return .trim(.init())
}
}
}
Loading