rustix/path/
arg.rs

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