Skip to content
Closed
Show file tree
Hide file tree
Changes from 7 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
51 changes: 42 additions & 9 deletions vminitd/Sources/vmexec/ExecCommand.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ import LCShim
import Logging
import Musl

#if canImport(Glibc)
import Glibc
#endif

struct ExecCommand: ParsableCommand {
static let configuration = CommandConfiguration(
commandName: "exec",
Expand Down Expand Up @@ -75,6 +79,7 @@ struct ExecCommand: ParsableCommand {

let childPipe = Pipe()
try childPipe.setCloexec()
var hostFd: Int32 = -1
let processID = fork()

guard processID != -1 else {
Expand All @@ -93,11 +98,45 @@ struct ExecCommand: ParsableCommand {
throw App.Errno(stage: "setsid()")
}

if process.terminal {
var containerFd: Int32 = 0
var ws = winsize(ws_row: 40, ws_col: 120, ws_xpixel: 0, ws_ypixel: 0)
guard openpty(&hostFd, &containerFd, nil, nil, &ws) == 0 else {
throw App.Errno(stage: "openpty()")
}

guard dup3(containerFd, 0, 0) != -1 else {
throw App.Errno(stage: "dup3(slave->stdin)")
}
guard dup3(containerFd, 1, 0) != -1 else {
throw App.Errno(stage: "dup3(slave->stdout)")
}
guard dup3(containerFd, 2, 0) != -1 else {
throw App.Errno(stage: "dup3(slave->stderr)")
}
_ = close(containerFd)

guard ioctl(0, UInt(TIOCSCTTY), 0) != -1 else {
throw App.Errno(stage: "setctty()")
}
}

var fdCopy = hostFd
let fdData = Data(bytes: &fdCopy, count: MemoryLayout.size(ofValue: fdCopy))
try childPipe.fileHandleForWriting.write(contentsOf: fdData)
try childPipe.fileHandleForWriting.close()

var ackBuf: UInt8 = 0
_ = read(4, &ackBuf, 1)
close(4)
close(hostFd)

// 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)

// Change stdio to be owned by the requested user.
Expand All @@ -106,12 +145,6 @@ struct ExecCommand: ParsableCommand {
// 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()
Expand All @@ -120,9 +153,9 @@ struct ExecCommand: ParsableCommand {
_ = 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))
// send our child's pid and the host fd to our parent before we exit.
var payload = [Int32(processID), hostFd]
let data = Data(bytes: &payload, count: MemoryLayout<Int32>.size * 2)

try syncfd.write(contentsOf: data)
try syncfd.close()
Expand Down
74 changes: 64 additions & 10 deletions vminitd/Sources/vmexec/RunCommand.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,14 @@ import ContainerizationOCI
import Foundation
import LCShim
import Logging

#if canImport(Musl)
import Musl
private let _ptsname = Musl.ptsname
#elseif canImport(Glibc)
import Glibc
private let _ptsname = Glibc.ptsname
#endif

struct RunCommand: ParsableCommand {
static let configuration = CommandConfiguration(
Expand Down Expand Up @@ -68,6 +75,7 @@ struct RunCommand: ParsableCommand {

let childPipe = Pipe()
try childPipe.setCloexec()
var hostFd: Int32 = -1
let processID = fork()

guard processID != -1 else {
Expand All @@ -92,6 +100,35 @@ struct RunCommand: ParsableCommand {

try childRootSetup(rootfs: root, mounts: spec.mounts, log: log)

if process.terminal {
let containerMount = ContainerMount(rootfs: root.path, mounts: spec.mounts)
try containerMount.configureConsole()
var containerFd: Int32 = 0
var ws = winsize(ws_row: 40, ws_col: 120, ws_xpixel: 0, ws_ypixel: 0)
guard openpty(&hostFd, &containerFd, nil, nil, &ws) == 0 else {
throw App.Errno(stage: "openpty()")
}

guard dup3(containerFd, 0, 0) != -1 else {
throw App.Errno(stage: "dup3(slave->stdin)")
}
guard dup3(containerFd, 1, 0) != -1 else {
throw App.Errno(stage: "dup3(slave->stdout)")
}
guard dup3(containerFd, 2, 0) != -1 else {
throw App.Errno(stage: "dup3(slave->stderr)")
}
_ = close(containerFd)

guard ioctl(0, UInt(TIOCSCTTY), 0) != -1 else {
throw App.Errno(stage: "setctty()")
}

if let cPtr = _ptsname(hostFd) {
try mountConsole(path: String(cString: cPtr))
}
}

if !spec.hostname.isEmpty {
let errCode = spec.hostname.withCString { ptr in
Musl.sethostname(ptr, spec.hostname.count)
Expand All @@ -101,6 +138,16 @@ struct RunCommand: ParsableCommand {
}
}

var fdCopy = hostFd
let fdData = Data(bytes: &fdCopy, count: MemoryLayout.size(ofValue: fdCopy))
try childPipe.fileHandleForWriting.write(contentsOf: fdData)
try childPipe.fileHandleForWriting.close()

var ackBuf: UInt8 = 0
_ = read(4, &ackBuf, 1)
close(4)
close(hostFd)

// 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
Expand All @@ -115,12 +162,6 @@ struct RunCommand: ParsableCommand {
// 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()
Expand All @@ -129,9 +170,9 @@ struct RunCommand: ParsableCommand {
_ = 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))
// send our child's pid and the host fd to our parent before we exit.
var payload = [Int32(processID), hostFd]
let data = Data(bytes: &payload, count: MemoryLayout<Int32>.size * 2)

try syncfd.write(contentsOf: data)
try syncfd.close()
Expand All @@ -141,7 +182,6 @@ struct RunCommand: ParsableCommand {
private func mountRootfs(rootfs: String, mounts: [ContainerizationOCI.Mount]) throws {
let containerMount = ContainerMount(rootfs: rootfs, mounts: mounts)
try containerMount.mountToRootfs()
try containerMount.configureConsole()
}

private func prepareRoot(rootfs: String) throws {
Expand Down Expand Up @@ -256,4 +296,18 @@ struct RunCommand: ParsableCommand {
_ = cStringCopy.initialize(from: cString)
return UnsafeMutablePointer(cStringCopy.baseAddress)
}

private func mountConsole(path: String) throws {
let console = "/dev/console"
if access(console, F_OK) != 0 {
let fd = open(console, O_RDWR | O_CREAT, mode_t(UInt16(0o600)))
guard fd != -1 else {
throw App.Errno(stage: "open(/dev/console)")
}
close(fd)
}
guard mount(path, console, "", UInt(MS_BIND), nil) == 0 else {
throw App.Errno(stage: "mount(console)")
}
}
}
42 changes: 35 additions & 7 deletions vminitd/Sources/vminitd/ManagedProcess.swift
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ final class ManagedProcess: Sendable {
private let lock: Mutex<State>
private let syncfd: Pipe
private let owningPid: Int32?
private let ack: FileHandle
private let terminal: Bool

private struct State {
init(io: IO) {
Expand All @@ -53,6 +55,7 @@ final class ManagedProcess: Sendable {
// swiftlint: disable type_name
protocol IO {
func start() throws
func attach(pid: Int32, fd: Int32) throws
func closeAfterExec() throws
func resize(size: Terminal.Size) throws
func close() throws
Expand All @@ -75,11 +78,16 @@ final class ManagedProcess: Sendable {
Self.localizeLogger(log: &log, id: id)
self.log = log
self.owningPid = owningPid
self.terminal = stdio.terminal

let syncfd = Pipe()
try syncfd.setCloexec()
self.syncfd = syncfd

let ackPipe = Pipe()
try ackPipe.setCloexec()
self.ack = ackPipe.fileHandleForWriting

let args: [String]
if let owningPid {
args = [
Expand All @@ -96,7 +104,7 @@ final class ManagedProcess: Sendable {
var process = Command(
"/sbin/vmexec",
arguments: args,
extraFiles: [syncfd.fileHandleForWriting]
extraFiles: [syncfd.fileHandleForWriting, ackPipe.fileHandleForReading]
)

var io: IO
Expand All @@ -118,11 +126,6 @@ final class ManagedProcess: Sendable {
)
}

log.info("starting io")

// Setup IO early. We expect the host to be listening already.
try io.start()

self.process = process
self.lock = Mutex(State(io: io))
}
Expand All @@ -137,6 +140,10 @@ extension ManagedProcess {
"id": "\(id)"
])

if !self.terminal {
try $0.io.start()
}

// Start the underlying process.
try process.start()

Expand All @@ -148,20 +155,41 @@ extension ManagedProcess {
throw ContainerizationError(.internalError, message: "no pid data from sync pipe")
}

guard piddata.count >= MemoryLayout<Int32>.size else {
throw ContainerizationError(.internalError, message: "invalid payload")
}

let i = piddata.withUnsafeBytes { ptr in
ptr.load(as: Int32.self)
}

log.info("got back pid data \(i)")
var fd: Int32 = -1
if piddata.count >= MemoryLayout<Int32>.size * 2 {
fd = piddata.withUnsafeBytes { ptr in
ptr.load(fromByteOffset: MemoryLayout<Int32>.size, as: Int32.self)
}
}

log.info("got back pid data \(i), fd \(fd)")
$0.pid = i

if self.terminal {
guard fd != -1 else {
throw ContainerizationError(.internalError, message: "vmexec did not return pty fd")
}
try $0.io.attach(pid: i, fd: fd)
}

log.debug(
"started managed process",
metadata: [
"pid": "\(i)",
"id": "\(id)",
])

try self.ack.write(contentsOf: Data([0]))
try self.ack.close()

return i
}
}
Expand Down
3 changes: 3 additions & 0 deletions vminitd/Sources/vminitd/StandardIO.swift
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,9 @@ final class StandardIO: ManagedProcess.IO & Sendable {
}
}

// NOP
func attach(pid: Int32, fd: Int32) throws {}

// NOP
func resize(size: Terminal.Size) throws {}

Expand Down
Loading