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(unix, any(feature = "pipes", feature = "buf-copy")))]
99pub use crate::features::pipes;
100#[cfg(all(unix, feature = "process"))]
101pub use crate::features::process;
102#[cfg(all(unix, not(target_os = "redox")))]
103pub use crate::features::safe_traversal;
104#[cfg(all(unix, not(target_os = "fuchsia"), feature = "signals"))]
105pub use crate::features::signals;
106#[cfg(all(
107 unix,
108 not(target_os = "android"),
109 not(target_os = "fuchsia"),
110 not(target_os = "openbsd"),
111 not(target_os = "redox"),
112 feature = "utmpx"
113))]
114pub use crate::features::utmpx;
115#[cfg(all(windows, feature = "wide"))]
117pub use crate::features::wide;
118
119#[cfg(feature = "fsext")]
120pub use crate::features::fsext;
121
122#[cfg(all(unix, feature = "fsxattr"))]
123pub use crate::features::fsxattr;
124
125#[cfg(all(feature = "selinux", any(target_os = "linux", target_os = "android")))]
126pub use crate::features::selinux;
127
128#[cfg(all(target_os = "linux", feature = "smack"))]
129pub use crate::features::smack;
130
131#[cfg(unix)]
134use nix::errno::Errno;
135#[cfg(unix)]
136use nix::sys::signal::{
137 SaFlags, SigAction, SigHandler::SigDfl, SigSet, Signal::SIGBUS, Signal::SIGSEGV, sigaction,
138};
139use std::borrow::Cow;
140use std::ffi::{OsStr, OsString};
141use std::io::{BufRead, BufReader};
142use std::iter;
143#[cfg(unix)]
144use std::os::unix::ffi::{OsStrExt, OsStringExt};
145#[cfg(target_os = "wasi")]
146use std::os::wasi::ffi::{OsStrExt, OsStringExt};
147use std::str;
148use std::str::Utf8Chunk;
149use std::sync::{LazyLock, atomic::Ordering};
150
151#[cfg(unix)]
154pub fn disable_rust_signal_handlers() -> Result<(), Errno> {
155 unsafe {
156 sigaction(
157 SIGSEGV,
158 &SigAction::new(SigDfl, SaFlags::empty(), SigSet::all()),
159 )
160 }?;
161 unsafe {
162 sigaction(
163 SIGBUS,
164 &SigAction::new(SigDfl, SaFlags::empty(), SigSet::all()),
165 )
166 }?;
167 Ok(())
168}
169
170pub fn get_canonical_util_name(util_name: &str) -> &str {
171 let util_name = &util_name[3..];
173 match util_name {
174 "[" => "test",
176 "dir" => "ls", "vdir" => "ls", _ => util_name,
181 }
182}
183
184#[macro_export]
189macro_rules! bin {
190 ($util:ident) => {
191 pub fn main() {
192 use std::io::Write;
193 use uucore::locale;
194
195 uucore::panic::preserve_inherited_sigpipe();
197
198 uucore::panic::mute_sigpipe_panic();
200 locale::setup_localization(uucore::get_canonical_util_name(stringify!($util)))
201 .unwrap_or_else(|err| {
202 match err {
203 uucore::locale::LocalizationError::ParseResource {
204 error: err_msg,
205 snippet,
206 } => eprintln!("Localization parse error at {snippet}: {err_msg:?}"),
207 other => eprintln!("Could not init the localization system: {other}"),
208 }
209 std::process::exit(99)
210 });
211
212 let code = $util::uumain(uucore::args_os());
214 if let Err(e) = std::io::stdout().flush() {
216 eprintln!("Error flushing stdout: {e}");
217 }
218
219 std::process::exit(code);
220 }
221 };
222}
223
224#[macro_export]
229macro_rules! crate_version {
230 () => {
231 concat!("(uutils coreutils) ", env!("CARGO_PKG_VERSION"))
232 };
233}
234
235pub fn format_usage(s: &str) -> String {
243 let s = s.replace('\n', &format!("\n{}", " ".repeat(7)));
244 s.replace("{}", execution_phrase())
245}
246
247pub fn localized_help_template(util_name: &str) -> clap::builder::StyledStr {
269 use std::io::IsTerminal;
270
271 let colors_enabled = if std::env::var("NO_COLOR").is_ok() {
273 false
274 } else if std::env::var("CLICOLOR_FORCE").is_ok() || std::env::var("FORCE_COLOR").is_ok() {
275 true
276 } else {
277 IsTerminal::is_terminal(&std::io::stdout())
278 && std::env::var("TERM").unwrap_or_default() != "dumb"
279 };
280
281 localized_help_template_with_colors(util_name, colors_enabled)
282}
283
284pub fn localized_help_template_with_colors(
287 util_name: &str,
288 colors_enabled: bool,
289) -> clap::builder::StyledStr {
290 use std::fmt::Write;
291
292 let _ = locale::setup_localization(util_name);
294
295 let usage_label = crate::locale::translate!("common-usage");
297
298 let mut template = clap::builder::StyledStr::new();
300
301 writeln!(template, "{{before-help}}{{about-with-newline}}").unwrap();
303
304 if colors_enabled {
306 write!(
307 template,
308 "\x1b[1m\x1b[4m{usage_label}:\x1b[0m {{usage}}\n\n"
309 )
310 .unwrap();
311 } else {
312 write!(template, "{usage_label}: {{usage}}\n\n").unwrap();
313 }
314
315 write!(template, "{{all-args}}{{after-help}}").unwrap();
317
318 template
319}
320
321pub fn get_utility_is_second_arg() -> bool {
324 macros::UTILITY_IS_SECOND_ARG.load(Ordering::SeqCst)
325}
326
327pub fn set_utility_is_second_arg() {
330 macros::UTILITY_IS_SECOND_ARG.store(true, Ordering::SeqCst);
331}
332
333#[cfg(windows)]
336static ARGV: LazyLock<Vec<OsString>> = LazyLock::new(|| wild::args_os().collect());
337#[cfg(not(windows))]
338static ARGV: LazyLock<Vec<OsString>> = LazyLock::new(|| std::env::args_os().collect());
339
340static UTIL_NAME: LazyLock<String> = LazyLock::new(|| {
341 let base_index = usize::from(get_utility_is_second_arg());
342 let is_man = usize::from(ARGV[base_index].eq("manpage"));
343 let argv_index = base_index + is_man;
344
345 std::path::Path::new(&ARGV[argv_index])
349 .file_name()
350 .unwrap_or(&ARGV[argv_index])
351 .to_string_lossy()
352 .into_owned()
353});
354
355pub fn util_name() -> &'static str {
357 &UTIL_NAME
358}
359
360static EXECUTION_PHRASE: LazyLock<String> = LazyLock::new(|| {
361 if get_utility_is_second_arg() {
362 ARGV.iter()
363 .take(2)
364 .map(|os_str| os_str.to_string_lossy().into_owned())
365 .collect::<Vec<_>>()
366 .join(" ")
367 } else {
368 ARGV[0].to_string_lossy().into_owned()
369 }
370});
371
372pub fn execution_phrase() -> &'static str {
374 &EXECUTION_PHRASE
375}
376
377pub trait Args: Iterator<Item = OsString> + Sized {
382 fn collect_lossy(self) -> Vec<String> {
384 self.map(|s| s.to_string_lossy().into_owned()).collect()
385 }
386
387 fn collect_ignore(self) -> Vec<String> {
389 self.filter_map(|s| s.into_string().ok()).collect()
390 }
391}
392
393impl<T: Iterator<Item = OsString> + Sized> Args for T {}
394
395pub fn args_os() -> impl Iterator<Item = OsString> {
398 ARGV.iter().cloned()
399}
400
401pub fn args_os_filtered() -> impl Iterator<Item = OsString> {
405 ARGV.iter().filter(|arg| !arg.is_empty()).cloned()
406}
407
408pub fn read_yes() -> bool {
410 let mut s = String::new();
411 match std::io::stdin().read_line(&mut s) {
412 Ok(_) => matches!(s.chars().next(), Some('y' | 'Y')),
413 _ => false,
414 }
415}
416
417#[derive(Debug)]
418pub struct NonUtf8OsStrError {
419 input_lossy_string: String,
420}
421
422impl std::fmt::Display for NonUtf8OsStrError {
423 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
424 use os_display::Quotable;
425 let quoted = self.input_lossy_string.quote();
426 f.write_fmt(format_args!(
427 "invalid UTF-8 input {quoted} encountered when converting to bytes on a platform that doesn't expose byte arguments",
428 ))
429 }
430}
431
432impl std::error::Error for NonUtf8OsStrError {}
433impl error::UError for NonUtf8OsStrError {}
434
435#[cfg_attr(any(unix, target_os = "wasi"), expect(clippy::unnecessary_wraps))]
440pub fn os_str_as_bytes(os_string: &OsStr) -> Result<&[u8], NonUtf8OsStrError> {
441 #[cfg(any(unix, target_os = "wasi"))]
442 return Ok(os_string.as_bytes());
443
444 #[cfg(not(any(unix, target_os = "wasi")))]
445 os_string
446 .to_str()
447 .ok_or_else(|| NonUtf8OsStrError {
448 input_lossy_string: os_string.to_string_lossy().into_owned(),
449 })
450 .map(str::as_bytes)
451}
452
453pub fn os_str_as_bytes_lossy(os_string: &OsStr) -> Cow<'_, [u8]> {
458 #[cfg(any(unix, target_os = "wasi"))]
459 return Cow::from(os_string.as_bytes());
460
461 #[cfg(not(any(unix, target_os = "wasi")))]
462 match os_string.to_string_lossy() {
463 Cow::Borrowed(slice) => Cow::from(slice.as_bytes()),
464 Cow::Owned(owned) => Cow::from(owned.into_bytes()),
465 }
466}
467
468#[cfg_attr(any(unix, target_os = "wasi"), expect(clippy::unnecessary_wraps))]
474pub fn os_str_from_bytes(bytes: &[u8]) -> error::UResult<Cow<'_, OsStr>> {
475 #[cfg(any(unix, target_os = "wasi"))]
476 return Ok(Cow::Borrowed(OsStr::from_bytes(bytes)));
477
478 #[cfg(not(any(unix, target_os = "wasi")))]
479 Ok(Cow::Owned(OsString::from(str::from_utf8(bytes).map_err(
480 |_| error::UUsageError::new(1, "Unable to transform bytes into OsStr"),
481 )?)))
482}
483
484#[cfg_attr(any(unix, target_os = "wasi"), expect(clippy::unnecessary_wraps))]
489pub fn os_string_from_vec(vec: Vec<u8>) -> error::UResult<OsString> {
490 #[cfg(any(unix, target_os = "wasi"))]
491 return Ok(OsString::from_vec(vec));
492
493 #[cfg(not(any(unix, target_os = "wasi")))]
494 Ok(OsString::from(String::from_utf8(vec).map_err(|_| {
495 error::UUsageError::new(1, "invalid UTF-8 was detected in one or more arguments")
496 })?))
497}
498
499#[cfg_attr(any(unix, target_os = "wasi"), expect(clippy::unnecessary_wraps))]
504pub fn os_string_to_vec(s: OsString) -> error::UResult<Vec<u8>> {
505 #[cfg(any(unix, target_os = "wasi"))]
506 let v = s.into_vec();
507 #[cfg(not(any(unix, target_os = "wasi")))]
508 let v = s
509 .into_string()
510 .map_err(|_| {
511 error::UUsageError::new(1, "invalid UTF-8 was detected in one or more arguments")
512 })?
513 .into();
514
515 Ok(v)
516}
517
518pub fn read_byte_lines<R: std::io::Read>(
521 mut buf_reader: BufReader<R>,
522) -> impl Iterator<Item = std::io::Result<Vec<u8>>> {
523 iter::from_fn(move || {
524 let mut buf = Vec::with_capacity(256);
525
526 match buf_reader.read_until(b'\n', &mut buf) {
527 Ok(0) => None,
528 Err(e) => Some(Err(e)),
529 Ok(_) => {
530 if buf.ends_with(b"\n") {
532 buf.pop();
533 if buf.ends_with(b"\r") {
534 buf.pop();
535 }
536 }
537
538 Some(Ok(buf))
539 }
540 }
541 })
542}
543
544pub fn read_os_string_lines<R: std::io::Read>(
548 buf_reader: BufReader<R>,
549) -> impl Iterator<Item = std::io::Result<OsString>> {
550 read_byte_lines(buf_reader)
551 .map(|byte_line_res| byte_line_res.map(|bl| os_string_from_vec(bl).expect("UTF-8 error")))
552}
553
554#[macro_export]
572macro_rules! prompt_yes(
573 ($($args:tt)+) => ({
574 use std::io::Write;
575 eprint!("{}: ", uucore::util_name());
576 eprint!($($args)+);
577 eprint!(" ");
578 let res = std::io::stderr().flush().map_err(|err| {
579 $crate::error::USimpleError::new(1, err.to_string())
580 });
581 uucore::show_if_err!(res);
582 uucore::read_yes()
583 })
584);
585
586#[derive(Debug, Clone, Copy, PartialEq, Eq)]
589pub enum CharByte {
590 Char(char),
591 Byte(u8),
592}
593
594impl From<char> for CharByte {
595 fn from(value: char) -> Self {
596 Self::Char(value)
597 }
598}
599
600impl From<u8> for CharByte {
601 fn from(value: u8) -> Self {
602 Self::Byte(value)
603 }
604}
605
606impl From<&u8> for CharByte {
607 fn from(value: &u8) -> Self {
608 Self::Byte(*value)
609 }
610}
611
612struct Utf8ChunkIterator<'a> {
613 iter: Box<dyn Iterator<Item = CharByte> + 'a>,
614}
615
616impl Iterator for Utf8ChunkIterator<'_> {
617 type Item = CharByte;
618
619 fn next(&mut self) -> Option<Self::Item> {
620 self.iter.next()
621 }
622}
623
624impl<'a> From<Utf8Chunk<'a>> for Utf8ChunkIterator<'a> {
625 fn from(chk: Utf8Chunk<'a>) -> Self {
626 Self {
627 iter: Box::new(
628 chk.valid()
629 .chars()
630 .map(CharByte::from)
631 .chain(chk.invalid().iter().map(CharByte::from)),
632 ),
633 }
634 }
635}
636
637pub struct CharByteIterator<'a> {
640 iter: Box<dyn Iterator<Item = CharByte> + 'a>,
641}
642
643impl<'a> CharByteIterator<'a> {
644 pub fn new(input: &'a [u8]) -> Self {
647 Self {
648 iter: Box::new(input.utf8_chunks().flat_map(Utf8ChunkIterator::from)),
649 }
650 }
651}
652
653impl Iterator for CharByteIterator<'_> {
654 type Item = CharByte;
655
656 fn next(&mut self) -> Option<Self::Item> {
657 self.iter.next()
658 }
659}
660
661pub trait IntoCharByteIterator<'a> {
662 fn iter_char_bytes(self) -> CharByteIterator<'a>;
663}
664
665impl<'a> IntoCharByteIterator<'a> for &'a [u8] {
666 fn iter_char_bytes(self) -> CharByteIterator<'a> {
667 CharByteIterator::new(self)
668 }
669}
670
671#[cfg(test)]
672mod tests {
673 use super::*;
674 use std::ffi::OsStr;
675
676 fn make_os_vec(os_str: &OsStr) -> Vec<OsString> {
677 vec![
678 OsString::from("test"),
679 OsString::from("สวัสดี"), os_str.to_os_string(),
681 ]
682 }
683
684 #[cfg(any(unix, target_os = "redox"))]
685 fn test_invalid_utf8_args_lossy(os_str: &OsStr) {
686 assert!(os_str.to_os_string().into_string().is_err());
688 let test_vec = make_os_vec(os_str);
689 let collected_to_str = test_vec.clone().into_iter().collect_lossy();
690 assert_eq!(collected_to_str.len(), test_vec.len());
692 for index in 0..2 {
694 assert_eq!(collected_to_str[index], test_vec[index].to_str().unwrap());
695 }
696 assert_eq!(
698 *collected_to_str[2],
699 os_str.to_os_string().to_string_lossy()
700 );
701 }
702
703 #[cfg(any(unix, target_os = "redox"))]
704 fn test_invalid_utf8_args_ignore(os_str: &OsStr) {
705 assert!(os_str.to_os_string().into_string().is_err());
707 let test_vec = make_os_vec(os_str);
708 let collected_to_str = test_vec.clone().into_iter().collect_ignore();
709 assert_eq!(collected_to_str.len(), test_vec.len() - 1);
711 for index in 0..2 {
713 assert_eq!(
714 collected_to_str.get(index).unwrap(),
715 test_vec.get(index).unwrap().to_str().unwrap()
716 );
717 }
718 }
719
720 #[test]
721 fn valid_utf8_encoding_args() {
722 let test_vec = make_os_vec(&OsString::from("test2"));
724 let _ = test_vec.into_iter().collect_lossy();
726 }
727
728 #[cfg(any(unix, target_os = "redox"))]
729 #[test]
730 fn invalid_utf8_args_unix() {
731 use std::os::unix::ffi::OsStrExt;
732
733 let source = [0x66, 0x6f, 0x80, 0x6f];
734 let os_str = OsStr::from_bytes(&source[..]);
735 test_invalid_utf8_args_lossy(os_str);
736 test_invalid_utf8_args_ignore(os_str);
737 }
738
739 #[test]
740 fn test_format_usage() {
741 assert_eq!(format_usage("expr EXPRESSION"), "expr EXPRESSION");
742 assert_eq!(
743 format_usage("expr EXPRESSION\nexpr OPTION"),
744 "expr EXPRESSION\n expr OPTION"
745 );
746 }
747}