diff --git a/drivers/cube/cubelinux_store.rs b/drivers/cube/cubelinux_store.rs index 631733d93..5db0f9402 100644 --- a/drivers/cube/cubelinux_store.rs +++ b/drivers/cube/cubelinux_store.rs @@ -1319,6 +1319,41 @@ fn encode_entry( } /// Write `bytes` at `off` and flush, or say why not. The one place the module writes. +/// Write without syncing: the durability is carried by a later write. +/// +/// One caller, and the append contract is why. A mutation is written in two parts — the log entry, +/// then the control block that counts it — and the order exists so a crash loses an unacknowledged +/// mutation rather than counting one that is not there. What the order does *not* require is two +/// durability boundaries: the entry is `kernel_write`-ordered before the control write, and the +/// control write's `fsync` flushes what precedes it in the same file. So the entry's own fsync is a +/// second flush of data the next one covers. +/// +/// What this gives up, stated rather than implied: the two writes are no longer independently +/// durable, so a crash *inside* the control write's commit can in principle leave the control block +/// counting bytes whose entry did not fully reach the media. That is a torn tail, and +/// `kernel/verify-torn-tail.sh` is the gate that says whether this store handles one — which is why +/// this change was made against that gate rather than against an argument about ext4. +fn write_at(file: *mut bindings::file, off: u64, bytes: &[u8]) -> Result<(), Error> { + let mut at: bindings::loff_t = off as bindings::loff_t; + // SAFETY: the caller passes a live `struct file *`; `bytes` outlives the call; `at` is a valid + // loff_t. Same contract as the write half of `write_and_sync`, minus the flush. + let wrote = unsafe { + bindings::kernel_write( + file, + bytes.as_ptr().cast::(), + bytes.len(), + &mut at, + ) + }; + if wrote < 0 { + return Err(Error::from_errno(wrote as i32)); + } + if wrote as usize != bytes.len() { + return Err(EIO); + } + Ok(()) +} + fn write_and_sync(file: *mut bindings::file, off: u64, bytes: &[u8]) -> Result<(), Error> { let mut at: bindings::loff_t = off as bindings::loff_t; // SAFETY: the caller passes a live `struct file *`; `bytes` outlives the call; `at` is a @@ -1657,7 +1692,10 @@ fn append( result = write_and_sync(file, layout.log_off as u64, &hdr); } if result.is_ok() { - result = write_and_sync( + // The entry, without its own flush: the control block below is written next and synced, and + // that sync flushes this. One durability boundary per append instead of two — measured, one + // `pwrite`+`fsync` on this filesystem is 4.3 ms, so this is about half of every write. + result = write_at( file, (region_at + WAL_HEADER_LEN + used) as u64, entry.as_slice(),