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