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 | |