[PATCH 3/5] rust: print: accept level in `print_macro` instead of expression

Gary Guo <[email protected]> Tue, 28 Jul 2026 15:52:10 +0100
Newsgroups org.kernel.vger.rust-for-linux,org.kernel.vger.linux-kernel
Message-ID <[email protected]>
Currently, `print_macro` (and `call_printk`) requires format string to be
one of the predefined ones for soundness. Define a new marker type for each
log level and encode the property on trait `level::Level` instead. This
makes `call_printk` safe.

Signed-off-by: Gary Guo <[email protected]>
---
 rust/kernel/print.rs | 105 +++++++++++++++++++++++----------------------------
 1 file changed, 48 insertions(+), 57 deletions(-)

diff --git a/rust/kernel/print.rs b/rust/kernel/print.rs
index 470c1001f37d..2c82f5e82700 100644
--- a/rust/kernel/print.rs
+++ b/rust/kernel/print.rs
@@ -34,16 +34,16 @@
     w.pos().cast()
 }
 
-/// Format strings.
+/// Format levels.
 ///
 /// Public but hidden since it should only be used from public macros.
 #[doc(hidden)]
-pub mod format_strings {
-    /// The length we copy from the `KERN_*` kernel prefixes.
-    const LENGTH_PREFIX: usize = 2;
+pub mod level {
+    use core::ffi::CStr;
 
-    /// The length of the fixed format strings.
-    pub const LENGTH: usize = 10;
+    pub trait Level {
+        const FORMAT_STRING: &'static CStr;
+    }
 
     /// Generates a fixed format string for the kernel's [`_printk`].
     ///
@@ -51,58 +51,58 @@ pub mod format_strings {
     /// given `prefix`, which are the kernel's `KERN_*` constants.
     ///
     /// [`_printk`]: srctree/include/linux/printk.h
-    const fn generate(prefix: &[u8; 3]) -> [u8; LENGTH] {
+    const fn generate(prefix: &[u8; 3]) -> [u8; 10] {
         // Ensure the `KERN_*` macros are what we expect.
+        assert!(prefix.len() == 3);
         assert!(prefix[0] == b'\x01');
         assert!(prefix[1] >= b'0' && prefix[1] <= b'7');
-        assert!(prefix[2] == b'\x00');
 
-        let suffix: &[u8; LENGTH - LENGTH_PREFIX] = b"%s: %pA\0";
+        let mut fmt = *b"\0\0%s: %pA\0";
+        fmt[0] = prefix[0];
+        fmt[1] = prefix[1];
+        fmt
+    }
 
-        [
-            prefix[0], prefix[1], suffix[0], suffix[1], suffix[2], suffix[3], suffix[4], suffix[5],
-            suffix[6], suffix[7],
-        ]
+    // #[rustfmt::skip] // Rustfmt formats the macro awkwardly.
+    macro_rules! define_level {
+        ($lvl:ident) => {
+            pub struct $lvl;
+
+            impl Level for $lvl {
+                const FORMAT_STRING: &'static CStr = match CStr::from_bytes_with_nul(&generate(
+                    macros::paste!(bindings::[<KERN_ $lvl>]),
+                )) {
+                    Ok(v) => v,
+                    Err(_) => unreachable!(),
+                };
+            }
+        };
     }
 
-    // Generate the format strings at compile-time.
-    //
-    // This avoids the compiler generating the contents on the fly in the stack.
-    //
-    // Furthermore, `static` instead of `const` is used to share the strings
-    // for all the kernel.
-    pub static EMERG: [u8; LENGTH] = generate(bindings::KERN_EMERG);
-    pub static ALERT: [u8; LENGTH] = generate(bindings::KERN_ALERT);
-    pub static CRIT: [u8; LENGTH] = generate(bindings::KERN_CRIT);
-    pub static ERR: [u8; LENGTH] = generate(bindings::KERN_ERR);
-    pub static WARNING: [u8; LENGTH] = generate(bindings::KERN_WARNING);
-    pub static NOTICE: [u8; LENGTH] = generate(bindings::KERN_NOTICE);
-    pub static INFO: [u8; LENGTH] = generate(bindings::KERN_INFO);
-    pub static DEBUG: [u8; LENGTH] = generate(bindings::KERN_DEBUG);
+    define_level!(EMERG);
+    define_level!(ALERT);
+    define_level!(CRIT);
+    define_level!(ERR);
+    define_level!(WARNING);
+    define_level!(NOTICE);
+    define_level!(INFO);
+    define_level!(DEBUG);
 }
 
 /// Prints a message via the kernel's [`_printk`].
 ///
 /// Public but hidden since it should only be used from public macros.
 ///
-/// # Safety
-///
-/// The format string must be one of the ones in [`format_strings`].
-///
 /// [`_printk`]: srctree/include/linux/_printk.h
 #[doc(hidden)]
 #[cfg_attr(not(CONFIG_PRINTK), allow(unused_variables))]
-pub unsafe fn call_printk(
-    format_string: &[u8; format_strings::LENGTH],
-    module_name: &CStr,
-    args: fmt::Arguments<'_>,
-) {
+pub fn call_printk<Lvl: level::Level>(module_name: &CStr, args: fmt::Arguments<'_>) {
     // `_printk` does not seem to fail in any path.
     #[cfg(CONFIG_PRINTK)]
     // SAFETY: TODO.
     unsafe {
         bindings::_printk(
-            format_string.as_ptr(),
+            Lvl::FORMAT_STRING.as_char_ptr(),
             module_name.as_char_ptr(),
             core::ptr::from_ref(&args).cast::<c_void>(),
         );
@@ -147,19 +147,10 @@ pub fn call_printk_cont(args: fmt::Arguments<'_>) {
 #[macro_export]
 #[expect(clippy::crate_in_macro_def)]
 macro_rules! print_macro (
-    ($format_string:path, $($arg:tt)+) => (
-        // To remain sound, `arg`s must be expanded outside the `unsafe` block.
-        // Typically one would use a `let` binding for that; however, `format_args!`
-        // takes borrows on the arguments, but does not extend the scope of temporaries.
-        // Therefore, a `match` expression is used to keep them around, since
-        // the scrutinee is kept until the end of the `match`.
+    ($level:ident, $($arg:tt)+) => (
         match $crate::prelude::fmt!($($arg)+) {
-            // SAFETY: This hidden macro should only be called by the documented
-            // printing macros which ensure the format string is one of the fixed
-            // ones.
-            args => unsafe {
-                $crate::print::call_printk(
-                    &$format_string,
+            args => {
+                $crate::print::call_printk::<$crate::print::level::$level>(
                     crate::__LOG_PREFIX,
                     args,
                 );
@@ -207,7 +198,7 @@ macro_rules! print_macro (
 #[macro_export]
 macro_rules! pr_emerg (
     ($($arg:tt)*) => (
-        $crate::print_macro!($crate::print::format_strings::EMERG, $($arg)*)
+        $crate::print_macro!(EMERG, $($arg)*)
     )
 );
 
@@ -232,7 +223,7 @@ macro_rules! pr_emerg (
 #[macro_export]
 macro_rules! pr_alert (
     ($($arg:tt)*) => (
-        $crate::print_macro!($crate::print::format_strings::ALERT, $($arg)*)
+        $crate::print_macro!(ALERT, $($arg)*)
     )
 );
 
@@ -257,7 +248,7 @@ macro_rules! pr_alert (
 #[macro_export]
 macro_rules! pr_crit (
     ($($arg:tt)*) => (
-        $crate::print_macro!($crate::print::format_strings::CRIT, $($arg)*)
+        $crate::print_macro!(CRIT, $($arg)*)
     )
 );
 
@@ -282,7 +273,7 @@ macro_rules! pr_crit (
 #[macro_export]
 macro_rules! pr_err (
     ($($arg:tt)*) => (
-        $crate::print_macro!($crate::print::format_strings::ERR, $($arg)*)
+        $crate::print_macro!(ERR, $($arg)*)
     )
 );
 
@@ -307,7 +298,7 @@ macro_rules! pr_err (
 #[macro_export]
 macro_rules! pr_warn (
     ($($arg:tt)*) => (
-        $crate::print_macro!($crate::print::format_strings::WARNING, $($arg)*)
+        $crate::print_macro!(WARNING, $($arg)*)
     )
 );
 
@@ -332,7 +323,7 @@ macro_rules! pr_warn (
 #[macro_export]
 macro_rules! pr_notice (
     ($($arg:tt)*) => (
-        $crate::print_macro!($crate::print::format_strings::NOTICE, $($arg)*)
+        $crate::print_macro!(NOTICE, $($arg)*)
     )
 );
 
@@ -358,7 +349,7 @@ macro_rules! pr_notice (
 #[doc(alias = "print")]
 macro_rules! pr_info (
     ($($arg:tt)*) => (
-        $crate::print_macro!($crate::print::format_strings::INFO, $($arg)*)
+        $crate::print_macro!(INFO, $($arg)*)
     )
 );
 
@@ -386,7 +377,7 @@ macro_rules! pr_info (
 macro_rules! pr_debug (
     ($($arg:tt)*) => (
         if cfg!(debug_assertions) {
-            $crate::print_macro!($crate::print::format_strings::DEBUG, $($arg)*)
+            $crate::print_macro!(DEBUG, $($arg)*)
         }
     )
 );

-- 
2.54.0