Skip to content

Commit 2a96a0d

Browse files
authored
Containerization: Always set TERM (#146)
Make sure we always set TERM for containers that ask for a tty. Right now this handling was spread around in a bunch of spots, but I'd wager setting it for the client on the host via LinuxContainer/Process is more sane and already what we do for a lot of the other fields.
1 parent 1992cfe commit 2a96a0d

File tree

10 files changed

+72
-11
lines changed

10 files changed

+72
-11
lines changed

Package.resolved

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Sources/Containerization/LinuxContainer.swift

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -411,12 +411,15 @@ extension LinuxContainer {
411411
set { config.spec.process!.rlimits = newValue }
412412
}
413413

414-
/// Set a pty device as the container's stdio.
414+
/// Set a pty device as the container's stdio. This additionally will
415+
/// set the TERM=xterm environment variable, and the OCI runtime specs
416+
/// `process.terminal` field to true.
415417
public var terminalDevice: Terminal? {
416418
get { config.terminal }
417419
set {
418420
config.spec.process!.terminal = newValue != nil ? true : false
419421
config.terminal = newValue
422+
config.spec.process!.env.append("TERM=xterm")
420423
config.ioHandlers.stdin = newValue
421424
config.ioHandlers.stdout = newValue
422425
config.ioHandlers.stderr = nil
@@ -426,7 +429,10 @@ extension LinuxContainer {
426429
/// If the container has a pty allocated.
427430
public var terminal: Bool {
428431
get { config.spec.process!.terminal }
429-
set { config.spec.process!.terminal = newValue }
432+
set {
433+
config.spec.process!.terminal = newValue
434+
config.spec.process!.env.append("TERM=xterm")
435+
}
430436
}
431437

432438
/// Set the stdin stream for the initial process of the container.

Sources/Containerization/LinuxProcess.swift

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,12 @@ public final class LinuxProcess: Sendable {
135135
/// be attached to the Process's Standard I/O.
136136
public var terminal: Bool {
137137
get { state.withLock { $0.spec.process!.terminal } }
138-
set { state.withLock { $0.spec.process!.terminal = newValue } }
138+
set {
139+
state.withLock {
140+
$0.spec.process!.terminal = newValue
141+
$0.spec.process!.env.append("TERM=xterm")
142+
}
143+
}
139144
}
140145

141146
/// The User a Process should execute under.

Sources/Integration/ProcessTests.swift

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import ArgumentParser
1818
import Containerization
1919
import ContainerizationOCI
20+
import ContainerizationOS
2021
import Crypto
2122
import Foundation
2223
import Logging
@@ -261,6 +262,44 @@ extension IntegrationSuite {
261262
}
262263
}
263264

265+
// Ensure if we ask for a terminal we set TERM.
266+
func testProcessTtyEnvvar() async throws {
267+
let id = "test-process-tty-envvar"
268+
269+
let bs = try await bootstrap()
270+
let container = LinuxContainer(
271+
id,
272+
rootfs: bs.rootfs,
273+
vmm: bs.vmm
274+
)
275+
container.arguments = ["env"]
276+
container.terminal = true
277+
278+
let buffer = BufferWriter()
279+
container.stdout = buffer
280+
281+
try await container.create()
282+
try await container.start()
283+
284+
let status = try await container.wait()
285+
try await container.stop()
286+
287+
guard status == 0 else {
288+
throw IntegrationError.assert(msg: "process status \(status) != 0")
289+
}
290+
291+
guard let str = String(data: buffer.data, encoding: .utf8) else {
292+
throw IntegrationError.assert(
293+
msg: "failed to convert standard output to a UTF8 string")
294+
}
295+
296+
let homeEnvvar = "TERM=xterm"
297+
guard str.contains(homeEnvvar) else {
298+
throw IntegrationError.assert(
299+
msg: "process should have TERM environment variable defined")
300+
}
301+
}
302+
264303
// Make sure we set HOME by default if we can find it in /etc/passwd in the guest.
265304
func testProcessHomeEnvvar() async throws {
266305
let id = "test-process-home-envvar"

Sources/Integration/Suite.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,7 @@ struct IntegrationSuite: AsyncParsableCommand {
200200
"process user": testProcessUser,
201201
"process home envvar": testProcessHomeEnvvar,
202202
"process custom home envvar": testProcessCustomHomeEnvvar,
203+
"process tty ensure TERM": testProcessTtyEnvvar,
203204
"multiple concurrent processes": testMultipleConcurrentProcesses,
204205
"multiple concurrent processes with output stress": testMultipleConcurrentProcessesOutputStress,
205206
"container hostname": testHostname,

Sources/cctl/RunCommand.swift

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,6 @@ extension Application {
100100

101101
container.terminalDevice = current
102102
container.arguments = arguments
103-
container.environment.append("TERM=xterm")
104103
container.workingDirectory = cwd
105104

106105
for mount in self.mounts {

vminitd/Package.resolved

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

vminitd/Sources/vminitd/ManagedProcess.swift

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,6 @@ final class ManagedProcess: Sendable {
104104
log.info("setting up terminal IO")
105105
let attrs = Command.Attrs(setsid: false, setctty: false)
106106
process.attrs = attrs
107-
process.environment.append("TERM=xterm")
108107
io = try TerminalIO(
109108
process: &process,
110109
stdio: stdio,

vminitd/Sources/vminitd/Server+GRPC.swift

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -850,6 +850,15 @@ extension Initd {
850850
process.env.append("HOME=\(parsedUser.home)")
851851
}
852852

853+
// Defensive programming a tad, but ensure we have TERM set if
854+
// the client requested a pty.
855+
if process.terminal {
856+
let termEnv = "TERM="
857+
if !process.env.contains(where: { $0.hasPrefix(termEnv) }) {
858+
process.env.append("TERM=xterm")
859+
}
860+
}
861+
853862
ociSpec.process = process
854863
}
855864
}

vminitd/Sources/vminitd/TerminalIO.swift

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,12 @@ final class TerminalIO: ManagedProcess.IO & Sendable {
4343
self.log = log
4444

4545
let ptyHandle = child.handle
46-
process.stdin = stdio.stdin != nil ? ptyHandle : nil
46+
let useHandles = stdio.stdin != nil || stdio.stdout != nil
47+
// We currently set stdin to the controlling terminal always, so
48+
// it must be a valid pty descriptor.
49+
process.stdin = useHandles ? ptyHandle : nil
4750

48-
let stdoutHandle = stdio.stdout != nil ? ptyHandle : nil
51+
let stdoutHandle = useHandles ? ptyHandle : nil
4952
process.stdout = stdoutHandle
5053
process.stderr = stdoutHandle
5154
}

0 commit comments

Comments
 (0)