| 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 as _}; |
| 10 | use crate::ffi::CStr; |
| 11 | use core::fmt; |
| 12 | use core::hint::unreachable_unchecked; |
| 13 | use core::mem::{self, MaybeUninit}; |
| 14 | use core::num::{NonZeroU8, NonZeroUsize}; |
| 15 | #[cfg (all(feature = "std" , unix))] |
| 16 | use std::os::unix::ffi::OsStrExt; |
| 17 | #[cfg (all( |
| 18 | feature = "std" , |
| 19 | target_os = "wasi" , |
| 20 | any(not(target_env = "p2" ), wasip2) |
| 21 | ))] |
| 22 | use std::os::wasi::ffi::OsStrExt; |
| 23 | #[cfg (feature = "std" )] |
| 24 | use {std::ffi::OsStr, 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 | /// # Examples |
| 32 | /// |
| 33 | /// ``` |
| 34 | /// # #[cfg (any(feature = "fs" , feature = "net" ))] |
| 35 | /// use rustix::path::DecInt; |
| 36 | /// |
| 37 | /// # #[cfg (any(feature = "fs" , feature = "net" ))] |
| 38 | /// assert_eq!( |
| 39 | /// format!("hello {}" , DecInt::new(9876).as_ref().display()), |
| 40 | /// "hello 9876" |
| 41 | /// ); |
| 42 | /// ``` |
| 43 | #[derive (Clone)] |
| 44 | pub struct DecInt { |
| 45 | buf: [MaybeUninit<u8>; BUF_LEN], |
| 46 | len: NonZeroU8, |
| 47 | } |
| 48 | |
| 49 | /// Enough to hold an {u,i}64 and NUL terminator. |
| 50 | const BUF_LEN: usize = U64_MAX_STR_LEN + 1; |
| 51 | |
| 52 | /// Maximum length of a formatted [`u64`]. |
| 53 | const U64_MAX_STR_LEN: usize = "18446744073709551615" .len(); |
| 54 | |
| 55 | /// Maximum length of a formatted [`i64`]. |
| 56 | #[allow (dead_code)] |
| 57 | const I64_MAX_STR_LEN: usize = "-9223372036854775808" .len(); |
| 58 | |
| 59 | const _: () = assert!(U64_MAX_STR_LEN == I64_MAX_STR_LEN); |
| 60 | |
| 61 | mod private { |
| 62 | pub trait Sealed: Copy { |
| 63 | type Unsigned: super::Integer; |
| 64 | |
| 65 | fn as_unsigned(self) -> (bool, Self::Unsigned); |
| 66 | fn eq_zero(self) -> bool; |
| 67 | fn div_mod_10(&mut self) -> u8; |
| 68 | } |
| 69 | |
| 70 | macro_rules! impl_unsigned { |
| 71 | ($($ty:ty)+) => { $( |
| 72 | impl Sealed for $ty { |
| 73 | type Unsigned = $ty; |
| 74 | |
| 75 | #[inline] |
| 76 | fn as_unsigned(self) -> (bool, $ty) { |
| 77 | (false, self) |
| 78 | } |
| 79 | |
| 80 | #[inline] |
| 81 | fn eq_zero(self) -> bool { |
| 82 | self == 0 |
| 83 | } |
| 84 | |
| 85 | #[inline] |
| 86 | fn div_mod_10(&mut self) -> u8 { |
| 87 | let result = (*self % 10) as u8; |
| 88 | *self /= 10; |
| 89 | result |
| 90 | } |
| 91 | } |
| 92 | )+ } |
| 93 | } |
| 94 | |
| 95 | macro_rules! impl_signed { |
| 96 | ($($signed:ty : $unsigned:ty)+) => { $( |
| 97 | impl Sealed for $signed { |
| 98 | type Unsigned = $unsigned; |
| 99 | |
| 100 | #[inline] |
| 101 | fn as_unsigned(self) -> (bool, $unsigned) { |
| 102 | if self >= 0 { |
| 103 | (false, self as $unsigned) |
| 104 | } else { |
| 105 | (true, !(self as $unsigned) + 1) |
| 106 | } |
| 107 | } |
| 108 | |
| 109 | #[inline] |
| 110 | fn eq_zero(self) -> bool { |
| 111 | unimplemented!() |
| 112 | } |
| 113 | |
| 114 | #[inline] |
| 115 | fn div_mod_10(&mut self) -> u8 { |
| 116 | unimplemented!() |
| 117 | } |
| 118 | } |
| 119 | )+ } |
| 120 | } |
| 121 | |
| 122 | impl_unsigned!(u8 u16 u32 u64); |
| 123 | impl_signed!(i8:u8 i16:u16 i32:u32 i64:u64); |
| 124 | |
| 125 | #[cfg (any( |
| 126 | target_pointer_width = "16" , |
| 127 | target_pointer_width = "32" , |
| 128 | target_pointer_width = "64" |
| 129 | ))] |
| 130 | const _: () = { |
| 131 | impl_unsigned!(usize); |
| 132 | impl_signed!(isize:usize); |
| 133 | }; |
| 134 | } |
| 135 | |
| 136 | /// An integer that can be used by [`DecInt::new`]. |
| 137 | pub trait Integer: private::Sealed {} |
| 138 | |
| 139 | impl Integer for i8 {} |
| 140 | impl Integer for i16 {} |
| 141 | impl Integer for i32 {} |
| 142 | impl Integer for i64 {} |
| 143 | impl Integer for u8 {} |
| 144 | impl Integer for u16 {} |
| 145 | impl Integer for u32 {} |
| 146 | impl Integer for u64 {} |
| 147 | |
| 148 | #[cfg (any( |
| 149 | target_pointer_width = "16" , |
| 150 | target_pointer_width = "32" , |
| 151 | target_pointer_width = "64" |
| 152 | ))] |
| 153 | const _: () = { |
| 154 | impl Integer for isize {} |
| 155 | impl Integer for usize {} |
| 156 | }; |
| 157 | |
| 158 | impl DecInt { |
| 159 | /// Construct a new path component from an integer. |
| 160 | pub fn new<Int: Integer>(i: Int) -> Self { |
| 161 | use private::Sealed as _; |
| 162 | |
| 163 | let (is_neg, mut i) = i.as_unsigned(); |
| 164 | let mut len = 1; |
| 165 | let mut buf = [MaybeUninit::uninit(); BUF_LEN]; |
| 166 | buf[BUF_LEN - 1] = MaybeUninit::new(b' \0' ); |
| 167 | |
| 168 | // We use `loop { …; if cond { break } }` instead of |
| 169 | // `while !cond { … }` so the loop is entered at least once. This way |
| 170 | // `0` does not need a special handling. |
| 171 | loop { |
| 172 | len += 1; |
| 173 | if len > BUF_LEN { |
| 174 | // SAFETY: A stringified `i64`/`u64` cannot be longer than |
| 175 | // `U64_MAX_STR_LEN` bytes. |
| 176 | unsafe { unreachable_unchecked() }; |
| 177 | } |
| 178 | buf[BUF_LEN - len] = MaybeUninit::new(b'0' + i.div_mod_10()); |
| 179 | if i.eq_zero() { |
| 180 | break; |
| 181 | } |
| 182 | } |
| 183 | |
| 184 | if is_neg { |
| 185 | len += 1; |
| 186 | if len > BUF_LEN { |
| 187 | // SAFETY: A stringified `i64`/`u64` cannot be longer than |
| 188 | // `U64_MAX_STR_LEN` bytes. |
| 189 | unsafe { unreachable_unchecked() }; |
| 190 | } |
| 191 | buf[BUF_LEN - len] = MaybeUninit::new(b'-' ); |
| 192 | } |
| 193 | |
| 194 | Self { |
| 195 | buf, |
| 196 | len: NonZeroU8::new(len as u8).unwrap(), |
| 197 | } |
| 198 | } |
| 199 | |
| 200 | /// Construct a new path component from a file descriptor. |
| 201 | #[inline ] |
| 202 | pub fn from_fd<Fd: AsFd>(fd: Fd) -> Self { |
| 203 | Self::new(fd.as_fd().as_raw_fd()) |
| 204 | } |
| 205 | |
| 206 | /// Return the raw byte buffer as a `&str`. |
| 207 | #[inline ] |
| 208 | pub fn as_str(&self) -> &str { |
| 209 | // SAFETY: `DecInt` always holds a formatted decimal number, so it's |
| 210 | // always valid UTF-8. |
| 211 | unsafe { core::str::from_utf8_unchecked(self.as_bytes()) } |
| 212 | } |
| 213 | |
| 214 | /// Return the raw byte buffer as a `&CStr`. |
| 215 | #[inline ] |
| 216 | pub fn as_c_str(&self) -> &CStr { |
| 217 | let bytes_with_nul = self.as_bytes_with_nul(); |
| 218 | debug_assert!(CStr::from_bytes_with_nul(bytes_with_nul).is_ok()); |
| 219 | |
| 220 | // SAFETY: `self.buf` holds a single decimal ASCII representation and |
| 221 | // at least one extra NUL byte. |
| 222 | unsafe { CStr::from_bytes_with_nul_unchecked(bytes_with_nul) } |
| 223 | } |
| 224 | |
| 225 | /// Return the raw byte buffer including the NUL byte. |
| 226 | #[inline ] |
| 227 | pub fn as_bytes_with_nul(&self) -> &[u8] { |
| 228 | let len = NonZeroUsize::from(self.len).get(); |
| 229 | if len > BUF_LEN { |
| 230 | // SAFETY: A stringified `i64`/`u64` cannot be longer than |
| 231 | // `U64_MAX_STR_LEN` bytes. |
| 232 | unsafe { unreachable_unchecked() }; |
| 233 | } |
| 234 | let init = &self.buf[(self.buf.len() - len)..]; |
| 235 | // SAFETY: We're guaranteed to have initialized `len + 1` bytes. |
| 236 | unsafe { mem::transmute::<&[MaybeUninit<u8>], &[u8]>(init) } |
| 237 | } |
| 238 | |
| 239 | /// Return the raw byte buffer. |
| 240 | #[inline ] |
| 241 | pub fn as_bytes(&self) -> &[u8] { |
| 242 | let bytes = self.as_bytes_with_nul(); |
| 243 | &bytes[..bytes.len() - 1] |
| 244 | } |
| 245 | } |
| 246 | |
| 247 | #[cfg (feature = "std" )] |
| 248 | #[cfg (any(not(target_os = "wasi" ), not(target_env = "p2" ), wasip2))] |
| 249 | impl AsRef<Path> for DecInt { |
| 250 | #[inline ] |
| 251 | fn as_ref(&self) -> &Path { |
| 252 | let as_os_str: &OsStr = OsStrExt::from_bytes(self.as_bytes()); |
| 253 | Path::new(as_os_str) |
| 254 | } |
| 255 | } |
| 256 | |
| 257 | impl fmt::Debug for DecInt { |
| 258 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
| 259 | self.as_str().fmt(f) |
| 260 | } |
| 261 | } |
| 262 | |