Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

rust: support formatting of foreign types

Introduce a `fmt!` macro which wraps all arguments in
`kernel::fmt::Adapter` and a `kernel::fmt::Display` trait. This enables
formatting of foreign types (like `core::ffi::CStr`) that do not
implement `core::fmt::Display` due to concerns around lossy conversions
which do not apply in the kernel.

Suggested-by: Alice Ryhl <aliceryhl@google.com>
Link: https://rust-for-linux.zulipchat.com/#narrow/channel/288089-General/topic/Custom.20formatting/with/516476467
Acked-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Reviewed-by: Alice Ryhl <aliceryhl@google.com>
Acked-by: Danilo Krummrich <dakr@kernel.org>
Reviewed-by: Benno Lossin <lossin@kernel.org>
Signed-off-by: Tamir Duberstein <tamird@gmail.com>
Link: https://patch.msgid.link/20251018-cstr-core-v18-15-9378a54385f8@gmail.com
Signed-off-by: Miguel Ojeda <ojeda@kernel.org>

authored by

Tamir Duberstein and committed by
Miguel Ojeda
c5cf01ba 1dcd763b

+207 -3
+86 -1
rust/kernel/fmt.rs
··· 4 4 //! 5 5 //! This module is intended to be used in place of `core::fmt` in kernel code. 6 6 7 - pub use core::fmt::{Arguments, Debug, Display, Error, Formatter, Result, Write}; 7 + pub use core::fmt::{Arguments, Debug, Error, Formatter, Result, Write}; 8 + 9 + /// Internal adapter used to route allow implementations of formatting traits for foreign types. 10 + /// 11 + /// It is inserted automatically by the [`fmt!`] macro and is not meant to be used directly. 12 + /// 13 + /// [`fmt!`]: crate::prelude::fmt! 14 + #[doc(hidden)] 15 + pub struct Adapter<T>(pub T); 16 + 17 + macro_rules! impl_fmt_adapter_forward { 18 + ($($trait:ident),* $(,)?) => { 19 + $( 20 + impl<T: $trait> $trait for Adapter<T> { 21 + fn fmt(&self, f: &mut Formatter<'_>) -> Result { 22 + let Self(t) = self; 23 + $trait::fmt(t, f) 24 + } 25 + } 26 + )* 27 + }; 28 + } 29 + 30 + use core::fmt::{Binary, LowerExp, LowerHex, Octal, Pointer, UpperExp, UpperHex}; 31 + impl_fmt_adapter_forward!(Debug, LowerHex, UpperHex, Octal, Binary, Pointer, LowerExp, UpperExp); 32 + 33 + /// A copy of [`core::fmt::Display`] that allows us to implement it for foreign types. 34 + /// 35 + /// Types should implement this trait rather than [`core::fmt::Display`]. Together with the 36 + /// [`Adapter`] type and [`fmt!`] macro, it allows for formatting foreign types (e.g. types from 37 + /// core) which do not implement [`core::fmt::Display`] directly. 38 + /// 39 + /// [`fmt!`]: crate::prelude::fmt! 40 + pub trait Display { 41 + /// Same as [`core::fmt::Display::fmt`]. 42 + fn fmt(&self, f: &mut Formatter<'_>) -> Result; 43 + } 44 + 45 + impl<T: ?Sized + Display> Display for &T { 46 + fn fmt(&self, f: &mut Formatter<'_>) -> Result { 47 + Display::fmt(*self, f) 48 + } 49 + } 50 + 51 + impl<T: ?Sized + Display> core::fmt::Display for Adapter<&T> { 52 + fn fmt(&self, f: &mut Formatter<'_>) -> Result { 53 + let Self(t) = self; 54 + Display::fmt(t, f) 55 + } 56 + } 57 + 58 + macro_rules! impl_display_forward { 59 + ($( 60 + $( { $($generics:tt)* } )? $ty:ty $( { where $($where:tt)* } )? 61 + ),* $(,)?) => { 62 + $( 63 + impl$($($generics)*)? Display for $ty $(where $($where)*)? { 64 + fn fmt(&self, f: &mut Formatter<'_>) -> Result { 65 + core::fmt::Display::fmt(self, f) 66 + } 67 + } 68 + )* 69 + }; 70 + } 71 + 72 + impl_display_forward!( 73 + bool, 74 + char, 75 + core::panic::PanicInfo<'_>, 76 + Arguments<'_>, 77 + i128, 78 + i16, 79 + i32, 80 + i64, 81 + i8, 82 + isize, 83 + str, 84 + u128, 85 + u16, 86 + u32, 87 + u64, 88 + u8, 89 + usize, 90 + {<T: ?Sized>} crate::sync::Arc<T> {where crate::sync::Arc<T>: core::fmt::Display}, 91 + {<T: ?Sized>} crate::sync::UniqueArc<T> {where crate::sync::UniqueArc<T>: core::fmt::Display}, 92 + );
+1 -2
rust/kernel/prelude.rs
··· 25 25 pub use crate::alloc::{flags::*, Box, KBox, KVBox, KVVec, KVec, VBox, VVec, Vec}; 26 26 27 27 #[doc(no_inline)] 28 - pub use macros::{export, kunit_tests, module, vtable}; 28 + pub use macros::{export, fmt, kunit_tests, module, vtable}; 29 29 30 30 pub use pin_init::{init, pin_data, pin_init, pinned_drop, InPlaceWrite, Init, PinInit, Zeroable}; 31 31 ··· 36 36 pub use super::dbg; 37 37 pub use super::{dev_alert, dev_crit, dev_dbg, dev_emerg, dev_err, dev_info, dev_notice, dev_warn}; 38 38 pub use super::{pr_alert, pr_crit, pr_debug, pr_emerg, pr_err, pr_info, pr_notice, pr_warn}; 39 - pub use core::format_args as fmt; 40 39 41 40 pub use super::{try_init, try_pin_init}; 42 41
+94
rust/macros/fmt.rs
··· 1 + // SPDX-License-Identifier: GPL-2.0 2 + 3 + use proc_macro::{Ident, TokenStream, TokenTree}; 4 + use std::collections::BTreeSet; 5 + 6 + /// Please see [`crate::fmt`] for documentation. 7 + pub(crate) fn fmt(input: TokenStream) -> TokenStream { 8 + let mut input = input.into_iter(); 9 + 10 + let first_opt = input.next(); 11 + let first_owned_str; 12 + let mut names = BTreeSet::new(); 13 + let first_span = { 14 + let Some((mut first_str, first_span)) = (match first_opt.as_ref() { 15 + Some(TokenTree::Literal(first_lit)) => { 16 + first_owned_str = first_lit.to_string(); 17 + Some(first_owned_str.as_str()).and_then(|first| { 18 + let first = first.strip_prefix('"')?; 19 + let first = first.strip_suffix('"')?; 20 + Some((first, first_lit.span())) 21 + }) 22 + } 23 + _ => None, 24 + }) else { 25 + return first_opt.into_iter().chain(input).collect(); 26 + }; 27 + 28 + // Parse `identifier`s from the format string. 29 + // 30 + // See https://doc.rust-lang.org/std/fmt/index.html#syntax. 31 + while let Some((_, rest)) = first_str.split_once('{') { 32 + first_str = rest; 33 + if let Some(rest) = first_str.strip_prefix('{') { 34 + first_str = rest; 35 + continue; 36 + } 37 + if let Some((name, rest)) = first_str.split_once('}') { 38 + first_str = rest; 39 + let name = name.split_once(':').map_or(name, |(name, _)| name); 40 + if !name.is_empty() && !name.chars().all(|c| c.is_ascii_digit()) { 41 + names.insert(name); 42 + } 43 + } 44 + } 45 + first_span 46 + }; 47 + 48 + let adapter = quote_spanned!(first_span => ::kernel::fmt::Adapter); 49 + 50 + let mut args = TokenStream::from_iter(first_opt); 51 + { 52 + let mut flush = |args: &mut TokenStream, current: &mut TokenStream| { 53 + let current = std::mem::take(current); 54 + if !current.is_empty() { 55 + let (lhs, rhs) = (|| { 56 + let mut current = current.into_iter(); 57 + let mut acc = TokenStream::new(); 58 + while let Some(tt) = current.next() { 59 + // Split on `=` only once to handle cases like `a = b = c`. 60 + if matches!(&tt, TokenTree::Punct(p) if p.as_char() == '=') { 61 + names.remove(acc.to_string().as_str()); 62 + // Include the `=` itself to keep the handling below uniform. 63 + acc.extend([tt]); 64 + return (Some(acc), current.collect::<TokenStream>()); 65 + } 66 + acc.extend([tt]); 67 + } 68 + (None, acc) 69 + })(); 70 + args.extend(quote_spanned!(first_span => #lhs #adapter(&#rhs))); 71 + } 72 + }; 73 + 74 + let mut current = TokenStream::new(); 75 + for tt in input { 76 + match &tt { 77 + TokenTree::Punct(p) if p.as_char() == ',' => { 78 + flush(&mut args, &mut current); 79 + &mut args 80 + } 81 + _ => &mut current, 82 + } 83 + .extend([tt]); 84 + } 85 + flush(&mut args, &mut current); 86 + } 87 + 88 + for name in names { 89 + let name = Ident::new(name, first_span); 90 + args.extend(quote_spanned!(first_span => , #name = #adapter(&#name))); 91 + } 92 + 93 + quote_spanned!(first_span => ::core::format_args!(#args)) 94 + }
+19
rust/macros/lib.rs
··· 15 15 mod quote; 16 16 mod concat_idents; 17 17 mod export; 18 + mod fmt; 18 19 mod helpers; 19 20 mod kunit; 20 21 mod module; ··· 200 199 #[proc_macro_attribute] 201 200 pub fn export(attr: TokenStream, ts: TokenStream) -> TokenStream { 202 201 export::export(attr, ts) 202 + } 203 + 204 + /// Like [`core::format_args!`], but automatically wraps arguments in [`kernel::fmt::Adapter`]. 205 + /// 206 + /// This macro allows generating `fmt::Arguments` while ensuring that each argument is wrapped with 207 + /// `::kernel::fmt::Adapter`, which customizes formatting behavior for kernel logging. 208 + /// 209 + /// Named arguments used in the format string (e.g. `{foo}`) are detected and resolved from local 210 + /// bindings. All positional and named arguments are automatically wrapped. 211 + /// 212 + /// This macro is an implementation detail of other kernel logging macros like [`pr_info!`] and 213 + /// should not typically be used directly. 214 + /// 215 + /// [`kernel::fmt::Adapter`]: ../kernel/fmt/struct.Adapter.html 216 + /// [`pr_info!`]: ../kernel/macro.pr_info.html 217 + #[proc_macro] 218 + pub fn fmt(input: TokenStream) -> TokenStream { 219 + fmt::fmt(input) 203 220 } 204 221 205 222 /// Concatenate two identifiers.
+7
rust/macros/quote.rs
··· 48 48 ($span:expr => $($tt:tt)*) => {{ 49 49 let mut tokens = ::proc_macro::TokenStream::new(); 50 50 { 51 + #[allow(unused_variables)] 51 52 let span = $span; 52 53 quote_spanned!(@proc tokens span $($tt)*); 53 54 } ··· 144 143 (@proc $v:ident $span:ident # $($tt:tt)*) => { 145 144 $v.extend([::proc_macro::TokenTree::Punct( 146 145 ::proc_macro::Punct::new('#', ::proc_macro::Spacing::Alone), 146 + )]); 147 + quote_spanned!(@proc $v $span $($tt)*); 148 + }; 149 + (@proc $v:ident $span:ident & $($tt:tt)*) => { 150 + $v.extend([::proc_macro::TokenTree::Punct( 151 + ::proc_macro::Punct::new('&', ::proc_macro::Spacing::Alone), 147 152 )]); 148 153 quote_spanned!(@proc $v $span $($tt)*); 149 154 };