Skip to content

Commit 7cd8a36

Browse files
committed
updates
1 parent 7285ef1 commit 7cd8a36

5 files changed

Lines changed: 154 additions & 9 deletions

File tree

Sources/Containerization/LinuxContainer.swift

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1014,14 +1014,15 @@ extension LinuxContainer {
10141014
}
10151015

10161016
// Perform filesystem operations in the container.
1017-
public func filesystemOperation(operation: FilesystemOperation, path: String) async throws {
1017+
public func filesystemOperation(operation: FilesystemOperation, path: String) async throws -> UInt64? {
10181018
try await self.state.withLock {
10191019
let state = try $0.startedState("filesystemOperation")
1020-
try await state.vm.withAgent { agent in
1020+
return try await state.vm.withAgent { agent in
10211021
guard let vminitd = agent as? Vminitd else {
10221022
throw ContainerizationError(.unsupported, message: "filesystemOperation requires Vminitd agent")
10231023
}
1024-
try await vminitd.filesystemOperation(operation: operation, path: path)
1024+
let guestPath = URL(filePath: Self.guestRootfsPath(self.id)).appending(path: path).path
1025+
return try await vminitd.filesystemOperation(operation: operation, path: guestPath)
10251026
}
10261027
}
10271028
}

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
@@ -203,12 +203,19 @@ extension Vminitd: VirtualMachineAgent {
203203
}
204204

205205
/// Perform a filesystem operation on a path inside the sandbox's environment.
206-
public func filesystemOperation(operation: FilesystemOperation, path: String) async throws {
207-
_ = try await client.filesystemOperation(
206+
public func filesystemOperation(operation: FilesystemOperation, path: String) async throws -> UInt64? {
207+
let response = try await client.filesystemOperation(
208208
.with {
209209
$0.operation = operation.toProtoOperation()
210210
$0.path = path
211211
})
212+
213+
switch operation {
214+
case .trim:
215+
return response.trim.trimmedBytes
216+
case .freeze, .thaw:
217+
return nil
218+
}
212219
}
213220

214221
public func createProcess(

Sources/Integration/ContainerTests.swift

Lines changed: 139 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4034,7 +4034,7 @@ extension IntegrationSuite {
40344034
try await writerContainer.create()
40354035
try await writerContainer.start()
40364036

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

40394039
let writeExec = try await writerContainer.exec("write-hello") { config in
40404040
config.arguments = ["/bin/sh", "-c", "echo hello > /data/hello.txt"]
@@ -4052,13 +4052,13 @@ extension IntegrationSuite {
40524052
try? FileManager.default.removeItem(at: cloneImageURL)
40534053
}
40544054

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

40574057
try await writerContainer.kill(.kill)
40584058
_ = try await writerContainer.wait()
40594059
try await writerContainer.stop()
40604060
} catch {
4061-
try? await writerContainer.filesystemOperation(operation: .thaw, path: "/data")
4061+
_ = try? await writerContainer.filesystemOperation(operation: .thaw, path: "/data")
40624062
try? await writerContainer.stop()
40634063
throw error
40644064
}
@@ -4138,6 +4138,142 @@ extension IntegrationSuite {
41384138
}
41394139
}
41404140

4141+
func testTrimExt4Clone() async throws {
4142+
let id = "test-trim-ext4-clone"
4143+
let bs = try await bootstrap(id)
4144+
4145+
let diskImageURL = Self.testDir.appending(component: "\(id)-data.ext4")
4146+
try? FileManager.default.removeItem(at: diskImageURL)
4147+
4148+
let filesystem = try EXT4.Formatter(FilePath(diskImageURL.absolutePath()), minDiskSize: 64.mib())
4149+
try filesystem.close()
4150+
4151+
let cloneImageURL = Self.testDir.appending(component: "\(id)-data-clone.ext4")
4152+
try? FileManager.default.removeItem(at: cloneImageURL)
4153+
4154+
let writerContainer = try LinuxContainer("\(id)-writer", rootfs: bs.rootfs, vmm: bs.vmm) { config in
4155+
config.process.arguments = ["/bin/sleep", "1000"]
4156+
config.mounts.append(
4157+
Mount.block(
4158+
format: "ext4",
4159+
source: diskImageURL.absolutePath(),
4160+
destination: "/data"
4161+
))
4162+
config.bootLog = bs.bootLog
4163+
}
4164+
4165+
do {
4166+
try await writerContainer.create()
4167+
try await writerContainer.start()
4168+
4169+
let writeExec = try await writerContainer.exec("write-temp") { config in
4170+
config.arguments = [
4171+
"/bin/sh",
4172+
"-c",
4173+
"dd if=/dev/zero of=/data/trim.dat bs=1M count=8 status=none && sync && rm /data/trim.dat && sync",
4174+
]
4175+
}
4176+
try await writeExec.start()
4177+
let writeStatus = try await writeExec.wait()
4178+
try await writeExec.delete()
4179+
guard writeStatus.exitCode == 0 else {
4180+
throw IntegrationError.assert(msg: "trim setup exec failed with status \(writeStatus)")
4181+
}
4182+
4183+
let trimmedBytes = try await writerContainer.filesystemOperation(operation: .trim, path: "/data")
4184+
guard let trimmedBytes, trimmedBytes > 0 else {
4185+
throw IntegrationError.assert(msg: "expected trim to reclaim bytes")
4186+
}
4187+
4188+
try FileManager.default.copyItem(at: diskImageURL, to: cloneImageURL)
4189+
defer {
4190+
try? FileManager.default.removeItem(at: diskImageURL)
4191+
try? FileManager.default.removeItem(at: cloneImageURL)
4192+
}
4193+
4194+
try await writerContainer.kill(.kill)
4195+
_ = try await writerContainer.wait()
4196+
try await writerContainer.stop()
4197+
} catch {
4198+
try? await writerContainer.stop()
4199+
throw error
4200+
}
4201+
4202+
let verifyContainer = try LinuxContainer("\(id)-reader", rootfs: bs.rootfs, vmm: bs.vmm) { config in
4203+
config.mounts.append(
4204+
Mount.block(
4205+
format: "ext4",
4206+
source: cloneImageURL.absolutePath(),
4207+
destination: "/data"
4208+
))
4209+
config.process.arguments = ["/bin/sleep", "1000"]
4210+
config.bootLog = bs.bootLog
4211+
}
4212+
4213+
do {
4214+
try await verifyContainer.create()
4215+
try await verifyContainer.start()
4216+
4217+
let mountBuffer = BufferWriter()
4218+
let mountExec = try await verifyContainer.exec("verify-mount") { config in
4219+
config.arguments = ["/bin/sh", "-c", "grep ' /data ' /proc/mounts"]
4220+
config.stdout = mountBuffer
4221+
}
4222+
try await mountExec.start()
4223+
var status = try await mountExec.wait()
4224+
try await mountExec.delete()
4225+
guard status.exitCode == 0 else {
4226+
throw IntegrationError.assert(msg: "failed to verify /data mount, status \(status)")
4227+
}
4228+
4229+
let mountOutput = String(data: mountBuffer.data, encoding: .utf8) ?? ""
4230+
guard mountOutput.contains(" /data ") && mountOutput.contains(" ext4 ") else {
4231+
throw IntegrationError.assert(msg: "expected ext4 mount at /data, got: \(mountOutput)")
4232+
}
4233+
4234+
let dmesgBuffer = BufferWriter()
4235+
let dmesgExec = try await verifyContainer.exec("verify-dmesg-clean") { config in
4236+
config.arguments = [
4237+
"/bin/sh", "-c",
4238+
"if dmesg | grep -Eiq 'fsck|recovering journal|recovery complete'; then dmesg | grep -Ei 'fsck|recovering journal|recovery complete'; exit 1; fi",
4239+
]
4240+
config.stdout = dmesgBuffer
4241+
}
4242+
try await dmesgExec.start()
4243+
status = try await dmesgExec.wait()
4244+
try await dmesgExec.delete()
4245+
guard status.exitCode == 0 else {
4246+
let dmesgOutput = String(data: dmesgBuffer.data, encoding: .utf8) ?? ""
4247+
throw IntegrationError.assert(msg: "dmesg indicates filesystem recovery on cloned image: \(dmesgOutput)")
4248+
}
4249+
4250+
let lsBuffer = BufferWriter()
4251+
let lsExec = try await verifyContainer.exec("verify-no-hello") { config in
4252+
config.arguments = ["ls", "-1", "/data"]
4253+
config.stdout = lsBuffer
4254+
}
4255+
try await lsExec.start()
4256+
status = try await lsExec.wait()
4257+
try await lsExec.delete()
4258+
guard status.exitCode == 0 else {
4259+
throw IntegrationError.assert(msg: "ls /data failed with status \(status)")
4260+
}
4261+
4262+
let lsOutput = String(data: lsBuffer.data, encoding: .utf8) ?? ""
4263+
let listedFiles = Set(lsOutput.split(whereSeparator: \.isNewline).map(String.init))
4264+
guard !listedFiles.contains("trim.dat") else {
4265+
throw IntegrationError.assert(msg: "expected cloned /data to not contain trim.dat, got: \(lsOutput)")
4266+
}
4267+
4268+
try await verifyContainer.kill(.kill)
4269+
_ = try await verifyContainer.wait()
4270+
try await verifyContainer.stop()
4271+
} catch {
4272+
try? await verifyContainer.stop()
4273+
throw error
4274+
}
4275+
}
4276+
41414277
func testUseInitBasic() async throws {
41424278
let id = "test-use-init-basic"
41434279

Sources/Integration/Suite.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -351,6 +351,7 @@ struct IntegrationSuite: AsyncParsableCommand {
351351
Test("container writable layer size", testWritableLayerSize),
352352
Test("container writable layer DNS and hosts", testWritableLayerWithDNSAndHosts),
353353
Test("container frozen ext4 clone", testFrozenExt4Clone),
354+
Test("container trim ext4 clone", testTrimExt4Clone),
354355
Test("large stdin input", testLargeStdinInput),
355356
Test("exec large stdin input", testExecLargeStdinInput),
356357
Test("exec custom path resolution", testExecCustomPathResolution),

0 commit comments

Comments
 (0)