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/// # Example
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}
1098