Skip to main content

kernel/
fmt.rs

1// SPDX-License-Identifier: GPL-2.0
2
3//! Formatting utilities.
4//!
5//! This module is intended to be used in place of `core::fmt` in kernel code.
6
7use kernel::prelude::*;
8
9pub use core::fmt::{
10    Arguments,
11    Debug,
12    Error,
13    Formatter,
14    Result,
15    Write, //
16};
17
18/// Internal adapter used to route and allow implementations of formatting traits for foreign types.
19///
20/// It is inserted automatically by the [`fmt!`] macro and is not meant to be used directly.
21///
22/// [`fmt!`]: crate::prelude::fmt!
23#[doc(hidden)]
24pub struct Adapter<T>(pub T);
25
26macro_rules! impl_fmt_adapter_forward {
27    ($($trait:ident),* $(,)?) => {
28        $(
29            impl<T: $trait> $trait for Adapter<T> {
30                fn fmt(&self, f: &mut Formatter<'_>) -> Result {
31                    let Self(t) = self;
32                    $trait::fmt(t, f)
33                }
34            }
35        )*
36    };
37}
38
39use core::fmt::{
40    Binary,
41    LowerExp,
42    LowerHex,
43    Octal,
44    UpperExp,
45    UpperHex, //
46};
47use core::ptr::NonNull;
48impl_fmt_adapter_forward!(Debug, LowerHex, UpperHex, Octal, Binary, LowerExp, UpperExp);
49
50/// A copy of [`core::fmt::Pointer`] that allows implementing pointer formatting for foreign types.
51///
52/// Together with the [`Adapter`] type and [`fmt!`] macro, it enables raw pointer formatting to be
53/// intercepted and routed to [`HashedPtr`] (kernel's `%p` hashed format), preventing kernel address
54/// leaks.
55///
56/// [`fmt!`]: crate::prelude::fmt!
57pub trait Pointer {
58    /// Same as [`core::fmt::Pointer::fmt`].
59    fn fmt(&self, f: &mut Formatter<'_>) -> Result;
60}
61
62/// A wrapper for pointers that formats them using kernel's `%p` format specifier.
63///
64/// By default, `%p` prints a hashed representation of the pointer address to prevent kernel address
65/// leaks. When the `no_hash_pointers` kernel command-line parameter is enabled, the real address is
66/// printed instead (for debugging purposes).
67pub struct HashedPtr<T: ?Sized>(pub *const T);
68
69impl<T: ?Sized> Pointer for HashedPtr<T> {
70    fn fmt(&self, f: &mut Formatter<'_>) -> Result {
71        use crate::str::CStrExt as _;
72
73        let mut buf = [0u8; 32];
74
75        // Use `%#0*p` for the `0x` prefix and zero-padding; `+2` compensates for
76        // the prefix counting toward the field width.
77        let default_width = (2 * size_of::<usize>() + 2) as c_int;
78        let width = match (f.sign_aware_zero_pad(), f.width()) {
79            (true, Some(w)) if w > 0 => w.min(buf.len() - 1) as c_int,
80            _ => default_width,
81        };
82
83        // SAFETY: `buf` is a valid, writable 32-byte buffer, sufficient for
84        // all architectures (max 19 bytes for 64-bit under the default width).
85        // The format string is null-terminated; `width` (c_int) and pointer
86        // match the `%*` and `%p` specifiers.
87        let len = unsafe {
88            crate::bindings::scnprintf(
89                buf.as_mut_ptr().cast(),
90                buf.len(),
91                c"%#0*p".as_char_ptr(),
92                width,
93                self.0.cast::<c_void>(),
94            )
95        };
96
97        // SAFETY: `%#0*p` produces only ASCII, which is valid UTF-8.
98        let s = unsafe { core::str::from_utf8_unchecked(&buf[..len as usize]) };
99
100        if f.sign_aware_zero_pad() {
101            // `scnprintf` already applied the width and zero-padding via `%#0*p`.
102            f.write_str(s)
103        } else {
104            f.pad(s)
105        }
106    }
107}
108
109// Raw pointers are formatted via `HashedPtr` (kernel `%p`: hashed by default, plain with
110// `no_hash_pointers`).
111impl<T: ?Sized> Pointer for *const T {
112    #[inline]
113    fn fmt(&self, f: &mut Formatter<'_>) -> Result {
114        Pointer::fmt(&HashedPtr(*self), f)
115    }
116}
117
118impl<T: ?Sized> Pointer for *mut T {
119    #[inline]
120    fn fmt(&self, f: &mut Formatter<'_>) -> Result {
121        Pointer::fmt(&HashedPtr(*self), f)
122    }
123}
124
125impl<T: ?Sized> Pointer for &T {
126    #[inline]
127    fn fmt(&self, f: &mut Formatter<'_>) -> Result {
128        Pointer::fmt(&HashedPtr(*self), f)
129    }
130}
131
132impl<T: ?Sized> Pointer for &mut T {
133    #[inline]
134    fn fmt(&self, f: &mut Formatter<'_>) -> Result {
135        Pointer::fmt(&HashedPtr(core::ptr::from_ref(*self)), f)
136    }
137}
138
139impl<T: ?Sized> Pointer for NonNull<T> {
140    #[inline]
141    fn fmt(&self, f: &mut Formatter<'_>) -> Result {
142        Pointer::fmt(&HashedPtr(self.as_ptr()), f)
143    }
144}
145
146// `Adapter<&T>` bridges our `Pointer` trait to `core::fmt::Pointer`
147impl<T: Pointer> core::fmt::Pointer for Adapter<&T> {
148    #[inline]
149    fn fmt(&self, f: &mut Formatter<'_>) -> Result {
150        Pointer::fmt(self.0, f)
151    }
152}
153
154/// A copy of [`core::fmt::Display`] that allows us to implement it for foreign types.
155///
156/// Types should implement this trait rather than [`core::fmt::Display`]. Together with the
157/// [`Adapter`] type and [`fmt!`] macro, it allows for formatting foreign types (e.g. types from
158/// core) which do not implement [`core::fmt::Display`] directly.
159///
160/// [`fmt!`]: crate::prelude::fmt!
161pub trait Display {
162    /// Same as [`core::fmt::Display::fmt`].
163    fn fmt(&self, f: &mut Formatter<'_>) -> Result;
164}
165
166impl<T: ?Sized + Display> Display for &T {
167    fn fmt(&self, f: &mut Formatter<'_>) -> Result {
168        Display::fmt(*self, f)
169    }
170}
171
172impl<T: ?Sized + Display> core::fmt::Display for Adapter<&T> {
173    fn fmt(&self, f: &mut Formatter<'_>) -> Result {
174        let Self(t) = self;
175        Display::fmt(t, f)
176    }
177}
178
179macro_rules! impl_display_forward {
180    ($(
181        $( { $($generics:tt)* } )? $ty:ty $( { where $($where:tt)* } )?
182    ),* $(,)?) => {
183        $(
184            impl$($($generics)*)? Display for $ty $(where $($where)*)? {
185                fn fmt(&self, f: &mut Formatter<'_>) -> Result {
186                    core::fmt::Display::fmt(self, f)
187                }
188            }
189        )*
190    };
191}
192
193impl_display_forward!(
194    bool,
195    char,
196    core::panic::PanicInfo<'_>,
197    Arguments<'_>,
198    i128,
199    i16,
200    i32,
201    i64,
202    i8,
203    isize,
204    str,
205    u128,
206    u16,
207    u32,
208    u64,
209    u8,
210    usize,
211    {<T: ?Sized>} crate::sync::Arc<T> {where crate::sync::Arc<T>: core::fmt::Display},
212    {<T: ?Sized>} crate::sync::UniqueArc<T> {where crate::sync::UniqueArc<T>: core::fmt::Display},
213);
214
215#[macros::kunit_tests(rust_kernel_fmt)]
216mod tests {
217    use crate::{
218        bindings,
219        prelude::fmt,
220        str::CString, //
221    };
222
223    #[cfg(CONFIG_64BIT)]
224    mod expected {
225        pub(super) const PTR_VALUE: usize = 0xffffffffdeadbeef;
226        pub(super) const PTR_VAL_NO_CRNG: &str = "(____ptrval____)";
227        pub(super) const HASHED_PREFIX: &str = "0x00000000";
228        pub(super) const RAW_POINTER: &str = "0xffffffffdeadbeef";
229        pub(super) const PADDED_RIGHT: &str = "      0xffffffffdeadbeef";
230        pub(super) const ZERO_PADDED: &str = "0x000000ffffffffdeadbeef";
231        pub(super) const HASHED_PADDED_RIGHT_PREFIX: &str = "      ";
232        pub(super) const HASHED_ZERO_PADDED_PREFIX: &str = "0x00000000000000";
233        pub(super) const CLAMPED: &str = "0x0000000000000ffffffffdeadbeef";
234    }
235
236    #[cfg(not(CONFIG_64BIT))]
237    mod expected {
238        pub(super) const PTR_VALUE: usize = 0xdeadbeef;
239        pub(super) const PTR_VAL_NO_CRNG: &str = "(ptrval)";
240        pub(super) const HASHED_PREFIX: &str = "0x";
241        pub(super) const RAW_POINTER: &str = "0xdeadbeef";
242        pub(super) const PADDED_RIGHT: &str = "              0xdeadbeef";
243        pub(super) const ZERO_PADDED: &str = "0x00000000000000deadbeef";
244        pub(super) const HASHED_PADDED_RIGHT_PREFIX: &str = "              ";
245        pub(super) const HASHED_ZERO_PADDED_PREFIX: &str = "0x00000000000000";
246        pub(super) const CLAMPED: &str = "0x0000000000000000000000deadbeef";
247    }
248
249    #[test]
250    fn test_ptr_formatting() -> core::result::Result<(), crate::error::Error> {
251        let ptr: *const u8 = core::ptr::without_provenance(expected::PTR_VALUE);
252
253        // SAFETY: `no_hash_pointers` is a global variable that is never concurrently modified —
254        // KUnit tests may run at boot (before `mark_readonly()`) or manually afterwards (when the
255        // variable is read-only). Reading is always safe.
256        let no_hash = unsafe { bindings::no_hash_pointers };
257
258        if no_hash {
259            let cstr = CString::try_from_fmt(fmt!("{:p}", ptr))?;
260            assert_eq!(cstr.to_str()?, expected::RAW_POINTER);
261
262            let cstr = CString::try_from_fmt(fmt!("{:>24p}", ptr))?;
263            assert_eq!(cstr.to_str()?, expected::PADDED_RIGHT);
264
265            let cstr = CString::try_from_fmt(fmt!("{:024p}", ptr))?;
266            assert_eq!(cstr.to_str()?, expected::ZERO_PADDED);
267
268            let cstr = CString::try_from_fmt(fmt!("{:0100p}", ptr))?;
269            assert_eq!(cstr.to_str()?, expected::CLAMPED);
270        } else {
271            let cstr = CString::try_from_fmt(fmt!("{:p}", ptr))?;
272            let formatted = cstr.to_str()?;
273            // If the RNG is not yet ready, `%p` falls back to a placeholder.
274            if formatted == expected::PTR_VAL_NO_CRNG {
275                return Ok(());
276            }
277            assert!(formatted.starts_with(expected::HASHED_PREFIX));
278            assert_ne!(formatted, expected::RAW_POINTER);
279
280            let cstr = CString::try_from_fmt(fmt!("{:>24p}", ptr))?;
281            assert!(cstr
282                .to_str()?
283                .starts_with(expected::HASHED_PADDED_RIGHT_PREFIX));
284
285            let cstr = CString::try_from_fmt(fmt!("{:024p}", ptr))?;
286            assert!(cstr
287                .to_str()?
288                .starts_with(expected::HASHED_ZERO_PADDED_PREFIX));
289
290            let cstr = CString::try_from_fmt(fmt!("{:0100p}", ptr))?;
291            let output = cstr.to_str()?;
292            assert!(output.starts_with("0x"));
293            assert!(!output[2..].chars().all(|c| c == '0'));
294        }
295
296        Ok(())
297    }
298}