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")]
232impl Arg for &OsStr {
233    #[inline]
234    fn as_str(&self) -> io::Result<&str> {
235        self.to_str().ok_or(io::Errno::INVAL)
236    }
237
238    #[inline]
239    fn to_string_lossy(&self) -> Cow<'_, str> {
240        OsStr::to_string_lossy(self)
241    }
242
243    #[inline]
244    fn as_cow_c_str(&self) -> io::Result<Cow<'_, CStr>> {
245        self.into_c_str()
246    }
247
248    #[inline]
249    fn into_c_str<'b>(self) -> io::Result<Cow<'b, CStr>>
250    where
251        Self: 'b,
252    {
253        #[cfg(all(target_os = "wasi", target_env = "p2", not(wasip2)))]
254        return self.to_str().ok_or(io::Errno::INVAL)?.into_c_str();
255        #[cfg(any(wasip2, not(all(target_os = "wasi", target_env = "p2"))))]
256        return self.as_bytes().into_c_str();
257    }
258
259    #[inline]
260    fn into_with_c_str<T, F>(self, f: F) -> io::Result<T>
261    where
262        Self: Sized,
263        F: FnOnce(&CStr) -> io::Result<T>,
264    {
265        #[cfg(all(target_os = "wasi", target_env = "p2", not(wasip2)))]
266        return self.as_str()?.into_with_c_str(f);
267
268        #[cfg(any(wasip2, not(all(target_os = "wasi", target_env = "p2"))))]
269        return self.as_bytes().into_with_c_str(f);
270    }
271}
272
273#[cfg(feature = "std")]
274impl Arg for &OsString {
275    #[inline]
276    fn as_str(&self) -> io::Result<&str> {
277        OsString::as_os_str(self).to_str().ok_or(io::Errno::INVAL)
278    }
279
280    #[inline]
281    fn to_string_lossy(&self) -> Cow<'_, str> {
282        self.as_os_str().to_string_lossy()
283    }
284
285    #[inline]
286    fn as_cow_c_str(&self) -> io::Result<Cow<'_, CStr>> {
287        self.as_os_str().into_c_str()
288    }
289
290    #[inline]
291    fn into_c_str<'b>(self) -> io::Result<Cow<'b, CStr>>
292    where
293        Self: 'b,
294    {
295        self.as_os_str().into_c_str()
296    }
297
298    #[inline]
299    fn into_with_c_str<T, F>(self, f: F) -> io::Result<T>
300    where
301        Self: Sized,
302        F: FnOnce(&CStr) -> io::Result<T>,
303    {
304        self.as_os_str().into_with_c_str(f)
305    }
306}
307
308#[cfg(feature = "std")]
309impl Arg for OsString {
310    #[inline]
311    fn as_str(&self) -> io::Result<&str> {
312        self.as_os_str().to_str().ok_or(io::Errno::INVAL)
313    }
314
315    #[inline]
316    fn to_string_lossy(&self) -> Cow<'_, str> {
317        self.as_os_str().to_string_lossy()
318    }
319
320    #[inline]
321    fn as_cow_c_str(&self) -> io::Result<Cow<'_, CStr>> {
322        self.as_os_str().into_c_str()
323    }
324
325    #[inline]
326    fn into_c_str<'b>(self) -> io::Result<Cow<'b, CStr>>
327    where
328        Self: 'b,
329    {
330        #[cfg(all(target_os = "wasi", target_env = "p2", not(wasip2)))]
331        return self
332            .into_string()
333            .map_err(|_strng_err| io::Errno::INVAL)?
334            .into_c_str();
335        #[cfg(any(wasip2, not(all(target_os = "wasi", target_env = "p2"))))]
336        self.into_vec().into_c_str()
337    }
338
339    #[inline]
340    fn into_with_c_str<T, F>(self, f: F) -> io::Result<T>
341    where
342        Self: Sized,
343        F: FnOnce(&CStr) -> io::Result<T>,
344    {
345        f(&self.into_c_str()?)
346    }
347}
348
349#[cfg(feature = "std")]
350impl Arg for &Path {
351    #[inline]
352    fn as_str(&self) -> io::Result<&str> {
353        self.as_os_str().to_str().ok_or(io::Errno::INVAL)
354    }
355
356    #[inline]
357    fn to_string_lossy(&self) -> Cow<'_, str> {
358        Path::to_string_lossy(self)
359    }
360
361    #[inline]
362    fn as_cow_c_str(&self) -> io::Result<Cow<'_, CStr>> {
363        self.as_os_str().into_c_str()
364    }
365
366    #[inline]
367    fn into_c_str<'b>(self) -> io::Result<Cow<'b, CStr>>
368    where
369        Self: 'b,
370    {
371        self.as_os_str().into_c_str()
372    }
373
374    #[inline]
375    fn into_with_c_str<T, F>(self, f: F) -> io::Result<T>
376    where
377        Self: Sized,
378        F: FnOnce(&CStr) -> io::Result<T>,
379    {
380        self.as_os_str().into_with_c_str(f)
381    }
382}
383
384#[cfg(feature = "std")]
385impl Arg for &PathBuf {
386    #[inline]
387    fn as_str(&self) -> io::Result<&str> {
388        self.as_os_str().to_str().ok_or(io::Errno::INVAL)
389    }
390
391    #[inline]
392    fn to_string_lossy(&self) -> Cow<'_, str> {
393        self.as_path().to_string_lossy()
394    }
395
396    #[inline]
397    fn as_cow_c_str(&self) -> io::Result<Cow<'_, CStr>> {
398        self.as_os_str().into_c_str()
399    }
400
401    #[inline]
402    fn into_c_str<'b>(self) -> io::Result<Cow<'b, CStr>>
403    where
404        Self: 'b,
405    {
406        self.as_path().into_c_str()
407    }
408
409    #[inline]
410    fn into_with_c_str<T, F>(self, f: F) -> io::Result<T>
411    where
412        Self: Sized,
413        F: FnOnce(&CStr) -> io::Result<T>,
414    {
415        self.as_os_str().into_with_c_str(f)
416    }
417}
418
419#[cfg(feature = "std")]
420impl Arg for PathBuf {
421    #[inline]
422    fn as_str(&self) -> io::Result<&str> {
423        self.as_os_str().to_str().ok_or(io::Errno::INVAL)
424    }
425
426    #[inline]
427    fn to_string_lossy(&self) -> Cow<'_, str> {
428        self.as_os_str().to_string_lossy()
429    }
430
431    #[inline]
432    fn as_cow_c_str(&self) -> io::Result<Cow<'_, CStr>> {
433        self.as_os_str().into_c_str()
434    }
435
436    #[inline]
437    fn into_c_str<'b>(self) -> io::Result<Cow<'b, CStr>>
438    where
439        Self: 'b,
440    {
441        self.into_os_string().into_c_str()
442    }
443
444    #[inline]
445    fn into_with_c_str<T, F>(self, f: F) -> io::Result<T>
446    where
447        Self: Sized,
448        F: FnOnce(&CStr) -> io::Result<T>,
449    {
450        self.into_os_string().into_with_c_str(f)
451    }
452}
453
454impl Arg for &CStr {
455    #[inline]
456    fn as_str(&self) -> io::Result<&str> {
457        self.to_str().map_err(|_utf8_err| io::Errno::INVAL)
458    }
459
460    #[cfg(feature = "alloc")]
461    #[inline]
462    fn to_string_lossy(&self) -> Cow<'_, str> {
463        CStr::to_string_lossy(self)
464    }
465
466    #[cfg(feature = "alloc")]
467    #[inline]
468    fn as_cow_c_str(&self) -> io::Result<Cow<'_, CStr>> {
469        Ok(Cow::Borrowed(self))
470    }
471
472    #[cfg(feature = "alloc")]
473    #[inline]
474    fn into_c_str<'b>(self) -> io::Result<Cow<'b, CStr>>
475    where
476        Self: 'b,
477    {
478        Ok(Cow::Borrowed(self))
479    }
480
481    #[inline]
482    fn into_with_c_str<T, F>(self, f: F) -> io::Result<T>
483    where
484        Self: Sized,
485        F: FnOnce(&CStr) -> io::Result<T>,
486    {
487        f(self)
488    }
489}
490
491#[cfg(feature = "alloc")]
492impl Arg for &CString {
493    #[inline]
494    fn as_str(&self) -> io::Result<&str> {
495        self.to_str().map_err(|_utf8_err| io::Errno::INVAL)
496    }
497
498    #[inline]
499    fn to_string_lossy(&self) -> Cow<'_, str> {
500        CStr::to_string_lossy(self)
501    }
502
503    #[inline]
504    fn as_cow_c_str(&self) -> io::Result<Cow<'_, CStr>> {
505        Ok(Cow::Borrowed(self))
506    }
507
508    #[inline]
509    fn into_c_str<'b>(self) -> io::Result<Cow<'b, CStr>>
510    where
511        Self: 'b,
512    {
513        Ok(Cow::Borrowed(self))
514    }
515
516    #[inline]
517    fn into_with_c_str<T, F>(self, f: F) -> io::Result<T>
518    where
519        Self: Sized,
520        F: FnOnce(&CStr) -> io::Result<T>,
521    {
522        f(self)
523    }
524}
525
526#[cfg(feature = "alloc")]
527impl Arg for CString {
528    #[inline]
529    fn as_str(&self) -> io::Result<&str> {
530        self.to_str().map_err(|_utf8_err| io::Errno::INVAL)
531    }
532
533    #[inline]
534    fn to_string_lossy(&self) -> Cow<'_, str> {
535        CStr::to_string_lossy(self)
536    }
537
538    #[inline]
539    fn as_cow_c_str(&self) -> io::Result<Cow<'_, CStr>> {
540        Ok(Cow::Borrowed(self))
541    }
542
543    #[inline]
544    fn into_c_str<'b>(self) -> io::Result<Cow<'b, CStr>>
545    where
546        Self: 'b,
547    {
548        Ok(Cow::Owned(self))
549    }
550
551    #[inline]
552    fn into_with_c_str<T, F>(self, f: F) -> io::Result<T>
553    where
554        Self: Sized,
555        F: FnOnce(&CStr) -> io::Result<T>,
556    {
557        f(&self)
558    }
559}
560
561#[cfg(feature = "alloc")]
562impl<'a> Arg for Cow<'a, str> {
563    #[inline]
564    fn as_str(&self) -> io::Result<&str> {
565        Ok(self)
566    }
567
568    #[inline]
569    fn to_string_lossy(&self) -> Cow<'_, str> {
570        Cow::Borrowed(self)
571    }
572
573    #[inline]
574    fn as_cow_c_str(&self) -> io::Result<Cow<'_, CStr>> {
575        Ok(Cow::Owned(
576            CString::new(self.as_ref()).map_err(|_cstr_err| io::Errno::INVAL)?,
577        ))
578    }
579
580    #[inline]
581    fn into_c_str<'b>(self) -> io::Result<Cow<'b, CStr>>
582    where
583        Self: 'b,
584    {
585        Ok(Cow::Owned(
586            match self {
587                Cow::Owned(s) => CString::new(s),
588                Cow::Borrowed(s) => CString::new(s),
589            }
590            .map_err(|_cstr_err| io::Errno::INVAL)?,
591        ))
592    }
593
594    #[inline]
595    fn into_with_c_str<T, F>(self, f: F) -> io::Result<T>
596    where
597        Self: Sized,
598        F: FnOnce(&CStr) -> io::Result<T>,
599    {
600        with_c_str(self.as_bytes(), f)
601    }
602}
603
604#[cfg(feature = "std")]
605impl<'a> Arg for Cow<'a, OsStr> {
606    #[inline]
607    fn as_str(&self) -> io::Result<&str> {
608        (**self).to_str().ok_or(io::Errno::INVAL)
609    }
610
611    #[inline]
612    fn to_string_lossy(&self) -> Cow<'_, str> {
613        (**self).to_string_lossy()
614    }
615
616    #[inline]
617    fn as_cow_c_str(&self) -> io::Result<Cow<'_, CStr>> {
618        (&**self).into_c_str()
619    }
620
621    #[inline]
622    fn into_c_str<'b>(self) -> io::Result<Cow<'b, CStr>>
623    where
624        Self: 'b,
625    {
626        match self {
627            Cow::Owned(os) => os.into_c_str(),
628            Cow::Borrowed(os) => os.into_c_str(),
629        }
630    }
631
632    #[inline]
633    fn into_with_c_str<T, F>(self, f: F) -> io::Result<T>
634    where
635        Self: Sized,
636        F: FnOnce(&CStr) -> io::Result<T>,
637    {
638        (&*self).into_with_c_str(f)
639    }
640}
641
642#[cfg(feature = "alloc")]
643impl<'a> Arg for Cow<'a, CStr> {
644    #[inline]
645    fn as_str(&self) -> io::Result<&str> {
646        self.to_str().map_err(|_utf8_err| io::Errno::INVAL)
647    }
648
649    #[inline]
650    fn to_string_lossy(&self) -> Cow<'_, str> {
651        let borrow: &CStr = core::borrow::Borrow::borrow(self);
652        borrow.to_string_lossy()
653    }
654
655    #[inline]
656    fn as_cow_c_str(&self) -> io::Result<Cow<'_, CStr>> {
657        Ok(Cow::Borrowed(self))
658    }
659
660    #[inline]
661    fn into_c_str<'b>(self) -> io::Result<Cow<'b, CStr>>
662    where
663        Self: 'b,
664    {
665        Ok(self)
666    }
667
668    #[inline]
669    fn into_with_c_str<T, F>(self, f: F) -> io::Result<T>
670    where
671        Self: Sized,
672        F: FnOnce(&CStr) -> io::Result<T>,
673    {
674        f(&self)
675    }
676}
677
678#[cfg(feature = "std")]
679impl<'a> Arg for Component<'a> {
680    #[inline]
681    fn as_str(&self) -> io::Result<&str> {
682        self.as_os_str().to_str().ok_or(io::Errno::INVAL)
683    }
684
685    #[inline]
686    fn to_string_lossy(&self) -> Cow<'_, str> {
687        self.as_os_str().to_string_lossy()
688    }
689
690    #[inline]
691    fn as_cow_c_str(&self) -> io::Result<Cow<'_, CStr>> {
692        self.as_os_str().into_c_str()
693    }
694
695    #[inline]
696    fn into_c_str<'b>(self) -> io::Result<Cow<'b, CStr>>
697    where
698        Self: 'b,
699    {
700        self.as_os_str().into_c_str()
701    }
702
703    #[inline]
704    fn into_with_c_str<T, F>(self, f: F) -> io::Result<T>
705    where
706        Self: Sized,
707        F: FnOnce(&CStr) -> io::Result<T>,
708    {
709        self.as_os_str().into_with_c_str(f)
710    }
711}
712
713#[cfg(feature = "std")]
714impl<'a> Arg for Components<'a> {
715    #[inline]
716    fn as_str(&self) -> io::Result<&str> {
717        self.as_path().to_str().ok_or(io::Errno::INVAL)
718    }
719
720    #[inline]
721    fn to_string_lossy(&self) -> Cow<'_, str> {
722        self.as_path().to_string_lossy()
723    }
724
725    #[inline]
726    fn as_cow_c_str(&self) -> io::Result<Cow<'_, CStr>> {
727        self.as_path().into_c_str()
728    }
729
730    #[inline]
731    fn into_c_str<'b>(self) -> io::Result<Cow<'b, CStr>>
732    where
733        Self: 'b,
734    {
735        self.as_path().into_c_str()
736    }
737
738    #[inline]
739    fn into_with_c_str<T, F>(self, f: F) -> io::Result<T>
740    where
741        Self: Sized,
742        F: FnOnce(&CStr) -> io::Result<T>,
743    {
744        self.as_path().into_with_c_str(f)
745    }
746}
747
748#[cfg(feature = "std")]
749impl<'a> Arg for Iter<'a> {
750    #[inline]
751    fn as_str(&self) -> io::Result<&str> {
752        self.as_path().to_str().ok_or(io::Errno::INVAL)
753    }
754
755    #[inline]
756    fn to_string_lossy(&self) -> Cow<'_, str> {
757        self.as_path().to_string_lossy()
758    }
759
760    #[inline]
761    fn as_cow_c_str(&self) -> io::Result<Cow<'_, CStr>> {
762        self.as_path().into_c_str()
763    }
764
765    #[inline]
766    fn into_c_str<'b>(self) -> io::Result<Cow<'b, CStr>>
767    where
768        Self: 'b,
769    {
770        self.as_path().into_c_str()
771    }
772
773    #[inline]
774    fn into_with_c_str<T, F>(self, f: F) -> io::Result<T>
775    where
776        Self: Sized,
777        F: FnOnce(&CStr) -> io::Result<T>,
778    {
779        self.as_path().into_with_c_str(f)
780    }
781}
782
783impl Arg for &[u8] {
784    #[inline]
785    fn as_str(&self) -> io::Result<&str> {
786        str::from_utf8(self).map_err(|_utf8_err| io::Errno::INVAL)
787    }
788
789    #[cfg(feature = "alloc")]
790    #[inline]
791    fn to_string_lossy(&self) -> Cow<'_, str> {
792        String::from_utf8_lossy(self)
793    }
794
795    #[cfg(feature = "alloc")]
796    #[inline]
797    fn as_cow_c_str(&self) -> io::Result<Cow<'_, CStr>> {
798        Ok(Cow::Owned(
799            CString::new(*self).map_err(|_cstr_err| io::Errno::INVAL)?,
800        ))
801    }
802
803    #[cfg(feature = "alloc")]
804    #[inline]
805    fn into_c_str<'b>(self) -> io::Result<Cow<'b, CStr>>
806    where
807        Self: 'b,
808    {
809        Ok(Cow::Owned(
810            CString::new(self).map_err(|_cstr_err| io::Errno::INVAL)?,
811        ))
812    }
813
814    #[inline]
815    fn into_with_c_str<T, F>(self, f: F) -> io::Result<T>
816    where
817        Self: Sized,
818        F: FnOnce(&CStr) -> io::Result<T>,
819    {
820        with_c_str(self, f)
821    }
822}
823
824#[cfg(feature = "alloc")]
825impl Arg for &Vec<u8> {
826    #[inline]
827    fn as_str(&self) -> io::Result<&str> {
828        str::from_utf8(self).map_err(|_utf8_err| io::Errno::INVAL)
829    }
830
831    #[cfg(feature = "alloc")]
832    #[inline]
833    fn to_string_lossy(&self) -> Cow<'_, str> {
834        String::from_utf8_lossy(self)
835    }
836
837    #[cfg(feature = "alloc")]
838    #[inline]
839    fn as_cow_c_str(&self) -> io::Result<Cow<'_, CStr>> {
840        Ok(Cow::Owned(
841            CString::new(self.as_slice()).map_err(|_cstr_err| io::Errno::INVAL)?,
842        ))
843    }
844
845    #[cfg(feature = "alloc")]
846    #[inline]
847    fn into_c_str<'b>(self) -> io::Result<Cow<'b, CStr>>
848    where
849        Self: 'b,
850    {
851        Ok(Cow::Owned(
852            CString::new(self.as_slice()).map_err(|_cstr_err| io::Errno::INVAL)?,
853        ))
854    }
855
856    #[inline]
857    fn into_with_c_str<T, F>(self, f: F) -> io::Result<T>
858    where
859        Self: Sized,
860        F: FnOnce(&CStr) -> io::Result<T>,
861    {
862        with_c_str(self, f)
863    }
864}
865
866#[cfg(feature = "alloc")]
867impl Arg for Vec<u8> {
868    #[inline]
869    fn as_str(&self) -> io::Result<&str> {
870        str::from_utf8(self).map_err(|_utf8_err| io::Errno::INVAL)
871    }
872
873    #[cfg(feature = "alloc")]
874    #[inline]
875    fn to_string_lossy(&self) -> Cow<'_, str> {
876        String::from_utf8_lossy(self)
877    }
878
879    #[cfg(feature = "alloc")]
880    #[inline]
881    fn as_cow_c_str(&self) -> io::Result<Cow<'_, CStr>> {
882        Ok(Cow::Owned(
883            CString::new(self.as_slice()).map_err(|_cstr_err| io::Errno::INVAL)?,
884        ))
885    }
886
887    #[cfg(feature = "alloc")]
888    #[inline]
889    fn into_c_str<'b>(self) -> io::Result<Cow<'b, CStr>>
890    where
891        Self: 'b,
892    {
893        Ok(Cow::Owned(
894            CString::new(self).map_err(|_cstr_err| io::Errno::INVAL)?,
895        ))
896    }
897
898    #[inline]
899    fn into_with_c_str<T, F>(self, f: F) -> io::Result<T>
900    where
901        Self: Sized,
902        F: FnOnce(&CStr) -> io::Result<T>,
903    {
904        f(&CString::new(self).map_err(|_cstr_err| io::Errno::INVAL)?)
905    }
906}
907
908impl Arg for DecInt {
909    #[inline]
910    fn as_str(&self) -> io::Result<&str> {
911        Ok(self.as_str())
912    }
913
914    #[cfg(feature = "alloc")]
915    #[inline]
916    fn to_string_lossy(&self) -> Cow<'_, str> {
917        Cow::Borrowed(self.as_str())
918    }
919
920    #[cfg(feature = "alloc")]
921    #[inline]
922    fn as_cow_c_str(&self) -> io::Result<Cow<'_, CStr>> {
923        Ok(Cow::Borrowed(self.as_c_str()))
924    }
925
926    #[cfg(feature = "alloc")]
927    #[inline]
928    fn into_c_str<'b>(self) -> io::Result<Cow<'b, CStr>>
929    where
930        Self: 'b,
931    {
932        Ok(Cow::Owned(self.as_c_str().to_owned()))
933    }
934
935    #[inline]
936    fn into_with_c_str<T, F>(self, f: F) -> io::Result<T>
937    where
938        Self: Sized,
939        F: FnOnce(&CStr) -> io::Result<T>,
940    {
941        f(self.as_c_str())
942    }
943}
944
945/// Runs a closure with `bytes` passed in as a `&CStr`.
946#[allow(unsafe_code, clippy::int_plus_one)]
947#[inline]
948fn with_c_str<T, F>(bytes: &[u8], f: F) -> io::Result<T>
949where
950    F: FnOnce(&CStr) -> io::Result<T>,
951{
952    // Most paths are less than `SMALL_PATH_BUFFER_SIZE` long. The rest can go
953    // through the dynamic allocation path. If you're opening many files in a
954    // directory with a long path, consider opening the directory and using
955    // `openat` to open the files under it, which will avoid this, and is often
956    // faster in the OS as well.
957
958    // Test with `>=` so that we have room for the trailing NUL.
959    if bytes.len() >= SMALL_PATH_BUFFER_SIZE {
960        return with_c_str_slow_path(bytes, f);
961    }
962
963    // Taken from
964    // <https://github.com/rust-lang/rust/blob/a00f8ba7fcac1b27341679c51bf5a3271fa82df3/library/std/src/sys/common/small_c_string.rs>
965    let mut buf = MaybeUninit::<[u8; SMALL_PATH_BUFFER_SIZE]>::uninit();
966    let buf_ptr = buf.as_mut_ptr().cast::<u8>();
967
968    // This helps test our safety condition below.
969    debug_assert!(bytes.len() + 1 <= SMALL_PATH_BUFFER_SIZE);
970
971    // SAFETY: `bytes.len() < SMALL_PATH_BUFFER_SIZE` which means we have space
972    // for `bytes.len() + 1` `u8`s:
973    unsafe {
974        ptr::copy_nonoverlapping(bytes.as_ptr(), buf_ptr, bytes.len());
975        buf_ptr.add(bytes.len()).write(b'\0');
976    }
977
978    // SAFETY: We just wrote the bytes above and they will remain valid for the
979    // duration of `f` because `buf` doesn't get dropped until the end of the
980    // function.
981    match CStr::from_bytes_with_nul(unsafe { slice::from_raw_parts(buf_ptr, bytes.len() + 1) }) {
982        Ok(s) => f(s),
983        Err(_) => Err(io::Errno::INVAL),
984    }
985}
986
987/// The slow path which handles any length. In theory OS's only support up to
988/// `PATH_MAX`, but we let the OS enforce that.
989#[allow(unsafe_code, clippy::int_plus_one)]
990#[cold]
991fn with_c_str_slow_path<T, F>(bytes: &[u8], f: F) -> io::Result<T>
992where
993    F: FnOnce(&CStr) -> io::Result<T>,
994{
995    #[cfg(feature = "alloc")]
996    {
997        f(&CString::new(bytes).map_err(|_cstr_err| io::Errno::INVAL)?)
998    }
999
1000    #[cfg(not(feature = "alloc"))]
1001    {
1002        #[cfg(all(
1003            libc,
1004            not(any(
1005                target_os = "espidf",
1006                target_os = "horizon",
1007                target_os = "hurd",
1008                target_os = "vita",
1009                target_os = "wasi"
1010            ))
1011        ))]
1012        const LARGE_PATH_BUFFER_SIZE: usize = libc::PATH_MAX as usize;
1013        #[cfg(linux_raw)]
1014        const LARGE_PATH_BUFFER_SIZE: usize = linux_raw_sys::general::PATH_MAX as usize;
1015        #[cfg(any(
1016            target_os = "espidf",
1017            target_os = "horizon",
1018            target_os = "hurd",
1019            target_os = "vita",
1020            target_os = "wasi"
1021        ))]
1022        const LARGE_PATH_BUFFER_SIZE: usize = 4096 as usize; // TODO: upstream this
1023
1024        // Taken from
1025        // <https://github.com/rust-lang/rust/blob/a00f8ba7fcac1b27341679c51bf5a3271fa82df3/library/std/src/sys/common/small_c_string.rs>
1026        let mut buf = MaybeUninit::<[u8; LARGE_PATH_BUFFER_SIZE]>::uninit();
1027        let buf_ptr = buf.as_mut_ptr().cast::<u8>();
1028
1029        // This helps test our safety condition below.
1030        if bytes.len() + 1 > LARGE_PATH_BUFFER_SIZE {
1031            return Err(io::Errno::NAMETOOLONG);
1032        }
1033
1034        // SAFETY: `bytes.len() < LARGE_PATH_BUFFER_SIZE` which means we have
1035        // space for `bytes.len() + 1` `u8`s:
1036        unsafe {
1037            ptr::copy_nonoverlapping(bytes.as_ptr(), buf_ptr, bytes.len());
1038            buf_ptr.add(bytes.len()).write(b'\0');
1039        }
1040
1041        // SAFETY: We just wrote the bytes above and they will remain valid for
1042        // the duration of `f` because `buf` doesn't get dropped until the end
1043        // of the function.
1044        match CStr::from_bytes_with_nul(unsafe { slice::from_raw_parts(buf_ptr, bytes.len() + 1) })
1045        {
1046            Ok(s) => f(s),
1047            Err(_) => Err(io::Errno::INVAL),
1048        }
1049    }
1050}