diff --git a/crates/resetprop/src/info.rs b/crates/resetprop/src/info.rs index d66e2db..635ddf7 100644 --- a/crates/resetprop/src/info.rs +++ b/crates/resetprop/src/info.rs @@ -368,6 +368,18 @@ impl<'a> PropInfo<'a> { return Err(Error::ValueTooLong { len: value.len() }); } + let old_len = { + let mut len = 0usize; + let max = self.area.len() - abs; + unsafe { + let ptr = self.area.base().add(abs); + while len < max && *ptr.add(len) != 0 { + len += 1; + } + } + len + }; + unsafe { let ptr = self.area.base().add(abs); std::ptr::copy_nonoverlapping(value.as_ptr(), ptr, value.len()); @@ -378,6 +390,10 @@ impl<'a> PropInfo<'a> { std::sync::atomic::fence(Ordering::Release); self.serial_atomic().store(new_serial, Ordering::Release); + if value.len() < old_len { + self.area.compact()?; + } + Ok(()) } diff --git a/crates/resetprop/src/mock.rs b/crates/resetprop/src/mock.rs index 0f930e4..b1c84fe 100644 --- a/crates/resetprop/src/mock.rs +++ b/crates/resetprop/src/mock.rs @@ -556,6 +556,29 @@ mod tests { assert_eq!(area.arena_stats().bytes_used, before); } + #[test] + fn set_quiet_shrinking_long_value_compacts_arena() { + let mock = MockArea::new(); + let area = mock.open(); + + let old = "a".repeat(92); + let new = "b".repeat(86); + area.set("ro.build.fingerprint", &old).unwrap(); + let before = area.arena_stats().bytes_used; + + area.set_quiet("ro.build.fingerprint", &new).unwrap(); + + assert_eq!(area.get("ro.build.fingerprint").unwrap(), new); + assert!( + area.arena_stats().bytes_used < before, + "shrinking a long value should reclaim its trailing allocation gap" + ); + assert!( + !area.compact().unwrap(), + "quiet long-value shrink should leave no hole to compact" + ); + } + #[test] fn hexpatch_duplicate_length_segments() { let mock = MockArea::new();