@@ -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
0 commit comments