All of lore.kernel.org
 help / color / mirror / Atom feed
From: Gary Guo <gary@garyguo.net>
To: Miguel Ojeda <ojeda@kernel.org>
Cc: "Wedson Almeida Filho" <wedsonaf@gmail.com>,
	"Alex Gaynor" <alex.gaynor@gmail.com>,
	"Boqun Feng" <boqun.feng@gmail.com>,
	"Björn Roy Baron" <bjorn3_gh@protonmail.com>,
	rust-for-linux@vger.kernel.org, linux-kernel@vger.kernel.org,
	patches@lists.linux.dev
Subject: Re: [PATCH v1 21/28] rust: str: add `CString` type
Date: Mon, 14 Nov 2022 14:53:29 +0000	[thread overview]
Message-ID: <20221114145329.0f47a3ab@GaryWorkstation> (raw)
In-Reply-To: <20221110164152.26136-22-ojeda@kernel.org>

On Thu, 10 Nov 2022 17:41:33 +0100
Miguel Ojeda <ojeda@kernel.org> wrote:

> From: Wedson Almeida Filho <wedsonaf@gmail.com>
> 
> Add the `CString` type, which is an owned string that is guaranteed
> to have exactly one `NUL` byte at the end, i.e. the owned equivalent
> to `CStr` introduced earlier.
> 
> It is used for interoperability with kernel APIs that take C strings.
> 
> In order to do so, implement the `RawFormatter::new()` constructor
> and the `RawFormatter::bytes_written()` method as well.
> 
> Signed-off-by: Wedson Almeida Filho <wedsonaf@gmail.com>
> [Reworded, adapted for upstream and applied latest changes]
> Signed-off-by: Miguel Ojeda <ojeda@kernel.org>
> ---
>  rust/kernel/str.rs | 91 +++++++++++++++++++++++++++++++++++++++++++++-
>  1 file changed, 89 insertions(+), 2 deletions(-)
> 
> diff --git a/rust/kernel/str.rs b/rust/kernel/str.rs
> index db6473db31c6..877148b77e71 100644
> --- a/rust/kernel/str.rs
> +++ b/rust/kernel/str.rs
> @@ -2,6 +2,7 @@
>  
>  //! String representations.
>  
> +use alloc::vec::Vec;
>  use core::fmt::{self, Write};
>  use core::ops::{self, Deref, Index};
>  
> @@ -393,13 +394,22 @@ mod tests {
>  /// is less than `end`.
>  pub(crate) struct RawFormatter {
>      // Use `usize` to use `saturating_*` functions.
> -    #[allow(dead_code)]
>      beg: usize,
>      pos: usize,
>      end: usize,
>  }
>  
>  impl RawFormatter {
> +    /// Creates a new instance of [`RawFormatter`] with an empty buffer.
> +    fn new() -> Self {
> +        // INVARIANT: The buffer is empty, so the region that needs to be writable is empty.
> +        Self {
> +            beg: 0,
> +            pos: 0,
> +            end: 0,
> +        }
> +    }
> +
>      /// Creates a new instance of [`RawFormatter`] with the given buffer pointers.
>      ///
>      /// # Safety
> @@ -438,6 +448,11 @@ impl RawFormatter {
>      pub(crate) fn pos(&self) -> *mut u8 {
>          self.pos as _
>      }
> +
> +    /// Return the number of bytes written to the formatter.
> +    pub(crate) fn bytes_written(&self) -> usize {
> +        self.pos - self.beg
> +    }
>  }
>  
>  impl fmt::Write for RawFormatter {
> @@ -478,7 +493,6 @@ impl Formatter {
>      ///
>      /// The memory region starting at `buf` and extending for `len` bytes must be valid for writes
>      /// for the lifetime of the returned [`Formatter`].
> -    #[allow(dead_code)]
>      pub(crate) unsafe fn from_buffer(buf: *mut u8, len: usize) -> Self {
>          // SAFETY: The safety requirements of this function satisfy those of the callee.
>          Self(unsafe { RawFormatter::from_buffer(buf, len) })
> @@ -505,3 +519,76 @@ impl fmt::Write for Formatter {
>          }
>      }
>  }
> +
> +/// 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.
> +///
> +/// # Invariants
> +///
> +/// The string is always `NUL`-terminated and contains no other `NUL` bytes.
> +///
> +/// # Examples
> +///
> +/// ```
> +/// use kernel::str::CString;
> +///
> +/// let s = CString::try_from_fmt(fmt!("{}{}{}", "abc", 10, 20)).unwrap();
> +/// assert_eq!(s.as_bytes_with_nul(), "abc1020\0".as_bytes());
> +///
> +/// let tmp = "testing";
> +/// let s = CString::try_from_fmt(fmt!("{tmp}{}", 123)).unwrap();
> +/// assert_eq!(s.as_bytes_with_nul(), "testing123\0".as_bytes());
> +///
> +/// // This fails because it has an embedded `NUL` byte.
> +/// let s = CString::try_from_fmt(fmt!("a\0b{}", 123));
> +/// assert_eq!(s.is_ok(), false);
> +/// ```
> +pub struct CString {
> +    buf: Vec<u8>,
> +}
> +
> +impl CString {
> +    /// Creates an instance of [`CString`] from the given formatted arguments.
> +    pub fn try_from_fmt(args: fmt::Arguments<'_>) -> Result<Self, Error> {
> +        // Calculate the size needed (formatted string plus `NUL` terminator).
> +        let mut f = RawFormatter::new();
> +        f.write_fmt(args)?;
> +        f.write_str("\0")?;

I haven't checked the assembly, so this is possibly optimized out
already, but I feel that this line could be removed and we just use
`f.bytes_written() + 1` instead on the following line.

> +        let size = f.bytes_written();
> +
> +        // Allocate a vector with the required number of bytes, and write to it.
> +        let mut buf = Vec::try_with_capacity(size)?;
> +        // SAFETY: The buffer stored in `buf` is at least of size `size` and is valid for writes.
> +        let mut f = unsafe { Formatter::from_buffer(buf.as_mut_ptr(), size) };
> +        f.write_fmt(args)?;
> +        f.write_str("\0")?;
> +
> +        // SAFETY: The number of bytes that can be written to `f` is bounded by `size`, which is
> +        // `buf`'s capacity. The contents of the buffer have been initialised by writes to `f`.
> +        unsafe { buf.set_len(f.bytes_written()) };

`f.bytes_written() <= size` does not always hold. It holds here because
otherwise the `?` operator above would return an error early
(guaranteed by `impl Write for Formatter`). I feel that this fact is
not clearly stated in the safety comment but I don't have a good way to
rephrase this either.

> +
> +        // Check that there are no `NUL` bytes before the end.
> +        // SAFETY: The buffer is valid for read because `f.bytes_written()` is bounded by `size`
> +        // (which the minimum buffer size) and is non-zero (we wrote at least the `NUL` terminator)
> +        // so `f.bytes_written() - 1` doesn't underflow.
> +        let ptr = unsafe { bindings::memchr(buf.as_ptr().cast(), 0, (f.bytes_written() - 1) as _) };

How about just use `if buf[..(f.bytes_written() - 1)].contains(&0) {`
here? libcore specialises `<[u8]>::contains` to use a faster search
than `.iter().any(...)`.

> +        if !ptr.is_null() {
> +            return Err(EINVAL);
> +        }
> +
> +        // INVARIANT: We wrote the `NUL` terminator and checked above that no other `NUL` bytes
> +        // exist in the buffer.
> +        Ok(Self { buf })
> +    }
> +}
> +
> +impl Deref for CString {
> +    type Target = CStr;
> +
> +    fn deref(&self) -> &Self::Target {
> +        // SAFETY: The type invariants guarantee that the string is `NUL`-terminated and that no
> +        // other `NUL` bytes exist.
> +        unsafe { CStr::from_bytes_with_nul_unchecked(self.buf.as_slice()) }
> +    }
> +}


  reply	other threads:[~2022-11-14 14:53 UTC|newest]

Thread overview: 68+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2022-11-10 16:41 [PATCH v1 00/28] Rust core additions Miguel Ojeda
2022-11-10 16:41 ` [PATCH v1 01/28] rust: prelude: split re-exports into groups Miguel Ojeda
2022-11-10 18:05   ` Boqun Feng
2022-11-14 14:40   ` Wei Liu
2022-11-10 16:41 ` [PATCH v1 02/28] rust: print: add more `pr_*!` levels Miguel Ojeda
2022-11-10 18:12   ` Boqun Feng
2022-11-14 14:40   ` Wei Liu
2022-11-14 15:01     ` Sergio González Collado
2022-11-10 16:41 ` [PATCH v1 03/28] rust: print: add `pr_cont!` macro Miguel Ojeda
2022-11-14 14:40   ` Wei Liu
2022-11-14 15:04   ` Sergio González Collado
2022-11-10 16:41 ` [PATCH v1 04/28] rust: samples: add `rust_print` example Miguel Ojeda
2022-11-11  9:40   ` Finn Behrens
2022-11-11 17:31     ` Miguel Ojeda
2022-11-14 14:41   ` Wei Liu
2022-11-21 22:51   ` Sergio González Collado
2022-11-10 16:41 ` [PATCH v1 05/28] rust: macros: add `concat_idents!` proc macro Miguel Ojeda
2022-11-11  9:25   ` Finn Behrens
2022-11-14 14:26   ` Gary Guo
2022-11-14 14:39     ` Björn Roy Baron
2022-11-14 17:22     ` Miguel Ojeda
2022-11-10 16:41 ` [PATCH v1 06/28] rust: macros: add `#[vtable]` " Miguel Ojeda
2022-11-14 15:06   ` Sergio González Collado
2022-11-10 16:41 ` [PATCH v1 07/28] rust: macros: take string literals in `module!` Miguel Ojeda
2022-11-14 14:47   ` Wei Liu
2022-11-14 16:46     ` Miguel Ojeda
2022-11-14 17:25       ` Wei Liu
2022-11-10 16:41 ` [PATCH v1 08/28] rust: error: declare errors using macro Miguel Ojeda
2022-11-14 14:28   ` Gary Guo
2022-11-10 16:41 ` [PATCH v1 09/28] rust: error: add codes from `errno-base.h` Miguel Ojeda
2022-11-14 14:29   ` Gary Guo
2022-11-10 16:41 ` [PATCH v1 10/28] rust: error: add `From` implementations for `Error` Miguel Ojeda
2022-11-11  9:50   ` Finn Behrens
2022-11-10 16:41 ` [PATCH v1 11/28] rust: prelude: add `error::code::*` constant items Miguel Ojeda
2022-11-14 14:32   ` Gary Guo
2022-11-10 16:41 ` [PATCH v1 12/28] rust: alloc: add `RawVec::try_with_capacity_in()` constructor Miguel Ojeda
2022-11-14 14:34   ` Gary Guo
2022-11-10 16:41 ` [PATCH v1 13/28] rust: alloc: add `Vec::try_with_capacity{,_in}()` constructors Miguel Ojeda
2022-11-14 14:35   ` Gary Guo
2022-11-10 16:41 ` [PATCH v1 14/28] rust: str: add `BStr` type Miguel Ojeda
2022-11-10 16:41 ` [PATCH v1 15/28] rust: str: add `b_str!` macro Miguel Ojeda
2022-11-10 16:41 ` [PATCH v1 16/28] rust: str: add `CStr` type Miguel Ojeda
2022-11-10 16:41 ` [PATCH v1 17/28] rust: str: implement several traits for `CStr` Miguel Ojeda
2022-11-10 16:41 ` [PATCH v1 18/28] rust: str: add `CStr` unit tests Miguel Ojeda
2022-11-10 16:41 ` [PATCH v1 19/28] rust: str: add `c_str!` macro Miguel Ojeda
2022-11-14 14:39   ` Gary Guo
2022-11-14 18:28     ` Miguel Ojeda
2022-11-10 16:41 ` [PATCH v1 20/28] rust: str: add `Formatter` type Miguel Ojeda
2022-11-14 14:42   ` Gary Guo
2022-11-10 16:41 ` [PATCH v1 21/28] rust: str: add `CString` type Miguel Ojeda
2022-11-14 14:53   ` Gary Guo [this message]
2022-11-10 16:41 ` [PATCH v1 22/28] rust: str: add `fmt!` macro Miguel Ojeda
2022-11-14 14:58   ` Gary Guo
2022-11-10 16:41 ` [PATCH v1 23/28] rust: std_vendor: add `dbg!` macro based on `std`'s one Miguel Ojeda
2022-11-10 18:01   ` Boqun Feng
2022-11-10 19:14     ` Miguel Ojeda
2022-11-10 19:16       ` Boqun Feng
2022-11-10 19:20         ` Miguel Ojeda
2022-11-10 16:41 ` [PATCH v1 24/28] rust: static_assert: add `static_assert!` macro Miguel Ojeda
2022-11-10 16:41 ` [PATCH v1 25/28] rust: add `build_error` crate Miguel Ojeda
2022-11-14 14:30   ` Wei Liu
2022-11-14 18:22     ` Miguel Ojeda
2022-11-14 21:06       ` Wei Liu
2022-11-10 16:41 ` [PATCH v1 26/28] rust: build_assert: add `build_{error,assert}!` macros Miguel Ojeda
2022-11-10 16:41 ` [PATCH v1 27/28] rust: types: add `Either` type Miguel Ojeda
2022-11-14 14:32   ` Wei Liu
2022-11-10 16:41 ` [PATCH v1 28/28] rust: types: add `Opaque` type Miguel Ojeda
2022-11-14 15:03   ` Gary Guo

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20221114145329.0f47a3ab@GaryWorkstation \
    --to=gary@garyguo.net \
    --cc=alex.gaynor@gmail.com \
    --cc=bjorn3_gh@protonmail.com \
    --cc=boqun.feng@gmail.com \
    --cc=linux-kernel@vger.kernel.org \
    --cc=ojeda@kernel.org \
    --cc=patches@lists.linux.dev \
    --cc=rust-for-linux@vger.kernel.org \
    --cc=wedsonaf@gmail.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.