mirror of
https://github.com/RGBCube/uutils-coreutils
synced 2025-07-27 19:17:43 +00:00
parent
583ed341a7
commit
d504ae18c9
8 changed files with 307 additions and 0 deletions
10
Cargo.lock
generated
10
Cargo.lock
generated
|
@ -47,6 +47,7 @@ dependencies = [
|
||||||
"nproc 0.0.1",
|
"nproc 0.0.1",
|
||||||
"od 0.0.1",
|
"od 0.0.1",
|
||||||
"paste 0.0.1",
|
"paste 0.0.1",
|
||||||
|
"pathchk 0.0.1",
|
||||||
"primal 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
"primal 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"printenv 0.0.1",
|
"printenv 0.0.1",
|
||||||
"printf 0.0.1",
|
"printf 0.0.1",
|
||||||
|
@ -578,6 +579,15 @@ dependencies = [
|
||||||
"uucore 0.0.1",
|
"uucore 0.0.1",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pathchk"
|
||||||
|
version = "0.0.1"
|
||||||
|
dependencies = [
|
||||||
|
"getopts 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"libc 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"uucore 0.0.1",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pretty-bytes"
|
name = "pretty-bytes"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
|
|
@ -21,6 +21,7 @@ unix = [
|
||||||
"mv",
|
"mv",
|
||||||
"nice",
|
"nice",
|
||||||
"nohup",
|
"nohup",
|
||||||
|
"pathchk",
|
||||||
"stdbuf",
|
"stdbuf",
|
||||||
"timeout",
|
"timeout",
|
||||||
"touch",
|
"touch",
|
||||||
|
@ -136,6 +137,7 @@ nohup = { optional=true, path="src/nohup" }
|
||||||
nproc = { optional=true, path="src/nproc" }
|
nproc = { optional=true, path="src/nproc" }
|
||||||
od = { optional=true, path="src/od" }
|
od = { optional=true, path="src/od" }
|
||||||
paste = { optional=true, path="src/paste" }
|
paste = { optional=true, path="src/paste" }
|
||||||
|
pathchk = { optional=true, path="src/pathchk" }
|
||||||
printenv = { optional=true, path="src/printenv" }
|
printenv = { optional=true, path="src/printenv" }
|
||||||
printf = { optional=true, path="src/printf" }
|
printf = { optional=true, path="src/printf" }
|
||||||
ptx = { optional=true, path="src/ptx" }
|
ptx = { optional=true, path="src/ptx" }
|
||||||
|
|
2
Makefile
2
Makefile
|
@ -112,6 +112,7 @@ UNIX_PROGS := \
|
||||||
mv \
|
mv \
|
||||||
nice \
|
nice \
|
||||||
nohup \
|
nohup \
|
||||||
|
pathchk \
|
||||||
stdbuf \
|
stdbuf \
|
||||||
timeout \
|
timeout \
|
||||||
touch \
|
touch \
|
||||||
|
@ -156,6 +157,7 @@ TEST_PROGS := \
|
||||||
nl \
|
nl \
|
||||||
od \
|
od \
|
||||||
paste \
|
paste \
|
||||||
|
pathchk \
|
||||||
printf \
|
printf \
|
||||||
ptx \
|
ptx \
|
||||||
pwd \
|
pwd \
|
||||||
|
|
17
src/pathchk/Cargo.toml
Normal file
17
src/pathchk/Cargo.toml
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
[package]
|
||||||
|
name = "pathchk"
|
||||||
|
version = "0.0.1"
|
||||||
|
authors = []
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
name = "uu_pathchk"
|
||||||
|
path = "pathchk.rs"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
getopts = "*"
|
||||||
|
libc = "*"
|
||||||
|
uucore = { path="../uucore" }
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "pathchk"
|
||||||
|
path = "main.rs"
|
5
src/pathchk/main.rs
Normal file
5
src/pathchk/main.rs
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
extern crate uu_pathchk;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
std::process::exit(uu_pathchk::uumain(std::env::args().collect()));
|
||||||
|
}
|
229
src/pathchk/pathchk.rs
Normal file
229
src/pathchk/pathchk.rs
Normal file
|
@ -0,0 +1,229 @@
|
||||||
|
#![allow(unused_must_use)] // because we of writeln!
|
||||||
|
#![crate_name = "uu_pathchk"]
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the uutils coreutils package.
|
||||||
|
*
|
||||||
|
* (c) Inokentiy Babushkin <inokentiy.babushkin@googlemail.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE file
|
||||||
|
* that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
extern crate getopts;
|
||||||
|
extern crate libc;
|
||||||
|
|
||||||
|
#[macro_use]
|
||||||
|
extern crate uucore;
|
||||||
|
|
||||||
|
use getopts::Options;
|
||||||
|
use std::fs;
|
||||||
|
use std::io::{Write, ErrorKind};
|
||||||
|
|
||||||
|
// operating mode
|
||||||
|
enum Mode {
|
||||||
|
Default, // use filesystem to determine information and limits
|
||||||
|
Basic, // check basic compatibility with POSIX
|
||||||
|
Extra, // check for leading dashes and empty names
|
||||||
|
Both, // a combination of `Basic` and `Extra`
|
||||||
|
Help, // show help
|
||||||
|
Version // show version information
|
||||||
|
}
|
||||||
|
|
||||||
|
static NAME: &'static str = "pathchk";
|
||||||
|
static VERSION: &'static str = env!("CARGO_PKG_VERSION");
|
||||||
|
|
||||||
|
// a few global constants as used in the GNU implememntation
|
||||||
|
static POSIX_PATH_MAX: usize = 256;
|
||||||
|
static POSIX_NAME_MAX: usize = 14;
|
||||||
|
|
||||||
|
pub fn uumain(args: Vec<String>) -> i32 {
|
||||||
|
// add options
|
||||||
|
let mut opts = Options::new();
|
||||||
|
opts.optflag("p", "posix", "check for (most) POSIX systems");
|
||||||
|
opts.optflag("P",
|
||||||
|
"posix-special", "check for empty names and leading \"-\"");
|
||||||
|
opts.optflag("",
|
||||||
|
"portability", "check for all POSIX systems (equivalent to -p -P)");
|
||||||
|
opts.optflag("h", "help", "display this help text and exit");
|
||||||
|
opts.optflag("V", "version", "output version information and exit");
|
||||||
|
let matches = match opts.parse(&args[1..]) {
|
||||||
|
Ok(m) => m,
|
||||||
|
Err(e) => { crash!(1, "{}", e) }
|
||||||
|
};
|
||||||
|
|
||||||
|
// set working mode
|
||||||
|
let mode = if matches.opt_present("version") {
|
||||||
|
Mode::Version
|
||||||
|
} else if matches.opt_present("help") {
|
||||||
|
Mode::Help
|
||||||
|
} else if (matches.opt_present("posix") &&
|
||||||
|
matches.opt_present("posix-special")) ||
|
||||||
|
matches.opt_present("portability") {
|
||||||
|
Mode::Both
|
||||||
|
} else if matches.opt_present("posix") {
|
||||||
|
Mode::Basic
|
||||||
|
} else if matches.opt_present("posix-special") {
|
||||||
|
Mode::Extra
|
||||||
|
} else {
|
||||||
|
Mode::Default
|
||||||
|
};
|
||||||
|
|
||||||
|
// take necessary actions
|
||||||
|
match mode {
|
||||||
|
Mode::Help => { help(opts); 0 }
|
||||||
|
Mode::Version => { version(); 0 }
|
||||||
|
_ => {
|
||||||
|
let mut res = true;
|
||||||
|
if matches.free.len() == 0 {
|
||||||
|
show_error!(
|
||||||
|
"missing operand\nTry {} --help for more information", NAME
|
||||||
|
);
|
||||||
|
res = false;
|
||||||
|
}
|
||||||
|
// free strings are path operands
|
||||||
|
// FIXME: TCS, seems inefficient and overly verbose (?)
|
||||||
|
for p in matches.free {
|
||||||
|
let mut path = Vec::new();
|
||||||
|
for path_segment in p.split('/') {
|
||||||
|
path.push(path_segment.to_string());
|
||||||
|
}
|
||||||
|
res &= check_path(&mode, &path);
|
||||||
|
}
|
||||||
|
// determine error code
|
||||||
|
if res { 0 } else { 1 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// print help
|
||||||
|
fn help(opts: Options) {
|
||||||
|
let msg = format!("Usage: {} [OPTION]... NAME...\n\n\
|
||||||
|
Diagnose invalid or unportable file names.", NAME);
|
||||||
|
|
||||||
|
print!("{}", opts.usage(&msg));
|
||||||
|
}
|
||||||
|
|
||||||
|
// print version information
|
||||||
|
fn version() {
|
||||||
|
println!("{} {}", NAME, VERSION);
|
||||||
|
}
|
||||||
|
|
||||||
|
// check a path, given as a slice of it's components and an operating mode
|
||||||
|
fn check_path(mode: &Mode, path: &[String]) -> bool {
|
||||||
|
match *mode {
|
||||||
|
Mode::Basic => check_basic(&path),
|
||||||
|
Mode::Extra => check_default(&path) && check_extra(&path),
|
||||||
|
Mode::Both => check_basic(&path) && check_extra(&path),
|
||||||
|
_ => check_default(&path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// check a path in basic compatibility mode
|
||||||
|
fn check_basic(path: &[String]) -> bool {
|
||||||
|
let joined_path = path.join("/");
|
||||||
|
let total_len = joined_path.len();
|
||||||
|
// path length
|
||||||
|
if total_len > POSIX_PATH_MAX {
|
||||||
|
writeln!(&mut std::io::stderr(),
|
||||||
|
"limit {} exceeded by length {} of file name {}",
|
||||||
|
POSIX_PATH_MAX, total_len, joined_path);
|
||||||
|
return false;
|
||||||
|
} else if total_len == 0 {
|
||||||
|
writeln!(&mut std::io::stderr(), "empty file name");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// components: character portability and length
|
||||||
|
for p in path {
|
||||||
|
let component_len = p.len();
|
||||||
|
if component_len > POSIX_NAME_MAX {
|
||||||
|
writeln!(&mut std::io::stderr(),
|
||||||
|
"limit {} exceeded by length {} of file name component '{}'",
|
||||||
|
POSIX_NAME_MAX, component_len, p);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if !check_portable_chars(&p) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// permission checks
|
||||||
|
check_searchable(&joined_path)
|
||||||
|
}
|
||||||
|
|
||||||
|
// check a path in extra compatibility mode
|
||||||
|
fn check_extra(path: &[String]) -> bool {
|
||||||
|
// components: leading hyphens
|
||||||
|
for p in path {
|
||||||
|
if !no_leading_hyphen(&p) {
|
||||||
|
writeln!(&mut std::io::stderr(),
|
||||||
|
"leading hyphen in file name component '{}'", p);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// path length
|
||||||
|
if path.join("/").len() == 0 {
|
||||||
|
writeln!(&mut std::io::stderr(), "empty file name");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
// check a path in default mode (using the file system)
|
||||||
|
fn check_default(path: &[String]) -> bool {
|
||||||
|
let joined_path = path.join("/");
|
||||||
|
let total_len = joined_path.len();
|
||||||
|
// path length
|
||||||
|
if total_len > libc::PATH_MAX as usize {
|
||||||
|
writeln!(&mut std::io::stderr(),
|
||||||
|
"limit {} exceeded by length {} of file name '{}'",
|
||||||
|
libc::PATH_MAX, total_len, joined_path);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// components: length
|
||||||
|
for p in path {
|
||||||
|
let component_len = p.len();
|
||||||
|
if component_len > libc::FILENAME_MAX as usize {
|
||||||
|
writeln!(&mut std::io::stderr(),
|
||||||
|
"limit {} exceeded by length {} of file name component '{}'",
|
||||||
|
libc::FILENAME_MAX, component_len, p);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// permission checks
|
||||||
|
check_searchable(&joined_path)
|
||||||
|
}
|
||||||
|
|
||||||
|
// check whether a path is or if other problems arise
|
||||||
|
fn check_searchable(path: &String) -> bool {
|
||||||
|
// we use lstat, just like the original implementation
|
||||||
|
match fs::symlink_metadata(path) {
|
||||||
|
Ok(_) => true,
|
||||||
|
Err(e) => if e.kind() == ErrorKind::NotFound {
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
writeln!(&mut std::io::stderr(), "{}", e);
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// check for a hypthen at the beginning of a path segment
|
||||||
|
fn no_leading_hyphen(path_segment: &String) -> bool {
|
||||||
|
!path_segment.starts_with('-')
|
||||||
|
}
|
||||||
|
|
||||||
|
// check whether a path segment contains only valid (read: portable) characters
|
||||||
|
fn check_portable_chars(path_segment: &String) -> bool {
|
||||||
|
let valid_str =
|
||||||
|
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789._-"
|
||||||
|
.to_string();
|
||||||
|
for ch in path_segment.chars() {
|
||||||
|
if !valid_str.contains(ch) {
|
||||||
|
writeln!(&mut std::io::stderr(),
|
||||||
|
"nonportable character '{}' in file name component '{}'",
|
||||||
|
ch, path_segment);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
true
|
||||||
|
}
|
|
@ -41,6 +41,15 @@ macro_rules! assert_empty_stderr(
|
||||||
);
|
);
|
||||||
);
|
);
|
||||||
|
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! assert_empty_stdout(
|
||||||
|
($cond:expr) => (
|
||||||
|
if $cond.stdout.len() > 0 {
|
||||||
|
panic!(format!("stdout: {}", $cond.stdout))
|
||||||
|
}
|
||||||
|
);
|
||||||
|
);
|
||||||
|
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! assert_no_error(
|
macro_rules! assert_no_error(
|
||||||
($cond:expr) => (
|
($cond:expr) => (
|
||||||
|
@ -51,6 +60,14 @@ macro_rules! assert_no_error(
|
||||||
);
|
);
|
||||||
);
|
);
|
||||||
|
|
||||||
|
pub fn repeat_str(s: &str, n: u32) -> String {
|
||||||
|
let mut repeated = String::new();
|
||||||
|
for _ in 0..n {
|
||||||
|
repeated.push_str(s);
|
||||||
|
}
|
||||||
|
repeated
|
||||||
|
}
|
||||||
|
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! path_concat {
|
macro_rules! path_concat {
|
||||||
($e:expr, ..$n:expr) => {{
|
($e:expr, ..$n:expr) => {{
|
||||||
|
|
25
tests/pathchk.rs
Normal file
25
tests/pathchk.rs
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
#[macro_use]
|
||||||
|
mod common;
|
||||||
|
|
||||||
|
use common::util::*;
|
||||||
|
|
||||||
|
static UTIL_NAME: &'static str = "pathchk";
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_default_mode() {
|
||||||
|
// test the default mode
|
||||||
|
{
|
||||||
|
// accept some reasonable default
|
||||||
|
let (_, mut ucmd) = testing(UTIL_NAME);
|
||||||
|
let result = ucmd.args(&["abc/def"]).run();
|
||||||
|
assert_eq!(result.stdout, "");
|
||||||
|
assert!(result.success);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
// fail on long inputs
|
||||||
|
let (_, mut ucmd) = testing(UTIL_NAME);
|
||||||
|
let result = ucmd.args(&[repeat_str("test", 20000)]).run();
|
||||||
|
assert_eq!(result.stdout, "");
|
||||||
|
assert!(!result.success);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue