Commit cdde7a19 authored by Andreas Hindborg's avatar Andreas Hindborg Committed by Jens Axboe
Browse files

rust: str: introduce `NullTerminatedFormatter`



Add `NullTerminatedFormatter`, a formatter that writes a null terminated
string to an array or slice buffer. Because this type needs to manage the
trailing null marker, the existing formatters cannot be used to implement
this type.

Reviewed-by: default avatarAlice Ryhl <aliceryhl@google.com>
Reviewed-by: default avatarDaniel Almeida <daniel.almeida@collabora.com>
Signed-off-by: default avatarAndreas Hindborg <a.hindborg@kernel.org>
Link: https://lore.kernel.org/r/20250902-rnull-up-v6-16-v7-4-b5212cc89b98@kernel.org


Signed-off-by: default avatarJens Axboe <axboe@kernel.dk>
parent 8c5ac71c
Loading
Loading
Loading
Loading
+49 −0
Original line number Diff line number Diff line
@@ -871,6 +871,55 @@ fn write_str(&mut self, s: &str) -> fmt::Result {
    }
}

/// A mutable reference to a byte buffer where a string can be written into.
///
/// The buffer will be automatically null terminated after the last written character.
///
/// # Invariants
///
/// * The first byte of `buffer` is always zero.
/// * The length of `buffer` is at least 1.
pub(crate) struct NullTerminatedFormatter<'a> {
    buffer: &'a mut [u8],
}

impl<'a> NullTerminatedFormatter<'a> {
    /// Create a new [`Self`] instance.
    #[expect(dead_code)]
    pub(crate) fn new(buffer: &'a mut [u8]) -> Option<NullTerminatedFormatter<'a>> {
        *(buffer.first_mut()?) = 0;

        // INVARIANT:
        //  - We wrote zero to the first byte above.
        //  - If buffer was not at least length 1, `buffer.first_mut()` would return None.
        Some(Self { buffer })
    }
}

impl Write for NullTerminatedFormatter<'_> {
    fn write_str(&mut self, s: &str) -> fmt::Result {
        let bytes = s.as_bytes();
        let len = bytes.len();

        // We want space for a zero. By type invariant, buffer length is always at least 1, so no
        // underflow.
        if len > self.buffer.len() - 1 {
            return Err(fmt::Error);
        }

        let buffer = core::mem::take(&mut self.buffer);
        // We break the zero start invariant for a short while.
        buffer[..len].copy_from_slice(bytes);
        // INVARIANT: We checked above that buffer will have size at least 1 after this assignment.
        self.buffer = &mut buffer[len..];

        // INVARIANT: We write zero to the first byte of the buffer.
        self.buffer[0] = 0;

        Ok(())
    }
}

/// An owned string that is guaranteed to have exactly one `NUL` byte, which is at the end.
///
/// Used for interoperability with kernel APIs that take C strings.