Skip to content
Merged
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 Sources/ContainerCommands/System/SystemStart.swift
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,13 @@ extension Application {

@Option(
name: .shortAndLong,
help: "Application data directory",
help: "Path to the root directory for application data",
transform: { URL(filePath: $0) })
var appRoot = ApplicationRoot.defaultURL

@Option(
name: .long,
help: "Path to the installation root directory",
help: "Path to the root directory for application executables and plugins",
transform: { URL(filePath: $0) })
var installRoot = InstallRoot.defaultURL

Expand All @@ -54,7 +54,7 @@ extension Application {

public func run() async throws {
// Without the true path to the binary in the plist, `container-apiserver` won't launch properly.
// TODO: Use plugin loader for API server.
// TODO: Can we use the plugin loader to bootstrap the API server?
let executableUrl = CommandLine.executablePathUrl
.deletingLastPathComponent()
.appendingPathComponent("container-apiserver")
Expand Down
134 changes: 134 additions & 0 deletions Sources/Helpers/RuntimeLinux/RuntimeLinuxHelper+Start.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
//===----------------------------------------------------------------------===//
// Copyright © 2025 Apple Inc. and the container project authors. All rights reserved.
//
// 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.
//===----------------------------------------------------------------------===//

import ArgumentParser
import ContainerClient
import ContainerLog
import ContainerSandboxService
import ContainerXPC
import Foundation
import Logging
import NIO

extension RuntimeLinuxHelper {
struct Start: AsyncParsableCommand {
static let label = "com.apple.container.runtime.container-runtime-linux"

static let configuration = CommandConfiguration(
commandName: "start",
abstract: "Start helper for a Linux container"
)

@Flag(name: .long, help: "Enable debug logging")
var debug = false

@Option(name: .shortAndLong, help: "Sandbox UUID")
var uuid: String

@Option(name: .shortAndLong, help: "Root directory for the sandbox")
var root: String

var machServiceLabel: String {
"\(Self.label).\(uuid)"
}

func run() async throws {
let commandName = Self._commandName
let log = RuntimeLinuxHelper.setupLogger(debug: debug, metadata: ["uuid": "\(uuid)"])

log.info("starting \(commandName)")
defer {
log.info("stopping \(commandName)")
}

let eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: System.coreCount)
do {
try adjustLimits()
signal(SIGPIPE, SIG_IGN)

log.info("configuring XPC server")
let interfaceStrategy: any InterfaceStrategy
if #available(macOS 26, *) {
interfaceStrategy = NonisolatedInterfaceStrategy(log: log)
} else {
interfaceStrategy = IsolatedInterfaceStrategy()
}

nonisolated(unsafe) let anonymousConnection = xpc_connection_create(nil, nil)
let server = SandboxService(
root: .init(fileURLWithPath: root),
interfaceStrategy: interfaceStrategy,
eventLoopGroup: eventLoopGroup,
connection: anonymousConnection,
log: log
)

let endpointServer = XPCServer(
identifier: machServiceLabel,
routes: [
SandboxRoutes.createEndpoint.rawValue: server.createEndpoint
],
log: log
)

let mainServer = XPCServer(
connection: anonymousConnection,
routes: [
SandboxRoutes.bootstrap.rawValue: server.bootstrap,
SandboxRoutes.createProcess.rawValue: server.createProcess,
SandboxRoutes.state.rawValue: server.state,
SandboxRoutes.stop.rawValue: server.stop,
SandboxRoutes.kill.rawValue: server.kill,
SandboxRoutes.resize.rawValue: server.resize,
SandboxRoutes.wait.rawValue: server.wait,
SandboxRoutes.start.rawValue: server.startProcess,
SandboxRoutes.dial.rawValue: server.dial,
],
log: log
)

log.info("starting XPC server")
try await withThrowingTaskGroup(of: Void.self) { group in
group.addTask {
try await endpointServer.listen()
}
group.addTask {
try await mainServer.listen()
}
defer { group.cancelAll() }

_ = try await group.next()
}
} catch {
log.error("\(commandName) failed", metadata: ["error": "\(error)"])
try? await eventLoopGroup.shutdownGracefully()
RuntimeLinuxHelper.Start.exit(withError: error)
}
}

private func adjustLimits() throws {
var limits = rlimit()
guard getrlimit(RLIMIT_NOFILE, &limits) == 0 else {
throw POSIXError(.init(rawValue: errno)!)
}
limits.rlim_cur = 65536
limits.rlim_max = 65536
guard setrlimit(RLIMIT_NOFILE, &limits) == 0 else {
throw POSIXError(.init(rawValue: errno)!)
}
}
}
}
123 changes: 11 additions & 112 deletions Sources/Helpers/RuntimeLinux/RuntimeLinuxHelper.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,140 +15,39 @@
//===----------------------------------------------------------------------===//

import ArgumentParser
import ContainerClient
import ContainerLog
import ContainerNetworkService
import ContainerSandboxService
import ContainerVersion
import ContainerXPC
import Containerization
import ContainerizationError
import Foundation
import Logging
import NIO
import OSLog

@main
struct RuntimeLinuxHelper: AsyncParsableCommand {
static let label = "com.apple.container.runtime.container-runtime-linux"

static let configuration = CommandConfiguration(
commandName: "container-runtime-linux",
abstract: "XPC Service for managing a Linux sandbox",
version: ReleaseVersion.singleLine(appName: "container-runtime-linux")
version: ReleaseVersion.singleLine(appName: "container-runtime-linux"),
subcommands: [
Start.self
]
)

@Flag(name: .long, help: "Enable debug logging")
var debug = false

@Option(name: .shortAndLong, help: "Sandbox UUID")
var uuid: String

@Option(name: .shortAndLong, help: "Root directory for the sandbox")
var root: String

var machServiceLabel: String {
"\(Self.label).\(uuid)"
}

func run() async throws {
let commandName = Self._commandName
let log = setupLogger()
log.info("starting \(commandName)")
defer {
log.info("stopping \(commandName)")
}

let eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: System.coreCount)
do {
try adjustLimits()
signal(SIGPIPE, SIG_IGN)

log.info("configuring XPC server")

let interfaceStrategy: any InterfaceStrategy
if #available(macOS 26, *) {
interfaceStrategy = NonisolatedInterfaceStrategy(log: log)
} else {
interfaceStrategy = IsolatedInterfaceStrategy()
}

nonisolated(unsafe) let anonymousConnection = xpc_connection_create(nil, nil)
let server = SandboxService(
root: .init(fileURLWithPath: root),
interfaceStrategy: interfaceStrategy,
eventLoopGroup: eventLoopGroup,
connection: anonymousConnection,
log: log
)

let endpointServer = XPCServer(
identifier: machServiceLabel,
routes: [
SandboxRoutes.createEndpoint.rawValue: server.createEndpoint
],
log: log
)

let mainServer = XPCServer(
connection: anonymousConnection,
routes: [
SandboxRoutes.bootstrap.rawValue: server.bootstrap,
SandboxRoutes.createProcess.rawValue: server.createProcess,
SandboxRoutes.state.rawValue: server.state,
SandboxRoutes.stop.rawValue: server.stop,
SandboxRoutes.kill.rawValue: server.kill,
SandboxRoutes.resize.rawValue: server.resize,
SandboxRoutes.wait.rawValue: server.wait,
SandboxRoutes.start.rawValue: server.startProcess,
SandboxRoutes.dial.rawValue: server.dial,
SandboxRoutes.shutdown.rawValue: server.shutdown,
],
log: log
)

log.info("starting XPC server")
try await withThrowingTaskGroup(of: Void.self) { group in
group.addTask {
try await endpointServer.listen()
}
group.addTask {
try await mainServer.listen()
}
defer { group.cancelAll() }

_ = try await group.next()
}
} catch {
log.error("\(commandName) failed", metadata: ["error": "\(error)"])
try? await eventLoopGroup.shutdownGracefully()
RuntimeLinuxHelper.exit(withError: error)
}
}

private func setupLogger() -> Logger {
package static func setupLogger(debug: Bool, metadata: [String: Logging.Logger.Metadata.Value] = [:]) -> Logging.Logger {
LoggingSystem.bootstrap { label in
OSLogHandler(
label: label,
category: "RuntimeLinuxHelper"
)
}

var log = Logger(label: "com.apple.container")
if debug {
log.logLevel = .debug
}
log[metadataKey: "uuid"] = "\(uuid)"
return log
}

private func adjustLimits() throws {
var limits = rlimit()
guard getrlimit(RLIMIT_NOFILE, &limits) == 0 else {
throw POSIXError(.init(rawValue: errno)!)
}
limits.rlim_cur = 65536
limits.rlim_max = 65536
guard setrlimit(RLIMIT_NOFILE, &limits) == 0 else {
throw POSIXError(.init(rawValue: errno)!)
for (key, val) in metadata {
log[metadataKey: key] = val
}

return log
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -522,6 +522,7 @@ public actor ContainersService {
path: URL
) throws {
let args = [
"start",
"--root", path.path,
"--uuid", configuration.id,
"--debug",
Expand Down