Skip to content

Commit 19d1fa1

Browse files
committed
mount /dev/console
1 parent ebd2856 commit 19d1fa1

File tree

6 files changed

+192
-79
lines changed

6 files changed

+192
-79
lines changed

vminitd/Sources/vmexec/ExecCommand.swift

Lines changed: 42 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,10 @@ import LCShim
2121
import Logging
2222
import Musl
2323

24+
#if canImport(Glibc)
25+
import Glibc
26+
#endif
27+
2428
struct ExecCommand: ParsableCommand {
2529
static let configuration = CommandConfiguration(
2630
commandName: "exec",
@@ -75,6 +79,7 @@ struct ExecCommand: ParsableCommand {
7579

7680
let childPipe = Pipe()
7781
try childPipe.setCloexec()
82+
var hostFd: Int32 = -1
7883
let processID = fork()
7984

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

101+
if process.terminal {
102+
var containerFd: Int32 = 0
103+
var ws = winsize(ws_row: 40, ws_col: 120, ws_xpixel: 0, ws_ypixel: 0)
104+
guard openpty(&hostFd, &containerFd, nil, nil, &ws) == 0 else {
105+
throw App.Errno(stage: "openpty()")
106+
}
107+
108+
guard dup3(containerFd, 0, 0) != -1 else {
109+
throw App.Errno(stage: "dup3(slave->stdin)")
110+
}
111+
guard dup3(containerFd, 1, 0) != -1 else {
112+
throw App.Errno(stage: "dup3(slave->stdout)")
113+
}
114+
guard dup3(containerFd, 2, 0) != -1 else {
115+
throw App.Errno(stage: "dup3(slave->stderr)")
116+
}
117+
_ = close(containerFd)
118+
119+
guard ioctl(0, UInt(TIOCSCTTY), 0) != -1 else {
120+
throw App.Errno(stage: "setctty()")
121+
}
122+
}
123+
124+
var fdCopy = hostFd
125+
let fdData = Data(bytes: &fdCopy, count: MemoryLayout.size(ofValue: fdCopy))
126+
try childPipe.fileHandleForWriting.write(contentsOf: fdData)
127+
try childPipe.fileHandleForWriting.close()
128+
129+
var ackBuf: UInt8 = 0
130+
_ = read(4, &ackBuf, 1)
131+
close(4)
132+
close(hostFd)
133+
96134
// Apply O_CLOEXEC to all file descriptors except stdio.
97135
// This ensures that all unwanted fds we may have accidentally
98136
// inherited are marked close-on-exec so they stay out of the
99137
// container.
100138
try App.applyCloseExecOnFDs()
139+
101140
try App.setRLimits(rlimits: process.rlimits)
102141

103142
// Change stdio to be owned by the requested user.
@@ -106,12 +145,6 @@ struct ExecCommand: ParsableCommand {
106145
// Set uid, gid, and supplementary groups
107146
try App.setPermissions(user: process.user)
108147

109-
if process.terminal {
110-
guard ioctl(0, UInt(TIOCSCTTY), 0) != -1 else {
111-
throw App.Errno(stage: "setctty()")
112-
}
113-
}
114-
115148
try App.exec(process: process)
116149
} else { // parent process
117150
try childPipe.fileHandleForWriting.close()
@@ -120,9 +153,9 @@ struct ExecCommand: ParsableCommand {
120153
_ = try childPipe.fileHandleForReading.readToEnd()
121154
try childPipe.fileHandleForReading.close()
122155

123-
// send our child's pid to our parent before we exit.
124-
var childPid = processID
125-
let data = Data(bytes: &childPid, count: MemoryLayout.size(ofValue: childPid))
156+
// send our child's pid and the host fd to our parent before we exit.
157+
var payload = [Int32(processID), hostFd]
158+
let data = Data(bytes: &payload, count: MemoryLayout<Int32>.size * 2)
126159

127160
try syncfd.write(contentsOf: data)
128161
try syncfd.close()

vminitd/Sources/vmexec/Mount.swift

Lines changed: 1 addition & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,6 @@ import ContainerizationOCI
1818
import ContainerizationOS
1919
import Foundation
2020
import Musl
21-
#if canImport(Glibc)
22-
import Glibc
23-
#endif
2421

2522
struct ContainerMount {
2623
private let mounts: [ContainerizationOCI.Mount]
@@ -38,7 +35,7 @@ struct ContainerMount {
3835
}
3936
}
4037

41-
func configureConsole(process: ContainerizationOCI.Process) throws {
38+
func configureConsole() throws {
4239
let ptmx = self.rootfs.standardizingPath.appendingPathComponent("/dev/ptmx")
4340

4441
guard remove(ptmx) == 0 else {
@@ -47,29 +44,6 @@ struct ContainerMount {
4744
guard symlink("pts/ptmx", ptmx) == 0 else {
4845
throw App.Errno(stage: "symlink(pts/ptmx)")
4946
}
50-
51-
if process.terminal {
52-
var buf = [CChar](repeating: 0, count: 4096)
53-
let len = readlink("/proc/self/fd/0", &buf, buf.count - 1)
54-
if len != -1 {
55-
buf[Int(len)] = 0
56-
let ptyPath = String(cString: buf)
57-
58-
let console = self.rootfs.standardizingPath.appendingPathComponent("/dev/console")
59-
60-
if access(console, F_OK) != 0 {
61-
let fd = open(console, O_RDWR | O_CREAT, mode_t(UInt16(0o600)))
62-
if fd == -1 {
63-
throw App.erno(stage: "open(/dev/console)")
64-
}
65-
close(fd)
66-
}
67-
68-
if mount(ptyPath, console, "", UInt(MS_BIND), nil) != 0 {
69-
throw App.Errno(stage: "mount(/dev/console)")
70-
}
71-
}
72-
}
7347
}
7448

7549
private func mkdirAll(_ name: String, _ perm: Int16) throws {

vminitd/Sources/vmexec/RunCommand.swift

Lines changed: 66 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,14 @@ import LCShim
2121
import Logging
2222
import Musl
2323

24+
#if canImport(Musl)
25+
import Musl
26+
private let _ptsname = Musl.ptsname
27+
#elseif canImport(Glibc)
28+
import Glibc
29+
private let _ptsname = Glibc.ptsname
30+
#endif
31+
2432
struct RunCommand: ParsableCommand {
2533
static let configuration = CommandConfiguration(
2634
commandName: "run",
@@ -39,10 +47,10 @@ struct RunCommand: ParsableCommand {
3947
try execInNamespace(spec: ociSpec, log: log)
4048
}
4149

42-
private func childRootSetup(rootfs: ContainerizationOCI.Root, mounts: [ContainerizationOCI.Mount], process: ContainerizationOCI.Process, log: Logger) throws {
50+
private func childRootSetup(rootfs: ContainerizationOCI.Root, mounts: [ContainerizationOCI.Mount], log: Logger) throws {
4351
// setup rootfs
4452
try prepareRoot(rootfs: rootfs.path)
45-
try mountRootfs(rootfs: rootfs.path, mounts: mounts, process: process)
53+
try mountRootfs(rootfs: rootfs.path, mounts: mounts)
4654
try setDevSymlinks(rootfs: rootfs.path)
4755

4856
try pivotRoot(rootfs: rootfs.path)
@@ -68,6 +76,7 @@ struct RunCommand: ParsableCommand {
6876

6977
let childPipe = Pipe()
7078
try childPipe.setCloexec()
79+
var hostFd: Int32 = -1
7180
let processID = fork()
7281

7382
guard processID != -1 else {
@@ -90,7 +99,36 @@ struct RunCommand: ParsableCommand {
9099
throw App.Errno(stage: "setsid()")
91100
}
92101

93-
try childRootSetup(rootfs: root, mounts: spec.mounts, log: log, process: process)
102+
try childRootSetup(rootfs: root, mounts: spec.mounts, log: log)
103+
104+
if process.terminal {
105+
let containerMount = ContainerMount(rootfs: root.path, mounts: spec.mounts)
106+
try containerMount.configureConsole()
107+
var containerFd: Int32 = 0
108+
var ws = winsize(ws_row: 40, ws_col: 120, ws_xpixel: 0, ws_ypixel: 0)
109+
guard openpty(&hostFd, &containerFd, nil, nil, &ws) == 0 else {
110+
throw App.Errno(stage: "openpty()")
111+
}
112+
113+
guard dup3(containerFd, 0, 0) != -1 else {
114+
throw App.Errno(stage: "dup3(slave->stdin)")
115+
}
116+
guard dup3(containerFd, 1, 0) != -1 else {
117+
throw App.Errno(stage: "dup3(slave->stdout)")
118+
}
119+
guard dup3(containerFd, 2, 0) != -1 else {
120+
throw App.Errno(stage: "dup3(slave->stderr)")
121+
}
122+
_ = close(containerFd)
123+
124+
guard ioctl(0, UInt(TIOCSCTTY), 0) != -1 else {
125+
throw App.Errno(stage: "setctty()")
126+
}
127+
128+
if let cPtr = _ptsname(hostFd) {
129+
mountConsole(path: String(cString: cPtr))
130+
}
131+
}
94132

95133
if !spec.hostname.isEmpty {
96134
let errCode = spec.hostname.withCString { ptr in
@@ -101,6 +139,16 @@ struct RunCommand: ParsableCommand {
101139
}
102140
}
103141

142+
var fdCopy = hostFd
143+
var fdData = Data(bytes: &fdCopy, count: MemoryLayout.size(ofValue: fdCopy))
144+
try childPipe.fileHandleForWriting.write(contentsOf: fdData)
145+
try childPipe.fileHandleForWriting.close()
146+
147+
var ackBuf: UInt8 = 0
148+
_ = read(4, &ackBuf, 1)
149+
close(4)
150+
close(hostFd)
151+
104152
// Apply O_CLOEXEC to all file descriptors except stdio.
105153
// This ensures that all unwanted fds we may have accidentally
106154
// inherited are marked close-on-exec so they stay out of the
@@ -115,12 +163,6 @@ struct RunCommand: ParsableCommand {
115163
// Set uid, gid, and supplementary groups.
116164
try App.setPermissions(user: process.user)
117165

118-
if process.terminal {
119-
guard ioctl(0, UInt(TIOCSCTTY), 0) != -1 else {
120-
throw App.Errno(stage: "setctty()")
121-
}
122-
}
123-
124166
try App.exec(process: process)
125167
} else { // parent process
126168
try childPipe.fileHandleForWriting.close()
@@ -129,19 +171,18 @@ struct RunCommand: ParsableCommand {
129171
_ = try childPipe.fileHandleForReading.readToEnd()
130172
try childPipe.fileHandleForReading.close()
131173

132-
// send our child's pid to our parent before we exit.
133-
var childPid = processID
134-
let data = Data(bytes: &childPid, count: MemoryLayout.size(ofValue: childPid))
174+
// send our child's pid and the host fd to our parent before we exit.
175+
var payload = [Int32(processID), hostFd]
176+
let data = Data(bytes: &payload, count: MemoryLayout<Int32>.size * 2)
135177

136178
try syncfd.write(contentsOf: data)
137179
try syncfd.close()
138180
}
139181
}
140182

141-
private func mountRootfs(rootfs: String, mounts: [ContainerizationOCI.Mount], process: ContainerizationOCI.Process) throws {
183+
private func mountRootfs(rootfs: String, mounts: [ContainerizationOCI.Mount]) throws {
142184
let containerMount = ContainerMount(rootfs: rootfs, mounts: mounts)
143185
try containerMount.mountToRootfs()
144-
try containerMount.configureConsole(process)
145186
}
146187

147188
private func prepareRoot(rootfs: String) throws {
@@ -256,4 +297,15 @@ struct RunCommand: ParsableCommand {
256297
_ = cStringCopy.initialize(from: cString)
257298
return UnsafeMutablePointer(cStringCopy.baseAddress)
258299
}
300+
301+
private func mountConsole(path: String) {
302+
let console = "/dev/console"
303+
if access(console, F_OK) != 0 {
304+
let fd = open(console, O_RDWR | O_CREAT, mode_t(UInt16(0o600)))
305+
if fd != -1 {
306+
close(fd)
307+
}
308+
}
309+
_ = mount(path, console, "", UInt(MS_BIND), nil)
310+
}
259311
}

vminitd/Sources/vminitd/ManagedProcess.swift

Lines changed: 35 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ final class ManagedProcess: Sendable {
3131
private let lock: Mutex<State>
3232
private let syncfd: Pipe
3333
private let owningPid: Int32?
34+
private let ack: FileHandle
35+
private let terminal: Bool
3436

3537
private struct State {
3638
init(io: IO) {
@@ -53,6 +55,7 @@ final class ManagedProcess: Sendable {
5355
// swiftlint: disable type_name
5456
protocol IO {
5557
func start() throws
58+
func attach(pid: Int32, fd: Int32) throws
5659
func closeAfterExec() throws
5760
func resize(size: Terminal.Size) throws
5861
func close() throws
@@ -75,11 +78,16 @@ final class ManagedProcess: Sendable {
7578
Self.localizeLogger(log: &log, id: id)
7679
self.log = log
7780
self.owningPid = owningPid
81+
self.terminal = stdio.terminal
7882

7983
let syncfd = Pipe()
8084
try syncfd.setCloexec()
8185
self.syncfd = syncfd
8286

87+
let ackPipe = Pipe()
88+
try ackPipe.setCloexec()
89+
self.ack = ackPipe.fileHandleForWriting
90+
8391
let args: [String]
8492
if let owningPid {
8593
args = [
@@ -96,7 +104,7 @@ final class ManagedProcess: Sendable {
96104
var process = Command(
97105
"/sbin/vmexec",
98106
arguments: args,
99-
extraFiles: [syncfd.fileHandleForWriting]
107+
extraFiles: [syncfd.fileHandleForWriting, ackPipe.fileHandleForReading]
100108
)
101109

102110
var io: IO
@@ -118,11 +126,6 @@ final class ManagedProcess: Sendable {
118126
)
119127
}
120128

121-
log.info("starting io")
122-
123-
// Setup IO early. We expect the host to be listening already.
124-
try io.start()
125-
126129
self.process = process
127130
self.lock = Mutex(State(io: io))
128131
}
@@ -137,6 +140,10 @@ extension ManagedProcess {
137140
"id": "\(id)"
138141
])
139142

143+
if !self.terminal {
144+
try $0.io.start()
145+
}
146+
140147
// Start the underlying process.
141148
try process.start()
142149

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

158+
guard piddata.count >= MemoryLayout<Int32>.size else {
159+
throw ContainerizationError(.internalError, message: "invalid payload")
160+
}
161+
151162
let i = piddata.withUnsafeBytes { ptr in
152163
ptr.load(as: Int32.self)
153164
}
154165

155-
log.info("got back pid data \(i)")
166+
var fd: Int32 = -1
167+
if piddata.count >= MemoryLayout<Int32>.size * 2 {
168+
fd = piddata.withUnsafeBytes { ptr in
169+
ptr.load(fromByteOffset: MemoryLayout<Int32>.size, as: Int32.self)
170+
}
171+
}
172+
173+
log.info("got back pid data \(i), fd \(fd)")
156174
$0.pid = i
157175

176+
if self.terminal {
177+
guard fd != -1 else {
178+
throw ContainerizationError(.internalError, message: "vmexec did not return pty fd")
179+
}
180+
try self.terminal.attach(pid: i, fd: fd)
181+
}
182+
158183
log.debug(
159184
"started managed process",
160185
metadata: [
161186
"pid": "\(i)",
162187
"id": "\(id)",
163188
])
164189

190+
try self.ack.write(contentsOf: Data([0]))
191+
try self.ack.close()
192+
165193
return i
166194
}
167195
}

0 commit comments

Comments
 (0)