1#[cfg(feature = "libc")]
12pub extern crate libc;
13#[cfg(all(feature = "windows-sys", target_os = "windows"))]
14pub extern crate windows_sys;
15
16mod features; mod macros; mod mods; pub use uucore_procs::*;
23
24pub use crate::mods::clap_localization;
26pub use crate::mods::display;
27pub use crate::mods::error;
28#[cfg(feature = "fs")]
29pub use crate::mods::io;
30pub use crate::mods::line_ending;
31pub use crate::mods::locale;
32pub use crate::mods::os;
33pub use crate::mods::panic;
34pub use crate::mods::posix;
35
36#[cfg(feature = "backup-control")]
38pub use crate::features::backup_control;
39#[cfg(feature = "benchmark")]
40pub use crate::features::benchmark;
41#[cfg(feature = "buf-copy")]
42pub use crate::features::buf_copy;
43#[cfg(feature = "checksum")]
44pub use crate::features::checksum;
45#[cfg(feature = "colors")]
46pub use crate::features::colors;
47#[cfg(feature = "encoding")]
48pub use crate::features::encoding;
49#[cfg(feature = "extendedbigdecimal")]
50pub use crate::features::extendedbigdecimal;
51#[cfg(feature = "fast-inc")]
52pub use crate::features::fast_inc;
53#[cfg(feature = "format")]
54pub use crate::features::format;
55#[cfg(feature = "fs")]
56pub use crate::features::fs;
57#[cfg(feature = "hardware")]
58pub use crate::features::hardware;
59#[cfg(feature = "i18n-common")]
60pub use crate::features::i18n;
61#[cfg(feature = "lines")]
62pub use crate::features::lines;
63#[cfg(any(
64 feature = "parser",
65 feature = "parser-num",
66 feature = "parser-size",
67 feature = "parser-glob"
68))]
69pub use crate::features::parser;
70#[cfg(feature = "quoting-style")]
71pub use crate::features::quoting_style;
72#[cfg(feature = "ranges")]
73pub use crate::features::ranges;
74#[cfg(feature = "ringbuffer")]
75pub use crate::features::ringbuffer;
76#[cfg(feature = "sum")]
77pub use crate::features::sum;
78#[cfg(feature = "feat_systemd_logind")]
79pub use crate::features::systemd_logind;
80#[cfg(feature = "time")]
81pub use crate::features::time;
82#[cfg(feature = "update-control")]
83pub use crate::features::update_control;
84#[cfg(feature = "uptime")]
85pub use crate::features::uptime;
86#[cfg(feature = "version-cmp")]
87pub use crate::features::version_cmp;
88
89#[cfg(all(not(windows), feature = "mode"))]
92pub use crate::features::mode;
93#[cfg(all(unix, feature = "entries"))]
95pub use crate::features::entries;
96#[cfg(all(unix, feature = "perms"))]
97pub use crate::features::perms;
98#[cfg(all(
99 any(target_os = "linux", target_os = "android"),
100 any(feature = "pipes", feature = "buf-copy")
101))]
102pub use crate::features::pipes;
103#[cfg(all(unix, feature = "process"))]
104pub use crate::features::process;
105#[cfg(all(unix, feature = "safe-copy"))]
106pub use crate::features::safe_copy;
107#[cfg(all(unix, not(target_os = "redox")))]
108pub use crate::features::safe_traversal;
109#[cfg(all(unix, not(target_os = "fuchsia"), feature = "signals"))]
110pub use crate::features::signals;
111#[cfg(all(
112 unix,
113 not(target_os = "android"),
114 not(target_os = "fuchsia"),
115 not(target_os = "openbsd"),
116 not(target_os = "redox"),
117 feature = "utmpx"
118))]
119pub use crate::features::utmpx;
120#[cfg(all(windows, feature = "wide"))]
122pub use crate::features::wide;
123
124#[cfg(feature = "fsext")]
125pub use crate::features::fsext;
126
127#[cfg(all(unix, feature = "fsxattr"))]
128pub use crate::features::fsxattr;
129
130#[cfg(all(feature = "selinux", any(target_os = "linux", target_os = "android")))]
131pub use crate::features::selinux;
132
133#[cfg(all(feature = "smack", target_os = "linux"))]
134pub use crate::features::smack;
135
136#[cfg(unix)]
139use nix::errno::Errno;
140#[cfg(unix)]
141use nix::sys::signal::{
142 SaFlags, SigAction, SigHandler::SigDfl, SigSet, Signal::SIGBUS, Signal::SIGSEGV, sigaction,
143};
144use std::borrow::Cow;
145use std::ffi::{OsStr, OsString};
146use std::io::{BufRead, BufReader};
147use std::iter;
148#[cfg(unix)]
149use std::os::unix::ffi::{OsStrExt, OsStringExt};
150#[cfg(target_os = "wasi")]
151use std::os::wasi::ffi::{OsStrExt, OsStringExt};
152use std::str;
153use std::str::Utf8Chunk;
154use std::sync::{LazyLock, atomic::Ordering};
155
156#[cfg(unix)]
159pub fn disable_rust_signal_handlers() -> Result<(), Errno> {
160 unsafe {
161 sigaction(
162 SIGSEGV,
163 &SigAction::new(SigDfl, SaFlags::empty(), SigSet::all()),
164 )
165 }?;
166 unsafe {
167 sigaction(
168 SIGBUS,
169 &SigAction::new(SigDfl, SaFlags::empty(), SigSet::all()),
170 )
171 }?;
172 Ok(())
173}
174
175pub fn get_canonical_util_name(util_name: &str) -> &str {
176 let util_name = &util_name[3..];
178 match util_name {
179 "[" => "test",
181 "dir" => "ls", "vdir" => "ls", _ => util_name,
186 }
187}
188
189#[macro_export]
190macro_rules! bin_inner {
191 ($util:ident, $post:expr) => {
192 pub fn main() {
193 use std::io::Write;
194 use uucore::locale;
195
196 uucore::panic::preserve_inherited_sigpipe();
198
199 uucore::panic::mute_sigpipe_panic();
201 locale::setup_localization(uucore::get_canonical_util_name(stringify!($util)))
202 .unwrap_or_else(|err| {
203 match err {
204 uucore::locale::LocalizationError::ParseResource {
205 error: err_msg,
206 snippet,
207 } => eprintln!("Localization parse error at {snippet}: {err_msg:?}"),
208 other => eprintln!("Could not init the localization system: {other}"),
209 }
210 std::process::exit(99)
211 });
212
213 let code = $util::uumain(uucore::args_os());
215 $post
216
217 std::process::exit(code);
218 }
219 };
220}
221#[macro_export]
226macro_rules! bin {
227 ($util:ident, no_flush) => {
228 ::uucore::bin_inner! {$util, {}}
229 };
230 ($util:ident) => {
231 ::uucore::bin_inner! {$util, {
232 if let Err(e) = std::io::stdout().flush() {
234 eprintln!("Error flushing stdout: {e}");
235 }
236 }}
237 };
238}
239
240#[macro_export]
245macro_rules! crate_version {
246 () => {
247 concat!("(uutils coreutils) ", env!("CARGO_PKG_VERSION"))
248 };
249}
250
251pub fn format_usage(s: &str) -> String {
259 let s = s.replace('\n', &format!("\n{}", " ".repeat(7)));
260 s.replace("{}", execution_phrase())
261}
262
263pub fn localized_help_template(util_name: &str) -> clap::builder::StyledStr {
285 use std::io::IsTerminal;
286
287 let colors_enabled = if std::env::var("NO_COLOR").is_ok() {
289 false
290 } else if std::env::var("CLICOLOR_FORCE").is_ok() || std::env::var("FORCE_COLOR").is_ok() {
291 true
292 } else {
293 IsTerminal::is_terminal(&std::io::stdout())
294 && std::env::var("TERM").unwrap_or_default() != "dumb"
295 };
296
297 localized_help_template_with_colors(util_name, colors_enabled)
298}
299
300pub fn localized_help_template_with_colors(
303 util_name: &str,
304 colors_enabled: bool,
305) -> clap::builder::StyledStr {
306 use std::fmt::Write;
307
308 let _ = locale::setup_localization(util_name);
310
311 let usage_label = crate::locale::translate!("common-usage");
313
314 let mut template = clap::builder::StyledStr::new();
316
317 writeln!(template, "{{before-help}}{{about-with-newline}}").unwrap();
319
320 if colors_enabled {
322 write!(
323 template,
324 "\x1b[1m\x1b[4m{usage_label}:\x1b[0m {{usage}}\n\n"
325 )
326 .unwrap();
327 } else {
328 write!(template, "{usage_label}: {{usage}}\n\n").unwrap();
329 }
330
331 write!(template, "{{all-args}}{{after-help}}").unwrap();
333
334 template
335}
336
337pub fn get_utility_is_second_arg() -> bool {
340 macros::UTILITY_IS_SECOND_ARG.load(Ordering::SeqCst)
341}
342
343pub fn set_utility_is_second_arg() {
346 macros::UTILITY_IS_SECOND_ARG.store(true, Ordering::SeqCst);
347}
348
349#[cfg(windows)]
352static ARGV: LazyLock<Vec<OsString>> = LazyLock::new(|| wild::args_os().collect());
353#[cfg(not(windows))]
354static ARGV: LazyLock<Vec<OsString>> = LazyLock::new(|| std::env::args_os().collect());
355
356static UTIL_NAME: LazyLock<String> = LazyLock::new(|| {
357 let base_index = usize::from(get_utility_is_second_arg());
358 let is_man = usize::from(ARGV[base_index].eq("manpage"));
359 let argv_index = base_index + is_man;
360
361 std::path::Path::new(&ARGV[argv_index])
365 .file_name()
366 .unwrap_or(&ARGV[argv_index])
367 .to_string_lossy()
368 .into_owned()
369});
370
371pub fn util_name() -> &'static str {
373 &UTIL_NAME
374}
375
376static EXECUTION_PHRASE: LazyLock<String> = LazyLock::new(|| {
377 if get_utility_is_second_arg() {
378 ARGV.iter()
379 .take(2)
380 .map(|os_str| os_str.to_string_lossy().into_owned())
381 .collect::<Vec<_>>()
382 .join(" ")
383 } else {
384 ARGV[0].to_string_lossy().into_owned()
385 }
386});
387
388pub fn execution_phrase() -> &'static str {
390 &EXECUTION_PHRASE
391}
392
393pub trait Args: Iterator<Item = OsString> + Sized {
398 fn collect_lossy(self) -> Vec<String> {
400 self.map(|s| s.to_string_lossy().into_owned()).collect()
401 }
402
403 fn collect_ignore(self) -> Vec<String> {
405 self.filter_map(|s| s.into_string().ok()).collect()
406 }
407}
408
409impl<T: Iterator<Item = OsString> + Sized> Args for T {}
410
411pub fn args_os() -> impl Iterator<Item = OsString> {
414 ARGV.iter().cloned()
415}
416
417pub fn args_os_filtered() -> impl Iterator<Item = OsString> {
421 ARGV.iter().filter(|arg| !arg.is_empty()).cloned()
422}
423
424pub fn read_yes() -> bool {
426 let mut s = String::new();
427 match std::io::stdin().read_line(&mut s) {
428 Ok(_) => matches!(s.chars().next(), Some('y' | 'Y')),
429 _ => false,
430 }
431}
432
433#[derive(Debug)]
434pub struct NonUtf8OsStrError {
435 input_lossy_string: String,
436}
437
438impl std::fmt::Display for NonUtf8OsStrError {
439 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
440 use os_display::Quotable;
441 let quoted = self.input_lossy_string.quote();
442 f.write_fmt(format_args!(
443 "invalid UTF-8 input {quoted} encountered when converting to bytes on a platform that doesn't expose byte arguments",
444 ))
445 }
446}
447
448impl std::error::Error for NonUtf8OsStrError {}
449impl error::UError for NonUtf8OsStrError {}
450
451#[cfg_attr(any(unix, target_os = "wasi"), expect(clippy::unnecessary_wraps))]
456pub fn os_str_as_bytes(os_string: &OsStr) -> Result<&[u8], NonUtf8OsStrError> {
457 #[cfg(any(unix, target_os = "wasi"))]
458 return Ok(os_string.as_bytes());
459
460 #[cfg(not(any(unix, target_os = "wasi")))]
461 os_string
462 .to_str()
463 .ok_or_else(|| NonUtf8OsStrError {
464 input_lossy_string: os_string.to_string_lossy().into_owned(),
465 })
466 .map(str::as_bytes)
467}
468
469pub fn os_str_as_bytes_lossy(os_string: &OsStr) -> Cow<'_, [u8]> {
474 #[cfg(any(unix, target_os = "wasi"))]
475 return Cow::from(os_string.as_bytes());
476
477 #[cfg(not(any(unix, target_os = "wasi")))]
478 match os_string.to_string_lossy() {
479 Cow::Borrowed(slice) => Cow::from(slice.as_bytes()),
480 Cow::Owned(owned) => Cow::from(owned.into_bytes()),
481 }
482}
483
484#[cfg_attr(any(unix, target_os = "wasi"), expect(clippy::unnecessary_wraps))]
490pub fn os_str_from_bytes(bytes: &[u8]) -> error::UResult<Cow<'_, OsStr>> {
491 #[cfg(any(unix, target_os = "wasi"))]
492 return Ok(Cow::Borrowed(OsStr::from_bytes(bytes)));
493
494 #[cfg(not(any(unix, target_os = "wasi")))]
495 Ok(Cow::Owned(OsString::from(str::from_utf8(bytes).map_err(
496 |_| error::UUsageError::new(1, "Unable to transform bytes into OsStr"),
497 )?)))
498}
499
500#[cfg_attr(any(unix, target_os = "wasi"), expect(clippy::unnecessary_wraps))]
505pub fn os_string_from_vec(vec: Vec<u8>) -> error::UResult<OsString> {
506 #[cfg(any(unix, target_os = "wasi"))]
507 return Ok(OsString::from_vec(vec));
508
509 #[cfg(not(any(unix, target_os = "wasi")))]
510 Ok(OsString::from(String::from_utf8(vec).map_err(|_| {
511 error::UUsageError::new(1, "invalid UTF-8 was detected in one or more arguments")
512 })?))
513}
514
515#[cfg_attr(any(unix, target_os = "wasi"), expect(clippy::unnecessary_wraps))]
520pub fn os_string_to_vec(s: OsString) -> error::UResult<Vec<u8>> {
521 #[cfg(any(unix, target_os = "wasi"))]
522 let v = s.into_vec();
523 #[cfg(not(any(unix, target_os = "wasi")))]
524 let v = s
525 .into_string()
526 .map_err(|_| {
527 error::UUsageError::new(1, "invalid UTF-8 was detected in one or more arguments")
528 })?
529 .into();
530
531 Ok(v)
532}
533
534pub fn read_byte_lines<R: std::io::Read>(
537 mut buf_reader: BufReader<R>,
538) -> impl Iterator<Item = std::io::Result<Vec<u8>>> {
539 iter::from_fn(move || {
540 let mut buf = Vec::with_capacity(256);
541
542 match buf_reader.read_until(b'\n', &mut buf) {
543 Ok(0) => None,
544 Err(e) => Some(Err(e)),
545 Ok(_) => {
546 if buf.ends_with(b"\n") {
548 buf.pop();
549 if buf.ends_with(b"\r") {
550 buf.pop();
551 }
552 }
553
554 Some(Ok(buf))
555 }
556 }
557 })
558}
559
560pub fn read_os_string_lines<R: std::io::Read>(
564 buf_reader: BufReader<R>,
565) -> impl Iterator<Item = std::io::Result<OsString>> {
566 read_byte_lines(buf_reader)
567 .map(|byte_line_res| byte_line_res.map(|bl| os_string_from_vec(bl).expect("UTF-8 error")))
568}
569
570#[macro_export]
588macro_rules! prompt_yes(
589 ($($args:tt)+) => ({
590 use std::io::Write;
591 eprint!("{}: ", uucore::util_name());
592 eprint!($($args)+);
593 eprint!(" ");
594 let res = std::io::stderr().flush().map_err(|err| {
595 $crate::error::USimpleError::new(1, err.to_string())
596 });
597 uucore::show_if_err!(res);
598 uucore::read_yes()
599 })
600);
601
602#[derive(Debug, Clone, Copy, PartialEq, Eq)]
605pub enum CharByte {
606 Char(char),
607 Byte(u8),
608}
609
610impl From<char> for CharByte {
611 fn from(value: char) -> Self {
612 Self::Char(value)
613 }
614}
615
616impl From<u8> for CharByte {
617 fn from(value: u8) -> Self {
618 Self::Byte(value)
619 }
620}
621
622impl From<&u8> for CharByte {
623 fn from(value: &u8) -> Self {
624 Self::Byte(*value)
625 }
626}
627
628struct Utf8ChunkIterator<'a> {
629 iter: Box<dyn Iterator<Item = CharByte> + 'a>,
630}
631
632impl Iterator for Utf8ChunkIterator<'_> {
633 type Item = CharByte;
634
635 fn next(&mut self) -> Option<Self::Item> {
636 self.iter.next()
637 }
638}
639
640impl<'a> From<Utf8Chunk<'a>> for Utf8ChunkIterator<'a> {
641 fn from(chk: Utf8Chunk<'a>) -> Self {
642 Self {
643 iter: Box::new(
644 chk.valid()
645 .chars()
646 .map(CharByte::from)
647 .chain(chk.invalid().iter().map(CharByte::from)),
648 ),
649 }
650 }
651}
652
653pub struct CharByteIterator<'a> {
656 iter: Box<dyn Iterator<Item = CharByte> + 'a>,
657}
658
659impl<'a> CharByteIterator<'a> {
660 pub fn new(input: &'a [u8]) -> Self {
663 Self {
664 iter: Box::new(input.utf8_chunks().flat_map(Utf8ChunkIterator::from)),
665 }
666 }
667}
668
669impl Iterator for CharByteIterator<'_> {
670 type Item = CharByte;
671
672 fn next(&mut self) -> Option<Self::Item> {
673 self.iter.next()
674 }
675}
676
677pub trait IntoCharByteIterator<'a> {
678 fn iter_char_bytes(self) -> CharByteIterator<'a>;
679}
680
681impl<'a> IntoCharByteIterator<'a> for &'a [u8] {
682 fn iter_char_bytes(self) -> CharByteIterator<'a> {
683 CharByteIterator::new(self)
684 }
685}
686
687#[cfg(test)]
688mod tests {
689 use super::*;
690 use std::ffi::OsStr;
691
692 fn make_os_vec(os_str: &OsStr) -> Vec<OsString> {
693 vec![
694 OsString::from("test"),
695 OsString::from("สวัสดี"), os_str.to_os_string(),
697 ]
698 }
699
700 #[cfg(any(unix, target_os = "redox"))]
701 fn test_invalid_utf8_args_lossy(os_str: &OsStr) {
702 assert!(os_str.to_os_string().into_string().is_err());
704 let test_vec = make_os_vec(os_str);
705 let collected_to_str = test_vec.clone().into_iter().collect_lossy();
706 assert_eq!(collected_to_str.len(), test_vec.len());
708 for index in 0..2 {
710 assert_eq!(collected_to_str[index], test_vec[index].to_str().unwrap());
711 }
712 assert_eq!(
714 *collected_to_str[2],
715 os_str.to_os_string().to_string_lossy()
716 );
717 }
718
719 #[cfg(any(unix, target_os = "redox"))]
720 fn test_invalid_utf8_args_ignore(os_str: &OsStr) {
721 assert!(os_str.to_os_string().into_string().is_err());
723 let test_vec = make_os_vec(os_str);
724 let collected_to_str = test_vec.clone().into_iter().collect_ignore();
725 assert_eq!(collected_to_str.len(), test_vec.len() - 1);
727 for index in 0..2 {
729 assert_eq!(
730 collected_to_str.get(index).unwrap(),
731 test_vec.get(index).unwrap().to_str().unwrap()
732 );
733 }
734 }
735
736 #[test]
737 fn valid_utf8_encoding_args() {
738 let test_vec = make_os_vec(&OsString::from("test2"));
740 let _ = test_vec.into_iter().collect_lossy();
742 }
743
744 #[cfg(any(unix, target_os = "redox"))]
745 #[test]
746 fn invalid_utf8_args_unix() {
747 use std::os::unix::ffi::OsStrExt;
748
749 let source = [0x66, 0x6f, 0x80, 0x6f];
750 let os_str = OsStr::from_bytes(&source[..]);
751 test_invalid_utf8_args_lossy(os_str);
752 test_invalid_utf8_args_ignore(os_str);
753 }
754
755 #[test]
756 fn test_format_usage() {
757 assert_eq!(format_usage("expr EXPRESSION"), "expr EXPRESSION");
758 assert_eq!(
759 format_usage("expr EXPRESSION\nexpr OPTION"),
760 "expr EXPRESSION\n expr OPTION"
761 );
762 }
763}