1 //! Convenient and efficient string argument passing.
2 //!
3 //! This module defines the `Arg` trait and implements it for several common
4 //! string types. This allows users to pass any of these string types directly
5 //! to rustix APIs with string arguments, and it allows rustix to implement
6 //! NUL-termination without the need for copying where possible.
7 
8 use crate::ffi::CStr;
9 use crate::io;
10 #[cfg(feature = "itoa")]
11 use crate::path::DecInt;
12 use crate::path::SMALL_PATH_BUFFER_SIZE;
13 #[cfg(all(feature = "alloc", feature = "itoa"))]
14 use alloc::borrow::ToOwned;
15 use core::mem::MaybeUninit;
16 use core::{ptr, slice, str};
17 #[cfg(feature = "std")]
18 use std::ffi::{OsStr, OsString};
19 #[cfg(all(feature = "std", target_os = "hermit"))]
20 use std::os::hermit::ext::ffi::{OsStrExt, OsStringExt};
21 #[cfg(all(feature = "std", unix))]
22 use std::os::unix::ffi::{OsStrExt, OsStringExt};
23 #[cfg(all(feature = "std", target_os = "vxworks"))]
24 use std::os::vxworks::ext::ffi::{OsStrExt, OsStringExt};
25 #[cfg(all(feature = "std", target_os = "wasi"))]
26 use std::os::wasi::ffi::{OsStrExt, OsStringExt};
27 #[cfg(feature = "std")]
28 use std::path::{Component, Components, Iter, Path, PathBuf};
29 #[cfg(feature = "alloc")]
30 use {crate::ffi::CString, alloc::borrow::Cow};
31 #[cfg(feature = "alloc")]
32 use {alloc::string::String, alloc::vec::Vec};
33 
34 /// A trait for passing path arguments.
35 ///
36 /// This is similar to [`AsRef`]`<`[`Path`]`>`, but is implemented for more
37 /// kinds of strings and can convert into more kinds of strings.
38 ///
39 /// # Examples
40 ///
41 /// ```
42 /// # #[cfg(any(feature = "fs", feature = "net"))]
43 /// use rustix::ffi::CStr;
44 /// use rustix::io;
45 /// # #[cfg(any(feature = "fs", feature = "net"))]
46 /// use rustix::path::Arg;
47 ///
48 /// # #[cfg(any(feature = "fs", feature = "net"))]
49 /// pub fn touch<P: Arg>(path: P) -> io::Result<()> {
50 ///     let path = path.into_c_str()?;
51 ///     _touch(&path)
52 /// }
53 ///
54 /// # #[cfg(any(feature = "fs", feature = "net"))]
55 /// fn _touch(path: &CStr) -> io::Result<()> {
56 ///     // implementation goes here
57 ///     Ok(())
58 /// }
59 /// ```
60 ///
61 /// Users can then call `touch("foo")`, `touch(cstr!("foo"))`,
62 /// `touch(Path::new("foo"))`, or many other things.
63 ///
64 /// [`AsRef`]: std::convert::AsRef
65 pub trait Arg {
66     /// Returns a view of this string as a string slice.
as_str(&self) -> io::Result<&str>67     fn as_str(&self) -> io::Result<&str>;
68 
69     /// Returns a potentially-lossy rendering of this string as a
70     /// `Cow<'_, str>`.
71     #[cfg(feature = "alloc")]
to_string_lossy(&self) -> Cow<'_, str>72     fn to_string_lossy(&self) -> Cow<'_, str>;
73 
74     /// Returns a view of this string as a maybe-owned [`CStr`].
75     #[cfg(feature = "alloc")]
as_cow_c_str(&self) -> io::Result<Cow<'_, CStr>>76     fn as_cow_c_str(&self) -> io::Result<Cow<'_, CStr>>;
77 
78     /// Consumes `self` and returns a view of this string as a maybe-owned
79     /// [`CStr`].
80     #[cfg(feature = "alloc")]
into_c_str<'b>(self) -> io::Result<Cow<'b, CStr>> where Self: 'b81     fn into_c_str<'b>(self) -> io::Result<Cow<'b, CStr>>
82     where
83         Self: 'b;
84 
85     /// Runs a closure with `self` passed in as a `&CStr`.
into_with_c_str<T, F>(self, f: F) -> io::Result<T> where Self: Sized, F: FnOnce(&CStr) -> io::Result<T>86     fn into_with_c_str<T, F>(self, f: F) -> io::Result<T>
87     where
88         Self: Sized,
89         F: FnOnce(&CStr) -> io::Result<T>;
90 }
91 
92 impl Arg for &str {
93     #[inline]
as_str(&self) -> io::Result<&str>94     fn as_str(&self) -> io::Result<&str> {
95         Ok(self)
96     }
97 
98     #[cfg(feature = "alloc")]
99     #[inline]
to_string_lossy(&self) -> Cow<'_, str>100     fn to_string_lossy(&self) -> Cow<'_, str> {
101         Cow::Borrowed(self)
102     }
103 
104     #[cfg(feature = "alloc")]
105     #[inline]
as_cow_c_str(&self) -> io::Result<Cow<'_, CStr>>106     fn as_cow_c_str(&self) -> io::Result<Cow<'_, CStr>> {
107         Ok(Cow::Owned(
108             CString::new(*self).map_err(|_cstr_err| io::Errno::INVAL)?,
109         ))
110     }
111 
112     #[cfg(feature = "alloc")]
113     #[inline]
into_c_str<'b>(self) -> io::Result<Cow<'b, CStr>> where Self: 'b,114     fn into_c_str<'b>(self) -> io::Result<Cow<'b, CStr>>
115     where
116         Self: 'b,
117     {
118         Ok(Cow::Owned(
119             CString::new(self).map_err(|_cstr_err| io::Errno::INVAL)?,
120         ))
121     }
122 
123     #[inline]
into_with_c_str<T, F>(self, f: F) -> io::Result<T> where Self: Sized, F: FnOnce(&CStr) -> io::Result<T>,124     fn into_with_c_str<T, F>(self, f: F) -> io::Result<T>
125     where
126         Self: Sized,
127         F: FnOnce(&CStr) -> io::Result<T>,
128     {
129         with_c_str(self.as_bytes(), f)
130     }
131 }
132 
133 #[cfg(feature = "alloc")]
134 impl Arg for &String {
135     #[inline]
as_str(&self) -> io::Result<&str>136     fn as_str(&self) -> io::Result<&str> {
137         Ok(self)
138     }
139 
140     #[cfg(feature = "alloc")]
141     #[inline]
to_string_lossy(&self) -> Cow<'_, str>142     fn to_string_lossy(&self) -> Cow<'_, str> {
143         Cow::Borrowed(self)
144     }
145 
146     #[cfg(feature = "alloc")]
147     #[inline]
as_cow_c_str(&self) -> io::Result<Cow<'_, CStr>>148     fn as_cow_c_str(&self) -> io::Result<Cow<'_, CStr>> {
149         Ok(Cow::Owned(
150             CString::new(String::as_str(self)).map_err(|_cstr_err| io::Errno::INVAL)?,
151         ))
152     }
153 
154     #[cfg(feature = "alloc")]
155     #[inline]
into_c_str<'b>(self) -> io::Result<Cow<'b, CStr>> where Self: 'b,156     fn into_c_str<'b>(self) -> io::Result<Cow<'b, CStr>>
157     where
158         Self: 'b,
159     {
160         self.as_str().into_c_str()
161     }
162 
163     #[inline]
into_with_c_str<T, F>(self, f: F) -> io::Result<T> where Self: Sized, F: FnOnce(&CStr) -> io::Result<T>,164     fn into_with_c_str<T, F>(self, f: F) -> io::Result<T>
165     where
166         Self: Sized,
167         F: FnOnce(&CStr) -> io::Result<T>,
168     {
169         with_c_str(self.as_bytes(), f)
170     }
171 }
172 
173 #[cfg(feature = "alloc")]
174 impl Arg for String {
175     #[inline]
as_str(&self) -> io::Result<&str>176     fn as_str(&self) -> io::Result<&str> {
177         Ok(self)
178     }
179 
180     #[cfg(feature = "alloc")]
181     #[inline]
to_string_lossy(&self) -> Cow<'_, str>182     fn to_string_lossy(&self) -> Cow<'_, str> {
183         Cow::Borrowed(self)
184     }
185 
186     #[cfg(feature = "alloc")]
187     #[inline]
as_cow_c_str(&self) -> io::Result<Cow<'_, CStr>>188     fn as_cow_c_str(&self) -> io::Result<Cow<'_, CStr>> {
189         Ok(Cow::Owned(
190             CString::new(self.as_str()).map_err(|_cstr_err| io::Errno::INVAL)?,
191         ))
192     }
193 
194     #[cfg(feature = "alloc")]
195     #[inline]
into_c_str<'b>(self) -> io::Result<Cow<'b, CStr>> where Self: 'b,196     fn into_c_str<'b>(self) -> io::Result<Cow<'b, CStr>>
197     where
198         Self: 'b,
199     {
200         Ok(Cow::Owned(
201             CString::new(self).map_err(|_cstr_err| io::Errno::INVAL)?,
202         ))
203     }
204 
205     #[inline]
into_with_c_str<T, F>(self, f: F) -> io::Result<T> where Self: Sized, F: FnOnce(&CStr) -> io::Result<T>,206     fn into_with_c_str<T, F>(self, f: F) -> io::Result<T>
207     where
208         Self: Sized,
209         F: FnOnce(&CStr) -> io::Result<T>,
210     {
211         f(&CString::new(self).map_err(|_cstr_err| io::Errno::INVAL)?)
212     }
213 }
214 
215 #[cfg(feature = "std")]
216 impl Arg for &OsStr {
217     #[inline]
as_str(&self) -> io::Result<&str>218     fn as_str(&self) -> io::Result<&str> {
219         self.to_str().ok_or(io::Errno::INVAL)
220     }
221 
222     #[cfg(feature = "alloc")]
223     #[inline]
to_string_lossy(&self) -> Cow<'_, str>224     fn to_string_lossy(&self) -> Cow<'_, str> {
225         OsStr::to_string_lossy(self)
226     }
227 
228     #[cfg(feature = "alloc")]
229     #[inline]
as_cow_c_str(&self) -> io::Result<Cow<'_, CStr>>230     fn as_cow_c_str(&self) -> io::Result<Cow<'_, CStr>> {
231         Ok(Cow::Owned(
232             CString::new(self.as_bytes()).map_err(|_cstr_err| io::Errno::INVAL)?,
233         ))
234     }
235 
236     #[cfg(feature = "alloc")]
237     #[inline]
into_c_str<'b>(self) -> io::Result<Cow<'b, CStr>> where Self: 'b,238     fn into_c_str<'b>(self) -> io::Result<Cow<'b, CStr>>
239     where
240         Self: 'b,
241     {
242         Ok(Cow::Owned(
243             CString::new(self.as_bytes()).map_err(|_cstr_err| io::Errno::INVAL)?,
244         ))
245     }
246 
247     #[inline]
into_with_c_str<T, F>(self, f: F) -> io::Result<T> where Self: Sized, F: FnOnce(&CStr) -> io::Result<T>,248     fn into_with_c_str<T, F>(self, f: F) -> io::Result<T>
249     where
250         Self: Sized,
251         F: FnOnce(&CStr) -> io::Result<T>,
252     {
253         with_c_str(self.as_bytes(), f)
254     }
255 }
256 
257 #[cfg(feature = "std")]
258 impl Arg for &OsString {
259     #[inline]
as_str(&self) -> io::Result<&str>260     fn as_str(&self) -> io::Result<&str> {
261         OsString::as_os_str(self).to_str().ok_or(io::Errno::INVAL)
262     }
263 
264     #[cfg(feature = "alloc")]
265     #[inline]
to_string_lossy(&self) -> Cow<'_, str>266     fn to_string_lossy(&self) -> Cow<'_, str> {
267         self.as_os_str().to_string_lossy()
268     }
269 
270     #[cfg(feature = "alloc")]
271     #[inline]
as_cow_c_str(&self) -> io::Result<Cow<'_, CStr>>272     fn as_cow_c_str(&self) -> io::Result<Cow<'_, CStr>> {
273         Ok(Cow::Owned(
274             CString::new(OsString::as_os_str(self).as_bytes())
275                 .map_err(|_cstr_err| io::Errno::INVAL)?,
276         ))
277     }
278 
279     #[cfg(feature = "alloc")]
280     #[inline]
into_c_str<'b>(self) -> io::Result<Cow<'b, CStr>> where Self: 'b,281     fn into_c_str<'b>(self) -> io::Result<Cow<'b, CStr>>
282     where
283         Self: 'b,
284     {
285         self.as_os_str().into_c_str()
286     }
287 
288     #[inline]
into_with_c_str<T, F>(self, f: F) -> io::Result<T> where Self: Sized, F: FnOnce(&CStr) -> io::Result<T>,289     fn into_with_c_str<T, F>(self, f: F) -> io::Result<T>
290     where
291         Self: Sized,
292         F: FnOnce(&CStr) -> io::Result<T>,
293     {
294         with_c_str(self.as_bytes(), f)
295     }
296 }
297 
298 #[cfg(feature = "std")]
299 impl Arg for OsString {
300     #[inline]
as_str(&self) -> io::Result<&str>301     fn as_str(&self) -> io::Result<&str> {
302         self.as_os_str().to_str().ok_or(io::Errno::INVAL)
303     }
304 
305     #[cfg(feature = "alloc")]
306     #[inline]
to_string_lossy(&self) -> Cow<'_, str>307     fn to_string_lossy(&self) -> Cow<'_, str> {
308         self.as_os_str().to_string_lossy()
309     }
310 
311     #[cfg(feature = "alloc")]
312     #[inline]
as_cow_c_str(&self) -> io::Result<Cow<'_, CStr>>313     fn as_cow_c_str(&self) -> io::Result<Cow<'_, CStr>> {
314         Ok(Cow::Owned(
315             CString::new(self.as_bytes()).map_err(|_cstr_err| io::Errno::INVAL)?,
316         ))
317     }
318 
319     #[cfg(feature = "alloc")]
320     #[inline]
into_c_str<'b>(self) -> io::Result<Cow<'b, CStr>> where Self: 'b,321     fn into_c_str<'b>(self) -> io::Result<Cow<'b, CStr>>
322     where
323         Self: 'b,
324     {
325         Ok(Cow::Owned(
326             CString::new(self.into_vec()).map_err(|_cstr_err| io::Errno::INVAL)?,
327         ))
328     }
329 
330     #[inline]
into_with_c_str<T, F>(self, f: F) -> io::Result<T> where Self: Sized, F: FnOnce(&CStr) -> io::Result<T>,331     fn into_with_c_str<T, F>(self, f: F) -> io::Result<T>
332     where
333         Self: Sized,
334         F: FnOnce(&CStr) -> io::Result<T>,
335     {
336         f(&CString::new(self.into_vec()).map_err(|_cstr_err| io::Errno::INVAL)?)
337     }
338 }
339 
340 #[cfg(feature = "std")]
341 impl Arg for &Path {
342     #[inline]
as_str(&self) -> io::Result<&str>343     fn as_str(&self) -> io::Result<&str> {
344         self.as_os_str().to_str().ok_or(io::Errno::INVAL)
345     }
346 
347     #[cfg(feature = "alloc")]
348     #[inline]
to_string_lossy(&self) -> Cow<'_, str>349     fn to_string_lossy(&self) -> Cow<'_, str> {
350         Path::to_string_lossy(self)
351     }
352 
353     #[cfg(feature = "alloc")]
354     #[inline]
as_cow_c_str(&self) -> io::Result<Cow<'_, CStr>>355     fn as_cow_c_str(&self) -> io::Result<Cow<'_, CStr>> {
356         Ok(Cow::Owned(
357             CString::new(self.as_os_str().as_bytes()).map_err(|_cstr_err| io::Errno::INVAL)?,
358         ))
359     }
360 
361     #[cfg(feature = "alloc")]
362     #[inline]
into_c_str<'b>(self) -> io::Result<Cow<'b, CStr>> where Self: 'b,363     fn into_c_str<'b>(self) -> io::Result<Cow<'b, CStr>>
364     where
365         Self: 'b,
366     {
367         Ok(Cow::Owned(
368             CString::new(self.as_os_str().as_bytes()).map_err(|_cstr_err| io::Errno::INVAL)?,
369         ))
370     }
371 
372     #[inline]
into_with_c_str<T, F>(self, f: F) -> io::Result<T> where Self: Sized, F: FnOnce(&CStr) -> io::Result<T>,373     fn into_with_c_str<T, F>(self, f: F) -> io::Result<T>
374     where
375         Self: Sized,
376         F: FnOnce(&CStr) -> io::Result<T>,
377     {
378         with_c_str(self.as_os_str().as_bytes(), f)
379     }
380 }
381 
382 #[cfg(feature = "std")]
383 impl Arg for &PathBuf {
384     #[inline]
as_str(&self) -> io::Result<&str>385     fn as_str(&self) -> io::Result<&str> {
386         PathBuf::as_path(self)
387             .as_os_str()
388             .to_str()
389             .ok_or(io::Errno::INVAL)
390     }
391 
392     #[cfg(feature = "alloc")]
393     #[inline]
to_string_lossy(&self) -> Cow<'_, str>394     fn to_string_lossy(&self) -> Cow<'_, str> {
395         self.as_path().to_string_lossy()
396     }
397 
398     #[cfg(feature = "alloc")]
399     #[inline]
as_cow_c_str(&self) -> io::Result<Cow<'_, CStr>>400     fn as_cow_c_str(&self) -> io::Result<Cow<'_, CStr>> {
401         Ok(Cow::Owned(
402             CString::new(PathBuf::as_path(self).as_os_str().as_bytes())
403                 .map_err(|_cstr_err| io::Errno::INVAL)?,
404         ))
405     }
406 
407     #[cfg(feature = "alloc")]
408     #[inline]
into_c_str<'b>(self) -> io::Result<Cow<'b, CStr>> where Self: 'b,409     fn into_c_str<'b>(self) -> io::Result<Cow<'b, CStr>>
410     where
411         Self: 'b,
412     {
413         self.as_path().into_c_str()
414     }
415 
416     #[inline]
into_with_c_str<T, F>(self, f: F) -> io::Result<T> where Self: Sized, F: FnOnce(&CStr) -> io::Result<T>,417     fn into_with_c_str<T, F>(self, f: F) -> io::Result<T>
418     where
419         Self: Sized,
420         F: FnOnce(&CStr) -> io::Result<T>,
421     {
422         with_c_str(self.as_os_str().as_bytes(), f)
423     }
424 }
425 
426 #[cfg(feature = "std")]
427 impl Arg for PathBuf {
428     #[inline]
as_str(&self) -> io::Result<&str>429     fn as_str(&self) -> io::Result<&str> {
430         self.as_os_str().to_str().ok_or(io::Errno::INVAL)
431     }
432 
433     #[cfg(feature = "alloc")]
434     #[inline]
to_string_lossy(&self) -> Cow<'_, str>435     fn to_string_lossy(&self) -> Cow<'_, str> {
436         self.as_os_str().to_string_lossy()
437     }
438 
439     #[cfg(feature = "alloc")]
440     #[inline]
as_cow_c_str(&self) -> io::Result<Cow<'_, CStr>>441     fn as_cow_c_str(&self) -> io::Result<Cow<'_, CStr>> {
442         Ok(Cow::Owned(
443             CString::new(self.as_os_str().as_bytes()).map_err(|_cstr_err| io::Errno::INVAL)?,
444         ))
445     }
446 
447     #[cfg(feature = "alloc")]
448     #[inline]
into_c_str<'b>(self) -> io::Result<Cow<'b, CStr>> where Self: 'b,449     fn into_c_str<'b>(self) -> io::Result<Cow<'b, CStr>>
450     where
451         Self: 'b,
452     {
453         Ok(Cow::Owned(
454             CString::new(self.into_os_string().into_vec()).map_err(|_cstr_err| io::Errno::INVAL)?,
455         ))
456     }
457 
458     #[inline]
into_with_c_str<T, F>(self, f: F) -> io::Result<T> where Self: Sized, F: FnOnce(&CStr) -> io::Result<T>,459     fn into_with_c_str<T, F>(self, f: F) -> io::Result<T>
460     where
461         Self: Sized,
462         F: FnOnce(&CStr) -> io::Result<T>,
463     {
464         f(
465             &CString::new(self.into_os_string().into_vec())
466                 .map_err(|_cstr_err| io::Errno::INVAL)?,
467         )
468     }
469 }
470 
471 impl Arg for &CStr {
472     #[inline]
as_str(&self) -> io::Result<&str>473     fn as_str(&self) -> io::Result<&str> {
474         self.to_str().map_err(|_utf8_err| io::Errno::INVAL)
475     }
476 
477     #[cfg(feature = "alloc")]
478     #[inline]
to_string_lossy(&self) -> Cow<'_, str>479     fn to_string_lossy(&self) -> Cow<'_, str> {
480         CStr::to_string_lossy(self)
481     }
482 
483     #[cfg(feature = "alloc")]
484     #[inline]
as_cow_c_str(&self) -> io::Result<Cow<'_, CStr>>485     fn as_cow_c_str(&self) -> io::Result<Cow<'_, CStr>> {
486         Ok(Cow::Borrowed(self))
487     }
488 
489     #[cfg(feature = "alloc")]
490     #[inline]
into_c_str<'b>(self) -> io::Result<Cow<'b, CStr>> where Self: 'b,491     fn into_c_str<'b>(self) -> io::Result<Cow<'b, CStr>>
492     where
493         Self: 'b,
494     {
495         Ok(Cow::Borrowed(self))
496     }
497 
498     #[inline]
into_with_c_str<T, F>(self, f: F) -> io::Result<T> where Self: Sized, F: FnOnce(&CStr) -> io::Result<T>,499     fn into_with_c_str<T, F>(self, f: F) -> io::Result<T>
500     where
501         Self: Sized,
502         F: FnOnce(&CStr) -> io::Result<T>,
503     {
504         f(self)
505     }
506 }
507 
508 #[cfg(feature = "alloc")]
509 impl Arg for &CString {
510     #[inline]
as_str(&self) -> io::Result<&str>511     fn as_str(&self) -> io::Result<&str> {
512         unimplemented!()
513     }
514 
515     #[cfg(feature = "alloc")]
516     #[inline]
to_string_lossy(&self) -> Cow<'_, str>517     fn to_string_lossy(&self) -> Cow<'_, str> {
518         unimplemented!()
519     }
520 
521     #[cfg(feature = "alloc")]
522     #[inline]
as_cow_c_str(&self) -> io::Result<Cow<'_, CStr>>523     fn as_cow_c_str(&self) -> io::Result<Cow<'_, CStr>> {
524         Ok(Cow::Borrowed(self))
525     }
526 
527     #[cfg(feature = "alloc")]
528     #[inline]
into_c_str<'b>(self) -> io::Result<Cow<'b, CStr>> where Self: 'b,529     fn into_c_str<'b>(self) -> io::Result<Cow<'b, CStr>>
530     where
531         Self: 'b,
532     {
533         Ok(Cow::Borrowed(self))
534     }
535 
536     #[inline]
into_with_c_str<T, F>(self, f: F) -> io::Result<T> where Self: Sized, F: FnOnce(&CStr) -> io::Result<T>,537     fn into_with_c_str<T, F>(self, f: F) -> io::Result<T>
538     where
539         Self: Sized,
540         F: FnOnce(&CStr) -> io::Result<T>,
541     {
542         f(self)
543     }
544 }
545 
546 #[cfg(feature = "alloc")]
547 impl Arg for CString {
548     #[inline]
as_str(&self) -> io::Result<&str>549     fn as_str(&self) -> io::Result<&str> {
550         self.to_str().map_err(|_utf8_err| io::Errno::INVAL)
551     }
552 
553     #[cfg(feature = "alloc")]
554     #[inline]
to_string_lossy(&self) -> Cow<'_, str>555     fn to_string_lossy(&self) -> Cow<'_, str> {
556         CStr::to_string_lossy(self)
557     }
558 
559     #[cfg(feature = "alloc")]
560     #[inline]
as_cow_c_str(&self) -> io::Result<Cow<'_, CStr>>561     fn as_cow_c_str(&self) -> io::Result<Cow<'_, CStr>> {
562         Ok(Cow::Borrowed(self))
563     }
564 
565     #[cfg(feature = "alloc")]
566     #[inline]
into_c_str<'b>(self) -> io::Result<Cow<'b, CStr>> where Self: 'b,567     fn into_c_str<'b>(self) -> io::Result<Cow<'b, CStr>>
568     where
569         Self: 'b,
570     {
571         Ok(Cow::Owned(self))
572     }
573 
574     #[inline]
into_with_c_str<T, F>(self, f: F) -> io::Result<T> where Self: Sized, F: FnOnce(&CStr) -> io::Result<T>,575     fn into_with_c_str<T, F>(self, f: F) -> io::Result<T>
576     where
577         Self: Sized,
578         F: FnOnce(&CStr) -> io::Result<T>,
579     {
580         f(&self)
581     }
582 }
583 
584 #[cfg(feature = "alloc")]
585 impl<'a> Arg for Cow<'a, str> {
586     #[inline]
as_str(&self) -> io::Result<&str>587     fn as_str(&self) -> io::Result<&str> {
588         Ok(self)
589     }
590 
591     #[cfg(feature = "alloc")]
592     #[inline]
to_string_lossy(&self) -> Cow<'_, str>593     fn to_string_lossy(&self) -> Cow<'_, str> {
594         Cow::Borrowed(self)
595     }
596 
597     #[cfg(feature = "alloc")]
598     #[inline]
as_cow_c_str(&self) -> io::Result<Cow<'_, CStr>>599     fn as_cow_c_str(&self) -> io::Result<Cow<'_, CStr>> {
600         Ok(Cow::Owned(
601             CString::new(self.as_ref()).map_err(|_cstr_err| io::Errno::INVAL)?,
602         ))
603     }
604 
605     #[cfg(feature = "alloc")]
606     #[inline]
into_c_str<'b>(self) -> io::Result<Cow<'b, CStr>> where Self: 'b,607     fn into_c_str<'b>(self) -> io::Result<Cow<'b, CStr>>
608     where
609         Self: 'b,
610     {
611         Ok(Cow::Owned(
612             match self {
613                 Cow::Owned(s) => CString::new(s),
614                 Cow::Borrowed(s) => CString::new(s),
615             }
616             .map_err(|_cstr_err| io::Errno::INVAL)?,
617         ))
618     }
619 
620     #[inline]
into_with_c_str<T, F>(self, f: F) -> io::Result<T> where Self: Sized, F: FnOnce(&CStr) -> io::Result<T>,621     fn into_with_c_str<T, F>(self, f: F) -> io::Result<T>
622     where
623         Self: Sized,
624         F: FnOnce(&CStr) -> io::Result<T>,
625     {
626         with_c_str(self.as_bytes(), f)
627     }
628 }
629 
630 #[cfg(feature = "std")]
631 #[cfg(feature = "alloc")]
632 impl<'a> Arg for Cow<'a, OsStr> {
633     #[inline]
as_str(&self) -> io::Result<&str>634     fn as_str(&self) -> io::Result<&str> {
635         (**self).to_str().ok_or(io::Errno::INVAL)
636     }
637 
638     #[cfg(feature = "alloc")]
639     #[inline]
to_string_lossy(&self) -> Cow<'_, str>640     fn to_string_lossy(&self) -> Cow<'_, str> {
641         (**self).to_string_lossy()
642     }
643 
644     #[cfg(feature = "alloc")]
645     #[inline]
as_cow_c_str(&self) -> io::Result<Cow<'_, CStr>>646     fn as_cow_c_str(&self) -> io::Result<Cow<'_, CStr>> {
647         Ok(Cow::Owned(
648             CString::new(self.as_bytes()).map_err(|_cstr_err| io::Errno::INVAL)?,
649         ))
650     }
651 
652     #[cfg(feature = "alloc")]
653     #[inline]
into_c_str<'b>(self) -> io::Result<Cow<'b, CStr>> where Self: 'b,654     fn into_c_str<'b>(self) -> io::Result<Cow<'b, CStr>>
655     where
656         Self: 'b,
657     {
658         Ok(Cow::Owned(
659             match self {
660                 Cow::Owned(os) => CString::new(os.into_vec()),
661                 Cow::Borrowed(os) => CString::new(os.as_bytes()),
662             }
663             .map_err(|_cstr_err| io::Errno::INVAL)?,
664         ))
665     }
666 
667     #[inline]
into_with_c_str<T, F>(self, f: F) -> io::Result<T> where Self: Sized, F: FnOnce(&CStr) -> io::Result<T>,668     fn into_with_c_str<T, F>(self, f: F) -> io::Result<T>
669     where
670         Self: Sized,
671         F: FnOnce(&CStr) -> io::Result<T>,
672     {
673         with_c_str(self.as_bytes(), f)
674     }
675 }
676 
677 #[cfg(feature = "alloc")]
678 impl<'a> Arg for Cow<'a, CStr> {
679     #[inline]
as_str(&self) -> io::Result<&str>680     fn as_str(&self) -> io::Result<&str> {
681         self.to_str().map_err(|_utf8_err| io::Errno::INVAL)
682     }
683 
684     #[cfg(feature = "alloc")]
685     #[inline]
to_string_lossy(&self) -> Cow<'_, str>686     fn to_string_lossy(&self) -> Cow<'_, str> {
687         let borrow: &CStr = core::borrow::Borrow::borrow(self);
688         borrow.to_string_lossy()
689     }
690 
691     #[cfg(feature = "alloc")]
692     #[inline]
as_cow_c_str(&self) -> io::Result<Cow<'_, CStr>>693     fn as_cow_c_str(&self) -> io::Result<Cow<'_, CStr>> {
694         Ok(Cow::Borrowed(self))
695     }
696 
697     #[cfg(feature = "alloc")]
698     #[inline]
into_c_str<'b>(self) -> io::Result<Cow<'b, CStr>> where Self: 'b,699     fn into_c_str<'b>(self) -> io::Result<Cow<'b, CStr>>
700     where
701         Self: 'b,
702     {
703         Ok(self)
704     }
705 
706     #[inline]
into_with_c_str<T, F>(self, f: F) -> io::Result<T> where Self: Sized, F: FnOnce(&CStr) -> io::Result<T>,707     fn into_with_c_str<T, F>(self, f: F) -> io::Result<T>
708     where
709         Self: Sized,
710         F: FnOnce(&CStr) -> io::Result<T>,
711     {
712         f(&self)
713     }
714 }
715 
716 #[cfg(feature = "std")]
717 impl<'a> Arg for Component<'a> {
718     #[inline]
as_str(&self) -> io::Result<&str>719     fn as_str(&self) -> io::Result<&str> {
720         self.as_os_str().to_str().ok_or(io::Errno::INVAL)
721     }
722 
723     #[cfg(feature = "alloc")]
724     #[inline]
to_string_lossy(&self) -> Cow<'_, str>725     fn to_string_lossy(&self) -> Cow<'_, str> {
726         self.as_os_str().to_string_lossy()
727     }
728 
729     #[cfg(feature = "alloc")]
730     #[inline]
as_cow_c_str(&self) -> io::Result<Cow<'_, CStr>>731     fn as_cow_c_str(&self) -> io::Result<Cow<'_, CStr>> {
732         Ok(Cow::Owned(
733             CString::new(self.as_os_str().as_bytes()).map_err(|_cstr_err| io::Errno::INVAL)?,
734         ))
735     }
736 
737     #[cfg(feature = "alloc")]
738     #[inline]
into_c_str<'b>(self) -> io::Result<Cow<'b, CStr>> where Self: 'b,739     fn into_c_str<'b>(self) -> io::Result<Cow<'b, CStr>>
740     where
741         Self: 'b,
742     {
743         Ok(Cow::Owned(
744             CString::new(self.as_os_str().as_bytes()).map_err(|_cstr_err| io::Errno::INVAL)?,
745         ))
746     }
747 
748     #[inline]
into_with_c_str<T, F>(self, f: F) -> io::Result<T> where Self: Sized, F: FnOnce(&CStr) -> io::Result<T>,749     fn into_with_c_str<T, F>(self, f: F) -> io::Result<T>
750     where
751         Self: Sized,
752         F: FnOnce(&CStr) -> io::Result<T>,
753     {
754         with_c_str(self.as_os_str().as_bytes(), f)
755     }
756 }
757 
758 #[cfg(feature = "std")]
759 impl<'a> Arg for Components<'a> {
760     #[inline]
as_str(&self) -> io::Result<&str>761     fn as_str(&self) -> io::Result<&str> {
762         self.as_path().to_str().ok_or(io::Errno::INVAL)
763     }
764 
765     #[cfg(feature = "alloc")]
766     #[inline]
to_string_lossy(&self) -> Cow<'_, str>767     fn to_string_lossy(&self) -> Cow<'_, str> {
768         self.as_path().to_string_lossy()
769     }
770 
771     #[cfg(feature = "alloc")]
772     #[inline]
as_cow_c_str(&self) -> io::Result<Cow<'_, CStr>>773     fn as_cow_c_str(&self) -> io::Result<Cow<'_, CStr>> {
774         Ok(Cow::Owned(
775             CString::new(self.as_path().as_os_str().as_bytes())
776                 .map_err(|_cstr_err| io::Errno::INVAL)?,
777         ))
778     }
779 
780     #[cfg(feature = "alloc")]
781     #[inline]
into_c_str<'b>(self) -> io::Result<Cow<'b, CStr>> where Self: 'b,782     fn into_c_str<'b>(self) -> io::Result<Cow<'b, CStr>>
783     where
784         Self: 'b,
785     {
786         Ok(Cow::Owned(
787             CString::new(self.as_path().as_os_str().as_bytes())
788                 .map_err(|_cstr_err| io::Errno::INVAL)?,
789         ))
790     }
791 
792     #[inline]
into_with_c_str<T, F>(self, f: F) -> io::Result<T> where Self: Sized, F: FnOnce(&CStr) -> io::Result<T>,793     fn into_with_c_str<T, F>(self, f: F) -> io::Result<T>
794     where
795         Self: Sized,
796         F: FnOnce(&CStr) -> io::Result<T>,
797     {
798         with_c_str(self.as_path().as_os_str().as_bytes(), f)
799     }
800 }
801 
802 #[cfg(feature = "std")]
803 impl<'a> Arg for Iter<'a> {
804     #[inline]
as_str(&self) -> io::Result<&str>805     fn as_str(&self) -> io::Result<&str> {
806         self.as_path().to_str().ok_or(io::Errno::INVAL)
807     }
808 
809     #[cfg(feature = "alloc")]
810     #[inline]
to_string_lossy(&self) -> Cow<'_, str>811     fn to_string_lossy(&self) -> Cow<'_, str> {
812         self.as_path().to_string_lossy()
813     }
814 
815     #[cfg(feature = "alloc")]
816     #[inline]
as_cow_c_str(&self) -> io::Result<Cow<'_, CStr>>817     fn as_cow_c_str(&self) -> io::Result<Cow<'_, CStr>> {
818         Ok(Cow::Owned(
819             CString::new(self.as_path().as_os_str().as_bytes())
820                 .map_err(|_cstr_err| io::Errno::INVAL)?,
821         ))
822     }
823 
824     #[cfg(feature = "alloc")]
825     #[inline]
into_c_str<'b>(self) -> io::Result<Cow<'b, CStr>> where Self: 'b,826     fn into_c_str<'b>(self) -> io::Result<Cow<'b, CStr>>
827     where
828         Self: 'b,
829     {
830         Ok(Cow::Owned(
831             CString::new(self.as_path().as_os_str().as_bytes())
832                 .map_err(|_cstr_err| io::Errno::INVAL)?,
833         ))
834     }
835 
836     #[inline]
into_with_c_str<T, F>(self, f: F) -> io::Result<T> where Self: Sized, F: FnOnce(&CStr) -> io::Result<T>,837     fn into_with_c_str<T, F>(self, f: F) -> io::Result<T>
838     where
839         Self: Sized,
840         F: FnOnce(&CStr) -> io::Result<T>,
841     {
842         with_c_str(self.as_path().as_os_str().as_bytes(), f)
843     }
844 }
845 
846 impl Arg for &[u8] {
847     #[inline]
as_str(&self) -> io::Result<&str>848     fn as_str(&self) -> io::Result<&str> {
849         str::from_utf8(self).map_err(|_utf8_err| io::Errno::INVAL)
850     }
851 
852     #[cfg(feature = "alloc")]
853     #[inline]
to_string_lossy(&self) -> Cow<'_, str>854     fn to_string_lossy(&self) -> Cow<'_, str> {
855         String::from_utf8_lossy(self)
856     }
857 
858     #[cfg(feature = "alloc")]
859     #[inline]
as_cow_c_str(&self) -> io::Result<Cow<'_, CStr>>860     fn as_cow_c_str(&self) -> io::Result<Cow<'_, CStr>> {
861         Ok(Cow::Owned(
862             CString::new(*self).map_err(|_cstr_err| io::Errno::INVAL)?,
863         ))
864     }
865 
866     #[cfg(feature = "alloc")]
867     #[inline]
into_c_str<'b>(self) -> io::Result<Cow<'b, CStr>> where Self: 'b,868     fn into_c_str<'b>(self) -> io::Result<Cow<'b, CStr>>
869     where
870         Self: 'b,
871     {
872         Ok(Cow::Owned(
873             CString::new(self).map_err(|_cstr_err| io::Errno::INVAL)?,
874         ))
875     }
876 
877     #[inline]
into_with_c_str<T, F>(self, f: F) -> io::Result<T> where Self: Sized, F: FnOnce(&CStr) -> io::Result<T>,878     fn into_with_c_str<T, F>(self, f: F) -> io::Result<T>
879     where
880         Self: Sized,
881         F: FnOnce(&CStr) -> io::Result<T>,
882     {
883         with_c_str(self, f)
884     }
885 }
886 
887 #[cfg(feature = "alloc")]
888 impl Arg for &Vec<u8> {
889     #[inline]
as_str(&self) -> io::Result<&str>890     fn as_str(&self) -> io::Result<&str> {
891         str::from_utf8(self).map_err(|_utf8_err| io::Errno::INVAL)
892     }
893 
894     #[cfg(feature = "alloc")]
895     #[inline]
to_string_lossy(&self) -> Cow<'_, str>896     fn to_string_lossy(&self) -> Cow<'_, str> {
897         String::from_utf8_lossy(self)
898     }
899 
900     #[cfg(feature = "alloc")]
901     #[inline]
as_cow_c_str(&self) -> io::Result<Cow<'_, CStr>>902     fn as_cow_c_str(&self) -> io::Result<Cow<'_, CStr>> {
903         Ok(Cow::Owned(
904             CString::new(self.as_slice()).map_err(|_cstr_err| io::Errno::INVAL)?,
905         ))
906     }
907 
908     #[cfg(feature = "alloc")]
909     #[inline]
into_c_str<'b>(self) -> io::Result<Cow<'b, CStr>> where Self: 'b,910     fn into_c_str<'b>(self) -> io::Result<Cow<'b, CStr>>
911     where
912         Self: 'b,
913     {
914         Ok(Cow::Owned(
915             CString::new(self.as_slice()).map_err(|_cstr_err| io::Errno::INVAL)?,
916         ))
917     }
918 
919     #[inline]
into_with_c_str<T, F>(self, f: F) -> io::Result<T> where Self: Sized, F: FnOnce(&CStr) -> io::Result<T>,920     fn into_with_c_str<T, F>(self, f: F) -> io::Result<T>
921     where
922         Self: Sized,
923         F: FnOnce(&CStr) -> io::Result<T>,
924     {
925         with_c_str(self, f)
926     }
927 }
928 
929 #[cfg(feature = "alloc")]
930 impl Arg for Vec<u8> {
931     #[inline]
as_str(&self) -> io::Result<&str>932     fn as_str(&self) -> io::Result<&str> {
933         str::from_utf8(self).map_err(|_utf8_err| io::Errno::INVAL)
934     }
935 
936     #[cfg(feature = "alloc")]
937     #[inline]
to_string_lossy(&self) -> Cow<'_, str>938     fn to_string_lossy(&self) -> Cow<'_, str> {
939         String::from_utf8_lossy(self)
940     }
941 
942     #[cfg(feature = "alloc")]
943     #[inline]
as_cow_c_str(&self) -> io::Result<Cow<'_, CStr>>944     fn as_cow_c_str(&self) -> io::Result<Cow<'_, CStr>> {
945         Ok(Cow::Owned(
946             CString::new(self.as_slice()).map_err(|_cstr_err| io::Errno::INVAL)?,
947         ))
948     }
949 
950     #[cfg(feature = "alloc")]
951     #[inline]
into_c_str<'b>(self) -> io::Result<Cow<'b, CStr>> where Self: 'b,952     fn into_c_str<'b>(self) -> io::Result<Cow<'b, CStr>>
953     where
954         Self: 'b,
955     {
956         Ok(Cow::Owned(
957             CString::new(self).map_err(|_cstr_err| io::Errno::INVAL)?,
958         ))
959     }
960 
961     #[inline]
into_with_c_str<T, F>(self, f: F) -> io::Result<T> where Self: Sized, F: FnOnce(&CStr) -> io::Result<T>,962     fn into_with_c_str<T, F>(self, f: F) -> io::Result<T>
963     where
964         Self: Sized,
965         F: FnOnce(&CStr) -> io::Result<T>,
966     {
967         f(&CString::new(self).map_err(|_cstr_err| io::Errno::INVAL)?)
968     }
969 }
970 
971 #[cfg(feature = "itoa")]
972 impl Arg for DecInt {
973     #[inline]
as_str(&self) -> io::Result<&str>974     fn as_str(&self) -> io::Result<&str> {
975         Ok(self.as_str())
976     }
977 
978     #[cfg(feature = "alloc")]
979     #[inline]
to_string_lossy(&self) -> Cow<'_, str>980     fn to_string_lossy(&self) -> Cow<'_, str> {
981         Cow::Borrowed(self.as_str())
982     }
983 
984     #[cfg(feature = "alloc")]
985     #[inline]
as_cow_c_str(&self) -> io::Result<Cow<'_, CStr>>986     fn as_cow_c_str(&self) -> io::Result<Cow<'_, CStr>> {
987         Ok(Cow::Borrowed(self.as_c_str()))
988     }
989 
990     #[cfg(feature = "alloc")]
991     #[inline]
into_c_str<'b>(self) -> io::Result<Cow<'b, CStr>> where Self: 'b,992     fn into_c_str<'b>(self) -> io::Result<Cow<'b, CStr>>
993     where
994         Self: 'b,
995     {
996         Ok(Cow::Owned(self.as_c_str().to_owned()))
997     }
998 
999     #[inline]
into_with_c_str<T, F>(self, f: F) -> io::Result<T> where Self: Sized, F: FnOnce(&CStr) -> io::Result<T>,1000     fn into_with_c_str<T, F>(self, f: F) -> io::Result<T>
1001     where
1002         Self: Sized,
1003         F: FnOnce(&CStr) -> io::Result<T>,
1004     {
1005         f(self.as_c_str())
1006     }
1007 }
1008 
1009 /// Runs a closure with `bytes` passed in as a `&CStr`.
1010 #[allow(unsafe_code, clippy::int_plus_one)]
1011 #[inline]
with_c_str<T, F>(bytes: &[u8], f: F) -> io::Result<T> where F: FnOnce(&CStr) -> io::Result<T>,1012 fn with_c_str<T, F>(bytes: &[u8], f: F) -> io::Result<T>
1013 where
1014     F: FnOnce(&CStr) -> io::Result<T>,
1015 {
1016     // Most paths are less than `SMALL_PATH_BUFFER_SIZE` long. The rest can go
1017     // through the dynamic allocation path. If you're opening many files in a
1018     // directory with a long path, consider opening the directory and using
1019     // `openat` to open the files under it, which will avoid this, and is often
1020     // faster in the OS as well.
1021 
1022     // Test with >= so that we have room for the trailing NUL.
1023     if bytes.len() >= SMALL_PATH_BUFFER_SIZE {
1024         return with_c_str_slow_path(bytes, f);
1025     }
1026 
1027     // Taken from
1028     // <https://github.com/rust-lang/rust/blob/a00f8ba7fcac1b27341679c51bf5a3271fa82df3/library/std/src/sys/common/small_c_string.rs>
1029     let mut buf = MaybeUninit::<[u8; SMALL_PATH_BUFFER_SIZE]>::uninit();
1030     let buf_ptr = buf.as_mut_ptr().cast::<u8>();
1031 
1032     // This helps test our safety condition below.
1033     debug_assert!(bytes.len() + 1 <= SMALL_PATH_BUFFER_SIZE);
1034 
1035     // SAFETY: `bytes.len() < SMALL_PATH_BUFFER_SIZE` which means we have space
1036     // for `bytes.len() + 1` u8s:
1037     unsafe {
1038         ptr::copy_nonoverlapping(bytes.as_ptr(), buf_ptr, bytes.len());
1039         buf_ptr.add(bytes.len()).write(0);
1040     }
1041 
1042     // SAFETY: We just wrote the bytes above and they will remain valid for the
1043     // duration of `f` b/c buf doesn't get dropped until the end of the
1044     // function.
1045     match CStr::from_bytes_with_nul(unsafe { slice::from_raw_parts(buf_ptr, bytes.len() + 1) }) {
1046         Ok(s) => f(s),
1047         Err(_) => Err(io::Errno::INVAL),
1048     }
1049 }
1050 
1051 /// The slow path which handles any length. In theory OS's only support up to
1052 /// `PATH_MAX`, but we let the OS enforce that.
1053 #[allow(unsafe_code, clippy::int_plus_one)]
1054 #[cold]
with_c_str_slow_path<T, F>(bytes: &[u8], f: F) -> io::Result<T> where F: FnOnce(&CStr) -> io::Result<T>,1055 fn with_c_str_slow_path<T, F>(bytes: &[u8], f: F) -> io::Result<T>
1056 where
1057     F: FnOnce(&CStr) -> io::Result<T>,
1058 {
1059     #[cfg(feature = "alloc")]
1060     {
1061         f(&CString::new(bytes).map_err(|_cstr_err| io::Errno::INVAL)?)
1062     }
1063 
1064     #[cfg(not(feature = "alloc"))]
1065     {
1066         #[cfg(all(libc, not(target_os = "wasi")))]
1067         const LARGE_PATH_BUFFER_SIZE: usize = libc::PATH_MAX as usize;
1068         #[cfg(linux_raw)]
1069         const LARGE_PATH_BUFFER_SIZE: usize = linux_raw_sys::general::PATH_MAX as usize;
1070         #[cfg(target_os = "wasi")]
1071         const LARGE_PATH_BUFFER_SIZE: usize = 4096 as usize; // TODO: upstream this
1072 
1073         // Taken from
1074         // <https://github.com/rust-lang/rust/blob/a00f8ba7fcac1b27341679c51bf5a3271fa82df3/library/std/src/sys/common/small_c_string.rs>
1075         let mut buf = MaybeUninit::<[u8; LARGE_PATH_BUFFER_SIZE]>::uninit();
1076         let buf_ptr = buf.as_mut_ptr().cast::<u8>();
1077 
1078         // This helps test our safety condition below.
1079         if bytes.len() + 1 > LARGE_PATH_BUFFER_SIZE {
1080             return Err(io::Errno::NAMETOOLONG);
1081         }
1082 
1083         // SAFETY: `bytes.len() < LARGE_PATH_BUFFER_SIZE` which means we have
1084         // space for `bytes.len() + 1` u8s:
1085         unsafe {
1086             ptr::copy_nonoverlapping(bytes.as_ptr(), buf_ptr, bytes.len());
1087             buf_ptr.add(bytes.len()).write(0);
1088         }
1089 
1090         // SAFETY: We just wrote the bytes above and they will remain valid for
1091         // the duration of `f` b/c buf doesn't get dropped until the end of the
1092         // function.
1093         match CStr::from_bytes_with_nul(unsafe { slice::from_raw_parts(buf_ptr, bytes.len() + 1) })
1094         {
1095             Ok(s) => f(s),
1096             Err(_) => Err(io::Errno::INVAL),
1097         }
1098     }
1099 }
1100