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 = "buf-copy")]
40pub use crate::features::buf_copy;
41#[cfg(feature = "checksum")]
42pub use crate::features::checksum;
43#[cfg(feature = "colors")]
44pub use crate::features::colors;
45#[cfg(feature = "encoding")]
46pub use crate::features::encoding;
47#[cfg(feature = "extendedbigdecimal")]
48pub use crate::features::extendedbigdecimal;
49#[cfg(feature = "fast-inc")]
50pub use crate::features::fast_inc;
51#[cfg(feature = "format")]
52pub use crate::features::format;
53#[cfg(feature = "fs")]
54pub use crate::features::fs;
55#[cfg(feature = "i18n-common")]
56pub use crate::features::i18n;
57#[cfg(feature = "lines")]
58pub use crate::features::lines;
59#[cfg(feature = "parser")]
60pub use crate::features::parser;
61#[cfg(feature = "quoting-style")]
62pub use crate::features::quoting_style;
63#[cfg(feature = "ranges")]
64pub use crate::features::ranges;
65#[cfg(feature = "ringbuffer")]
66pub use crate::features::ringbuffer;
67#[cfg(feature = "sum")]
68pub use crate::features::sum;
69#[cfg(feature = "feat_systemd_logind")]
70pub use crate::features::systemd_logind;
71#[cfg(feature = "time")]
72pub use crate::features::time;
73#[cfg(feature = "update-control")]
74pub use crate::features::update_control;
75#[cfg(feature = "uptime")]
76pub use crate::features::uptime;
77#[cfg(feature = "version-cmp")]
78pub use crate::features::version_cmp;
79
80#[cfg(all(not(windows), feature = "mode"))]
83pub use crate::features::mode;
84#[cfg(all(unix, feature = "entries"))]
86pub use crate::features::entries;
87#[cfg(all(unix, feature = "perms"))]
88pub use crate::features::perms;
89#[cfg(all(unix, any(feature = "pipes", feature = "buf-copy")))]
90pub use crate::features::pipes;
91#[cfg(all(unix, feature = "process"))]
92pub use crate::features::process;
93#[cfg(all(unix, not(target_os = "fuchsia"), feature = "signals"))]
94pub use crate::features::signals;
95#[cfg(all(
96 unix,
97 not(target_os = "android"),
98 not(target_os = "fuchsia"),
99 not(target_os = "openbsd"),
100 not(target_os = "redox"),
101 feature = "utmpx"
102))]
103pub use crate::features::utmpx;
104#[cfg(all(windows, feature = "wide"))]
106pub use crate::features::wide;
107
108#[cfg(feature = "fsext")]
109pub use crate::features::fsext;
110
111#[cfg(all(unix, feature = "fsxattr"))]
112pub use crate::features::fsxattr;
113
114#[cfg(all(target_os = "linux", feature = "selinux"))]
115pub use crate::features::selinux;
116
117#[cfg(unix)]
120use nix::errno::Errno;
121#[cfg(unix)]
122use nix::sys::signal::{
123 SaFlags, SigAction, SigHandler::SigDfl, SigSet, Signal::SIGBUS, Signal::SIGSEGV, sigaction,
124};
125use std::borrow::Cow;
126use std::ffi::{OsStr, OsString};
127use std::io::{BufRead, BufReader};
128use std::iter;
129#[cfg(unix)]
130use std::os::unix::ffi::{OsStrExt, OsStringExt};
131use std::str;
132use std::str::Utf8Chunk;
133use std::sync::{LazyLock, atomic::Ordering};
134
135#[cfg(unix)]
138pub fn disable_rust_signal_handlers() -> Result<(), Errno> {
139 unsafe {
140 sigaction(
141 SIGSEGV,
142 &SigAction::new(SigDfl, SaFlags::empty(), SigSet::all()),
143 )
144 }?;
145 unsafe {
146 sigaction(
147 SIGBUS,
148 &SigAction::new(SigDfl, SaFlags::empty(), SigSet::all()),
149 )
150 }?;
151 Ok(())
152}
153
154pub fn get_canonical_util_name(util_name: &str) -> &str {
155 let util_name = &util_name[3..];
157 match util_name {
158 "[" => "test",
160
161 "md5sum" | "sha1sum" | "sha224sum" | "sha256sum" | "sha384sum" | "sha512sum"
163 | "sha3sum" | "sha3-224sum" | "sha3-256sum" | "sha3-384sum" | "sha3-512sum"
164 | "shake128sum" | "shake256sum" | "b2sum" | "b3sum" => "hashsum",
165
166 "dir" => "ls", _ => util_name,
170 }
171}
172
173#[macro_export]
178macro_rules! bin {
179 ($util:ident) => {
180 pub fn main() {
181 use std::io::Write;
182 use uucore::locale;
183 uucore::panic::mute_sigpipe_panic();
185 locale::setup_localization(uucore::get_canonical_util_name(stringify!($util)))
186 .unwrap_or_else(|err| {
187 match err {
188 uucore::locale::LocalizationError::ParseResource {
189 error: err_msg,
190 snippet,
191 } => eprintln!("Localization parse error at {snippet}: {err_msg:?}"),
192 other => eprintln!("Could not init the localization system: {other}"),
193 }
194 std::process::exit(99)
195 });
196
197 let code = $util::uumain(uucore::args_os());
199 if let Err(e) = std::io::stdout().flush() {
201 eprintln!("Error flushing stdout: {e}");
202 }
203
204 std::process::exit(code);
205 }
206 };
207}
208
209#[macro_export]
214macro_rules! crate_version {
215 () => {
216 concat!("(uutils coreutils) ", env!("CARGO_PKG_VERSION"))
217 };
218}
219
220pub fn format_usage(s: &str) -> String {
228 let s = s.replace('\n', &format!("\n{}", " ".repeat(7)));
229 s.replace("{}", crate::execution_phrase())
230}
231
232pub fn localized_help_template(util_name: &str) -> clap::builder::StyledStr {
254 use std::io::IsTerminal;
255
256 let colors_enabled = if std::env::var("NO_COLOR").is_ok() {
258 false
259 } else if std::env::var("CLICOLOR_FORCE").is_ok() || std::env::var("FORCE_COLOR").is_ok() {
260 true
261 } else {
262 IsTerminal::is_terminal(&std::io::stdout())
263 && std::env::var("TERM").unwrap_or_default() != "dumb"
264 };
265
266 localized_help_template_with_colors(util_name, colors_enabled)
267}
268
269pub fn localized_help_template_with_colors(
272 util_name: &str,
273 colors_enabled: bool,
274) -> clap::builder::StyledStr {
275 use std::fmt::Write;
276
277 let _ = crate::locale::setup_localization(util_name);
279
280 let usage_label = crate::locale::translate!("common-usage");
282
283 let mut template = clap::builder::StyledStr::new();
285
286 writeln!(template, "{{before-help}}{{about-with-newline}}").unwrap();
288
289 if colors_enabled {
291 write!(
292 template,
293 "\x1b[1m\x1b[4m{usage_label}:\x1b[0m {{usage}}\n\n"
294 )
295 .unwrap();
296 } else {
297 write!(template, "{usage_label}: {{usage}}\n\n").unwrap();
298 }
299
300 write!(template, "{{all-args}}{{after-help}}").unwrap();
302
303 template
304}
305
306pub fn get_utility_is_second_arg() -> bool {
309 crate::macros::UTILITY_IS_SECOND_ARG.load(Ordering::SeqCst)
310}
311
312pub fn set_utility_is_second_arg() {
315 crate::macros::UTILITY_IS_SECOND_ARG.store(true, Ordering::SeqCst);
316}
317
318static ARGV: LazyLock<Vec<OsString>> = LazyLock::new(|| wild::args_os().collect());
321
322static UTIL_NAME: LazyLock<String> = LazyLock::new(|| {
323 let base_index = usize::from(get_utility_is_second_arg());
324 let is_man = usize::from(ARGV[base_index].eq("manpage"));
325 let argv_index = base_index + is_man;
326
327 ARGV[argv_index].to_string_lossy().into_owned()
328});
329
330pub fn util_name() -> &'static str {
332 &UTIL_NAME
333}
334
335static EXECUTION_PHRASE: LazyLock<String> = LazyLock::new(|| {
336 if get_utility_is_second_arg() {
337 ARGV.iter()
338 .take(2)
339 .map(|os_str| os_str.to_string_lossy().into_owned())
340 .collect::<Vec<_>>()
341 .join(" ")
342 } else {
343 ARGV[0].to_string_lossy().into_owned()
344 }
345});
346
347pub fn execution_phrase() -> &'static str {
349 &EXECUTION_PHRASE
350}
351
352pub trait Args: Iterator<Item = OsString> + Sized {
357 fn collect_lossy(self) -> Vec<String> {
359 self.map(|s| s.to_string_lossy().into_owned()).collect()
360 }
361
362 fn collect_ignore(self) -> Vec<String> {
364 self.filter_map(|s| s.into_string().ok()).collect()
365 }
366}
367
368impl<T: Iterator<Item = OsString> + Sized> Args for T {}
369
370pub fn args_os() -> impl Iterator<Item = OsString> {
373 ARGV.iter().cloned()
374}
375
376pub fn read_yes() -> bool {
378 let mut s = String::new();
379 match std::io::stdin().read_line(&mut s) {
380 Ok(_) => matches!(s.chars().next(), Some('y' | 'Y')),
381 _ => false,
382 }
383}
384
385#[derive(Debug)]
386pub struct NonUtf8OsStrError {
387 input_lossy_string: String,
388}
389
390impl std::fmt::Display for NonUtf8OsStrError {
391 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
392 use os_display::Quotable;
393 let quoted = self.input_lossy_string.quote();
394 f.write_fmt(format_args!(
395 "invalid UTF-8 input {quoted} encountered when converting to bytes on a platform that doesn't expose byte arguments",
396 ))
397 }
398}
399
400impl std::error::Error for NonUtf8OsStrError {}
401impl error::UError for NonUtf8OsStrError {}
402
403pub fn os_str_as_bytes(os_string: &OsStr) -> Result<&[u8], NonUtf8OsStrError> {
408 #[cfg(unix)]
409 return Ok(os_string.as_bytes());
410
411 #[cfg(not(unix))]
412 os_string
413 .to_str()
414 .ok_or_else(|| NonUtf8OsStrError {
415 input_lossy_string: os_string.to_string_lossy().into_owned(),
416 })
417 .map(|s| s.as_bytes())
418}
419
420pub fn os_str_as_bytes_lossy(os_string: &OsStr) -> Cow<'_, [u8]> {
425 #[cfg(unix)]
426 return Cow::from(os_string.as_bytes());
427
428 #[cfg(not(unix))]
429 match os_string.to_string_lossy() {
430 Cow::Borrowed(slice) => Cow::from(slice.as_bytes()),
431 Cow::Owned(owned) => Cow::from(owned.into_bytes()),
432 }
433}
434
435pub fn os_str_from_bytes(bytes: &[u8]) -> mods::error::UResult<Cow<'_, OsStr>> {
441 #[cfg(unix)]
442 return Ok(Cow::Borrowed(OsStr::from_bytes(bytes)));
443
444 #[cfg(not(unix))]
445 Ok(Cow::Owned(OsString::from(str::from_utf8(bytes).map_err(
446 |_| mods::error::UUsageError::new(1, "Unable to transform bytes into OsStr"),
447 )?)))
448}
449
450pub fn os_string_from_vec(vec: Vec<u8>) -> mods::error::UResult<OsString> {
455 #[cfg(unix)]
456 return Ok(OsString::from_vec(vec));
457
458 #[cfg(not(unix))]
459 Ok(OsString::from(String::from_utf8(vec).map_err(|_| {
460 mods::error::UUsageError::new(1, "invalid UTF-8 was detected in one or more arguments")
461 })?))
462}
463
464pub fn os_string_to_vec(s: OsString) -> mods::error::UResult<Vec<u8>> {
469 #[cfg(unix)]
470 let v = s.into_vec();
471 #[cfg(not(unix))]
472 let v = s
473 .into_string()
474 .map_err(|_| {
475 mods::error::UUsageError::new(1, "invalid UTF-8 was detected in one or more arguments")
476 })?
477 .into();
478
479 Ok(v)
480}
481
482pub fn read_byte_lines<R: std::io::Read>(
485 mut buf_reader: BufReader<R>,
486) -> impl Iterator<Item = Vec<u8>> {
487 iter::from_fn(move || {
488 let mut buf = Vec::with_capacity(256);
489 let size = buf_reader.read_until(b'\n', &mut buf).ok()?;
490
491 if size == 0 {
492 return None;
493 }
494
495 if buf.ends_with(b"\n") {
497 buf.pop();
498 if buf.ends_with(b"\r") {
499 buf.pop();
500 }
501 }
502
503 Some(buf)
504 })
505}
506
507pub fn read_os_string_lines<R: std::io::Read>(
511 buf_reader: BufReader<R>,
512) -> impl Iterator<Item = OsString> {
513 read_byte_lines(buf_reader).map(|byte_line| os_string_from_vec(byte_line).expect("UTF-8 error"))
514}
515
516#[macro_export]
534macro_rules! prompt_yes(
535 ($($args:tt)+) => ({
536 use std::io::Write;
537 eprint!("{}: ", uucore::util_name());
538 eprint!($($args)+);
539 eprint!(" ");
540 let res = std::io::stderr().flush().map_err(|err| {
541 $crate::error::USimpleError::new(1, err.to_string())
542 });
543 uucore::show_if_err!(res);
544 uucore::read_yes()
545 })
546);
547
548#[derive(Debug, Clone, Copy, PartialEq, Eq)]
551pub enum CharByte {
552 Char(char),
553 Byte(u8),
554}
555
556impl From<char> for CharByte {
557 fn from(value: char) -> Self {
558 CharByte::Char(value)
559 }
560}
561
562impl From<u8> for CharByte {
563 fn from(value: u8) -> Self {
564 CharByte::Byte(value)
565 }
566}
567
568impl From<&u8> for CharByte {
569 fn from(value: &u8) -> Self {
570 CharByte::Byte(*value)
571 }
572}
573
574struct Utf8ChunkIterator<'a> {
575 iter: Box<dyn Iterator<Item = CharByte> + 'a>,
576}
577
578impl Iterator for Utf8ChunkIterator<'_> {
579 type Item = CharByte;
580
581 fn next(&mut self) -> Option<Self::Item> {
582 self.iter.next()
583 }
584}
585
586impl<'a> From<Utf8Chunk<'a>> for Utf8ChunkIterator<'a> {
587 fn from(chk: Utf8Chunk<'a>) -> Utf8ChunkIterator<'a> {
588 Self {
589 iter: Box::new(
590 chk.valid()
591 .chars()
592 .map(CharByte::from)
593 .chain(chk.invalid().iter().map(CharByte::from)),
594 ),
595 }
596 }
597}
598
599pub struct CharByteIterator<'a> {
602 iter: Box<dyn Iterator<Item = CharByte> + 'a>,
603}
604
605impl<'a> CharByteIterator<'a> {
606 pub fn new(input: &'a [u8]) -> CharByteIterator<'a> {
609 Self {
610 iter: Box::new(input.utf8_chunks().flat_map(Utf8ChunkIterator::from)),
611 }
612 }
613}
614
615impl Iterator for CharByteIterator<'_> {
616 type Item = CharByte;
617
618 fn next(&mut self) -> Option<Self::Item> {
619 self.iter.next()
620 }
621}
622
623pub trait IntoCharByteIterator<'a> {
624 fn iter_char_bytes(self) -> CharByteIterator<'a>;
625}
626
627impl<'a> IntoCharByteIterator<'a> for &'a [u8] {
628 fn iter_char_bytes(self) -> CharByteIterator<'a> {
629 CharByteIterator::new(self)
630 }
631}
632
633#[cfg(test)]
634mod tests {
635 use super::*;
636 use std::ffi::OsStr;
637
638 fn make_os_vec(os_str: &OsStr) -> Vec<OsString> {
639 vec![
640 OsString::from("test"),
641 OsString::from("สวัสดี"), os_str.to_os_string(),
643 ]
644 }
645
646 #[cfg(any(unix, target_os = "redox"))]
647 fn test_invalid_utf8_args_lossy(os_str: &OsStr) {
648 assert!(os_str.to_os_string().into_string().is_err());
650 let test_vec = make_os_vec(os_str);
651 let collected_to_str = test_vec.clone().into_iter().collect_lossy();
652 assert_eq!(collected_to_str.len(), test_vec.len());
654 for index in 0..2 {
656 assert_eq!(collected_to_str[index], test_vec[index].to_str().unwrap());
657 }
658 assert_eq!(
660 *collected_to_str[2],
661 os_str.to_os_string().to_string_lossy()
662 );
663 }
664
665 #[cfg(any(unix, target_os = "redox"))]
666 fn test_invalid_utf8_args_ignore(os_str: &OsStr) {
667 assert!(os_str.to_os_string().into_string().is_err());
669 let test_vec = make_os_vec(os_str);
670 let collected_to_str = test_vec.clone().into_iter().collect_ignore();
671 assert_eq!(collected_to_str.len(), test_vec.len() - 1);
673 for index in 0..2 {
675 assert_eq!(
676 collected_to_str.get(index).unwrap(),
677 test_vec.get(index).unwrap().to_str().unwrap()
678 );
679 }
680 }
681
682 #[test]
683 fn valid_utf8_encoding_args() {
684 let test_vec = make_os_vec(&OsString::from("test2"));
686 let _ = test_vec.into_iter().collect_lossy();
688 }
689
690 #[cfg(any(unix, target_os = "redox"))]
691 #[test]
692 fn invalid_utf8_args_unix() {
693 use std::os::unix::ffi::OsStrExt;
694
695 let source = [0x66, 0x6f, 0x80, 0x6f];
696 let os_str = OsStr::from_bytes(&source[..]);
697 test_invalid_utf8_args_lossy(os_str);
698 test_invalid_utf8_args_ignore(os_str);
699 }
700
701 #[test]
702 fn test_format_usage() {
703 assert_eq!(format_usage("expr EXPRESSION"), "expr EXPRESSION");
704 assert_eq!(
705 format_usage("expr EXPRESSION\nexpr OPTION"),
706 "expr EXPRESSION\n expr OPTION"
707 );
708 }
709}