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 where possible.
7
8use crate::ffi::CStr;
9use crate::io;
10#[cfg(feature = "itoa")]
11use crate::path::DecInt;
12use crate::path::SMALL_PATH_BUFFER_SIZE;
13#[cfg(all(feature = "alloc", feature = "itoa"))]
14use alloc::borrow::ToOwned;
15use core::mem::MaybeUninit;
16use core::{ptr, slice, str};
17#[cfg(feature = "std")]
18use std::ffi::{OsStr, OsString};
19#[cfg(all(feature = "std", target_os = "hermit"))]
20use std::os::hermit::ext::ffi::{OsStrExt, OsStringExt};
21#[cfg(all(feature = "std", unix))]
22use std::os::unix::ffi::{OsStrExt, OsStringExt};
23#[cfg(all(feature = "std", target_os = "vxworks"))]
24use std::os::vxworks::ext::ffi::{OsStrExt, OsStringExt};
25#[cfg(all(feature = "std", target_os = "wasi"))]
26use std::os::wasi::ffi::{OsStrExt, OsStringExt};
27#[cfg(feature = "std")]
28use std::path::{Component, Components, Iter, Path, PathBuf};
29#[cfg(feature = "alloc")]
30use {crate::ffi::CString, alloc::borrow::Cow};
31#[cfg(feature = "alloc")]
32use {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
65pub trait Arg {
66    /// Returns a view of this string as a string slice.
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")]
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")]
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")]
81    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`.
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
92impl Arg for &str {
93    #[inline]
94    fn as_str(&self) -> io::Result<&str> {
95        Ok(self)
96    }
97
98    #[cfg(feature = "alloc")]
99    #[inline]
100    fn to_string_lossy(&self) -> Cow<'_, str> {
101        Cow::Borrowed(self)
102    }
103
104    #[cfg(feature = "alloc")]
105    #[inline]
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]
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]
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")]
134impl Arg for &String {
135    #[inline]
136    fn as_str(&self) -> io::Result<&str> {
137        Ok(self)
138    }
139
140    #[cfg(feature = "alloc")]
141    #[inline]
142    fn to_string_lossy(&self) -> Cow<'_, str> {
143        Cow::Borrowed(self)
144    }
145
146    #[cfg(feature = "alloc")]
147    #[inline]
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]
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]
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")]
174impl Arg for String {
175    #[inline]
176    fn as_str(&self) -> io::Result<&str> {
177        Ok(self)
178    }
179
180    #[cfg(feature = "alloc")]
181    #[inline]
182    fn to_string_lossy(&self) -> Cow<'_, str> {
183        Cow::Borrowed(self)
184    }
185
186    #[cfg(feature = "alloc")]
187    #[inline]
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]
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]
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")]
216impl Arg for &OsStr {
217    #[inline]
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]
224    fn to_string_lossy(&self) -> Cow<'_, str> {
225        OsStr::to_string_lossy(self)
226    }
227
228    #[cfg(feature = "alloc")]
229    #[inline]
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]
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]
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")]
258impl Arg for &OsString {
259    #[inline]
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]
266    fn to_string_lossy(&self) -> Cow<'_, str> {
267        self.as_os_str().to_string_lossy()
268    }
269
270    #[cfg(feature = "alloc")]
271    #[inline]
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]
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]
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")]
299impl Arg for OsString {
300    #[inline]
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]
307    fn to_string_lossy(&self) -> Cow<'_, str> {
308        self.as_os_str().to_string_lossy()
309    }
310
311    #[cfg(feature = "alloc")]
312    #[inline]
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]
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]
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")]
341impl Arg for &Path {
342    #[inline]
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]
349    fn to_string_lossy(&self) -> Cow<'_, str> {
350        Path::to_string_lossy(self)
351    }
352
353    #[cfg(feature = "alloc")]
354    #[inline]
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]
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]
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")]
383impl Arg for &PathBuf {
384    #[inline]
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]
394    fn to_string_lossy(&self) -> Cow<'_, str> {
395        self.as_path().to_string_lossy()
396    }
397
398    #[cfg(feature = "alloc")]
399    #[inline]
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]
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]
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")]
427impl Arg for PathBuf {
428    #[inline]
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]
435    fn to_string_lossy(&self) -> Cow<'_, str> {
436        self.as_os_str().to_string_lossy()
437    }
438
439    #[cfg(feature = "alloc")]
440    #[inline]
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]
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]
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
471impl Arg for &CStr {
472    #[inline]
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]
479    fn to_string_lossy(&self) -> Cow<'_, str> {
480        CStr::to_string_lossy(self)
481    }
482
483    #[cfg(feature = "alloc")]
484    #[inline]
485    fn as_cow_c_str(&self) -> io::Result<Cow<'_, CStr>> {
486        Ok(Cow::Borrowed(self))
487    }
488
489    #[cfg(feature = "alloc")]
490    #[inline]
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]
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")]
509impl Arg for &CString {
510    #[inline]
511    fn as_str(&self) -> io::Result<&str> {
512        unimplemented!()
513    }
514
515    #[cfg(feature = "alloc")]
516    #[inline]
517    fn to_string_lossy(&self) -> Cow<'_, str> {
518        unimplemented!()
519    }
520
521    #[cfg(feature = "alloc")]
522    #[inline]
523    fn as_cow_c_str(&self) -> io::Result<Cow<'_, CStr>> {
524        Ok(Cow::Borrowed(self))
525    }
526
527    #[cfg(feature = "alloc")]
528    #[inline]
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]
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")]
547impl Arg for CString {
548    #[inline]
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]
555    fn to_string_lossy(&self) -> Cow<'_, str> {
556        CStr::to_string_lossy(self)
557    }
558
559    #[cfg(feature = "alloc")]
560    #[inline]
561    fn as_cow_c_str(&self) -> io::Result<Cow<'_, CStr>> {
562        Ok(Cow::Borrowed(self))
563    }
564
565    #[cfg(feature = "alloc")]
566    #[inline]
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]
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")]
585impl<'a> Arg for Cow<'a, str> {
586    #[inline]
587    fn as_str(&self) -> io::Result<&str> {
588        Ok(self)
589    }
590
591    #[cfg(feature = "alloc")]
592    #[inline]
593    fn to_string_lossy(&self) -> Cow<'_, str> {
594        Cow::Borrowed(self)
595    }
596
597    #[cfg(feature = "alloc")]
598    #[inline]
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]
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]
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")]
632impl<'a> Arg for Cow<'a, OsStr> {
633    #[inline]
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]
640    fn to_string_lossy(&self) -> Cow<'_, str> {
641        (**self).to_string_lossy()
642    }
643
644    #[cfg(feature = "alloc")]
645    #[inline]
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]
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]
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")]
678impl<'a> Arg for Cow<'a, CStr> {
679    #[inline]
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]
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]
693    fn as_cow_c_str(&self) -> io::Result<Cow<'_, CStr>> {
694        Ok(Cow::Borrowed(self))
695    }
696
697    #[cfg(feature = "alloc")]
698    #[inline]
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]
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")]
717impl<'a> Arg for Component<'a> {
718    #[inline]
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]
725    fn to_string_lossy(&self) -> Cow<'_, str> {
726        self.as_os_str().to_string_lossy()
727    }
728
729    #[cfg(feature = "alloc")]
730    #[inline]
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]
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]
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")]
759impl<'a> Arg for Components<'a> {
760    #[inline]
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]
767    fn to_string_lossy(&self) -> Cow<'_, str> {
768        self.as_path().to_string_lossy()
769    }
770
771    #[cfg(feature = "alloc")]
772    #[inline]
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]
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]
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")]
803impl<'a> Arg for Iter<'a> {
804    #[inline]
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]
811    fn to_string_lossy(&self) -> Cow<'_, str> {
812        self.as_path().to_string_lossy()
813    }
814
815    #[cfg(feature = "alloc")]
816    #[inline]
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]
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]
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
846impl Arg for &[u8] {
847    #[inline]
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]
854    fn to_string_lossy(&self) -> Cow<'_, str> {
855        String::from_utf8_lossy(self)
856    }
857
858    #[cfg(feature = "alloc")]
859    #[inline]
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]
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]
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")]
888impl Arg for &Vec<u8> {
889    #[inline]
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]
896    fn to_string_lossy(&self) -> Cow<'_, str> {
897        String::from_utf8_lossy(self)
898    }
899
900    #[cfg(feature = "alloc")]
901    #[inline]
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]
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]
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")]
930impl Arg for Vec<u8> {
931    #[inline]
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]
938    fn to_string_lossy(&self) -> Cow<'_, str> {
939        String::from_utf8_lossy(self)
940    }
941
942    #[cfg(feature = "alloc")]
943    #[inline]
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]
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]
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")]
972impl Arg for DecInt {
973    #[inline]
974    fn as_str(&self) -> io::Result<&str> {
975        Ok(self.as_str())
976    }
977
978    #[cfg(feature = "alloc")]
979    #[inline]
980    fn to_string_lossy(&self) -> Cow<'_, str> {
981        Cow::Borrowed(self.as_str())
982    }
983
984    #[cfg(feature = "alloc")]
985    #[inline]
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]
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]
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]
1012fn with_c_str<T, F>(bytes: &[u8], f: F) -> io::Result<T>
1013where
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]
1055fn with_c_str_slow_path<T, F>(bytes: &[u8], f: F) -> io::Result<T>
1056where
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(libc)]
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
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` u8s:
1083        unsafe {
1084            ptr::copy_nonoverlapping(bytes.as_ptr(), buf_ptr, bytes.len());
1085            buf_ptr.add(bytes.len()).write(0);
1086        }
1087
1088        // SAFETY: We just wrote the bytes above and they will remain valid for
1089        // the duration of `f` b/c buf doesn't get dropped until the end of the
1090        // 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}