From 8352a381be0ce31eef2032cb16a68e60ba5b04f6 Mon Sep 17 00:00:00 2001 From: Yibo Zhuang Date: Thu, 19 Mar 2026 19:30:53 -0700 Subject: [PATCH] vmexec: create working directory if it doesn't exist This seems like standard OCI runtime behavior, which is to create the working directory if it doesn't exist. --- Sources/Integration/ContainerTests.swift | 81 ++++++++++++++++++++++++ Sources/Integration/Suite.swift | 2 + vminitd/Sources/vmexec/vmexec.swift | 10 +++ 3 files changed, 93 insertions(+) diff --git a/Sources/Integration/ContainerTests.swift b/Sources/Integration/ContainerTests.swift index 2ead9afc..97f5866e 100644 --- a/Sources/Integration/ContainerTests.swift +++ b/Sources/Integration/ContainerTests.swift @@ -4218,6 +4218,87 @@ extension IntegrationSuite { } } + func testWorkingDirCreated() async throws { + let id = "test-working-dir-created" + let bs = try await bootstrap(id) + + let buffer = BufferWriter() + let container = try LinuxContainer(id, rootfs: bs.rootfs, vmm: bs.vmm) { config in + config.process.arguments = ["/bin/pwd"] + config.process.workingDirectory = "/does/not/exist" + config.process.stdout = buffer + config.bootLog = bs.bootLog + } + + do { + try await container.create() + try await container.start() + + let status = try await container.wait() + try await container.stop() + + guard status.exitCode == 0 else { + throw IntegrationError.assert(msg: "process with non-existent workingDir failed: \(status)") + } + + guard let output = String(data: buffer.data, encoding: .utf8)?.trimmingCharacters(in: .whitespacesAndNewlines) else { + throw IntegrationError.assert(msg: "failed to read stdout") + } + + guard output == "/does/not/exist" else { + throw IntegrationError.assert(msg: "expected cwd '/does/not/exist', got '\(output)'") + } + } catch { + try? await container.stop() + throw error + } + } + + func testWorkingDirExecCreated() async throws { + let id = "test-working-dir-exec-created" + let bs = try await bootstrap(id) + + let container = try LinuxContainer(id, rootfs: bs.rootfs, vmm: bs.vmm) { config in + config.process.arguments = ["/bin/sleep", "1000"] + config.bootLog = bs.bootLog + } + + do { + try await container.create() + try await container.start() + + let buffer = BufferWriter() + let exec = try await container.exec("cwd-exec") { config in + config.arguments = ["/bin/pwd"] + config.workingDirectory = "/a/b/c/d" + config.stdout = buffer + } + + try await exec.start() + let status = try await exec.wait() + try await exec.delete() + + guard status.exitCode == 0 else { + throw IntegrationError.assert(msg: "exec with non-existent workingDir failed: \(status)") + } + + guard let output = String(data: buffer.data, encoding: .utf8)?.trimmingCharacters(in: .whitespacesAndNewlines) else { + throw IntegrationError.assert(msg: "failed to read stdout") + } + + guard output == "/a/b/c/d" else { + throw IntegrationError.assert(msg: "expected cwd '/a/b/c/d', got '\(output)'") + } + + try await container.kill(SIGKILL) + try await container.wait() + try await container.stop() + } catch { + try? await container.stop() + throw error + } + } + func testNoNewPrivilegesExec() async throws { let id = "test-no-new-privileges-exec" diff --git a/Sources/Integration/Suite.swift b/Sources/Integration/Suite.swift index 9e18c8ae..2f0a079d 100644 --- a/Sources/Integration/Suite.swift +++ b/Sources/Integration/Suite.swift @@ -374,6 +374,8 @@ struct IntegrationSuite: AsyncParsableCommand { Test("container noNewPrivileges", testNoNewPrivileges), Test("container noNewPrivileges disabled", testNoNewPrivilegesDisabled), Test("container noNewPrivileges exec", testNoNewPrivilegesExec), + Test("container workingDir created", testWorkingDirCreated), + Test("container workingDir exec created", testWorkingDirExecCreated), // Pods Test("pod single container", testPodSingleContainer), diff --git a/vminitd/Sources/vmexec/vmexec.swift b/vminitd/Sources/vmexec/vmexec.swift index dd716d26..b1778c37 100644 --- a/vminitd/Sources/vmexec/vmexec.swift +++ b/vminitd/Sources/vmexec/vmexec.swift @@ -96,6 +96,16 @@ extension App { let env = process.env.map { strdup($0) } + [nil] let cwd = process.cwd + // Create the working directory if it doesn't exist, this seems like the expected + // OCI runtime spec behavior. + if !FileManager.default.fileExists(atPath: cwd) { + try FileManager.default.createDirectory( + atPath: cwd, + withIntermediateDirectories: true, + attributes: [.posixPermissions: 0o755] + ) + } + guard chdir(cwd) == 0 else { throw App.Errno(stage: "chdir(cwd)", info: "failed to change directory to '\(cwd)'") }