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 = "i18n-common")]
58pub use crate::features::i18n;
59#[cfg(feature = "lines")]
60pub use crate::features::lines;
61#[cfg(feature = "parser")]
62pub use crate::features::parser;
63#[cfg(feature = "quoting-style")]
64pub use crate::features::quoting_style;
65#[cfg(feature = "ranges")]
66pub use crate::features::ranges;
67#[cfg(feature = "ringbuffer")]
68pub use crate::features::ringbuffer;
69#[cfg(feature = "sum")]
70pub use crate::features::sum;
71#[cfg(feature = "feat_systemd_logind")]
72pub use crate::features::systemd_logind;
73#[cfg(feature = "time")]
74pub use crate::features::time;
75#[cfg(feature = "update-control")]
76pub use crate::features::update_control;
77#[cfg(feature = "uptime")]
78pub use crate::features::uptime;
79#[cfg(feature = "version-cmp")]
80pub use crate::features::version_cmp;
81
82#[cfg(all(not(windows), feature = "mode"))]
85pub use crate::features::mode;
86#[cfg(all(unix, feature = "entries"))]
88pub use crate::features::entries;
89#[cfg(all(unix, feature = "perms"))]
90pub use crate::features::perms;
91#[cfg(all(unix, any(feature = "pipes", feature = "buf-copy")))]
92pub use crate::features::pipes;
93#[cfg(all(unix, feature = "process"))]
94pub use crate::features::process;
95#[cfg(target_os = "linux")]
96pub use crate::features::safe_traversal;
97#[cfg(all(unix, not(target_os = "fuchsia"), feature = "signals"))]
98pub use crate::features::signals;
99#[cfg(all(
100 unix,
101 not(target_os = "android"),
102 not(target_os = "fuchsia"),
103 not(target_os = "openbsd"),
104 not(target_os = "redox"),
105 feature = "utmpx"
106))]
107pub use crate::features::utmpx;
108#[cfg(all(windows, feature = "wide"))]
110pub use crate::features::wide;
111
112#[cfg(feature = "fsext")]
113pub use crate::features::fsext;
114
115#[cfg(all(unix, feature = "fsxattr"))]
116pub use crate::features::fsxattr;
117
118#[cfg(all(target_os = "linux", feature = "selinux"))]
119pub use crate::features::selinux;
120
121#[cfg(unix)]
124use nix::errno::Errno;
125#[cfg(unix)]
126use nix::sys::signal::{
127 SaFlags, SigAction, SigHandler::SigDfl, SigSet, Signal::SIGBUS, Signal::SIGSEGV, sigaction,
128};
129use std::borrow::Cow;
130use std::ffi::{OsStr, OsString};
131use std::io::{BufRead, BufReader};
132use std::iter;
133#[cfg(unix)]
134use std::os::unix::ffi::{OsStrExt, OsStringExt};
135use std::str;
136use std::str::Utf8Chunk;
137use std::sync::{LazyLock, atomic::Ordering};
138
139#[cfg(unix)]
142pub fn disable_rust_signal_handlers() -> Result<(), Errno> {
143 unsafe {
144 sigaction(
145 SIGSEGV,
146 &SigAction::new(SigDfl, SaFlags::empty(), SigSet::all()),
147 )
148 }?;
149 unsafe {
150 sigaction(
151 SIGBUS,
152 &SigAction::new(SigDfl, SaFlags::empty(), SigSet::all()),
153 )
154 }?;
155 Ok(())
156}
157
158pub fn get_canonical_util_name(util_name: &str) -> &str {
159 let util_name = &util_name[3..];
161 match util_name {
162 "[" => "test",
164
165 "md5sum" | "sha1sum" | "sha224sum" | "sha256sum" | "sha384sum" | "sha512sum"
167 | "sha3sum" | "sha3-224sum" | "sha3-256sum" | "sha3-384sum" | "sha3-512sum"
168 | "shake128sum" | "shake256sum" | "b2sum" | "b3sum" => "hashsum",
169
170 "dir" => "ls", _ => util_name,
174 }
175}
176
177#[macro_export]
182macro_rules! bin {
183 ($util:ident) => {
184 pub fn main() {
185 use std::io::Write;
186 use uucore::locale;
187 uucore::panic::mute_sigpipe_panic();
189 locale::setup_localization(uucore::get_canonical_util_name(stringify!($util)))
190 .unwrap_or_else(|err| {
191 match err {
192 uucore::locale::LocalizationError::ParseResource {
193 error: err_msg,
194 snippet,
195 } => eprintln!("Localization parse error at {snippet}: {err_msg:?}"),
196 other => eprintln!("Could not init the localization system: {other}"),
197 }
198 std::process::exit(99)
199 });
200
201 let code = $util::uumain(uucore::args_os());
203 if let Err(e) = std::io::stdout().flush() {
205 eprintln!("Error flushing stdout: {e}");
206 }
207
208 std::process::exit(code);
209 }
210 };
211}
212
213#[macro_export]
218macro_rules! crate_version {
219 () => {
220 concat!("(uutils coreutils) ", env!("CARGO_PKG_VERSION"))
221 };
222}
223
224pub fn format_usage(s: &str) -> String {
232 let s = s.replace('\n', &format!("\n{}", " ".repeat(7)));
233 s.replace("{}", crate::execution_phrase())
234}
235
236pub fn localized_help_template(util_name: &str) -> clap::builder::StyledStr {
258 use std::io::IsTerminal;
259
260 let colors_enabled = if std::env::var("NO_COLOR").is_ok() {
262 false
263 } else if std::env::var("CLICOLOR_FORCE").is_ok() || std::env::var("FORCE_COLOR").is_ok() {
264 true
265 } else {
266 IsTerminal::is_terminal(&std::io::stdout())
267 && std::env::var("TERM").unwrap_or_default() != "dumb"
268 };
269
270 localized_help_template_with_colors(util_name, colors_enabled)
271}
272
273pub fn localized_help_template_with_colors(
276 util_name: &str,
277 colors_enabled: bool,
278) -> clap::builder::StyledStr {
279 use std::fmt::Write;
280
281 let _ = crate::locale::setup_localization(util_name);
283
284 let usage_label = crate::locale::translate!("common-usage");
286
287 let mut template = clap::builder::StyledStr::new();
289
290 writeln!(template, "{{before-help}}{{about-with-newline}}").unwrap();
292
293 if colors_enabled {
295 write!(
296 template,
297 "\x1b[1m\x1b[4m{usage_label}:\x1b[0m {{usage}}\n\n"
298 )
299 .unwrap();
300 } else {
301 write!(template, "{usage_label}: {{usage}}\n\n").unwrap();
302 }
303
304 write!(template, "{{all-args}}{{after-help}}").unwrap();
306
307 template
308}
309
310pub fn get_utility_is_second_arg() -> bool {
313 crate::macros::UTILITY_IS_SECOND_ARG.load(Ordering::SeqCst)
314}
315
316pub fn set_utility_is_second_arg() {
319 crate::macros::UTILITY_IS_SECOND_ARG.store(true, Ordering::SeqCst);
320}
321
322static ARGV: LazyLock<Vec<OsString>> = LazyLock::new(|| wild::args_os().collect());
325
326static UTIL_NAME: LazyLock<String> = LazyLock::new(|| {
327 let base_index = usize::from(get_utility_is_second_arg());
328 let is_man = usize::from(ARGV[base_index].eq("manpage"));
329 let argv_index = base_index + is_man;
330
331 std::path::Path::new(&ARGV[argv_index])
335 .file_name()
336 .unwrap_or(&ARGV[argv_index])
337 .to_string_lossy()
338 .into_owned()
339});
340
341pub fn util_name() -> &'static str {
343 &UTIL_NAME
344}
345
346static EXECUTION_PHRASE: LazyLock<String> = LazyLock::new(|| {
347 if get_utility_is_second_arg() {
348 ARGV.iter()
349 .take(2)
350 .map(|os_str| os_str.to_string_lossy().into_owned())
351 .collect::<Vec<_>>()
352 .join(" ")
353 } else {
354 ARGV[0].to_string_lossy().into_owned()
355 }
356});
357
358pub fn execution_phrase() -> &'static str {
360 &EXECUTION_PHRASE
361}
362
363pub trait Args: Iterator<Item = OsString> + Sized {
368 fn collect_lossy(self) -> Vec<String> {
370 self.map(|s| s.to_string_lossy().into_owned()).collect()
371 }
372
373 fn collect_ignore(self) -> Vec<String> {
375 self.filter_map(|s| s.into_string().ok()).collect()
376 }
377}
378
379impl<T: Iterator<Item = OsString> + Sized> Args for T {}
380
381pub fn args_os() -> impl Iterator<Item = OsString> {
384 ARGV.iter().cloned()
385}
386
387pub fn args_os_filtered() -> impl Iterator<Item = OsString> {
391 ARGV.iter().filter(|arg| !arg.is_empty()).cloned()
392}
393
394pub fn read_yes() -> bool {
396 let mut s = String::new();
397 match std::io::stdin().read_line(&mut s) {
398 Ok(_) => matches!(s.chars().next(), Some('y' | 'Y')),
399 _ => false,
400 }
401}
402
403#[derive(Debug)]
404pub struct NonUtf8OsStrError {
405 input_lossy_string: String,
406}
407
408impl std::fmt::Display for NonUtf8OsStrError {
409 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
410 use os_display::Quotable;
411 let quoted = self.input_lossy_string.quote();
412 f.write_fmt(format_args!(
413 "invalid UTF-8 input {quoted} encountered when converting to bytes on a platform that doesn't expose byte arguments",
414 ))
415 }
416}
417
418impl std::error::Error for NonUtf8OsStrError {}
419impl error::UError for NonUtf8OsStrError {}
420
421pub fn os_str_as_bytes(os_string: &OsStr) -> Result<&[u8], NonUtf8OsStrError> {
426 #[cfg(unix)]
427 return Ok(os_string.as_bytes());
428
429 #[cfg(not(unix))]
430 os_string
431 .to_str()
432 .ok_or_else(|| NonUtf8OsStrError {
433 input_lossy_string: os_string.to_string_lossy().into_owned(),
434 })
435 .map(|s| s.as_bytes())
436}
437
438pub fn os_str_as_bytes_lossy(os_string: &OsStr) -> Cow<'_, [u8]> {
443 #[cfg(unix)]
444 return Cow::from(os_string.as_bytes());
445
446 #[cfg(not(unix))]
447 match os_string.to_string_lossy() {
448 Cow::Borrowed(slice) => Cow::from(slice.as_bytes()),
449 Cow::Owned(owned) => Cow::from(owned.into_bytes()),
450 }
451}
452
453pub fn os_str_from_bytes(bytes: &[u8]) -> mods::error::UResult<Cow<'_, OsStr>> {
459 #[cfg(unix)]
460 return Ok(Cow::Borrowed(OsStr::from_bytes(bytes)));
461
462 #[cfg(not(unix))]
463 Ok(Cow::Owned(OsString::from(str::from_utf8(bytes).map_err(
464 |_| mods::error::UUsageError::new(1, "Unable to transform bytes into OsStr"),
465 )?)))
466}
467
468pub fn os_string_from_vec(vec: Vec<u8>) -> mods::error::UResult<OsString> {
473 #[cfg(unix)]
474 return Ok(OsString::from_vec(vec));
475
476 #[cfg(not(unix))]
477 Ok(OsString::from(String::from_utf8(vec).map_err(|_| {
478 mods::error::UUsageError::new(1, "invalid UTF-8 was detected in one or more arguments")
479 })?))
480}
481
482pub fn os_string_to_vec(s: OsString) -> mods::error::UResult<Vec<u8>> {
487 #[cfg(unix)]
488 let v = s.into_vec();
489 #[cfg(not(unix))]
490 let v = s
491 .into_string()
492 .map_err(|_| {
493 mods::error::UUsageError::new(1, "invalid UTF-8 was detected in one or more arguments")
494 })?
495 .into();
496
497 Ok(v)
498}
499
500pub fn read_byte_lines<R: std::io::Read>(
503 mut buf_reader: BufReader<R>,
504) -> impl Iterator<Item = Vec<u8>> {
505 iter::from_fn(move || {
506 let mut buf = Vec::with_capacity(256);
507 let size = buf_reader.read_until(b'\n', &mut buf).ok()?;
508
509 if size == 0 {
510 return None;
511 }
512
513 if buf.ends_with(b"\n") {
515 buf.pop();
516 if buf.ends_with(b"\r") {
517 buf.pop();
518 }
519 }
520
521 Some(buf)
522 })
523}
524
525pub fn read_os_string_lines<R: std::io::Read>(
529 buf_reader: BufReader<R>,
530) -> impl Iterator<Item = OsString> {
531 read_byte_lines(buf_reader).map(|byte_line| os_string_from_vec(byte_line).expect("UTF-8 error"))
532}
533
534#[macro_export]
552macro_rules! prompt_yes(
553 ($($args:tt)+) => ({
554 use std::io::Write;
555 eprint!("{}: ", uucore::util_name());
556 eprint!($($args)+);
557 eprint!(" ");
558 let res = std::io::stderr().flush().map_err(|err| {
559 $crate::error::USimpleError::new(1, err.to_string())
560 });
561 uucore::show_if_err!(res);
562 uucore::read_yes()
563 })
564);
565
566#[derive(Debug, Clone, Copy, PartialEq, Eq)]
569pub enum CharByte {
570 Char(char),
571 Byte(u8),
572}
573
574impl From<char> for CharByte {
575 fn from(value: char) -> Self {
576 Self::Char(value)
577 }
578}
579
580impl From<u8> for CharByte {
581 fn from(value: u8) -> Self {
582 Self::Byte(value)
583 }
584}
585
586impl From<&u8> for CharByte {
587 fn from(value: &u8) -> Self {
588 Self::Byte(*value)
589 }
590}
591
592struct Utf8ChunkIterator<'a> {
593 iter: Box<dyn Iterator<Item = CharByte> + 'a>,
594}
595
596impl Iterator for Utf8ChunkIterator<'_> {
597 type Item = CharByte;
598
599 fn next(&mut self) -> Option<Self::Item> {
600 self.iter.next()
601 }
602}
603
604impl<'a> From<Utf8Chunk<'a>> for Utf8ChunkIterator<'a> {
605 fn from(chk: Utf8Chunk<'a>) -> Self {
606 Self {
607 iter: Box::new(
608 chk.valid()
609 .chars()
610 .map(CharByte::from)
611 .chain(chk.invalid().iter().map(CharByte::from)),
612 ),
613 }
614 }
615}
616
617pub struct CharByteIterator<'a> {
620 iter: Box<dyn Iterator<Item = CharByte> + 'a>,
621}
622
623impl<'a> CharByteIterator<'a> {
624 pub fn new(input: &'a [u8]) -> Self {
627 Self {
628 iter: Box::new(input.utf8_chunks().flat_map(Utf8ChunkIterator::from)),
629 }
630 }
631}
632
633impl Iterator for CharByteIterator<'_> {
634 type Item = CharByte;
635
636 fn next(&mut self) -> Option<Self::Item> {
637 self.iter.next()
638 }
639}
640
641pub trait IntoCharByteIterator<'a> {
642 fn iter_char_bytes(self) -> CharByteIterator<'a>;
643}
644
645impl<'a> IntoCharByteIterator<'a> for &'a [u8] {
646 fn iter_char_bytes(self) -> CharByteIterator<'a> {
647 CharByteIterator::new(self)
648 }
649}
650
651#[cfg(test)]
652mod tests {
653 use super::*;
654 use std::ffi::OsStr;
655
656 fn make_os_vec(os_str: &OsStr) -> Vec<OsString> {
657 vec![
658 OsString::from("test"),
659 OsString::from("สวัสดี"), os_str.to_os_string(),
661 ]
662 }
663
664 #[cfg(any(unix, target_os = "redox"))]
665 fn test_invalid_utf8_args_lossy(os_str: &OsStr) {
666 assert!(os_str.to_os_string().into_string().is_err());
668 let test_vec = make_os_vec(os_str);
669 let collected_to_str = test_vec.clone().into_iter().collect_lossy();
670 assert_eq!(collected_to_str.len(), test_vec.len());
672 for index in 0..2 {
674 assert_eq!(collected_to_str[index], test_vec[index].to_str().unwrap());
675 }
676 assert_eq!(
678 *collected_to_str[2],
679 os_str.to_os_string().to_string_lossy()
680 );
681 }
682
683 #[cfg(any(unix, target_os = "redox"))]
684 fn test_invalid_utf8_args_ignore(os_str: &OsStr) {
685 assert!(os_str.to_os_string().into_string().is_err());
687 let test_vec = make_os_vec(os_str);
688 let collected_to_str = test_vec.clone().into_iter().collect_ignore();
689 assert_eq!(collected_to_str.len(), test_vec.len() - 1);
691 for index in 0..2 {
693 assert_eq!(
694 collected_to_str.get(index).unwrap(),
695 test_vec.get(index).unwrap().to_str().unwrap()
696 );
697 }
698 }
699
700 #[test]
701 fn valid_utf8_encoding_args() {
702 let test_vec = make_os_vec(&OsString::from("test2"));
704 let _ = test_vec.into_iter().collect_lossy();
706 }
707
708 #[cfg(any(unix, target_os = "redox"))]
709 #[test]
710 fn invalid_utf8_args_unix() {
711 use std::os::unix::ffi::OsStrExt;
712
713 let source = [0x66, 0x6f, 0x80, 0x6f];
714 let os_str = OsStr::from_bytes(&source[..]);
715 test_invalid_utf8_args_lossy(os_str);
716 test_invalid_utf8_args_ignore(os_str);
717 }
718
719 #[test]
720 fn test_format_usage() {
721 assert_eq!(format_usage("expr EXPRESSION"), "expr EXPRESSION");
722 assert_eq!(
723 format_usage("expr EXPRESSION\nexpr OPTION"),
724 "expr EXPRESSION\n expr OPTION"
725 );
726 }
727}