mirror of
https://github.com/apple/container.git
synced 2026-09-12 10:45:42 +00:00
- Capitalize containerization. - Wrap "All rights reserved" to a new line. Signed-off-by: Danny Canter <danny_canter@apple.com>
133 lines
4.5 KiB
Swift
133 lines
4.5 KiB
Swift
//===----------------------------------------------------------------------===//
|
|
// Copyright © 2025 Apple Inc. and the Containerization 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 Containerization
|
|
import ContainerizationError
|
|
import ContainerizationOCI
|
|
import ContainerizationOS
|
|
import Foundation
|
|
import LCShim
|
|
import Logging
|
|
import Musl
|
|
|
|
struct ExecCommand: ParsableCommand {
|
|
static let configuration = CommandConfiguration(
|
|
commandName: "exec",
|
|
abstract: "Exec in a container"
|
|
)
|
|
|
|
@Option(name: .long, help: "path to an OCI runtime spec process configuration")
|
|
var processPath: String
|
|
|
|
@Option(name: .long, help: "pid of the init process for the container")
|
|
var parentPid: Int
|
|
|
|
func run() throws {
|
|
LoggingSystem.bootstrap(App.standardError)
|
|
let log = Logger(label: "vmexec")
|
|
|
|
let src = URL(fileURLWithPath: processPath)
|
|
let processBytes = try Data(contentsOf: src)
|
|
let process = try JSONDecoder().decode(
|
|
ContainerizationOCI.Process.self,
|
|
from: processBytes
|
|
)
|
|
try execInNamespaces(process: process, log: log)
|
|
}
|
|
|
|
static func enterNS(path: String, nsType: Int32) throws {
|
|
let fd = open(path, O_RDONLY)
|
|
if fd <= 0 {
|
|
throw App.Errno(stage: "open(ns)")
|
|
}
|
|
defer { close(fd) }
|
|
|
|
guard setns(fd, nsType) == 0 else {
|
|
throw App.Errno(stage: "setns(fd)")
|
|
}
|
|
}
|
|
|
|
private func execInNamespaces(
|
|
process: ContainerizationOCI.Process,
|
|
log: Logger
|
|
) throws {
|
|
// CLOEXEC the pipe fd that signals process readiness.
|
|
let syncfd = FileHandle(fileDescriptor: 3)
|
|
if fcntl(3, F_SETFD, FD_CLOEXEC) == -1 {
|
|
throw App.Errno(stage: "cloexec(syncfd)")
|
|
}
|
|
|
|
try Self.enterNS(path: "/proc/\(self.parentPid)/ns/cgroup", nsType: CLONE_NEWCGROUP)
|
|
try Self.enterNS(path: "/proc/\(self.parentPid)/ns/pid", nsType: CLONE_NEWPID)
|
|
try Self.enterNS(path: "/proc/\(self.parentPid)/ns/uts", nsType: CLONE_NEWUTS)
|
|
try Self.enterNS(path: "/proc/\(self.parentPid)/ns/mnt", nsType: CLONE_NEWNS)
|
|
|
|
let childPipe = Pipe()
|
|
try childPipe.setCloexec()
|
|
let processID = fork()
|
|
|
|
guard processID != -1 else {
|
|
try? childPipe.fileHandleForReading.close()
|
|
try? childPipe.fileHandleForWriting.close()
|
|
try? syncfd.close()
|
|
|
|
throw App.Errno(stage: "fork")
|
|
}
|
|
|
|
if processID == 0 { // child
|
|
try childPipe.fileHandleForReading.close()
|
|
try syncfd.close()
|
|
|
|
guard setsid() != -1 else {
|
|
throw App.Errno(stage: "setsid()")
|
|
}
|
|
|
|
// Apply O_CLOEXEC to all file descriptors except stdio.
|
|
// This ensures that all unwanted fds we may have accidentally
|
|
// inherited are marked close-on-exec so they stay out of the
|
|
// container.
|
|
try App.applyCloseExecOnFDs()
|
|
try App.setRLimits(rlimits: process.rlimits)
|
|
|
|
// set uid, gid, and supplementary groups
|
|
try App.setPermissions(user: process.user)
|
|
|
|
if process.terminal {
|
|
guard ioctl(0, UInt(TIOCSCTTY), 0) != -1 else {
|
|
throw App.Errno(stage: "setctty()")
|
|
}
|
|
}
|
|
|
|
try App.exec(process: process)
|
|
} else { // parent process
|
|
try childPipe.fileHandleForWriting.close()
|
|
|
|
// wait until the pipe is closed then carry on.
|
|
_ = try childPipe.fileHandleForReading.readToEnd()
|
|
try childPipe.fileHandleForReading.close()
|
|
|
|
// send our child's pid to our parent before we exit.
|
|
var childPid = processID
|
|
let data = Data(bytes: &childPid, count: MemoryLayout.size(ofValue: childPid))
|
|
|
|
try syncfd.write(contentsOf: data)
|
|
try syncfd.close()
|
|
}
|
|
}
|
|
}
|