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