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