Skip to content

Commit afe1122

Browse files
committed
mount /dev/console
1 parent bdba5b5 commit afe1122

File tree

5 files changed

+187
-48
lines changed

5 files changed

+187
-48
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/RunCommand.swift

Lines changed: 62 additions & 10 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",
@@ -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 {
@@ -92,6 +101,35 @@ struct RunCommand: ParsableCommand {
92101

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

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+
}
132+
95133
if !spec.hostname.isEmpty {
96134
let errCode = spec.hostname.withCString { ptr in
97135
Musl.sethostname(ptr, spec.hostname.count)
@@ -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,9 +171,9 @@ 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()
@@ -141,7 +183,6 @@ struct RunCommand: ParsableCommand {
141183
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()
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
}

vminitd/Sources/vminitd/StandardIO.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,9 @@ final class StandardIO: ManagedProcess.IO & Sendable {
119119
}
120120
}
121121

122+
// NOP
123+
func attach(pid: Int32, fd: Int32) throws {}
124+
122125
// NOP
123126
func resize(size: Terminal.Size) throws {}
124127

0 commit comments

Comments
 (0)