mirror of
https://github.com/RGBCube/uutils-coreutils
synced 2025-07-30 12:37:49 +00:00
Merge branch 'master' into fix_chroot_2687
This commit is contained in:
commit
3e985cb029
49 changed files with 632 additions and 335 deletions
5
Cargo.lock
generated
5
Cargo.lock
generated
|
@ -669,9 +669,9 @@ checksum = "0e25ea47919b1560c4e3b7fe0aaab9becf5b84a10325ddf7db0f0ba5e1026499"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "digest"
|
name = "digest"
|
||||||
version = "0.6.2"
|
version = "0.6.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e5b29bf156f3f4b3c4f610a25ff69370616ae6e0657d416de22645483e72af0a"
|
checksum = "ecae1c064e29fcabb6c2e9939e53dc7da72ed90234ae36ebfe03a478742efbd1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"generic-array",
|
"generic-array",
|
||||||
]
|
]
|
||||||
|
@ -2608,6 +2608,7 @@ dependencies = [
|
||||||
"lscolors",
|
"lscolors",
|
||||||
"number_prefix",
|
"number_prefix",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
|
"selinux",
|
||||||
"term_grid",
|
"term_grid",
|
||||||
"termsize",
|
"termsize",
|
||||||
"unicode-width",
|
"unicode-width",
|
||||||
|
|
|
@ -148,7 +148,7 @@ feat_os_unix_musl = [
|
||||||
# NOTE:
|
# NOTE:
|
||||||
# The selinux(-sys) crate requires `libselinux` headers and shared library to be accessible in the C toolchain at compile time.
|
# The selinux(-sys) crate requires `libselinux` headers and shared library to be accessible in the C toolchain at compile time.
|
||||||
# Running a uutils compiled with `feat_selinux` requires an SELinux enabled Kernel at run time.
|
# Running a uutils compiled with `feat_selinux` requires an SELinux enabled Kernel at run time.
|
||||||
feat_selinux = ["cp/selinux", "id/selinux", "selinux", "feat_require_selinux"]
|
feat_selinux = ["cp/selinux", "id/selinux", "ls/selinux", "selinux", "feat_require_selinux"]
|
||||||
# "feat_acl" == set of utilities providing support for acl (access control lists) if enabled with `--features feat_acl`.
|
# "feat_acl" == set of utilities providing support for acl (access control lists) if enabled with `--features feat_acl`.
|
||||||
# NOTE:
|
# NOTE:
|
||||||
# On linux, the posix-acl/acl-sys crate requires `libacl` headers and shared library to be accessible in the C toolchain at compile time.
|
# On linux, the posix-acl/acl-sys crate requires `libacl` headers and shared library to be accessible in the C toolchain at compile time.
|
||||||
|
|
|
@ -6,9 +6,6 @@
|
||||||
// For the full copyright and license information, please view the LICENSE
|
// For the full copyright and license information, please view the LICENSE
|
||||||
// file that was distributed with this source code.
|
// file that was distributed with this source code.
|
||||||
|
|
||||||
#[macro_use]
|
|
||||||
extern crate uucore;
|
|
||||||
|
|
||||||
use platform_info::*;
|
use platform_info::*;
|
||||||
|
|
||||||
use clap::{crate_version, App};
|
use clap::{crate_version, App};
|
||||||
|
|
|
@ -5,13 +5,10 @@
|
||||||
// For the full copyright and license information, please view the LICENSE file
|
// For the full copyright and license information, please view the LICENSE file
|
||||||
// that was distributed with this source code.
|
// that was distributed with this source code.
|
||||||
|
|
||||||
#[macro_use]
|
|
||||||
extern crate uucore;
|
|
||||||
|
|
||||||
use std::io::{stdin, Read};
|
use std::io::{stdin, Read};
|
||||||
|
|
||||||
use clap::App;
|
use clap::App;
|
||||||
use uucore::encoding::Format;
|
use uucore::{encoding::Format, error::UResult};
|
||||||
|
|
||||||
pub mod base_common;
|
pub mod base_common;
|
||||||
|
|
||||||
|
@ -24,27 +21,22 @@ static ABOUT: &str = "
|
||||||
to attempt to recover from any other non-alphabet bytes in the
|
to attempt to recover from any other non-alphabet bytes in the
|
||||||
encoded stream.
|
encoded stream.
|
||||||
";
|
";
|
||||||
static VERSION: &str = env!("CARGO_PKG_VERSION");
|
|
||||||
|
|
||||||
static BASE_CMD_PARSE_ERROR: i32 = 1;
|
|
||||||
|
|
||||||
fn usage() -> String {
|
fn usage() -> String {
|
||||||
format!("{0} [OPTION]... [FILE]", uucore::execution_phrase())
|
format!("{0} [OPTION]... [FILE]", uucore::execution_phrase())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn uumain(args: impl uucore::Args) -> i32 {
|
#[uucore_procs::gen_uumain]
|
||||||
|
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
||||||
let format = Format::Base32;
|
let format = Format::Base32;
|
||||||
let usage = usage();
|
let usage = usage();
|
||||||
let name = uucore::util_name();
|
|
||||||
|
|
||||||
let config_result: Result<base_common::Config, String> =
|
let config: base_common::Config = base_common::parse_base_cmd_args(args, ABOUT, &usage)?;
|
||||||
base_common::parse_base_cmd_args(args, name, VERSION, ABOUT, &usage);
|
|
||||||
let config = config_result.unwrap_or_else(|s| crash!(BASE_CMD_PARSE_ERROR, "{}", s));
|
|
||||||
|
|
||||||
// Create a reference to stdin so we can return a locked stdin from
|
// Create a reference to stdin so we can return a locked stdin from
|
||||||
// parse_base_cmd_args
|
// parse_base_cmd_args
|
||||||
let stdin_raw = stdin();
|
let stdin_raw = stdin();
|
||||||
let mut input: Box<dyn Read> = base_common::get_input(&config, &stdin_raw);
|
let mut input: Box<dyn Read> = base_common::get_input(&config, &stdin_raw)?;
|
||||||
|
|
||||||
base_common::handle_input(
|
base_common::handle_input(
|
||||||
&mut input,
|
&mut input,
|
||||||
|
@ -52,12 +44,9 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
||||||
config.wrap_cols,
|
config.wrap_cols,
|
||||||
config.ignore_garbage,
|
config.ignore_garbage,
|
||||||
config.decode,
|
config.decode,
|
||||||
name,
|
)
|
||||||
);
|
|
||||||
|
|
||||||
0
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn uu_app() -> App<'static, 'static> {
|
pub fn uu_app() -> App<'static, 'static> {
|
||||||
base_common::base_app(uucore::util_name(), VERSION, ABOUT)
|
base_common::base_app(ABOUT)
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,13 +11,16 @@ use std::io::{stdout, Read, Write};
|
||||||
|
|
||||||
use uucore::display::Quotable;
|
use uucore::display::Quotable;
|
||||||
use uucore::encoding::{wrap_print, Data, Format};
|
use uucore::encoding::{wrap_print, Data, Format};
|
||||||
|
use uucore::error::{FromIo, UResult, USimpleError, UUsageError};
|
||||||
use uucore::InvalidEncodingHandling;
|
use uucore::InvalidEncodingHandling;
|
||||||
|
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::{BufReader, Stdin};
|
use std::io::{BufReader, Stdin};
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
use clap::{App, Arg};
|
use clap::{crate_version, App, Arg};
|
||||||
|
|
||||||
|
pub static BASE_CMD_PARSE_ERROR: i32 = 1;
|
||||||
|
|
||||||
// Config.
|
// Config.
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
|
@ -35,15 +38,14 @@ pub mod options {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Config {
|
impl Config {
|
||||||
pub fn from(app_name: &str, options: &clap::ArgMatches) -> Result<Config, String> {
|
pub fn from(options: &clap::ArgMatches) -> UResult<Config> {
|
||||||
let file: Option<String> = match options.values_of(options::FILE) {
|
let file: Option<String> = match options.values_of(options::FILE) {
|
||||||
Some(mut values) => {
|
Some(mut values) => {
|
||||||
let name = values.next().unwrap();
|
let name = values.next().unwrap();
|
||||||
if let Some(extra_op) = values.next() {
|
if let Some(extra_op) = values.next() {
|
||||||
return Err(format!(
|
return Err(UUsageError::new(
|
||||||
"extra operand {}\nTry '{} --help' for more information.",
|
BASE_CMD_PARSE_ERROR,
|
||||||
extra_op.quote(),
|
format!("extra operand {}", extra_op.quote(),),
|
||||||
app_name
|
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -51,7 +53,10 @@ impl Config {
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
if !Path::exists(Path::new(name)) {
|
if !Path::exists(Path::new(name)) {
|
||||||
return Err(format!("{}: No such file or directory", name.maybe_quote()));
|
return Err(USimpleError::new(
|
||||||
|
BASE_CMD_PARSE_ERROR,
|
||||||
|
format!("{}: No such file or directory", name.maybe_quote()),
|
||||||
|
));
|
||||||
}
|
}
|
||||||
Some(name.to_owned())
|
Some(name.to_owned())
|
||||||
}
|
}
|
||||||
|
@ -62,8 +67,12 @@ impl Config {
|
||||||
let cols = options
|
let cols = options
|
||||||
.value_of(options::WRAP)
|
.value_of(options::WRAP)
|
||||||
.map(|num| {
|
.map(|num| {
|
||||||
num.parse::<usize>()
|
num.parse::<usize>().map_err(|_| {
|
||||||
.map_err(|_| format!("invalid wrap size: {}", num.quote()))
|
USimpleError::new(
|
||||||
|
BASE_CMD_PARSE_ERROR,
|
||||||
|
format!("invalid wrap size: {}", num.quote()),
|
||||||
|
)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
.transpose()?;
|
.transpose()?;
|
||||||
|
|
||||||
|
@ -76,23 +85,17 @@ impl Config {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn parse_base_cmd_args(
|
pub fn parse_base_cmd_args(args: impl uucore::Args, about: &str, usage: &str) -> UResult<Config> {
|
||||||
args: impl uucore::Args,
|
let app = base_app(about).usage(usage);
|
||||||
name: &str,
|
|
||||||
version: &str,
|
|
||||||
about: &str,
|
|
||||||
usage: &str,
|
|
||||||
) -> Result<Config, String> {
|
|
||||||
let app = base_app(name, version, about).usage(usage);
|
|
||||||
let arg_list = args
|
let arg_list = args
|
||||||
.collect_str(InvalidEncodingHandling::ConvertLossy)
|
.collect_str(InvalidEncodingHandling::ConvertLossy)
|
||||||
.accept_any();
|
.accept_any();
|
||||||
Config::from(name, &app.get_matches_from(arg_list))
|
Config::from(&app.get_matches_from(arg_list))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn base_app<'a>(name: &str, version: &'a str, about: &'a str) -> App<'static, 'a> {
|
pub fn base_app<'a>(about: &'a str) -> App<'static, 'a> {
|
||||||
App::new(name)
|
App::new(uucore::util_name())
|
||||||
.version(version)
|
.version(crate_version!())
|
||||||
.about(about)
|
.about(about)
|
||||||
// Format arguments.
|
// Format arguments.
|
||||||
.arg(
|
.arg(
|
||||||
|
@ -121,14 +124,15 @@ pub fn base_app<'a>(name: &str, version: &'a str, about: &'a str) -> App<'static
|
||||||
.arg(Arg::with_name(options::FILE).index(1).multiple(true))
|
.arg(Arg::with_name(options::FILE).index(1).multiple(true))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_input<'a>(config: &Config, stdin_ref: &'a Stdin) -> Box<dyn Read + 'a> {
|
pub fn get_input<'a>(config: &Config, stdin_ref: &'a Stdin) -> UResult<Box<dyn Read + 'a>> {
|
||||||
match &config.to_read {
|
match &config.to_read {
|
||||||
Some(name) => {
|
Some(name) => {
|
||||||
let file_buf = crash_if_err!(1, File::open(Path::new(name)));
|
let file_buf =
|
||||||
Box::new(BufReader::new(file_buf)) // as Box<dyn Read>
|
File::open(Path::new(name)).map_err_context(|| name.maybe_quote().to_string())?;
|
||||||
|
Ok(Box::new(BufReader::new(file_buf))) // as Box<dyn Read>
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
Box::new(stdin_ref.lock()) // as Box<dyn Read>
|
Ok(Box::new(stdin_ref.lock())) // as Box<dyn Read>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -139,8 +143,7 @@ pub fn handle_input<R: Read>(
|
||||||
line_wrap: Option<usize>,
|
line_wrap: Option<usize>,
|
||||||
ignore_garbage: bool,
|
ignore_garbage: bool,
|
||||||
decode: bool,
|
decode: bool,
|
||||||
name: &str,
|
) -> UResult<()> {
|
||||||
) {
|
|
||||||
let mut data = Data::new(input, format).ignore_garbage(ignore_garbage);
|
let mut data = Data::new(input, format).ignore_garbage(ignore_garbage);
|
||||||
if let Some(wrap) = line_wrap {
|
if let Some(wrap) = line_wrap {
|
||||||
data = data.line_wrap(wrap);
|
data = data.line_wrap(wrap);
|
||||||
|
@ -150,28 +153,23 @@ pub fn handle_input<R: Read>(
|
||||||
match data.encode() {
|
match data.encode() {
|
||||||
Ok(s) => {
|
Ok(s) => {
|
||||||
wrap_print(&data, s);
|
wrap_print(&data, s);
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
Err(_) => {
|
Err(_) => Err(USimpleError::new(
|
||||||
eprintln!(
|
1,
|
||||||
"{}: error: invalid input (length must be multiple of 4 characters)",
|
"error: invalid input (length must be multiple of 4 characters)",
|
||||||
name
|
)),
|
||||||
);
|
|
||||||
exit!(1)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
match data.decode() {
|
match data.decode() {
|
||||||
Ok(s) => {
|
Ok(s) => {
|
||||||
if stdout().write_all(&s).is_err() {
|
if stdout().write_all(&s).is_err() {
|
||||||
// on windows console, writing invalid utf8 returns an error
|
// on windows console, writing invalid utf8 returns an error
|
||||||
eprintln!("{}: error: Cannot write non-utf8 data", name);
|
return Err(USimpleError::new(1, "error: cannot write non-utf8 data"));
|
||||||
exit!(1)
|
|
||||||
}
|
}
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
Err(_) => {
|
Err(_) => Err(USimpleError::new(1, "error: invalid input")),
|
||||||
eprintln!("{}: error: invalid input", name);
|
|
||||||
exit!(1)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,13 +6,10 @@
|
||||||
// For the full copyright and license information, please view the LICENSE file
|
// For the full copyright and license information, please view the LICENSE file
|
||||||
// that was distributed with this source code.
|
// that was distributed with this source code.
|
||||||
|
|
||||||
#[macro_use]
|
|
||||||
extern crate uucore;
|
|
||||||
|
|
||||||
use uu_base32::base_common;
|
use uu_base32::base_common;
|
||||||
pub use uu_base32::uu_app;
|
pub use uu_base32::uu_app;
|
||||||
|
|
||||||
use uucore::encoding::Format;
|
use uucore::{encoding::Format, error::UResult};
|
||||||
|
|
||||||
use std::io::{stdin, Read};
|
use std::io::{stdin, Read};
|
||||||
|
|
||||||
|
@ -25,26 +22,22 @@ static ABOUT: &str = "
|
||||||
to attempt to recover from any other non-alphabet bytes in the
|
to attempt to recover from any other non-alphabet bytes in the
|
||||||
encoded stream.
|
encoded stream.
|
||||||
";
|
";
|
||||||
static VERSION: &str = env!("CARGO_PKG_VERSION");
|
|
||||||
|
|
||||||
static BASE_CMD_PARSE_ERROR: i32 = 1;
|
|
||||||
|
|
||||||
fn usage() -> String {
|
fn usage() -> String {
|
||||||
format!("{0} [OPTION]... [FILE]", uucore::execution_phrase())
|
format!("{0} [OPTION]... [FILE]", uucore::execution_phrase())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn uumain(args: impl uucore::Args) -> i32 {
|
#[uucore_procs::gen_uumain]
|
||||||
|
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
||||||
let format = Format::Base64;
|
let format = Format::Base64;
|
||||||
let usage = usage();
|
let usage = usage();
|
||||||
let name = uucore::util_name();
|
|
||||||
let config_result: Result<base_common::Config, String> =
|
let config: base_common::Config = base_common::parse_base_cmd_args(args, ABOUT, &usage)?;
|
||||||
base_common::parse_base_cmd_args(args, name, VERSION, ABOUT, &usage);
|
|
||||||
let config = config_result.unwrap_or_else(|s| crash!(BASE_CMD_PARSE_ERROR, "{}", s));
|
|
||||||
|
|
||||||
// Create a reference to stdin so we can return a locked stdin from
|
// Create a reference to stdin so we can return a locked stdin from
|
||||||
// parse_base_cmd_args
|
// parse_base_cmd_args
|
||||||
let stdin_raw = stdin();
|
let stdin_raw = stdin();
|
||||||
let mut input: Box<dyn Read> = base_common::get_input(&config, &stdin_raw);
|
let mut input: Box<dyn Read> = base_common::get_input(&config, &stdin_raw)?;
|
||||||
|
|
||||||
base_common::handle_input(
|
base_common::handle_input(
|
||||||
&mut input,
|
&mut input,
|
||||||
|
@ -52,8 +45,5 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
||||||
config.wrap_cols,
|
config.wrap_cols,
|
||||||
config.ignore_garbage,
|
config.ignore_garbage,
|
||||||
config.decode,
|
config.decode,
|
||||||
name,
|
)
|
||||||
);
|
|
||||||
|
|
||||||
0
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,13 +8,14 @@
|
||||||
|
|
||||||
//spell-checker:ignore (args) lsbf msbf
|
//spell-checker:ignore (args) lsbf msbf
|
||||||
|
|
||||||
#[macro_use]
|
use clap::{App, Arg};
|
||||||
extern crate uucore;
|
use uu_base32::base_common::{self, Config, BASE_CMD_PARSE_ERROR};
|
||||||
|
|
||||||
use clap::{crate_version, App, Arg};
|
use uucore::{
|
||||||
use uu_base32::base_common::{self, Config};
|
encoding::Format,
|
||||||
|
error::{UResult, UUsageError},
|
||||||
use uucore::{encoding::Format, InvalidEncodingHandling};
|
InvalidEncodingHandling,
|
||||||
|
};
|
||||||
|
|
||||||
use std::io::{stdin, Read};
|
use std::io::{stdin, Read};
|
||||||
|
|
||||||
|
@ -26,8 +27,6 @@ static ABOUT: &str = "
|
||||||
from any other non-alphabet bytes in the encoded stream.
|
from any other non-alphabet bytes in the encoded stream.
|
||||||
";
|
";
|
||||||
|
|
||||||
static BASE_CMD_PARSE_ERROR: i32 = 1;
|
|
||||||
|
|
||||||
const ENCODINGS: &[(&str, Format)] = &[
|
const ENCODINGS: &[(&str, Format)] = &[
|
||||||
("base64", Format::Base64),
|
("base64", Format::Base64),
|
||||||
("base64url", Format::Base64Url),
|
("base64url", Format::Base64Url),
|
||||||
|
@ -47,14 +46,14 @@ fn usage() -> String {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn uu_app() -> App<'static, 'static> {
|
pub fn uu_app() -> App<'static, 'static> {
|
||||||
let mut app = base_common::base_app(uucore::util_name(), crate_version!(), ABOUT);
|
let mut app = base_common::base_app(ABOUT);
|
||||||
for encoding in ENCODINGS {
|
for encoding in ENCODINGS {
|
||||||
app = app.arg(Arg::with_name(encoding.0).long(encoding.0));
|
app = app.arg(Arg::with_name(encoding.0).long(encoding.0));
|
||||||
}
|
}
|
||||||
app
|
app
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_cmd_args(args: impl uucore::Args) -> (Config, Format) {
|
fn parse_cmd_args(args: impl uucore::Args) -> UResult<(Config, Format)> {
|
||||||
let usage = usage();
|
let usage = usage();
|
||||||
let matches = uu_app().usage(&usage[..]).get_matches_from(
|
let matches = uu_app().usage(&usage[..]).get_matches_from(
|
||||||
args.collect_str(InvalidEncodingHandling::ConvertLossy)
|
args.collect_str(InvalidEncodingHandling::ConvertLossy)
|
||||||
|
@ -63,24 +62,19 @@ fn parse_cmd_args(args: impl uucore::Args) -> (Config, Format) {
|
||||||
let format = ENCODINGS
|
let format = ENCODINGS
|
||||||
.iter()
|
.iter()
|
||||||
.find(|encoding| matches.is_present(encoding.0))
|
.find(|encoding| matches.is_present(encoding.0))
|
||||||
.unwrap_or_else(|| {
|
.ok_or_else(|| UUsageError::new(BASE_CMD_PARSE_ERROR, "missing encoding type"))?
|
||||||
show_usage_error!("missing encoding type");
|
|
||||||
std::process::exit(1)
|
|
||||||
})
|
|
||||||
.1;
|
.1;
|
||||||
(
|
let config = Config::from(&matches)?;
|
||||||
Config::from("basenc", &matches).unwrap_or_else(|s| crash!(BASE_CMD_PARSE_ERROR, "{}", s)),
|
Ok((config, format))
|
||||||
format,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn uumain(args: impl uucore::Args) -> i32 {
|
#[uucore_procs::gen_uumain]
|
||||||
let name = uucore::util_name();
|
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
||||||
let (config, format) = parse_cmd_args(args);
|
let (config, format) = parse_cmd_args(args)?;
|
||||||
// Create a reference to stdin so we can return a locked stdin from
|
// Create a reference to stdin so we can return a locked stdin from
|
||||||
// parse_base_cmd_args
|
// parse_base_cmd_args
|
||||||
let stdin_raw = stdin();
|
let stdin_raw = stdin();
|
||||||
let mut input: Box<dyn Read> = base_common::get_input(&config, &stdin_raw);
|
let mut input: Box<dyn Read> = base_common::get_input(&config, &stdin_raw)?;
|
||||||
|
|
||||||
base_common::handle_input(
|
base_common::handle_input(
|
||||||
&mut input,
|
&mut input,
|
||||||
|
@ -88,8 +82,5 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
||||||
config.wrap_cols,
|
config.wrap_cols,
|
||||||
config.ignore_garbage,
|
config.ignore_garbage,
|
||||||
config.decode,
|
config.decode,
|
||||||
name,
|
)
|
||||||
);
|
|
||||||
|
|
||||||
0
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,8 +12,6 @@
|
||||||
|
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
extern crate unix_socket;
|
extern crate unix_socket;
|
||||||
#[macro_use]
|
|
||||||
extern crate uucore;
|
|
||||||
|
|
||||||
// last synced with: cat (GNU coreutils) 8.13
|
// last synced with: cat (GNU coreutils) 8.13
|
||||||
use clap::{crate_version, App, Arg};
|
use clap::{crate_version, App, Arg};
|
||||||
|
|
|
@ -7,8 +7,6 @@
|
||||||
|
|
||||||
// spell-checker:ignore (ToDO) COMFOLLOW Chowner RFILE RFILE's derefer dgid nonblank nonprint nonprinting
|
// spell-checker:ignore (ToDO) COMFOLLOW Chowner RFILE RFILE's derefer dgid nonblank nonprint nonprinting
|
||||||
|
|
||||||
#[macro_use]
|
|
||||||
extern crate uucore;
|
|
||||||
use uucore::display::Quotable;
|
use uucore::display::Quotable;
|
||||||
pub use uucore::entries;
|
pub use uucore::entries;
|
||||||
use uucore::error::{FromIo, UResult, USimpleError};
|
use uucore::error::{FromIo, UResult, USimpleError};
|
||||||
|
|
|
@ -7,8 +7,6 @@
|
||||||
|
|
||||||
// spell-checker:ignore (ToDO) COMFOLLOW Passwd RFILE RFILE's derefer dgid duid groupname
|
// spell-checker:ignore (ToDO) COMFOLLOW Passwd RFILE RFILE's derefer dgid duid groupname
|
||||||
|
|
||||||
#[macro_use]
|
|
||||||
extern crate uucore;
|
|
||||||
use uucore::display::Quotable;
|
use uucore::display::Quotable;
|
||||||
pub use uucore::entries::{self, Group, Locate, Passwd};
|
pub use uucore::entries::{self, Group, Locate, Passwd};
|
||||||
use uucore::perms::{chown_base, options, IfFrom};
|
use uucore::perms::{chown_base, options, IfFrom};
|
||||||
|
|
|
@ -49,6 +49,7 @@ use std::path::{Path, PathBuf, StripPrefixError};
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use std::string::ToString;
|
use std::string::ToString;
|
||||||
use uucore::backup_control::{self, BackupMode};
|
use uucore::backup_control::{self, BackupMode};
|
||||||
|
use uucore::error::{set_exit_code, ExitCode, UError, UResult};
|
||||||
use uucore::fs::{canonicalize, MissingHandling, ResolveMode};
|
use uucore::fs::{canonicalize, MissingHandling, ResolveMode};
|
||||||
use walkdir::WalkDir;
|
use walkdir::WalkDir;
|
||||||
|
|
||||||
|
@ -105,6 +106,12 @@ quick_error! {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl UError for Error {
|
||||||
|
fn code(&self) -> i32 {
|
||||||
|
EXIT_ERR
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Continue next iteration of loop if result of expression is error
|
/// Continue next iteration of loop if result of expression is error
|
||||||
macro_rules! or_continue(
|
macro_rules! or_continue(
|
||||||
($expr:expr) => (match $expr {
|
($expr:expr) => (match $expr {
|
||||||
|
@ -220,7 +227,6 @@ pub struct Options {
|
||||||
|
|
||||||
static ABOUT: &str = "Copy SOURCE to DEST, or multiple SOURCE(s) to DIRECTORY.";
|
static ABOUT: &str = "Copy SOURCE to DEST, or multiple SOURCE(s) to DIRECTORY.";
|
||||||
static LONG_HELP: &str = "";
|
static LONG_HELP: &str = "";
|
||||||
static EXIT_OK: i32 = 0;
|
|
||||||
static EXIT_ERR: i32 = 1;
|
static EXIT_ERR: i32 = 1;
|
||||||
|
|
||||||
fn usage() -> String {
|
fn usage() -> String {
|
||||||
|
@ -446,7 +452,8 @@ pub fn uu_app() -> App<'static, 'static> {
|
||||||
.multiple(true))
|
.multiple(true))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn uumain(args: impl uucore::Args) -> i32 {
|
#[uucore_procs::gen_uumain]
|
||||||
|
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
||||||
let usage = usage();
|
let usage = usage();
|
||||||
let matches = uu_app()
|
let matches = uu_app()
|
||||||
.after_help(&*format!(
|
.after_help(&*format!(
|
||||||
|
@ -457,11 +464,11 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
||||||
.usage(&usage[..])
|
.usage(&usage[..])
|
||||||
.get_matches_from(args);
|
.get_matches_from(args);
|
||||||
|
|
||||||
let options = crash_if_err!(EXIT_ERR, Options::from_matches(&matches));
|
let options = Options::from_matches(&matches)?;
|
||||||
|
|
||||||
if options.overwrite == OverwriteMode::NoClobber && options.backup != BackupMode::NoBackup {
|
if options.overwrite == OverwriteMode::NoClobber && options.backup != BackupMode::NoBackup {
|
||||||
show_usage_error!("options --backup and --no-clobber are mutually exclusive");
|
show_usage_error!("options --backup and --no-clobber are mutually exclusive");
|
||||||
return 1;
|
return Err(ExitCode(EXIT_ERR).into());
|
||||||
}
|
}
|
||||||
|
|
||||||
let paths: Vec<String> = matches
|
let paths: Vec<String> = matches
|
||||||
|
@ -469,7 +476,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
||||||
.map(|v| v.map(ToString::to_string).collect())
|
.map(|v| v.map(ToString::to_string).collect())
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
|
|
||||||
let (sources, target) = crash_if_err!(EXIT_ERR, parse_path_args(&paths, &options));
|
let (sources, target) = parse_path_args(&paths, &options)?;
|
||||||
|
|
||||||
if let Err(error) = copy(&sources, &target, &options) {
|
if let Err(error) = copy(&sources, &target, &options) {
|
||||||
match error {
|
match error {
|
||||||
|
@ -479,10 +486,10 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
||||||
// Else we caught a fatal bubbled-up error, log it to stderr
|
// Else we caught a fatal bubbled-up error, log it to stderr
|
||||||
_ => show_error!("{}", error),
|
_ => show_error!("{}", error),
|
||||||
};
|
};
|
||||||
return EXIT_ERR;
|
set_exit_code(EXIT_ERR);
|
||||||
}
|
}
|
||||||
|
|
||||||
EXIT_OK
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ClobberMode {
|
impl ClobberMode {
|
||||||
|
@ -1124,7 +1131,7 @@ fn copy_attribute(source: &Path, dest: &Path, attribute: &Attribute) -> CopyResu
|
||||||
let xattrs = xattr::list(source)?;
|
let xattrs = xattr::list(source)?;
|
||||||
for attr in xattrs {
|
for attr in xattrs {
|
||||||
if let Some(attr_value) = xattr::get(source, attr.clone())? {
|
if let Some(attr_value) = xattr::get(source, attr.clone())? {
|
||||||
crash_if_err!(EXIT_ERR, xattr::set(dest, attr, &attr_value[..]));
|
xattr::set(dest, attr, &attr_value[..])?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -320,18 +320,19 @@ impl<'a> SplitWriter<'a> {
|
||||||
let l = line?;
|
let l = line?;
|
||||||
match n.cmp(&(&ln + 1)) {
|
match n.cmp(&(&ln + 1)) {
|
||||||
Ordering::Less => {
|
Ordering::Less => {
|
||||||
if input_iter.add_line_to_buffer(ln, l).is_some() {
|
assert!(
|
||||||
panic!("the buffer is big enough to contain 1 line");
|
input_iter.add_line_to_buffer(ln, l).is_none(),
|
||||||
}
|
"the buffer is big enough to contain 1 line"
|
||||||
|
);
|
||||||
ret = Ok(());
|
ret = Ok(());
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
Ordering::Equal => {
|
Ordering::Equal => {
|
||||||
if !self.options.suppress_matched
|
assert!(
|
||||||
&& input_iter.add_line_to_buffer(ln, l).is_some()
|
self.options.suppress_matched
|
||||||
{
|
|| input_iter.add_line_to_buffer(ln, l).is_none(),
|
||||||
panic!("the buffer is big enough to contain 1 line");
|
"the buffer is big enough to contain 1 line"
|
||||||
}
|
);
|
||||||
ret = Ok(());
|
ret = Ok(());
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -378,9 +379,10 @@ impl<'a> SplitWriter<'a> {
|
||||||
match (self.options.suppress_matched, offset) {
|
match (self.options.suppress_matched, offset) {
|
||||||
// no offset, add the line to the next split
|
// no offset, add the line to the next split
|
||||||
(false, 0) => {
|
(false, 0) => {
|
||||||
if input_iter.add_line_to_buffer(ln, l).is_some() {
|
assert!(
|
||||||
panic!("the buffer is big enough to contain 1 line");
|
input_iter.add_line_to_buffer(ln, l).is_none(),
|
||||||
}
|
"the buffer is big enough to contain 1 line"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
// a positive offset, some more lines need to be added to the current split
|
// a positive offset, some more lines need to be added to the current split
|
||||||
(false, _) => self.writeln(l)?,
|
(false, _) => self.writeln(l)?,
|
||||||
|
@ -425,9 +427,10 @@ impl<'a> SplitWriter<'a> {
|
||||||
if !self.options.suppress_matched {
|
if !self.options.suppress_matched {
|
||||||
// add 1 to the buffer size to make place for the matched line
|
// add 1 to the buffer size to make place for the matched line
|
||||||
input_iter.set_size_of_buffer(offset_usize + 1);
|
input_iter.set_size_of_buffer(offset_usize + 1);
|
||||||
if input_iter.add_line_to_buffer(ln, l).is_some() {
|
assert!(
|
||||||
panic!("should be big enough to hold every lines");
|
input_iter.add_line_to_buffer(ln, l).is_none(),
|
||||||
}
|
"should be big enough to hold every lines"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
self.finish_split();
|
self.finish_split();
|
||||||
if input_iter.buffer_len() < offset_usize {
|
if input_iter.buffer_len() < offset_usize {
|
||||||
|
|
|
@ -35,12 +35,11 @@ fn unimplemented_flags_should_error_non_linux() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !succeeded.is_empty() {
|
assert!(
|
||||||
panic!(
|
succeeded.is_empty(),
|
||||||
"The following flags did not panic as expected: {:?}",
|
"The following flags did not panic as expected: {:?}",
|
||||||
succeeded
|
succeeded
|
||||||
);
|
);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -64,12 +63,11 @@ fn unimplemented_flags_should_error() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !succeeded.is_empty() {
|
assert!(
|
||||||
panic!(
|
succeeded.is_empty(),
|
||||||
"The following flags did not panic as expected: {:?}",
|
"The following flags did not panic as expected: {:?}",
|
||||||
succeeded
|
succeeded
|
||||||
);
|
);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
@ -6,8 +6,6 @@
|
||||||
// For the full copyright and license information, please view the LICENSE file
|
// For the full copyright and license information, please view the LICENSE file
|
||||||
// that was distributed with this source code.
|
// that was distributed with this source code.
|
||||||
|
|
||||||
#[macro_use]
|
|
||||||
extern crate uucore;
|
|
||||||
use uucore::error::UError;
|
use uucore::error::UError;
|
||||||
use uucore::error::UResult;
|
use uucore::error::UResult;
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
|
|
|
@ -5,9 +5,6 @@
|
||||||
// For the full copyright and license information, please view the LICENSE
|
// For the full copyright and license information, please view the LICENSE
|
||||||
// file that was distributed with this source code.
|
// file that was distributed with this source code.
|
||||||
|
|
||||||
#[macro_use]
|
|
||||||
extern crate uucore;
|
|
||||||
|
|
||||||
use clap::{crate_version, App, Arg};
|
use clap::{crate_version, App, Arg};
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use uucore::display::print_verbatim;
|
use uucore::display::print_verbatim;
|
||||||
|
|
|
@ -6,9 +6,6 @@
|
||||||
// For the full copyright and license information, please view the LICENSE
|
// For the full copyright and license information, please view the LICENSE
|
||||||
// file that was distributed with this source code.
|
// file that was distributed with this source code.
|
||||||
|
|
||||||
#[macro_use]
|
|
||||||
extern crate uucore;
|
|
||||||
|
|
||||||
use clap::{crate_version, App, Arg};
|
use clap::{crate_version, App, Arg};
|
||||||
use std::io::{self, Write};
|
use std::io::{self, Write};
|
||||||
use std::iter::Peekable;
|
use std::iter::Peekable;
|
||||||
|
|
|
@ -5,10 +5,8 @@
|
||||||
//* For the full copyright and license information, please view the LICENSE
|
//* For the full copyright and license information, please view the LICENSE
|
||||||
//* file that was distributed with this source code.
|
//* file that was distributed with this source code.
|
||||||
|
|
||||||
#[macro_use]
|
|
||||||
extern crate uucore;
|
|
||||||
|
|
||||||
use clap::{crate_version, App, Arg};
|
use clap::{crate_version, App, Arg};
|
||||||
|
use uucore::error::{UResult, USimpleError};
|
||||||
use uucore::InvalidEncodingHandling;
|
use uucore::InvalidEncodingHandling;
|
||||||
|
|
||||||
mod syntax_tree;
|
mod syntax_tree;
|
||||||
|
@ -23,7 +21,8 @@ pub fn uu_app() -> App<'static, 'static> {
|
||||||
.arg(Arg::with_name(HELP).long(HELP))
|
.arg(Arg::with_name(HELP).long(HELP))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn uumain(args: impl uucore::Args) -> i32 {
|
#[uucore_procs::gen_uumain]
|
||||||
|
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
||||||
let args = args
|
let args = args
|
||||||
.collect_str(InvalidEncodingHandling::ConvertLossy)
|
.collect_str(InvalidEncodingHandling::ConvertLossy)
|
||||||
.accept_any();
|
.accept_any();
|
||||||
|
@ -32,13 +31,13 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
||||||
// The following usage should work without escaping hyphens: `expr -15 = 1 + 2 \* \( 3 - -4 \)`
|
// The following usage should work without escaping hyphens: `expr -15 = 1 + 2 \* \( 3 - -4 \)`
|
||||||
|
|
||||||
if maybe_handle_help_or_version(&args) {
|
if maybe_handle_help_or_version(&args) {
|
||||||
0
|
Ok(())
|
||||||
} else {
|
} else {
|
||||||
let token_strings = args[1..].to_vec();
|
let token_strings = args[1..].to_vec();
|
||||||
|
|
||||||
match process_expr(&token_strings) {
|
match process_expr(&token_strings) {
|
||||||
Ok(expr_result) => print_expr_ok(&expr_result),
|
Ok(expr_result) => print_expr_ok(&expr_result),
|
||||||
Err(expr_error) => print_expr_error(&expr_error),
|
Err(expr_error) => Err(USimpleError::new(2, &expr_error)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -49,19 +48,15 @@ fn process_expr(token_strings: &[String]) -> Result<String, String> {
|
||||||
evaluate_ast(maybe_ast)
|
evaluate_ast(maybe_ast)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn print_expr_ok(expr_result: &str) -> i32 {
|
fn print_expr_ok(expr_result: &str) -> UResult<()> {
|
||||||
println!("{}", expr_result);
|
println!("{}", expr_result);
|
||||||
if expr_result == "0" || expr_result.is_empty() {
|
if expr_result == "0" || expr_result.is_empty() {
|
||||||
1
|
Err(1.into())
|
||||||
} else {
|
} else {
|
||||||
0
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn print_expr_error(expr_error: &str) -> ! {
|
|
||||||
crash!(2, "{}", expr_error)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn evaluate_ast(maybe_ast: Result<Box<syntax_tree::AstNode>, String>) -> Result<String, String> {
|
fn evaluate_ast(maybe_ast: Result<Box<syntax_tree::AstNode>, String>) -> Result<String, String> {
|
||||||
maybe_ast.and_then(|ast| ast.evaluate())
|
maybe_ast.and_then(|ast| ast.evaluate())
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,9 +5,6 @@
|
||||||
// * For the full copyright and license information, please view the LICENSE
|
// * For the full copyright and license information, please view the LICENSE
|
||||||
// * file that was distributed with this source code.
|
// * file that was distributed with this source code.
|
||||||
|
|
||||||
#[macro_use]
|
|
||||||
extern crate uucore;
|
|
||||||
|
|
||||||
use clap::App;
|
use clap::App;
|
||||||
use uucore::error::UResult;
|
use uucore::error::UResult;
|
||||||
|
|
||||||
|
|
|
@ -15,7 +15,7 @@ edition = "2018"
|
||||||
path = "src/hashsum.rs"
|
path = "src/hashsum.rs"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
digest = "0.6.2"
|
digest = "0.6.1"
|
||||||
clap = { version = "2.33", features = ["wrap_help"] }
|
clap = { version = "2.33", features = ["wrap_help"] }
|
||||||
hex = "0.2.0"
|
hex = "0.2.0"
|
||||||
libc = "0.2.42"
|
libc = "0.2.42"
|
||||||
|
|
|
@ -7,9 +7,6 @@
|
||||||
|
|
||||||
// spell-checker:ignore (ToDO) gethostid
|
// spell-checker:ignore (ToDO) gethostid
|
||||||
|
|
||||||
#[macro_use]
|
|
||||||
extern crate uucore;
|
|
||||||
|
|
||||||
use clap::{crate_version, App};
|
use clap::{crate_version, App};
|
||||||
use libc::c_long;
|
use libc::c_long;
|
||||||
use uucore::error::UResult;
|
use uucore::error::UResult;
|
||||||
|
|
|
@ -7,9 +7,6 @@
|
||||||
|
|
||||||
// spell-checker:ignore (ToDO) MAKEWORD addrs hashset
|
// spell-checker:ignore (ToDO) MAKEWORD addrs hashset
|
||||||
|
|
||||||
#[macro_use]
|
|
||||||
extern crate uucore;
|
|
||||||
|
|
||||||
use std::collections::hash_set::HashSet;
|
use std::collections::hash_set::HashSet;
|
||||||
use std::net::ToSocketAddrs;
|
use std::net::ToSocketAddrs;
|
||||||
use std::str;
|
use std::str;
|
||||||
|
|
|
@ -158,18 +158,13 @@ fn print_signal(signal_name_or_value: &str) -> UResult<()> {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn print_signals() {
|
fn print_signals() {
|
||||||
let mut pos = 0;
|
|
||||||
for (idx, signal) in ALL_SIGNALS.iter().enumerate() {
|
for (idx, signal) in ALL_SIGNALS.iter().enumerate() {
|
||||||
pos += signal.len();
|
if idx > 0 {
|
||||||
print!("{}", signal);
|
|
||||||
if idx > 0 && pos > 73 {
|
|
||||||
println!();
|
|
||||||
pos = 0;
|
|
||||||
} else {
|
|
||||||
pos += 1;
|
|
||||||
print!(" ");
|
print!(" ");
|
||||||
}
|
}
|
||||||
|
print!("{}", signal);
|
||||||
}
|
}
|
||||||
|
println!();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn list(arg: Option<String>) -> UResult<()> {
|
fn list(arg: Option<String>) -> UResult<()> {
|
||||||
|
|
|
@ -27,6 +27,7 @@ uucore = { version = ">=0.0.8", package = "uucore", path = "../../uucore", featu
|
||||||
uucore_procs = { version=">=0.0.6", package = "uucore_procs", path = "../../uucore_procs" }
|
uucore_procs = { version=">=0.0.6", package = "uucore_procs", path = "../../uucore_procs" }
|
||||||
once_cell = "1.7.2"
|
once_cell = "1.7.2"
|
||||||
atty = "0.2"
|
atty = "0.2"
|
||||||
|
selinux = { version="0.2.1", optional = true }
|
||||||
|
|
||||||
[target.'cfg(unix)'.dependencies]
|
[target.'cfg(unix)'.dependencies]
|
||||||
lazy_static = "1.4.0"
|
lazy_static = "1.4.0"
|
||||||
|
@ -35,6 +36,9 @@ lazy_static = "1.4.0"
|
||||||
name = "ls"
|
name = "ls"
|
||||||
path = "src/main.rs"
|
path = "src/main.rs"
|
||||||
|
|
||||||
|
[features]
|
||||||
|
feat_selinux = ["selinux"]
|
||||||
|
|
||||||
[package.metadata.cargo-udeps.ignore]
|
[package.metadata.cargo-udeps.ignore]
|
||||||
# Necessary for "make all"
|
# Necessary for "make all"
|
||||||
normal = ["uucore_procs"]
|
normal = ["uucore_procs"]
|
||||||
|
|
|
@ -50,6 +50,11 @@ use unicode_width::UnicodeWidthStr;
|
||||||
use uucore::libc::{S_IXGRP, S_IXOTH, S_IXUSR};
|
use uucore::libc::{S_IXGRP, S_IXOTH, S_IXUSR};
|
||||||
use uucore::{fs::display_permissions, version_cmp::version_cmp};
|
use uucore::{fs::display_permissions, version_cmp::version_cmp};
|
||||||
|
|
||||||
|
#[cfg(not(feature = "selinux"))]
|
||||||
|
static CONTEXT_HELP_TEXT: &str = "print any security context of each file (not enabled)";
|
||||||
|
#[cfg(feature = "selinux")]
|
||||||
|
static CONTEXT_HELP_TEXT: &str = "print any security context of each file";
|
||||||
|
|
||||||
fn usage() -> String {
|
fn usage() -> String {
|
||||||
format!("{0} [OPTION]... [FILE]...", uucore::execution_phrase())
|
format!("{0} [OPTION]... [FILE]...", uucore::execution_phrase())
|
||||||
}
|
}
|
||||||
|
@ -129,6 +134,7 @@ pub mod options {
|
||||||
pub static FULL_TIME: &str = "full-time";
|
pub static FULL_TIME: &str = "full-time";
|
||||||
pub static HIDE: &str = "hide";
|
pub static HIDE: &str = "hide";
|
||||||
pub static IGNORE: &str = "ignore";
|
pub static IGNORE: &str = "ignore";
|
||||||
|
pub static CONTEXT: &str = "context";
|
||||||
}
|
}
|
||||||
|
|
||||||
const DEFAULT_TERM_WIDTH: u16 = 80;
|
const DEFAULT_TERM_WIDTH: u16 = 80;
|
||||||
|
@ -239,6 +245,8 @@ struct Config {
|
||||||
quoting_style: QuotingStyle,
|
quoting_style: QuotingStyle,
|
||||||
indicator_style: IndicatorStyle,
|
indicator_style: IndicatorStyle,
|
||||||
time_style: TimeStyle,
|
time_style: TimeStyle,
|
||||||
|
context: bool,
|
||||||
|
selinux_supported: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fields that can be removed or added to the long format
|
// Fields that can be removed or added to the long format
|
||||||
|
@ -250,9 +258,18 @@ struct LongFormat {
|
||||||
numeric_uid_gid: bool,
|
numeric_uid_gid: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct PaddingCollection {
|
||||||
|
longest_link_count_len: usize,
|
||||||
|
longest_uname_len: usize,
|
||||||
|
longest_group_len: usize,
|
||||||
|
longest_context_len: usize,
|
||||||
|
longest_size_len: usize,
|
||||||
|
}
|
||||||
|
|
||||||
impl Config {
|
impl Config {
|
||||||
#[allow(clippy::cognitive_complexity)]
|
#[allow(clippy::cognitive_complexity)]
|
||||||
fn from(options: &clap::ArgMatches) -> UResult<Config> {
|
fn from(options: &clap::ArgMatches) -> UResult<Config> {
|
||||||
|
let context = options.is_present(options::CONTEXT);
|
||||||
let (mut format, opt) = if let Some(format_) = options.value_of(options::FORMAT) {
|
let (mut format, opt) = if let Some(format_) = options.value_of(options::FORMAT) {
|
||||||
(
|
(
|
||||||
match format_ {
|
match format_ {
|
||||||
|
@ -596,6 +613,17 @@ impl Config {
|
||||||
quoting_style,
|
quoting_style,
|
||||||
indicator_style,
|
indicator_style,
|
||||||
time_style,
|
time_style,
|
||||||
|
context,
|
||||||
|
selinux_supported: {
|
||||||
|
#[cfg(feature = "selinux")]
|
||||||
|
{
|
||||||
|
selinux::kernel_support() != selinux::KernelSupport::Unsupported
|
||||||
|
}
|
||||||
|
#[cfg(not(feature = "selinux"))]
|
||||||
|
{
|
||||||
|
false
|
||||||
|
}
|
||||||
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1157,6 +1185,12 @@ only ignore '.' and '..'.",
|
||||||
.overrides_with(options::FULL_TIME)
|
.overrides_with(options::FULL_TIME)
|
||||||
.help("like -l --time-style=full-iso"),
|
.help("like -l --time-style=full-iso"),
|
||||||
)
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::with_name(options::CONTEXT)
|
||||||
|
.short("Z")
|
||||||
|
.long(options::CONTEXT)
|
||||||
|
.help(CONTEXT_HELP_TEXT),
|
||||||
|
)
|
||||||
// Positional arguments
|
// Positional arguments
|
||||||
.arg(
|
.arg(
|
||||||
Arg::with_name(options::PATHS)
|
Arg::with_name(options::PATHS)
|
||||||
|
@ -1181,6 +1215,7 @@ struct PathData {
|
||||||
// PathBuf that all above data corresponds to
|
// PathBuf that all above data corresponds to
|
||||||
p_buf: PathBuf,
|
p_buf: PathBuf,
|
||||||
must_dereference: bool,
|
must_dereference: bool,
|
||||||
|
security_context: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PathData {
|
impl PathData {
|
||||||
|
@ -1224,12 +1259,19 @@ impl PathData {
|
||||||
None => OnceCell::new(),
|
None => OnceCell::new(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let security_context = if config.context {
|
||||||
|
get_security_context(config, &p_buf, must_dereference)
|
||||||
|
} else {
|
||||||
|
String::new()
|
||||||
|
};
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
md: OnceCell::new(),
|
md: OnceCell::new(),
|
||||||
ft,
|
ft,
|
||||||
display_name,
|
display_name,
|
||||||
p_buf,
|
p_buf,
|
||||||
must_dereference,
|
must_dereference,
|
||||||
|
security_context,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1398,7 +1440,7 @@ fn get_metadata(entry: &Path, dereference: bool) -> std::io::Result<Metadata> {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn display_dir_entry_size(entry: &PathData, config: &Config) -> (usize, usize, usize, usize) {
|
fn display_dir_entry_size(entry: &PathData, config: &Config) -> (usize, usize, usize, usize) {
|
||||||
// TODO: Cache/memoize the display_* results so we don't have to recalculate them.
|
// TODO: Cache/memorize the display_* results so we don't have to recalculate them.
|
||||||
if let Some(md) = entry.md() {
|
if let Some(md) = entry.md() {
|
||||||
(
|
(
|
||||||
display_symlink_count(md).len(),
|
display_symlink_count(md).len(),
|
||||||
|
@ -1411,31 +1453,40 @@ fn display_dir_entry_size(entry: &PathData, config: &Config) -> (usize, usize, u
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn pad_left(string: String, count: usize) -> String {
|
fn pad_left(string: &str, count: usize) -> String {
|
||||||
format!("{:>width$}", string, width = count)
|
format!("{:>width$}", string, width = count)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn pad_right(string: String, count: usize) -> String {
|
fn pad_right(string: &str, count: usize) -> String {
|
||||||
format!("{:<width$}", string, width = count)
|
format!("{:<width$}", string, width = count)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn display_items(items: &[PathData], config: &Config, out: &mut BufWriter<Stdout>) {
|
fn display_items(items: &[PathData], config: &Config, out: &mut BufWriter<Stdout>) {
|
||||||
|
// `-Z`, `--context`:
|
||||||
|
// Display the SELinux security context or '?' if none is found. When used with the `-l`
|
||||||
|
// option, print the security context to the left of the size column.
|
||||||
|
|
||||||
if config.format == Format::Long {
|
if config.format == Format::Long {
|
||||||
let (
|
let (
|
||||||
mut longest_link_count_len,
|
mut longest_link_count_len,
|
||||||
mut longest_uname_len,
|
mut longest_uname_len,
|
||||||
mut longest_group_len,
|
mut longest_group_len,
|
||||||
|
mut longest_context_len,
|
||||||
mut longest_size_len,
|
mut longest_size_len,
|
||||||
) = (1, 1, 1, 1);
|
) = (1, 1, 1, 1, 1);
|
||||||
let mut total_size = 0;
|
let mut total_size = 0;
|
||||||
|
|
||||||
for item in items {
|
for item in items {
|
||||||
|
let context_len = item.security_context.len();
|
||||||
let (link_count_len, uname_len, group_len, size_len) =
|
let (link_count_len, uname_len, group_len, size_len) =
|
||||||
display_dir_entry_size(item, config);
|
display_dir_entry_size(item, config);
|
||||||
longest_link_count_len = link_count_len.max(longest_link_count_len);
|
longest_link_count_len = link_count_len.max(longest_link_count_len);
|
||||||
longest_size_len = size_len.max(longest_size_len);
|
longest_size_len = size_len.max(longest_size_len);
|
||||||
longest_uname_len = uname_len.max(longest_uname_len);
|
longest_uname_len = uname_len.max(longest_uname_len);
|
||||||
longest_group_len = group_len.max(longest_group_len);
|
longest_group_len = group_len.max(longest_group_len);
|
||||||
|
if config.context {
|
||||||
|
longest_context_len = context_len.max(longest_context_len);
|
||||||
|
}
|
||||||
longest_size_len = size_len.max(longest_size_len);
|
longest_size_len = size_len.max(longest_size_len);
|
||||||
total_size += item.md().map_or(0, |md| get_block_size(md, config));
|
total_size += item.md().map_or(0, |md| get_block_size(md, config));
|
||||||
}
|
}
|
||||||
|
@ -1447,16 +1498,31 @@ fn display_items(items: &[PathData], config: &Config, out: &mut BufWriter<Stdout
|
||||||
for item in items {
|
for item in items {
|
||||||
display_item_long(
|
display_item_long(
|
||||||
item,
|
item,
|
||||||
|
PaddingCollection {
|
||||||
longest_link_count_len,
|
longest_link_count_len,
|
||||||
longest_uname_len,
|
longest_uname_len,
|
||||||
longest_group_len,
|
longest_group_len,
|
||||||
|
longest_context_len,
|
||||||
longest_size_len,
|
longest_size_len,
|
||||||
|
},
|
||||||
config,
|
config,
|
||||||
out,
|
out,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let names = items.iter().filter_map(|i| display_file_name(i, config));
|
let mut longest_context_len = 1;
|
||||||
|
let prefix_context = if config.context {
|
||||||
|
for item in items {
|
||||||
|
let context_len = item.security_context.len();
|
||||||
|
longest_context_len = context_len.max(longest_context_len);
|
||||||
|
}
|
||||||
|
Some(longest_context_len)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
let names = items
|
||||||
|
.iter()
|
||||||
|
.filter_map(|i| display_file_name(i, config, prefix_context));
|
||||||
|
|
||||||
match config.format {
|
match config.format {
|
||||||
Format::Columns => display_grid(names, config.width, Direction::TopToBottom, out),
|
Format::Columns => display_grid(names, config.width, Direction::TopToBottom, out),
|
||||||
|
@ -1581,15 +1647,13 @@ fn display_grid(
|
||||||
/// longest_link_count_len: usize,
|
/// longest_link_count_len: usize,
|
||||||
/// longest_uname_len: usize,
|
/// longest_uname_len: usize,
|
||||||
/// longest_group_len: usize,
|
/// longest_group_len: usize,
|
||||||
|
/// longest_context_len: usize,
|
||||||
/// longest_size_len: usize,
|
/// longest_size_len: usize,
|
||||||
/// ```
|
/// ```
|
||||||
/// that decide the maximum possible character count of each field.
|
/// that decide the maximum possible character count of each field.
|
||||||
fn display_item_long(
|
fn display_item_long(
|
||||||
item: &PathData,
|
item: &PathData,
|
||||||
longest_link_count_len: usize,
|
padding: PaddingCollection,
|
||||||
longest_uname_len: usize,
|
|
||||||
longest_group_len: usize,
|
|
||||||
longest_size_len: usize,
|
|
||||||
config: &Config,
|
config: &Config,
|
||||||
out: &mut BufWriter<Stdout>,
|
out: &mut BufWriter<Stdout>,
|
||||||
) {
|
) {
|
||||||
|
@ -1610,16 +1674,23 @@ fn display_item_long(
|
||||||
|
|
||||||
let _ = write!(
|
let _ = write!(
|
||||||
out,
|
out,
|
||||||
"{} {}",
|
"{}{} {}",
|
||||||
display_permissions(md, true),
|
display_permissions(md, true),
|
||||||
pad_left(display_symlink_count(md), longest_link_count_len),
|
if item.security_context.len() > 1 {
|
||||||
|
// GNU `ls` uses a "." character to indicate a file with a security context,
|
||||||
|
// but not other alternate access method.
|
||||||
|
"."
|
||||||
|
} else {
|
||||||
|
""
|
||||||
|
},
|
||||||
|
pad_left(&display_symlink_count(md), padding.longest_link_count_len),
|
||||||
);
|
);
|
||||||
|
|
||||||
if config.long.owner {
|
if config.long.owner {
|
||||||
let _ = write!(
|
let _ = write!(
|
||||||
out,
|
out,
|
||||||
" {}",
|
" {}",
|
||||||
pad_right(display_uname(md, config), longest_uname_len)
|
pad_right(&display_uname(md, config), padding.longest_uname_len)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1627,7 +1698,15 @@ fn display_item_long(
|
||||||
let _ = write!(
|
let _ = write!(
|
||||||
out,
|
out,
|
||||||
" {}",
|
" {}",
|
||||||
pad_right(display_group(md, config), longest_group_len)
|
pad_right(&display_group(md, config), padding.longest_group_len)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if config.context {
|
||||||
|
let _ = write!(
|
||||||
|
out,
|
||||||
|
" {}",
|
||||||
|
pad_right(&item.security_context, padding.longest_context_len)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1637,19 +1716,19 @@ fn display_item_long(
|
||||||
let _ = write!(
|
let _ = write!(
|
||||||
out,
|
out,
|
||||||
" {}",
|
" {}",
|
||||||
pad_right(display_uname(md, config), longest_uname_len)
|
pad_right(&display_uname(md, config), padding.longest_uname_len)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let _ = writeln!(
|
let _ = writeln!(
|
||||||
out,
|
out,
|
||||||
" {} {} {}",
|
" {} {} {}",
|
||||||
pad_left(display_size_or_rdev(md, config), longest_size_len),
|
pad_left(&display_size_or_rdev(md, config), padding.longest_size_len),
|
||||||
display_date(md, config),
|
display_date(md, config),
|
||||||
// unwrap is fine because it fails when metadata is not available
|
// unwrap is fine because it fails when metadata is not available
|
||||||
// but we already know that it is because it's checked at the
|
// but we already know that it is because it's checked at the
|
||||||
// start of the function.
|
// start of the function.
|
||||||
display_file_name(item, config).unwrap().contents,
|
display_file_name(item, config, None).unwrap().contents,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1873,21 +1952,22 @@ fn classify_file(path: &PathData) -> Option<char> {
|
||||||
/// * `config.indicator_style` to append specific characters to `name` using [`classify_file`].
|
/// * `config.indicator_style` to append specific characters to `name` using [`classify_file`].
|
||||||
/// * `config.format` to display symlink targets if `Format::Long`. This function is also
|
/// * `config.format` to display symlink targets if `Format::Long`. This function is also
|
||||||
/// responsible for coloring symlink target names if `config.color` is specified.
|
/// responsible for coloring symlink target names if `config.color` is specified.
|
||||||
|
/// * `config.context` to prepend security context to `name` if compiled with `feat_selinux`.
|
||||||
///
|
///
|
||||||
/// Note that non-unicode sequences in symlink targets are dealt with using
|
/// Note that non-unicode sequences in symlink targets are dealt with using
|
||||||
/// [`std::path::Path::to_string_lossy`].
|
/// [`std::path::Path::to_string_lossy`].
|
||||||
fn display_file_name(path: &PathData, config: &Config) -> Option<Cell> {
|
fn display_file_name(
|
||||||
|
path: &PathData,
|
||||||
|
config: &Config,
|
||||||
|
prefix_context: Option<usize>,
|
||||||
|
) -> Option<Cell> {
|
||||||
// This is our return value. We start by `&path.display_name` and modify it along the way.
|
// This is our return value. We start by `&path.display_name` and modify it along the way.
|
||||||
let mut name = escape_name(&path.display_name, &config.quoting_style);
|
let mut name = escape_name(&path.display_name, &config.quoting_style);
|
||||||
|
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
{
|
{
|
||||||
if config.format != Format::Long && config.inode {
|
if config.format != Format::Long && config.inode {
|
||||||
name = path
|
name = path.md().map_or_else(|| "?".to_string(), get_inode) + " " + &name;
|
||||||
.md()
|
|
||||||
.map_or_else(|| "?".to_string(), |md| get_inode(md))
|
|
||||||
+ " "
|
|
||||||
+ &name;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1968,6 +2048,20 @@ fn display_file_name(path: &PathData, config: &Config) -> Option<Cell> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Prepend the security context to the `name` and adjust `width` in order
|
||||||
|
// to get correct alignment from later calls to`display_grid()`.
|
||||||
|
if config.context {
|
||||||
|
if let Some(pad_count) = prefix_context {
|
||||||
|
let security_context = if !matches!(config.format, Format::Commas) {
|
||||||
|
pad_left(&path.security_context, pad_count)
|
||||||
|
} else {
|
||||||
|
path.security_context.to_owned()
|
||||||
|
};
|
||||||
|
name = format!("{} {}", security_context, name);
|
||||||
|
width += security_context.len() + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Some(Cell {
|
Some(Cell {
|
||||||
contents: name,
|
contents: name,
|
||||||
width,
|
width,
|
||||||
|
@ -1992,3 +2086,44 @@ fn display_symlink_count(_metadata: &Metadata) -> String {
|
||||||
fn display_symlink_count(metadata: &Metadata) -> String {
|
fn display_symlink_count(metadata: &Metadata) -> String {
|
||||||
metadata.nlink().to_string()
|
metadata.nlink().to_string()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This returns the SELinux security context as UTF8 `String`.
|
||||||
|
// In the long term this should be changed to `OsStr`, see discussions at #2621/#2656
|
||||||
|
#[allow(unused_variables)]
|
||||||
|
fn get_security_context(config: &Config, p_buf: &Path, must_dereference: bool) -> String {
|
||||||
|
let substitute_string = "?".to_string();
|
||||||
|
if config.selinux_supported {
|
||||||
|
#[cfg(feature = "selinux")]
|
||||||
|
{
|
||||||
|
match selinux::SecurityContext::of_path(p_buf, must_dereference, false) {
|
||||||
|
Err(_r) => {
|
||||||
|
// TODO: show the actual reason why it failed
|
||||||
|
show_warning!("failed to get security context of: {}", p_buf.quote());
|
||||||
|
substitute_string
|
||||||
|
}
|
||||||
|
Ok(None) => substitute_string,
|
||||||
|
Ok(Some(context)) => {
|
||||||
|
let mut context = context.as_bytes();
|
||||||
|
if context.ends_with(&[0]) {
|
||||||
|
// TODO: replace with `strip_prefix()` when MSRV >= 1.51
|
||||||
|
context = &context[..context.len() - 1]
|
||||||
|
};
|
||||||
|
String::from_utf8(context.to_vec()).unwrap_or_else(|e| {
|
||||||
|
show_warning!(
|
||||||
|
"getting security context of: {}: {}",
|
||||||
|
p_buf.quote(),
|
||||||
|
e.to_string()
|
||||||
|
);
|
||||||
|
String::from_utf8_lossy(context).into_owned()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[cfg(not(feature = "selinux"))]
|
||||||
|
{
|
||||||
|
substitute_string
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
substitute_string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -8,9 +8,6 @@
|
||||||
|
|
||||||
// spell-checker:ignore (paths) GPGHome
|
// spell-checker:ignore (paths) GPGHome
|
||||||
|
|
||||||
#[macro_use]
|
|
||||||
extern crate uucore;
|
|
||||||
|
|
||||||
use clap::{crate_version, App, Arg};
|
use clap::{crate_version, App, Arg};
|
||||||
use uucore::display::{println_verbatim, Quotable};
|
use uucore::display::{println_verbatim, Quotable};
|
||||||
use uucore::error::{FromIo, UError, UResult};
|
use uucore::error::{FromIo, UError, UResult};
|
||||||
|
|
|
@ -210,7 +210,7 @@ fn reset_term(stdout: &mut std::io::Stdout) {
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn reset_term(_: &mut usize) {}
|
fn reset_term(_: &mut usize) {}
|
||||||
|
|
||||||
fn more(buff: &str, mut stdout: &mut Stdout, next_file: Option<&str>, silent: bool) {
|
fn more(buff: &str, stdout: &mut Stdout, next_file: Option<&str>, silent: bool) {
|
||||||
let (cols, rows) = terminal::size().unwrap();
|
let (cols, rows) = terminal::size().unwrap();
|
||||||
let lines = break_buff(buff, usize::from(cols));
|
let lines = break_buff(buff, usize::from(cols));
|
||||||
|
|
||||||
|
@ -232,7 +232,7 @@ fn more(buff: &str, mut stdout: &mut Stdout, next_file: Option<&str>, silent: bo
|
||||||
code: KeyCode::Char('c'),
|
code: KeyCode::Char('c'),
|
||||||
modifiers: KeyModifiers::CONTROL,
|
modifiers: KeyModifiers::CONTROL,
|
||||||
}) => {
|
}) => {
|
||||||
reset_term(&mut stdout);
|
reset_term(stdout);
|
||||||
std::process::exit(0);
|
std::process::exit(0);
|
||||||
}
|
}
|
||||||
Event::Key(KeyEvent {
|
Event::Key(KeyEvent {
|
||||||
|
|
|
@ -145,13 +145,12 @@ impl OutputInfo {
|
||||||
byte_size_block: usize,
|
byte_size_block: usize,
|
||||||
print_width_block: usize,
|
print_width_block: usize,
|
||||||
) -> [usize; MAX_BYTES_PER_UNIT] {
|
) -> [usize; MAX_BYTES_PER_UNIT] {
|
||||||
if byte_size_block > MAX_BYTES_PER_UNIT {
|
assert!(
|
||||||
panic!(
|
byte_size_block <= MAX_BYTES_PER_UNIT,
|
||||||
"{}-bits types are unsupported. Current max={}-bits.",
|
"{}-bits types are unsupported. Current max={}-bits.",
|
||||||
8 * byte_size_block,
|
8 * byte_size_block,
|
||||||
8 * MAX_BYTES_PER_UNIT
|
8 * MAX_BYTES_PER_UNIT
|
||||||
);
|
);
|
||||||
}
|
|
||||||
let mut spacing = [0; MAX_BYTES_PER_UNIT];
|
let mut spacing = [0; MAX_BYTES_PER_UNIT];
|
||||||
|
|
||||||
let mut byte_size = sf.byte_size();
|
let mut byte_size = sf.byte_size();
|
||||||
|
|
|
@ -5,9 +5,6 @@
|
||||||
// * For the full copyright and license information, please view the LICENSE
|
// * For the full copyright and license information, please view the LICENSE
|
||||||
// * file that was distributed with this source code.
|
// * file that was distributed with this source code.
|
||||||
|
|
||||||
#[macro_use]
|
|
||||||
extern crate uucore;
|
|
||||||
|
|
||||||
use clap::{crate_version, App, Arg};
|
use clap::{crate_version, App, Arg};
|
||||||
use std::env;
|
use std::env;
|
||||||
use std::io;
|
use std::io;
|
||||||
|
|
|
@ -88,13 +88,47 @@ impl FromStr for Number {
|
||||||
if s.starts_with('+') {
|
if s.starts_with('+') {
|
||||||
s = &s[1..];
|
s = &s[1..];
|
||||||
}
|
}
|
||||||
|
let is_neg = s.starts_with('-');
|
||||||
|
|
||||||
match s.parse::<BigInt>() {
|
match s.to_lowercase().find("0x") {
|
||||||
|
Some(i) if i <= 1 => match &s.as_bytes()[i + 2] {
|
||||||
|
b'-' | b'+' => Err(format!(
|
||||||
|
"invalid hexadecimal argument: {}\nTry '{} --help' for more information.",
|
||||||
|
s.quote(),
|
||||||
|
uucore::execution_phrase(),
|
||||||
|
)),
|
||||||
|
// TODO: hexadecimal floating point parsing (see #2660)
|
||||||
|
b'.' => Err(format!(
|
||||||
|
"NotImplemented: hexadecimal floating point numbers: {}\nTry '{} --help' for more information.",
|
||||||
|
s.quote(),
|
||||||
|
uucore::execution_phrase(),
|
||||||
|
)),
|
||||||
|
_ => {
|
||||||
|
let num = BigInt::from_str_radix(&s[i + 2..], 16)
|
||||||
|
.map_err(|_| format!(
|
||||||
|
"invalid hexadecimal argument: {}\nTry '{} --help' for more information.",
|
||||||
|
s.quote(),
|
||||||
|
uucore::execution_phrase(),
|
||||||
|
))?;
|
||||||
|
match (is_neg, num == BigInt::zero()) {
|
||||||
|
(true, true) => Ok(Number::MinusZero),
|
||||||
|
(true, false) => Ok(Number::BigInt(-num)),
|
||||||
|
(false, _) => Ok(Number::BigInt(num)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Some(_) => Err(format!(
|
||||||
|
"invalid hexadecimal argument: {}\nTry '{} --help' for more information.",
|
||||||
|
s.quote(),
|
||||||
|
uucore::execution_phrase(),
|
||||||
|
)),
|
||||||
|
|
||||||
|
None => match s.parse::<BigInt>() {
|
||||||
Ok(n) => {
|
Ok(n) => {
|
||||||
// If `s` is '-0', then `parse()` returns
|
// If `s` is '-0', then `parse()` returns
|
||||||
// `BigInt::zero()`, but we need to return
|
// `BigInt::zero()`, but we need to return
|
||||||
// `Number::MinusZero` instead.
|
// `Number::MinusZero` instead.
|
||||||
if n == BigInt::zero() && s.starts_with('-') {
|
if n == BigInt::zero() && is_neg {
|
||||||
Ok(Number::MinusZero)
|
Ok(Number::MinusZero)
|
||||||
} else {
|
} else {
|
||||||
Ok(Number::BigInt(n))
|
Ok(Number::BigInt(n))
|
||||||
|
@ -113,6 +147,7 @@ impl FromStr for Number {
|
||||||
uucore::execution_phrase(),
|
uucore::execution_phrase(),
|
||||||
)),
|
)),
|
||||||
},
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,9 +5,6 @@
|
||||||
// * For the full copyright and license information, please view the LICENSE
|
// * For the full copyright and license information, please view the LICENSE
|
||||||
// * file that was distributed with this source code.
|
// * file that was distributed with this source code.
|
||||||
|
|
||||||
#[macro_use]
|
|
||||||
extern crate uucore;
|
|
||||||
|
|
||||||
use std::thread;
|
use std::thread;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
|
|
|
@ -825,7 +825,7 @@ impl FieldSelector {
|
||||||
fn parse(key: &str, global_settings: &GlobalSettings) -> UResult<Self> {
|
fn parse(key: &str, global_settings: &GlobalSettings) -> UResult<Self> {
|
||||||
let mut from_to = key.split(',');
|
let mut from_to = key.split(',');
|
||||||
let (from, from_options) = Self::split_key_options(from_to.next().unwrap());
|
let (from, from_options) = Self::split_key_options(from_to.next().unwrap());
|
||||||
let to = from_to.next().map(|to| Self::split_key_options(to));
|
let to = from_to.next().map(Self::split_key_options);
|
||||||
let options_are_empty = from_options.is_empty() && matches!(to, None | Some((_, "")));
|
let options_are_empty = from_options.is_empty() && matches!(to, None | Some((_, "")));
|
||||||
|
|
||||||
if options_are_empty {
|
if options_are_empty {
|
||||||
|
|
|
@ -5,9 +5,6 @@
|
||||||
// * For the full copyright and license information, please view the LICENSE
|
// * For the full copyright and license information, please view the LICENSE
|
||||||
// * file that was distributed with this source code.
|
// * file that was distributed with this source code.
|
||||||
|
|
||||||
#[macro_use]
|
|
||||||
extern crate uucore;
|
|
||||||
|
|
||||||
use clap::App;
|
use clap::App;
|
||||||
use uucore::error::UResult;
|
use uucore::error::UResult;
|
||||||
|
|
||||||
|
|
|
@ -7,9 +7,6 @@
|
||||||
|
|
||||||
/* last synced with: unlink (GNU coreutils) 8.21 */
|
/* last synced with: unlink (GNU coreutils) 8.21 */
|
||||||
|
|
||||||
#[macro_use]
|
|
||||||
extern crate uucore;
|
|
||||||
|
|
||||||
use std::fs::remove_file;
|
use std::fs::remove_file;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
|
|
|
@ -11,8 +11,6 @@
|
||||||
use chrono::{Local, TimeZone, Utc};
|
use chrono::{Local, TimeZone, Utc};
|
||||||
use clap::{crate_version, App, Arg};
|
use clap::{crate_version, App, Arg};
|
||||||
|
|
||||||
#[macro_use]
|
|
||||||
extern crate uucore;
|
|
||||||
// import crate time from utmpx
|
// import crate time from utmpx
|
||||||
pub use uucore::libc;
|
pub use uucore::libc;
|
||||||
use uucore::libc::time_t;
|
use uucore::libc::time_t;
|
||||||
|
|
|
@ -11,6 +11,7 @@
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
use clap::{crate_version, App, Arg};
|
use clap::{crate_version, App, Arg};
|
||||||
|
use uucore::error::UResult;
|
||||||
use uucore::utmpx::{self, Utmpx};
|
use uucore::utmpx::{self, Utmpx};
|
||||||
|
|
||||||
static ABOUT: &str = "Print the user names of users currently logged in to the current host";
|
static ABOUT: &str = "Print the user names of users currently logged in to the current host";
|
||||||
|
@ -29,7 +30,8 @@ If FILE is not specified, use {}. /var/log/wtmp as FILE is common.",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn uumain(args: impl uucore::Args) -> i32 {
|
#[uucore_procs::gen_uumain]
|
||||||
|
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
||||||
let usage = usage();
|
let usage = usage();
|
||||||
let after_help = get_long_usage();
|
let after_help = get_long_usage();
|
||||||
|
|
||||||
|
@ -59,7 +61,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
||||||
println!("{}", users.join(" "));
|
println!("{}", users.join(" "));
|
||||||
}
|
}
|
||||||
|
|
||||||
0
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn uu_app() -> App<'static, 'static> {
|
pub fn uu_app() -> App<'static, 'static> {
|
||||||
|
|
|
@ -9,8 +9,6 @@
|
||||||
|
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate clap;
|
extern crate clap;
|
||||||
#[macro_use]
|
|
||||||
extern crate uucore;
|
|
||||||
|
|
||||||
use clap::App;
|
use clap::App;
|
||||||
|
|
||||||
|
|
|
@ -12,8 +12,6 @@ use std::io::{self, Write};
|
||||||
|
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate clap;
|
extern crate clap;
|
||||||
#[macro_use]
|
|
||||||
extern crate uucore;
|
|
||||||
|
|
||||||
use clap::{App, Arg};
|
use clap::{App, Arg};
|
||||||
use uucore::error::{UResult, USimpleError};
|
use uucore::error::{UResult, USimpleError};
|
||||||
|
|
|
@ -10,7 +10,7 @@ pub mod fsext;
|
||||||
pub mod ringbuffer;
|
pub mod ringbuffer;
|
||||||
|
|
||||||
// * (platform-specific) feature-gated modules
|
// * (platform-specific) feature-gated modules
|
||||||
// ** non-windows
|
// ** non-windows (i.e. Unix + Fuchsia)
|
||||||
#[cfg(all(not(windows), feature = "mode"))]
|
#[cfg(all(not(windows), feature = "mode"))]
|
||||||
pub mod mode;
|
pub mod mode;
|
||||||
|
|
||||||
|
|
|
@ -26,16 +26,12 @@ const MAX_PATH: usize = 266;
|
||||||
static EXIT_ERR: i32 = 1;
|
static EXIT_ERR: i32 = 1;
|
||||||
|
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
use std::ffi::OsString;
|
use std::ffi::OsStr;
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
use std::os::windows::ffi::OsStrExt;
|
use std::os::windows::ffi::OsStrExt;
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
use std::os::windows::ffi::OsStringExt;
|
|
||||||
#[cfg(windows)]
|
|
||||||
use winapi::shared::minwindef::DWORD;
|
use winapi::shared::minwindef::DWORD;
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
use winapi::um::errhandlingapi::GetLastError;
|
|
||||||
#[cfg(windows)]
|
|
||||||
use winapi::um::fileapi::GetDiskFreeSpaceW;
|
use winapi::um::fileapi::GetDiskFreeSpaceW;
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
use winapi::um::fileapi::{
|
use winapi::um::fileapi::{
|
||||||
|
@ -47,11 +43,12 @@ use winapi::um::handleapi::INVALID_HANDLE_VALUE;
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
use winapi::um::winbase::DRIVE_REMOTE;
|
use winapi::um::winbase::DRIVE_REMOTE;
|
||||||
|
|
||||||
|
// Warning: the pointer has to be used *immediately* or the Vec
|
||||||
|
// it points to will be dropped!
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
macro_rules! String2LPWSTR {
|
macro_rules! String2LPWSTR {
|
||||||
($str: expr) => {
|
($str: expr) => {
|
||||||
OsString::from($str.clone())
|
OsStr::new(&$str)
|
||||||
.as_os_str()
|
|
||||||
.encode_wide()
|
.encode_wide()
|
||||||
.chain(Some(0))
|
.chain(Some(0))
|
||||||
.collect::<Vec<u16>>()
|
.collect::<Vec<u16>>()
|
||||||
|
@ -62,10 +59,8 @@ macro_rules! String2LPWSTR {
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
fn LPWSTR2String(buf: &[u16]) -> String {
|
fn LPWSTR2String(buf: &[u16]) -> String {
|
||||||
let len = unsafe { libc::wcslen(buf.as_ptr()) };
|
let len = buf.iter().position(|&n| n == 0).unwrap();
|
||||||
OsString::from_wide(&buf[..len as usize])
|
String::from_utf16(&buf[..len]).unwrap()
|
||||||
.into_string()
|
|
||||||
.unwrap()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
use self::time::Timespec;
|
use self::time::Timespec;
|
||||||
|
@ -77,7 +72,6 @@ use std::borrow::Cow;
|
||||||
use std::convert::{AsRef, From};
|
use std::convert::{AsRef, From};
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
use std::ffi::CString;
|
use std::ffi::CString;
|
||||||
#[cfg(unix)]
|
|
||||||
use std::io::Error as IOError;
|
use std::io::Error as IOError;
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
use std::mem;
|
use std::mem;
|
||||||
|
@ -157,18 +151,16 @@ impl MountInfo {
|
||||||
fn set_missing_fields(&mut self) {
|
fn set_missing_fields(&mut self) {
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
{
|
{
|
||||||
|
use std::os::unix::fs::MetadataExt;
|
||||||
// We want to keep the dev_id on Windows
|
// We want to keep the dev_id on Windows
|
||||||
// but set dev_id
|
// but set dev_id
|
||||||
let path = CString::new(self.mount_dir.clone()).unwrap();
|
if let Ok(stat) = std::fs::metadata(&self.mount_dir) {
|
||||||
unsafe {
|
// Why do we cast this to i32?
|
||||||
let mut stat = mem::zeroed();
|
self.dev_id = (stat.dev() as i32).to_string()
|
||||||
if libc::stat(path.as_ptr(), &mut stat) == 0 {
|
|
||||||
self.dev_id = (stat.st_dev as i32).to_string();
|
|
||||||
} else {
|
} else {
|
||||||
self.dev_id = "".to_string();
|
self.dev_id = "".to_string();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
// set MountInfo::dummy
|
// set MountInfo::dummy
|
||||||
// spell-checker:disable
|
// spell-checker:disable
|
||||||
match self.fs_type.as_ref() {
|
match self.fs_type.as_ref() {
|
||||||
|
@ -247,8 +239,7 @@ impl MountInfo {
|
||||||
volume_name.pop();
|
volume_name.pop();
|
||||||
unsafe {
|
unsafe {
|
||||||
QueryDosDeviceW(
|
QueryDosDeviceW(
|
||||||
OsString::from(volume_name.clone())
|
OsStr::new(&volume_name)
|
||||||
.as_os_str()
|
|
||||||
.encode_wide()
|
.encode_wide()
|
||||||
.chain(Some(0))
|
.chain(Some(0))
|
||||||
.skip(4)
|
.skip(4)
|
||||||
|
@ -445,9 +436,11 @@ pub fn read_fs_list() -> Vec<MountInfo> {
|
||||||
FindFirstVolumeW(volume_name_buf.as_mut_ptr(), volume_name_buf.len() as DWORD)
|
FindFirstVolumeW(volume_name_buf.as_mut_ptr(), volume_name_buf.len() as DWORD)
|
||||||
};
|
};
|
||||||
if INVALID_HANDLE_VALUE == find_handle {
|
if INVALID_HANDLE_VALUE == find_handle {
|
||||||
crash!(EXIT_ERR, "FindFirstVolumeW failed: {}", unsafe {
|
crash!(
|
||||||
GetLastError()
|
EXIT_ERR,
|
||||||
});
|
"FindFirstVolumeW failed: {}",
|
||||||
|
IOError::last_os_error()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
let mut mounts = Vec::<MountInfo>::new();
|
let mut mounts = Vec::<MountInfo>::new();
|
||||||
loop {
|
loop {
|
||||||
|
@ -466,8 +459,9 @@ pub fn read_fs_list() -> Vec<MountInfo> {
|
||||||
volume_name_buf.len() as DWORD,
|
volume_name_buf.len() as DWORD,
|
||||||
)
|
)
|
||||||
} {
|
} {
|
||||||
let err = unsafe { GetLastError() };
|
let err = IOError::last_os_error();
|
||||||
if err != winapi::shared::winerror::ERROR_NO_MORE_FILES {
|
if err.raw_os_error() != Some(winapi::shared::winerror::ERROR_NO_MORE_FILES as i32)
|
||||||
|
{
|
||||||
crash!(EXIT_ERR, "FindNextVolumeW failed: {}", err);
|
crash!(EXIT_ERR, "FindNextVolumeW failed: {}", err);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
@ -527,7 +521,7 @@ impl FsUsage {
|
||||||
crash!(
|
crash!(
|
||||||
EXIT_ERR,
|
EXIT_ERR,
|
||||||
"GetVolumePathNamesForVolumeNameW failed: {}",
|
"GetVolumePathNamesForVolumeNameW failed: {}",
|
||||||
unsafe { GetLastError() }
|
IOError::last_os_error()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -547,9 +541,11 @@ impl FsUsage {
|
||||||
};
|
};
|
||||||
if 0 == success {
|
if 0 == success {
|
||||||
// Fails in case of CD for example
|
// Fails in case of CD for example
|
||||||
//crash!(EXIT_ERR, "GetDiskFreeSpaceW failed: {}", unsafe {
|
// crash!(
|
||||||
//GetLastError()
|
// EXIT_ERR,
|
||||||
//});
|
// "GetDiskFreeSpaceW failed: {}",
|
||||||
|
// IOError::last_os_error()
|
||||||
|
// );
|
||||||
}
|
}
|
||||||
|
|
||||||
let bytes_per_cluster = sectors_per_cluster as u64 * bytes_per_sector as u64;
|
let bytes_per_cluster = sectors_per_cluster as u64 * bytes_per_sector as u64;
|
||||||
|
|
|
@ -143,6 +143,13 @@ pub fn parse_mode(mode: &str) -> Result<mode_t, String> {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_umask() -> u32 {
|
pub fn get_umask() -> u32 {
|
||||||
|
// There's no portable way to read the umask without changing it.
|
||||||
|
// We have to replace it and then quickly set it back, hopefully before
|
||||||
|
// some other thread is affected.
|
||||||
|
// On modern Linux kernels the current umask could instead be read
|
||||||
|
// from /proc/self/status. But that's a lot of work.
|
||||||
|
// SAFETY: umask always succeeds and doesn't operate on memory. Races are
|
||||||
|
// possible but it can't violate Rust's guarantees.
|
||||||
let mask = unsafe { umask(0) };
|
let mask = unsafe { umask(0) };
|
||||||
unsafe { umask(mask) };
|
unsafe { umask(mask) };
|
||||||
mask as u32
|
mask as u32
|
||||||
|
|
|
@ -19,6 +19,8 @@ use std::process::ExitStatus as StdExitStatus;
|
||||||
use std::thread;
|
use std::thread;
|
||||||
use std::time::{Duration, Instant};
|
use std::time::{Duration, Instant};
|
||||||
|
|
||||||
|
// SAFETY: These functions always succeed and return simple integers.
|
||||||
|
|
||||||
/// `geteuid()` returns the effective user ID of the calling process.
|
/// `geteuid()` returns the effective user ID of the calling process.
|
||||||
pub fn geteuid() -> uid_t {
|
pub fn geteuid() -> uid_t {
|
||||||
unsafe { libc::geteuid() }
|
unsafe { libc::geteuid() }
|
||||||
|
@ -96,6 +98,9 @@ impl fmt::Display for ExitStatus {
|
||||||
/// Missing methods for Child objects
|
/// Missing methods for Child objects
|
||||||
pub trait ChildExt {
|
pub trait ChildExt {
|
||||||
/// Send a signal to a Child process.
|
/// Send a signal to a Child process.
|
||||||
|
///
|
||||||
|
/// Caller beware: if the process already exited then you may accidentally
|
||||||
|
/// send the signal to an unrelated process that recycled the PID.
|
||||||
fn send_signal(&mut self, signal: usize) -> io::Result<()>;
|
fn send_signal(&mut self, signal: usize) -> io::Result<()>;
|
||||||
|
|
||||||
/// Wait for a process to finish or return after the specified duration.
|
/// Wait for a process to finish or return after the specified duration.
|
||||||
|
|
|
@ -41,7 +41,7 @@ pub use crate::features::fsext;
|
||||||
pub use crate::features::ringbuffer;
|
pub use crate::features::ringbuffer;
|
||||||
|
|
||||||
// * (platform-specific) feature-gated modules
|
// * (platform-specific) feature-gated modules
|
||||||
// ** non-windows
|
// ** non-windows (i.e. Unix + Fuchsia)
|
||||||
#[cfg(all(not(windows), feature = "mode"))]
|
#[cfg(all(not(windows), feature = "mode"))]
|
||||||
pub use crate::features::mode;
|
pub use crate::features::mode;
|
||||||
// ** unix-only
|
// ** unix-only
|
||||||
|
|
|
@ -101,7 +101,7 @@ pub fn gen_uumain(_args: TokenStream, stream: TokenStream) -> TokenStream {
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
let s = format!("{}", e);
|
let s = format!("{}", e);
|
||||||
if s != "" {
|
if s != "" {
|
||||||
show_error!("{}", s);
|
uucore::show_error!("{}", s);
|
||||||
}
|
}
|
||||||
if e.usage() {
|
if e.usage() {
|
||||||
eprintln!("Try '{} --help' for more information.", uucore::execution_phrase());
|
eprintln!("Try '{} --help' for more information.", uucore::execution_phrase());
|
||||||
|
|
|
@ -113,12 +113,18 @@ fn test_wrap_bad_arg() {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_base32_extra_operand() {
|
fn test_base32_extra_operand() {
|
||||||
|
let ts = TestScenario::new(util_name!());
|
||||||
|
|
||||||
// Expect a failure when multiple files are specified.
|
// Expect a failure when multiple files are specified.
|
||||||
new_ucmd!()
|
ts.ucmd()
|
||||||
.arg("a.txt")
|
.arg("a.txt")
|
||||||
.arg("b.txt")
|
.arg("b.txt")
|
||||||
.fails()
|
.fails()
|
||||||
.stderr_only("base32: extra operand 'b.txt'\nTry 'base32 --help' for more information.");
|
.stderr_only(format!(
|
||||||
|
"{0}: extra operand 'b.txt'\nTry '{1} {0} --help' for more information.",
|
||||||
|
ts.util_name,
|
||||||
|
ts.bin_path.to_string_lossy()
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
@ -95,12 +95,18 @@ fn test_wrap_bad_arg() {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_base64_extra_operand() {
|
fn test_base64_extra_operand() {
|
||||||
|
let ts = TestScenario::new(util_name!());
|
||||||
|
|
||||||
// Expect a failure when multiple files are specified.
|
// Expect a failure when multiple files are specified.
|
||||||
new_ucmd!()
|
ts.ucmd()
|
||||||
.arg("a.txt")
|
.arg("a.txt")
|
||||||
.arg("b.txt")
|
.arg("b.txt")
|
||||||
.fails()
|
.fails()
|
||||||
.stderr_only("base64: extra operand 'b.txt'\nTry 'base64 --help' for more information.");
|
.stderr_only(format!(
|
||||||
|
"{0}: extra operand 'b.txt'\nTry '{1} {0} --help' for more information.",
|
||||||
|
ts.util_name,
|
||||||
|
ts.bin_path.to_string_lossy()
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
@ -56,6 +56,12 @@ fn test_kill_list_all_signals() {
|
||||||
.stdout_contains("HUP");
|
.stdout_contains("HUP");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_kill_list_final_new_line() {
|
||||||
|
let re = Regex::new("\\n$").unwrap();
|
||||||
|
assert!(re.is_match(new_ucmd!().arg("-l").succeeds().stdout_str()));
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_kill_list_all_signals_as_table() {
|
fn test_kill_list_all_signals_as_table() {
|
||||||
// Check for a few signals. Do not try to be comprehensive.
|
// Check for a few signals. Do not try to be comprehensive.
|
||||||
|
|
|
@ -347,6 +347,7 @@ fn test_ls_long_format() {
|
||||||
// A line of the output should be:
|
// A line of the output should be:
|
||||||
// One of the characters -bcCdDlMnpPsStTx?
|
// One of the characters -bcCdDlMnpPsStTx?
|
||||||
// rwx, with - for missing permissions, thrice.
|
// rwx, with - for missing permissions, thrice.
|
||||||
|
// Zero or one "." for indicating a file with security context
|
||||||
// A number, preceded by column whitespace, and followed by a single space.
|
// A number, preceded by column whitespace, and followed by a single space.
|
||||||
// A username, currently [^ ], followed by column whitespace, twice (or thrice for Hurd).
|
// A username, currently [^ ], followed by column whitespace, twice (or thrice for Hurd).
|
||||||
// A number, followed by a single space.
|
// A number, followed by a single space.
|
||||||
|
@ -356,13 +357,13 @@ fn test_ls_long_format() {
|
||||||
// and followed by a single space.
|
// and followed by a single space.
|
||||||
// Whatever comes after is irrelevant to this specific test.
|
// Whatever comes after is irrelevant to this specific test.
|
||||||
scene.ucmd().arg(arg).arg("test-long-dir").succeeds().stdout_matches(&Regex::new(
|
scene.ucmd().arg(arg).arg("test-long-dir").succeeds().stdout_matches(&Regex::new(
|
||||||
r"\n[-bcCdDlMnpPsStTx?]([r-][w-][xt-]){3} +\d+ [^ ]+ +[^ ]+( +[^ ]+)? +\d+ [A-Z][a-z]{2} {0,2}\d{0,2} {0,2}[0-9:]+ "
|
r"\n[-bcCdDlMnpPsStTx?]([r-][w-][xt-]){3}\.? +\d+ [^ ]+ +[^ ]+( +[^ ]+)? +\d+ [A-Z][a-z]{2} {0,2}\d{0,2} {0,2}[0-9:]+ "
|
||||||
).unwrap());
|
).unwrap());
|
||||||
}
|
}
|
||||||
|
|
||||||
// This checks for the line with the .. entry. The uname and group should be digits.
|
// This checks for the line with the .. entry. The uname and group should be digits.
|
||||||
scene.ucmd().arg("-lan").arg("test-long-dir").succeeds().stdout_matches(&Regex::new(
|
scene.ucmd().arg("-lan").arg("test-long-dir").succeeds().stdout_matches(&Regex::new(
|
||||||
r"\nd([r-][w-][xt-]){3} +\d+ \d+ +\d+( +\d+)? +\d+ [A-Z][a-z]{2} {0,2}\d{0,2} {0,2}[0-9:]+ \.\."
|
r"\nd([r-][w-][xt-]){3}\.? +\d+ \d+ +\d+( +\d+)? +\d+ [A-Z][a-z]{2} {0,2}\d{0,2} {0,2}[0-9:]+ \.\."
|
||||||
).unwrap());
|
).unwrap());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -370,6 +371,7 @@ fn test_ls_long_format() {
|
||||||
/// This test is mainly about coloring, but, the recursion, symlink `->` processing,
|
/// This test is mainly about coloring, but, the recursion, symlink `->` processing,
|
||||||
/// and `.` and `..` being present in `-a` all need to work for the test to pass.
|
/// and `.` and `..` being present in `-a` all need to work for the test to pass.
|
||||||
/// This test does not really test anything provided by `-l` but the file names and symlinks.
|
/// This test does not really test anything provided by `-l` but the file names and symlinks.
|
||||||
|
#[cfg(all(feature = "ln", feature = "mkdir", feature = "touch"))]
|
||||||
#[test]
|
#[test]
|
||||||
#[cfg(all(feature = "ln", feature = "mkdir", feature = "touch"))]
|
#[cfg(all(feature = "ln", feature = "mkdir", feature = "touch"))]
|
||||||
fn test_ls_long_symlink_color() {
|
fn test_ls_long_symlink_color() {
|
||||||
|
@ -636,55 +638,57 @@ fn test_ls_long_formats() {
|
||||||
let at = &scene.fixtures;
|
let at = &scene.fixtures;
|
||||||
at.touch(&at.plus_as_string("test-long-formats"));
|
at.touch(&at.plus_as_string("test-long-formats"));
|
||||||
|
|
||||||
|
// Zero or one "." for indicating a file with security context
|
||||||
|
|
||||||
// Regex for three names, so all of author, group and owner
|
// Regex for three names, so all of author, group and owner
|
||||||
let re_three = Regex::new(r"[xrw-]{9} \d ([-0-9_a-z]+ ){3}0").unwrap();
|
let re_three = Regex::new(r"[xrw-]{9}\.? \d ([-0-9_a-z]+ ){3}0").unwrap();
|
||||||
|
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
let re_three_num = Regex::new(r"[xrw-]{9} \d (\d+ ){3}0").unwrap();
|
let re_three_num = Regex::new(r"[xrw-]{9}\.? \d (\d+ ){3}0").unwrap();
|
||||||
|
|
||||||
// Regex for two names, either:
|
// Regex for two names, either:
|
||||||
// - group and owner
|
// - group and owner
|
||||||
// - author and owner
|
// - author and owner
|
||||||
// - author and group
|
// - author and group
|
||||||
let re_two = Regex::new(r"[xrw-]{9} \d ([-0-9_a-z]+ ){2}0").unwrap();
|
let re_two = Regex::new(r"[xrw-]{9}\.? \d ([-0-9_a-z]+ ){2}0").unwrap();
|
||||||
|
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
let re_two_num = Regex::new(r"[xrw-]{9} \d (\d+ ){2}0").unwrap();
|
let re_two_num = Regex::new(r"[xrw-]{9}\.? \d (\d+ ){2}0").unwrap();
|
||||||
|
|
||||||
// Regex for one name: author, group or owner
|
// Regex for one name: author, group or owner
|
||||||
let re_one = Regex::new(r"[xrw-]{9} \d [-0-9_a-z]+ 0").unwrap();
|
let re_one = Regex::new(r"[xrw-]{9}\.? \d [-0-9_a-z]+ 0").unwrap();
|
||||||
|
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
let re_one_num = Regex::new(r"[xrw-]{9} \d \d+ 0").unwrap();
|
let re_one_num = Regex::new(r"[xrw-]{9}\.? \d \d+ 0").unwrap();
|
||||||
|
|
||||||
// Regex for no names
|
// Regex for no names
|
||||||
let re_zero = Regex::new(r"[xrw-]{9} \d 0").unwrap();
|
let re_zero = Regex::new(r"[xrw-]{9}\.? \d 0").unwrap();
|
||||||
|
|
||||||
let result = scene
|
scene
|
||||||
.ucmd()
|
.ucmd()
|
||||||
.arg("-l")
|
.arg("-l")
|
||||||
.arg("--author")
|
.arg("--author")
|
||||||
.arg("test-long-formats")
|
.arg("test-long-formats")
|
||||||
.succeeds();
|
.succeeds()
|
||||||
assert!(re_three.is_match(result.stdout_str()));
|
.stdout_matches(&re_three);
|
||||||
|
|
||||||
let result = scene
|
scene
|
||||||
.ucmd()
|
.ucmd()
|
||||||
.arg("-l1")
|
.arg("-l1")
|
||||||
.arg("--author")
|
.arg("--author")
|
||||||
.arg("test-long-formats")
|
.arg("test-long-formats")
|
||||||
.succeeds();
|
.succeeds()
|
||||||
assert!(re_three.is_match(result.stdout_str()));
|
.stdout_matches(&re_three);
|
||||||
|
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
{
|
{
|
||||||
let result = scene
|
scene
|
||||||
.ucmd()
|
.ucmd()
|
||||||
.arg("-n")
|
.arg("-n")
|
||||||
.arg("--author")
|
.arg("--author")
|
||||||
.arg("test-long-formats")
|
.arg("test-long-formats")
|
||||||
.succeeds();
|
.succeeds()
|
||||||
assert!(re_three_num.is_match(result.stdout_str()));
|
.stdout_matches(&re_three_num);
|
||||||
}
|
}
|
||||||
|
|
||||||
for arg in &[
|
for arg in &[
|
||||||
|
@ -694,22 +698,22 @@ fn test_ls_long_formats() {
|
||||||
"-lG --author", // only author and owner
|
"-lG --author", // only author and owner
|
||||||
"-l --no-group --author", // only author and owner
|
"-l --no-group --author", // only author and owner
|
||||||
] {
|
] {
|
||||||
let result = scene
|
scene
|
||||||
.ucmd()
|
.ucmd()
|
||||||
.args(&arg.split(' ').collect::<Vec<_>>())
|
.args(&arg.split(' ').collect::<Vec<_>>())
|
||||||
.arg("test-long-formats")
|
.arg("test-long-formats")
|
||||||
.succeeds();
|
.succeeds()
|
||||||
assert!(re_two.is_match(result.stdout_str()));
|
.stdout_matches(&re_two);
|
||||||
|
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
{
|
{
|
||||||
let result = scene
|
scene
|
||||||
.ucmd()
|
.ucmd()
|
||||||
.arg("-n")
|
.arg("-n")
|
||||||
.args(&arg.split(' ').collect::<Vec<_>>())
|
.args(&arg.split(' ').collect::<Vec<_>>())
|
||||||
.arg("test-long-formats")
|
.arg("test-long-formats")
|
||||||
.succeeds();
|
.succeeds()
|
||||||
assert!(re_two_num.is_match(result.stdout_str()));
|
.stdout_matches(&re_two_num);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -723,22 +727,22 @@ fn test_ls_long_formats() {
|
||||||
"-l --no-group", // only owner
|
"-l --no-group", // only owner
|
||||||
"-gG --author", // only author
|
"-gG --author", // only author
|
||||||
] {
|
] {
|
||||||
let result = scene
|
scene
|
||||||
.ucmd()
|
.ucmd()
|
||||||
.args(&arg.split(' ').collect::<Vec<_>>())
|
.args(&arg.split(' ').collect::<Vec<_>>())
|
||||||
.arg("test-long-formats")
|
.arg("test-long-formats")
|
||||||
.succeeds();
|
.succeeds()
|
||||||
assert!(re_one.is_match(result.stdout_str()));
|
.stdout_matches(&re_one);
|
||||||
|
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
{
|
{
|
||||||
let result = scene
|
scene
|
||||||
.ucmd()
|
.ucmd()
|
||||||
.arg("-n")
|
.arg("-n")
|
||||||
.args(&arg.split(' ').collect::<Vec<_>>())
|
.args(&arg.split(' ').collect::<Vec<_>>())
|
||||||
.arg("test-long-formats")
|
.arg("test-long-formats")
|
||||||
.succeeds();
|
.succeeds()
|
||||||
assert!(re_one_num.is_match(result.stdout_str()));
|
.stdout_matches(&re_one_num);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -755,22 +759,22 @@ fn test_ls_long_formats() {
|
||||||
"-og1",
|
"-og1",
|
||||||
"-og1l",
|
"-og1l",
|
||||||
] {
|
] {
|
||||||
let result = scene
|
scene
|
||||||
.ucmd()
|
.ucmd()
|
||||||
.args(&arg.split(' ').collect::<Vec<_>>())
|
.args(&arg.split(' ').collect::<Vec<_>>())
|
||||||
.arg("test-long-formats")
|
.arg("test-long-formats")
|
||||||
.succeeds();
|
.succeeds()
|
||||||
assert!(re_zero.is_match(result.stdout_str()));
|
.stdout_matches(&re_zero);
|
||||||
|
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
{
|
{
|
||||||
let result = scene
|
scene
|
||||||
.ucmd()
|
.ucmd()
|
||||||
.arg("-n")
|
.arg("-n")
|
||||||
.args(&arg.split(' ').collect::<Vec<_>>())
|
.args(&arg.split(' ').collect::<Vec<_>>())
|
||||||
.arg("test-long-formats")
|
.arg("test-long-formats")
|
||||||
.succeeds();
|
.succeeds()
|
||||||
assert!(re_zero.is_match(result.stdout_str()));
|
.stdout_matches(&re_zero);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1248,7 +1252,7 @@ fn test_ls_inode() {
|
||||||
at.touch(file);
|
at.touch(file);
|
||||||
|
|
||||||
let re_short = Regex::new(r" *(\d+) test_inode").unwrap();
|
let re_short = Regex::new(r" *(\d+) test_inode").unwrap();
|
||||||
let re_long = Regex::new(r" *(\d+) [xrw-]{10} \d .+ test_inode").unwrap();
|
let re_long = Regex::new(r" *(\d+) [xrw-]{10}\.? \d .+ test_inode").unwrap();
|
||||||
|
|
||||||
let result = scene.ucmd().arg("test_inode").arg("-i").succeeds();
|
let result = scene.ucmd().arg("test_inode").arg("-i").succeeds();
|
||||||
assert!(re_short.is_match(result.stdout_str()));
|
assert!(re_short.is_match(result.stdout_str()));
|
||||||
|
@ -2272,3 +2276,68 @@ fn test_ls_dangling_symlinks() {
|
||||||
.succeeds() // this should fail, though at the moment, ls lacks a way to propagate errors encountered during display
|
.succeeds() // this should fail, though at the moment, ls lacks a way to propagate errors encountered during display
|
||||||
.stdout_contains(if cfg!(windows) { "dangle" } else { "? dangle" });
|
.stdout_contains(if cfg!(windows) { "dangle" } else { "? dangle" });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(feature = "feat_selinux")]
|
||||||
|
fn test_ls_context1() {
|
||||||
|
use selinux::{self, KernelSupport};
|
||||||
|
if selinux::kernel_support() == KernelSupport::Unsupported {
|
||||||
|
println!("test skipped: Kernel has no support for SElinux context",);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let file = "test_ls_context_file";
|
||||||
|
let expected = format!("unconfined_u:object_r:user_tmp_t:s0 {}\n", file);
|
||||||
|
let (at, mut ucmd) = at_and_ucmd!();
|
||||||
|
at.touch(file);
|
||||||
|
ucmd.args(&["-Z", file]).succeeds().stdout_is(expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(feature = "feat_selinux")]
|
||||||
|
fn test_ls_context2() {
|
||||||
|
use selinux::{self, KernelSupport};
|
||||||
|
if selinux::kernel_support() == KernelSupport::Unsupported {
|
||||||
|
println!("test skipped: Kernel has no support for SElinux context",);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let ts = TestScenario::new(util_name!());
|
||||||
|
for c_flag in &["-Z", "--context"] {
|
||||||
|
ts.ucmd()
|
||||||
|
.args(&[c_flag, &"/"])
|
||||||
|
.succeeds()
|
||||||
|
.stdout_only(unwrap_or_return!(expected_result(&ts, &[c_flag, &"/"])).stdout_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(feature = "feat_selinux")]
|
||||||
|
fn test_ls_context_format() {
|
||||||
|
use selinux::{self, KernelSupport};
|
||||||
|
if selinux::kernel_support() == KernelSupport::Unsupported {
|
||||||
|
println!("test skipped: Kernel has no support for SElinux context",);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let ts = TestScenario::new(util_name!());
|
||||||
|
// NOTE:
|
||||||
|
// --format=long/verbose matches the output of GNU's ls for --context
|
||||||
|
// except for the size count which may differ to the size count reported by GNU's ls.
|
||||||
|
for word in &[
|
||||||
|
"across",
|
||||||
|
"commas",
|
||||||
|
"horizontal",
|
||||||
|
// "long",
|
||||||
|
"single-column",
|
||||||
|
// "verbose",
|
||||||
|
"vertical",
|
||||||
|
] {
|
||||||
|
let format = format!("--format={}", word);
|
||||||
|
ts.ucmd()
|
||||||
|
.args(&[&"-Z", &format.as_str(), &"/"])
|
||||||
|
.succeeds()
|
||||||
|
.stdout_only(
|
||||||
|
unwrap_or_return!(expected_result(&ts, &[&"-Z", &format.as_str(), &"/"]))
|
||||||
|
.stdout_str(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,6 +1,69 @@
|
||||||
use crate::common::util::*;
|
use crate::common::util::*;
|
||||||
use std::io::Read;
|
use std::io::Read;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_hex_rejects_sign_after_identifier() {
|
||||||
|
new_ucmd!()
|
||||||
|
.args(&["0x-123ABC"])
|
||||||
|
.fails()
|
||||||
|
.no_stdout()
|
||||||
|
.stderr_contains("invalid hexadecimal argument: '0x-123ABC'")
|
||||||
|
.stderr_contains("for more information.");
|
||||||
|
new_ucmd!()
|
||||||
|
.args(&["0x+123ABC"])
|
||||||
|
.fails()
|
||||||
|
.no_stdout()
|
||||||
|
.stderr_contains("invalid hexadecimal argument: '0x+123ABC'")
|
||||||
|
.stderr_contains("for more information.");
|
||||||
|
new_ucmd!()
|
||||||
|
.args(&["-0x-123ABC"])
|
||||||
|
.fails()
|
||||||
|
.no_stdout()
|
||||||
|
.stderr_contains("invalid hexadecimal argument: '-0x-123ABC'")
|
||||||
|
.stderr_contains("for more information.");
|
||||||
|
new_ucmd!()
|
||||||
|
.args(&["-0x+123ABC"])
|
||||||
|
.fails()
|
||||||
|
.no_stdout()
|
||||||
|
.stderr_contains("invalid hexadecimal argument: '-0x+123ABC'")
|
||||||
|
.stderr_contains("for more information.");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_hex_lowercase_uppercase() {
|
||||||
|
new_ucmd!()
|
||||||
|
.args(&["0xa", "0xA"])
|
||||||
|
.succeeds()
|
||||||
|
.stdout_is("10\n");
|
||||||
|
new_ucmd!()
|
||||||
|
.args(&["0Xa", "0XA"])
|
||||||
|
.succeeds()
|
||||||
|
.stdout_is("10\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_hex_big_number() {
|
||||||
|
new_ucmd!()
|
||||||
|
.args(&[
|
||||||
|
"0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF",
|
||||||
|
"0x100000000000000000000000000000000",
|
||||||
|
])
|
||||||
|
.succeeds()
|
||||||
|
.stdout_is(
|
||||||
|
"340282366920938463463374607431768211455\n340282366920938463463374607431768211456\n",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_hex_identifier_in_wrong_place() {
|
||||||
|
new_ucmd!()
|
||||||
|
.args(&["1234ABCD0x"])
|
||||||
|
.fails()
|
||||||
|
.no_stdout()
|
||||||
|
.stderr_contains("invalid hexadecimal argument: '1234ABCD0x'")
|
||||||
|
.stderr_contains("for more information.");
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_rejects_nan() {
|
fn test_rejects_nan() {
|
||||||
let ts = TestScenario::new(util_name!());
|
let ts = TestScenario::new(util_name!());
|
||||||
|
|
|
@ -1169,7 +1169,7 @@ pub fn check_coreutil_version(
|
||||||
if s.contains(&format!("(GNU coreutils) {}", version_expected)) {
|
if s.contains(&format!("(GNU coreutils) {}", version_expected)) {
|
||||||
Ok(format!("{}: {}", UUTILS_INFO, s.to_string()))
|
Ok(format!("{}: {}", UUTILS_INFO, s.to_string()))
|
||||||
} else if s.contains("(GNU coreutils)") {
|
} else if s.contains("(GNU coreutils)") {
|
||||||
let version_found = s.split_whitespace().last().unwrap()[..4].parse::<f32>().unwrap_or_default();
|
let version_found = parse_coreutil_version(s);
|
||||||
let version_expected = version_expected.parse::<f32>().unwrap_or_default();
|
let version_expected = version_expected.parse::<f32>().unwrap_or_default();
|
||||||
if version_found > version_expected {
|
if version_found > version_expected {
|
||||||
Ok(format!("{}: version for the reference coreutil '{}' is higher than expected; expected: {}, found: {}", UUTILS_INFO, util_name, version_expected, version_found))
|
Ok(format!("{}: version for the reference coreutil '{}' is higher than expected; expected: {}, found: {}", UUTILS_INFO, util_name, version_expected, version_found))
|
||||||
|
@ -1182,6 +1182,20 @@ pub fn check_coreutil_version(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// simple heuristic to parse the coreutils SemVer string, e.g. "id (GNU coreutils) 8.32.263-0475"
|
||||||
|
fn parse_coreutil_version(version_string: &str) -> f32 {
|
||||||
|
version_string
|
||||||
|
.split_whitespace()
|
||||||
|
.last()
|
||||||
|
.unwrap()
|
||||||
|
.split('.')
|
||||||
|
.take(2)
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join(".")
|
||||||
|
.parse::<f32>()
|
||||||
|
.unwrap_or_default()
|
||||||
|
}
|
||||||
|
|
||||||
/// This runs the GNU coreutils `util_name` binary in `$PATH` in order to
|
/// This runs the GNU coreutils `util_name` binary in `$PATH` in order to
|
||||||
/// dynamically gather reference values on the system.
|
/// dynamically gather reference values on the system.
|
||||||
/// If the `util_name` in `$PATH` doesn't include a coreutils version string,
|
/// If the `util_name` in `$PATH` doesn't include a coreutils version string,
|
||||||
|
@ -1474,6 +1488,36 @@ mod tests {
|
||||||
res.normalized_newlines_stdout_is("A\r\nB\nC\n");
|
res.normalized_newlines_stdout_is("A\r\nB\nC\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(unix)]
|
||||||
|
fn test_parse_coreutil_version() {
|
||||||
|
use std::assert_eq;
|
||||||
|
assert_eq!(
|
||||||
|
parse_coreutil_version("id (GNU coreutils) 9.0.123-0123").to_string(),
|
||||||
|
"9"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
parse_coreutil_version("id (GNU coreutils) 8.32.263-0475").to_string(),
|
||||||
|
"8.32"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
parse_coreutil_version("id (GNU coreutils) 8.25.123-0123").to_string(),
|
||||||
|
"8.25"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
parse_coreutil_version("id (GNU coreutils) 9.0").to_string(),
|
||||||
|
"9"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
parse_coreutil_version("id (GNU coreutils) 8.32").to_string(),
|
||||||
|
"8.32"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
parse_coreutil_version("id (GNU coreutils) 8.25").to_string(),
|
||||||
|
"8.25"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
fn test_check_coreutil_version() {
|
fn test_check_coreutil_version() {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue