chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,457 @@
|
||||
//===----------------------------------------------------------------------===//
|
||||
// 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
|
||||
)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user