1//! Efficient decimal integer formatting.
2//!
3//! # Safety
4//!
5//! This uses `CStr::from_bytes_with_nul_unchecked` and
6//! `str::from_utf8_unchecked`on the buffer that it filled itself.
7#![allow(unsafe_code)]
8
9use crate::backend::fd::{AsFd, AsRawFd};
10use crate::ffi::CStr;
11#[cfg(feature = "std")]
12use core::fmt;
13use core::fmt::Write;
14use itoa::{Buffer, Integer};
15#[cfg(feature = "std")]
16use std::ffi::OsStr;
17#[cfg(feature = "std")]
18#[cfg(unix)]
19use std::os::unix::ffi::OsStrExt;
20#[cfg(feature = "std")]
21#[cfg(target_os = "wasi")]
22use std::os::wasi::ffi::OsStrExt;
23#[cfg(feature = "std")]
24use std::path::Path;
25
26/// Format an integer into a decimal `Path` component, without constructing a
27/// temporary `PathBuf` or `String`.
28///
29/// This is used for opening paths such as `/proc/self/fd/<fd>` on Linux.
30///
31/// # Example
32///
33/// ```rust
34/// # #[cfg(feature = "path")]
35/// use rustix::path::DecInt;
36///
37/// # #[cfg(feature = "path")]
38/// assert_eq!(
39///     format!("hello {}", DecInt::new(9876).as_ref().display()),
40///     "hello 9876"
41/// );
42/// ```
43#[derive(Clone)]
44pub struct DecInt {
45    // 20 `u8`s is enough to hold the decimal ASCII representation of any
46    // `u64`, and we add one for a NUL terminator for `as_c_str`.
47    buf: [u8; 20 + 1],
48    len: usize,
49}
50
51impl DecInt {
52    /// Construct a new path component from an integer.
53    #[inline]
54    pub fn new<Int: Integer>(i: Int) -> Self {
55        let mut me = DecIntWriter(Self {
56            buf: [0; 20 + 1],
57            len: 0,
58        });
59        let mut buf = Buffer::new();
60        me.write_str(buf.format(i)).unwrap();
61        me.0
62    }
63
64    /// Construct a new path component from a file descriptor.
65    #[inline]
66    pub fn from_fd<Fd: AsFd>(fd: Fd) -> Self {
67        Self::new(fd.as_fd().as_raw_fd())
68    }
69
70    /// Return the raw byte buffer as a `&str`.
71    #[inline]
72    pub fn as_str(&self) -> &str {
73        // Safety: `DecInt` always holds a formatted decimal number, so it's
74        // always valid UTF-8.
75        unsafe { core::str::from_utf8_unchecked(self.as_bytes()) }
76    }
77
78    /// Return the raw byte buffer as a `&CStr`.
79    #[inline]
80    pub fn as_c_str(&self) -> &CStr {
81        let bytes_with_nul = &self.buf[..=self.len];
82        debug_assert!(CStr::from_bytes_with_nul(bytes_with_nul).is_ok());
83
84        // Safety: `self.buf` holds a single decimal ASCII representation and
85        // at least one extra NUL byte.
86        unsafe { CStr::from_bytes_with_nul_unchecked(bytes_with_nul) }
87    }
88
89    /// Return the raw byte buffer.
90    #[inline]
91    pub fn as_bytes(&self) -> &[u8] {
92        &self.buf[..self.len]
93    }
94}
95
96struct DecIntWriter(DecInt);
97
98impl core::fmt::Write for DecIntWriter {
99    #[inline]
100    fn write_str(&mut self, s: &str) -> core::fmt::Result {
101        match self.0.buf.get_mut(self.0.len..self.0.len + s.len()) {
102            Some(slice) => {
103                slice.copy_from_slice(s.as_bytes());
104                self.0.len += s.len();
105                Ok(())
106            }
107            None => Err(core::fmt::Error),
108        }
109    }
110}
111
112#[cfg(feature = "std")]
113impl AsRef<Path> for DecInt {
114    #[inline]
115    fn as_ref(&self) -> &Path {
116        let as_os_str: &OsStr = OsStrExt::from_bytes(&self.buf[..self.len]);
117        Path::new(as_os_str)
118    }
119}
120
121#[cfg(feature = "std")]
122impl fmt::Debug for DecInt {
123    fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
124        self.as_str().fmt(fmt)
125    }
126}
127