Skip to content

Commit 76c7fd9

Browse files
committed
updates
1 parent bdfa0af commit 76c7fd9

5 files changed

Lines changed: 153 additions & 9 deletions

File tree

Sources/Containerization/LinuxContainer.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1044,15 +1044,15 @@ extension LinuxContainer {
10441044
}
10451045

10461046
// Perform filesystem operations in the container.
1047-
public func filesystemOperation(operation: FilesystemOperation, path: String) async throws {
1047+
public func filesystemOperation(operation: FilesystemOperation, path: String) async throws -> UInt64? {
10481048
try await self.state.withLock {
10491049
let state = try $0.startedState("filesystemOperation")
1050-
try await state.vm.withAgent { agent in
1050+
return try await state.vm.withAgent { agent in
10511051
guard let vminitd = agent as? Vminitd else {
10521052
throw ContainerizationError(.unsupported, message: "filesystemOperation requires Vminitd agent")
10531053
}
10541054
let guestPath = URL(filePath: Self.guestRootfsPath(self.id)).appending(path: path).path
1055-
try await vminitd.filesystemOperation(operation: operation, path: guestPath)
1055+
return try await vminitd.filesystemOperation(operation: operation, path: guestPath)
10561056
}
10571057
}
10581058
}

Sources/Containerization/VirtualMachineAgent.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ public protocol VirtualMachineAgent: Sendable {
4141
/// Close any resources held by the agent.
4242
func close() async throws
4343
// Perform a filesystem operation on the given path.
44-
func filesystemOperation(operation: FilesystemOperation, path: String) async throws
44+
func filesystemOperation(operation: FilesystemOperation, path: String) async throws -> UInt64?
4545

4646
// POSIX-y
4747
func getenv(key: String) async throws -> String

Sources/Containerization/Vminitd.swift

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -208,12 +208,19 @@ extension Vminitd: VirtualMachineAgent {
208208
}
209209

210210
/// Perform a filesystem operation on a path inside the sandbox's environment.
211-
public func filesystemOperation(operation: FilesystemOperation, path: String) async throws {
212-
_ = try await client.filesystemOperation(
211+
public func filesystemOperation(operation: FilesystemOperation, path: String) async throws -> UInt64? {
212+
let response = try await client.filesystemOperation(
213213
.with {
214214
$0.operation = operation.toProtoOperation()
215215
$0.path = path
216216
})
217+
218+
switch operation {
219+
case .trim:
220+
return response.trim.trimmedBytes
221+
case .freeze, .thaw:
222+
return nil
223+
}
217224
}
218225

219226
public func createProcess(

Sources/Integration/ContainerTests.swift

Lines changed: 139 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4243,7 +4243,7 @@ extension IntegrationSuite {
42434243
try await writerContainer.create()
42444244
try await writerContainer.start()
42454245

4246-
try await writerContainer.filesystemOperation(operation: .freeze, path: "/data")
4246+
_ = try await writerContainer.filesystemOperation(operation: .freeze, path: "/data")
42474247

42484248
let writeExec = try await writerContainer.exec("write-hello") { config in
42494249
config.arguments = ["/bin/sh", "-c", "echo hello > /data/hello.txt"]
@@ -4261,13 +4261,13 @@ extension IntegrationSuite {
42614261
try? FileManager.default.removeItem(at: cloneImageURL)
42624262
}
42634263

4264-
try await writerContainer.filesystemOperation(operation: .thaw, path: "/data")
4264+
_ = try await writerContainer.filesystemOperation(operation: .thaw, path: "/data")
42654265

42664266
try await writerContainer.kill(.kill)
42674267
_ = try await writerContainer.wait()
42684268
try await writerContainer.stop()
42694269
} catch {
4270-
try? await writerContainer.filesystemOperation(operation: .thaw, path: "/data")
4270+
_ = try? await writerContainer.filesystemOperation(operation: .thaw, path: "/data")
42714271
try? await writerContainer.stop()
42724272
throw error
42734273
}
@@ -4347,6 +4347,142 @@ extension IntegrationSuite {
43474347
}
43484348
}
43494349

4350+
func testTrimExt4Clone() async throws {
4351+
let id = "test-trim-ext4-clone"
4352+
let bs = try await bootstrap(id)
4353+
4354+
let diskImageURL = Self.testDir.appending(component: "\(id)-data.ext4")
4355+
try? FileManager.default.removeItem(at: diskImageURL)
4356+
4357+
let filesystem = try EXT4.Formatter(FilePath(diskImageURL.absolutePath()), minDiskSize: 64.mib())
4358+
try filesystem.close()
4359+
4360+
let cloneImageURL = Self.testDir.appending(component: "\(id)-data-clone.ext4")
4361+
try? FileManager.default.removeItem(at: cloneImageURL)
4362+
4363+
let writerContainer = try LinuxContainer("\(id)-writer", rootfs: bs.rootfs, vmm: bs.vmm) { config in
4364+
config.process.arguments = ["/bin/sleep", "1000"]
4365+
config.mounts.append(
4366+
Mount.block(
4367+
format: "ext4",
4368+
source: diskImageURL.absolutePath(),
4369+
destination: "/data"
4370+
))
4371+
config.bootLog = bs.bootLog
4372+
}
4373+
4374+
do {
4375+
try await writerContainer.create()
4376+
try await writerContainer.start()
4377+
4378+
let writeExec = try await writerContainer.exec("write-temp") { config in
4379+
config.arguments = [
4380+
"/bin/sh",
4381+
"-c",
4382+
"dd if=/dev/zero of=/data/trim.dat bs=1M count=8 status=none && sync && rm /data/trim.dat && sync",
4383+
]
4384+
}
4385+
try await writeExec.start()
4386+
let writeStatus = try await writeExec.wait()
4387+
try await writeExec.delete()
4388+
guard writeStatus.exitCode == 0 else {
4389+
throw IntegrationError.assert(msg: "trim setup exec failed with status \(writeStatus)")
4390+
}
4391+
4392+
let trimmedBytes = try await writerContainer.filesystemOperation(operation: .trim, path: "/data")
4393+
guard let trimmedBytes, trimmedBytes > 0 else {
4394+
throw IntegrationError.assert(msg: "expected trim to reclaim bytes")
4395+
}
4396+
4397+
try FileManager.default.copyItem(at: diskImageURL, to: cloneImageURL)
4398+
defer {
4399+
try? FileManager.default.removeItem(at: diskImageURL)
4400+
try? FileManager.default.removeItem(at: cloneImageURL)
4401+
}
4402+
4403+
try await writerContainer.kill(.kill)
4404+
_ = try await writerContainer.wait()
4405+
try await writerContainer.stop()
4406+
} catch {
4407+
try? await writerContainer.stop()
4408+
throw error
4409+
}
4410+
4411+
let verifyContainer = try LinuxContainer("\(id)-reader", rootfs: bs.rootfs, vmm: bs.vmm) { config in
4412+
config.mounts.append(
4413+
Mount.block(
4414+
format: "ext4",
4415+
source: cloneImageURL.absolutePath(),
4416+
destination: "/data"
4417+
))
4418+
config.process.arguments = ["/bin/sleep", "1000"]
4419+
config.bootLog = bs.bootLog
4420+
}
4421+
4422+
do {
4423+
try await verifyContainer.create()
4424+
try await verifyContainer.start()
4425+
4426+
let mountBuffer = BufferWriter()
4427+
let mountExec = try await verifyContainer.exec("verify-mount") { config in
4428+
config.arguments = ["/bin/sh", "-c", "grep ' /data ' /proc/mounts"]
4429+
config.stdout = mountBuffer
4430+
}
4431+
try await mountExec.start()
4432+
var status = try await mountExec.wait()
4433+
try await mountExec.delete()
4434+
guard status.exitCode == 0 else {
4435+
throw IntegrationError.assert(msg: "failed to verify /data mount, status \(status)")
4436+
}
4437+
4438+
let mountOutput = String(data: mountBuffer.data, encoding: .utf8) ?? ""
4439+
guard mountOutput.contains(" /data ") && mountOutput.contains(" ext4 ") else {
4440+
throw IntegrationError.assert(msg: "expected ext4 mount at /data, got: \(mountOutput)")
4441+
}
4442+
4443+
let dmesgBuffer = BufferWriter()
4444+
let dmesgExec = try await verifyContainer.exec("verify-dmesg-clean") { config in
4445+
config.arguments = [
4446+
"/bin/sh", "-c",
4447+
"if dmesg | grep -Eiq 'fsck|recovering journal|recovery complete'; then dmesg | grep -Ei 'fsck|recovering journal|recovery complete'; exit 1; fi",
4448+
]
4449+
config.stdout = dmesgBuffer
4450+
}
4451+
try await dmesgExec.start()
4452+
status = try await dmesgExec.wait()
4453+
try await dmesgExec.delete()
4454+
guard status.exitCode == 0 else {
4455+
let dmesgOutput = String(data: dmesgBuffer.data, encoding: .utf8) ?? ""
4456+
throw IntegrationError.assert(msg: "dmesg indicates filesystem recovery on cloned image: \(dmesgOutput)")
4457+
}
4458+
4459+
let lsBuffer = BufferWriter()
4460+
let lsExec = try await verifyContainer.exec("verify-no-hello") { config in
4461+
config.arguments = ["ls", "-1", "/data"]
4462+
config.stdout = lsBuffer
4463+
}
4464+
try await lsExec.start()
4465+
status = try await lsExec.wait()
4466+
try await lsExec.delete()
4467+
guard status.exitCode == 0 else {
4468+
throw IntegrationError.assert(msg: "ls /data failed with status \(status)")
4469+
}
4470+
4471+
let lsOutput = String(data: lsBuffer.data, encoding: .utf8) ?? ""
4472+
let listedFiles = Set(lsOutput.split(whereSeparator: \.isNewline).map(String.init))
4473+
guard !listedFiles.contains("trim.dat") else {
4474+
throw IntegrationError.assert(msg: "expected cloned /data to not contain trim.dat, got: \(lsOutput)")
4475+
}
4476+
4477+
try await verifyContainer.kill(.kill)
4478+
_ = try await verifyContainer.wait()
4479+
try await verifyContainer.stop()
4480+
} catch {
4481+
try? await verifyContainer.stop()
4482+
throw error
4483+
}
4484+
}
4485+
43504486
func testUseInitBasic() async throws {
43514487
let id = "test-use-init-basic"
43524488

Sources/Integration/Suite.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -355,6 +355,7 @@ struct IntegrationSuite: AsyncParsableCommand {
355355
Test("container writable layer size", testWritableLayerSize),
356356
Test("container writable layer DNS and hosts", testWritableLayerWithDNSAndHosts),
357357
Test("container frozen ext4 clone", testFrozenExt4Clone),
358+
Test("container trim ext4 clone", testTrimExt4Clone),
358359
Test("large stdin input", testLargeStdinInput),
359360
Test("exec large stdin input", testExecLargeStdinInput),
360361
Test("exec custom path resolution", testExecCustomPathResolution),

0 commit comments

Comments
 (0)