Files
wehub-resource-sync 680845cb1c
Linux build / Determine Swift version (push) Waiting to run
Linux build / Linux compile check (push) Blocked by required conditions
Build containerization / Verify commit signatures (push) Has been skipped
Build containerization / containerization (push) Successful in 0s
chore: import upstream snapshot with attribution
2026-07-13 12:25:30 +08:00

458 lines
15 KiB
Swift

//===----------------------------------------------------------------------===//
// 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.
//===----------------------------------------------------------------------===//
import ContainerizationError
import ContainerizationOCI
import ContainerizationOS
/// A resource limit (rlimit) configuration for a container process.
public struct LinuxRLimit: Sendable, Hashable {
/// The kind of resource limit.
public var kind: Kind
/// The hard limit value.
public var hard: UInt64
/// The soft limit value.
public var soft: UInt64
/// Creates a new resource limit.
///
/// - Parameters:
/// - kind: The kind of resource limit.
/// - hard: The hard limit value.
/// - soft: The soft limit value.
public init(kind: Kind, hard: UInt64, soft: UInt64) {
self.kind = kind
self.hard = hard
self.soft = soft
}
/// Creates a new resource limit with the same value for both hard and soft limits.
///
/// - Parameters:
/// - kind: The kind of resource limit.
/// - limit: The limit value for both hard and soft limits.
public init(kind: Kind, limit: UInt64) {
self.kind = kind
self.hard = limit
self.soft = limit
}
/// Convert to OCI POSIXRlimit format for transport.
public func toOCI() -> POSIXRlimit {
POSIXRlimit(type: self.kind.description, hard: self.hard, soft: self.soft)
}
}
extension LinuxRLimit {
/// The kind of resource limit.
public struct Kind: Sendable, Hashable {
private enum Value: Hashable, Sendable, CaseIterable {
case addressSpace
case coreFileSize
case cpuTime
case dataSize
case fileSize
case locks
case lockedMemory
case messageQueue
case nice
case openFiles
case numberOfProcesses
case residentSetSize
case realtimePriority
case realtimeTimeout
case signalsPending
case stackSize
}
private var value: Value
private init(_ value: Value) {
self.value = value
}
/// Maximum size of the process's virtual memory (address space) in bytes.
public static var addressSpace: Self {
Self(.addressSpace)
}
/// Maximum size of a core file in bytes.
public static var coreFileSize: Self {
Self(.coreFileSize)
}
/// Maximum amount of CPU time the process can consume in seconds.
public static var cpuTime: Self {
Self(.cpuTime)
}
/// Maximum size of the process's data segment in bytes.
public static var dataSize: Self {
Self(.dataSize)
}
/// Maximum size of files the process may create in bytes.
public static var fileSize: Self {
Self(.fileSize)
}
/// Maximum number of file locks.
public static var locks: Self {
Self(.locks)
}
/// Maximum number of bytes of memory that may be locked into RAM.
public static var lockedMemory: Self {
Self(.lockedMemory)
}
/// Maximum number of bytes that can be allocated for POSIX message queues.
public static var messageQueue: Self {
Self(.messageQueue)
}
/// Maximum nice value that can be set.
public static var nice: Self {
Self(.nice)
}
/// Maximum number of open file descriptors.
public static var openFiles: Self {
Self(.openFiles)
}
/// Maximum number of processes that can be created by the user.
public static var numberOfProcesses: Self {
Self(.numberOfProcesses)
}
/// Maximum size of the process's resident set (physical memory) in bytes.
public static var residentSetSize: Self {
Self(.residentSetSize)
}
/// Maximum real-time scheduling priority.
public static var realtimePriority: Self {
Self(.realtimePriority)
}
/// Maximum amount of CPU time for real-time scheduling in microseconds.
public static var realtimeTimeout: Self {
Self(.realtimeTimeout)
}
/// Maximum number of signals that may be queued.
public static var signalsPending: Self {
Self(.signalsPending)
}
/// Maximum size of the process stack in bytes.
public static var stackSize: Self {
Self(.stackSize)
}
/// Creates a Kind from its OCI string representation.
///
/// - Parameter string: The OCI string representation (e.g., "RLIMIT_NOFILE").
/// - Throws: `ContainerizationError` with code `.invalidArgument` if the string doesn't match a known rlimit kind.
public init(_ string: String) throws {
switch string {
case "RLIMIT_AS":
self = .addressSpace
case "RLIMIT_CORE":
self = .coreFileSize
case "RLIMIT_CPU":
self = .cpuTime
case "RLIMIT_DATA":
self = .dataSize
case "RLIMIT_FSIZE":
self = .fileSize
case "RLIMIT_LOCKS":
self = .locks
case "RLIMIT_MEMLOCK":
self = .lockedMemory
case "RLIMIT_MSGQUEUE":
self = .messageQueue
case "RLIMIT_NICE":
self = .nice
case "RLIMIT_NOFILE":
self = .openFiles
case "RLIMIT_NPROC":
self = .numberOfProcesses
case "RLIMIT_RSS":
self = .residentSetSize
case "RLIMIT_RTPRIO":
self = .realtimePriority
case "RLIMIT_RTTIME":
self = .realtimeTimeout
case "RLIMIT_SIGPENDING":
self = .signalsPending
case "RLIMIT_STACK":
self = .stackSize
default:
throw ContainerizationError(.invalidArgument, message: "invalid rlimit kind: '\(string)'")
}
}
}
}
extension LinuxRLimit.Kind: CustomStringConvertible {
/// The OCI string representation of the resource limit kind.
public var description: String {
switch self.value {
case .addressSpace:
"RLIMIT_AS"
case .coreFileSize:
"RLIMIT_CORE"
case .cpuTime:
"RLIMIT_CPU"
case .dataSize:
"RLIMIT_DATA"
case .fileSize:
"RLIMIT_FSIZE"
case .locks:
"RLIMIT_LOCKS"
case .lockedMemory:
"RLIMIT_MEMLOCK"
case .messageQueue:
"RLIMIT_MSGQUEUE"
case .nice:
"RLIMIT_NICE"
case .openFiles:
"RLIMIT_NOFILE"
case .numberOfProcesses:
"RLIMIT_NPROC"
case .residentSetSize:
"RLIMIT_RSS"
case .realtimePriority:
"RLIMIT_RTPRIO"
case .realtimeTimeout:
"RLIMIT_RTTIME"
case .signalsPending:
"RLIMIT_SIGPENDING"
case .stackSize:
"RLIMIT_STACK"
}
}
}
/// User-friendly Linux capabilities configuration
public struct LinuxCapabilities: Sendable {
/// Capabilities that define the maximum set of capabilities a process can have
public var bounding: [CapabilityName] = []
/// Capabilities that are actually in effect for the current process
public var effective: [CapabilityName] = []
/// Capabilities that can be inherited by child processes
public var inheritable: [CapabilityName] = []
/// Capabilities that are currently permitted for the process
public var permitted: [CapabilityName] = []
/// Capabilities that are preserved across execve() calls
public var ambient: [CapabilityName] = []
/// Grant all capabilities
public static let allCapabilities = LinuxCapabilities(
bounding: CapabilityName.allCases,
effective: CapabilityName.allCases,
inheritable: CapabilityName.allCases,
permitted: CapabilityName.allCases,
ambient: CapabilityName.allCases
)
/// Default configuration
public static let defaultOCICapabilities = LinuxCapabilities(
bounding: [
.chown,
.dacOverride,
.fsetid,
.fowner,
.mknod,
.netRaw,
.setgid,
.setuid,
.setfcap,
.setpcap,
.netBindService,
.sysChroot,
.kill,
.auditWrite,
],
effective: [
.chown,
.dacOverride,
.fsetid,
.fowner,
.mknod,
.netRaw,
.setgid,
.setuid,
.setfcap,
.setpcap,
.netBindService,
.sysChroot,
.kill,
.auditWrite,
],
permitted: [
.chown,
.dacOverride,
.fsetid,
.fowner,
.mknod,
.netRaw,
.setgid,
.setuid,
.setfcap,
.setpcap,
.netBindService,
.sysChroot,
.kill,
.auditWrite,
],
)
public init(
bounding: [CapabilityName] = [],
effective: [CapabilityName] = [],
inheritable: [CapabilityName] = [],
permitted: [CapabilityName] = [],
ambient: [CapabilityName] = []
) {
self.bounding = bounding
self.effective = effective
self.inheritable = inheritable
self.permitted = permitted
self.ambient = ambient
}
/// Convenience initializer that sets the same capabilities to effective, permitted, and bounding sets
/// This matches the typical pattern used by containerd/runc
public init(capabilities: [CapabilityName]) {
self.bounding = capabilities
self.effective = capabilities
self.inheritable = []
self.permitted = capabilities
self.ambient = []
}
/// Convert to OCI format for transport
public func toOCI() -> ContainerizationOCI.LinuxCapabilities {
ContainerizationOCI.LinuxCapabilities(
bounding: bounding.isEmpty ? nil : bounding.map { $0.description },
effective: effective.isEmpty ? nil : effective.map { $0.description },
inheritable: inheritable.isEmpty ? nil : inheritable.map { $0.description },
permitted: permitted.isEmpty ? nil : permitted.map { $0.description },
ambient: ambient.isEmpty ? nil : ambient.map { $0.description }
)
}
}
public struct LinuxProcessConfiguration: Sendable {
/// The default PATH value for a process.
public static let defaultPath = "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
/// The arguments for the container process.
public var arguments: [String] = []
/// The environment variables for the container process.
public var environmentVariables: [String] = ["PATH=\(Self.defaultPath)"]
/// The working directory for the container process.
public var workingDirectory: String = "/"
/// The user the container process will run as.
public var user: ContainerizationOCI.User = .init()
/// The rlimits for the container process.
public var rlimits: [LinuxRLimit] = []
/// Whether to set the no_new_privileges bit on the container process. When true, the
/// process and its children cannot gain additional privileges via setuid/setgid binaries
/// or file capabilities.
public var noNewPrivileges: Bool = false
/// The Linux capabilities for the container process. Defaults to
/// ``LinuxCapabilities/defaultOCICapabilities`` — the restricted baseline used by
/// runc/containerd, which excludes privileged capabilities such as `CAP_SYS_ADMIN`.
/// Callers that require additional capabilities (for example, privileged containers)
/// must opt in explicitly, e.g. by setting ``LinuxCapabilities/allCapabilities``.
public var capabilities: LinuxCapabilities = .defaultOCICapabilities
/// Whether to allocate a pseudo terminal for the process. If you'd like interactive
/// behavior and are planning to use a terminal for stdin/out/err on the client side,
/// this should likely be set to true.
public var terminal: Bool = false
/// The stdin for the process.
public var stdin: ReaderStream?
/// The stdout for the process.
public var stdout: Writer?
/// The stderr for the process.
public var stderr: Writer?
public init() {}
public init(
arguments: [String],
environmentVariables: [String] = ["PATH=\(Self.defaultPath)"],
workingDirectory: String = "/",
user: ContainerizationOCI.User = .init(),
rlimits: [LinuxRLimit] = [],
noNewPrivileges: Bool = false,
capabilities: LinuxCapabilities = .defaultOCICapabilities,
terminal: Bool = false,
stdin: ReaderStream? = nil,
stdout: Writer? = nil,
stderr: Writer? = nil
) {
self.arguments = arguments
self.environmentVariables = environmentVariables
self.workingDirectory = workingDirectory
self.user = user
self.rlimits = rlimits
self.noNewPrivileges = noNewPrivileges
self.capabilities = capabilities
self.terminal = terminal
self.stdin = stdin
self.stdout = stdout
self.stderr = stderr
}
public init(from config: ImageConfig) {
self.workingDirectory = config.workingDir ?? "/"
self.environmentVariables = config.env ?? []
self.arguments = (config.entrypoint ?? []) + (config.cmd ?? [])
self.user = {
if let rawString = config.user {
return User(username: rawString)
}
return User()
}()
}
/// Sets up IO to be handled by the passed in Terminal, and edits the
/// process configuration to set the necessary state for using a pty.
mutating public func setTerminalIO(terminal: Terminal) {
self.environmentVariables.append("TERM=xterm")
self.terminal = true
self.stdin = terminal
self.stdout = terminal
}
func toOCI() -> ContainerizationOCI.Process {
ContainerizationOCI.Process(
args: self.arguments,
cwd: self.workingDirectory,
env: self.environmentVariables,
noNewPrivileges: self.noNewPrivileges,
capabilities: self.capabilities.toOCI(),
user: self.user,
rlimits: self.rlimits.map { $0.toOCI() },
terminal: self.terminal
)
}
}