pathchk implemented (see #841) (#860)

* Added pathchk
This commit is contained in:
ibabushkin 2016-05-22 13:59:57 +02:00 committed by Heather
parent 583ed341a7
commit d504ae18c9
8 changed files with 307 additions and 0 deletions

10
Cargo.lock generated
View file

@ -47,6 +47,7 @@ dependencies = [
"nproc 0.0.1",
"od 0.0.1",
"paste 0.0.1",
"pathchk 0.0.1",
"primal 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
"printenv 0.0.1",
"printf 0.0.1",
@ -578,6 +579,15 @@ dependencies = [
"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]]
name = "pretty-bytes"
version = "0.1.0"

View file

@ -21,6 +21,7 @@ unix = [
"mv",
"nice",
"nohup",
"pathchk",
"stdbuf",
"timeout",
"touch",
@ -136,6 +137,7 @@ nohup = { optional=true, path="src/nohup" }
nproc = { optional=true, path="src/nproc" }
od = { optional=true, path="src/od" }
paste = { optional=true, path="src/paste" }
pathchk = { optional=true, path="src/pathchk" }
printenv = { optional=true, path="src/printenv" }
printf = { optional=true, path="src/printf" }
ptx = { optional=true, path="src/ptx" }

View file

@ -112,6 +112,7 @@ UNIX_PROGS := \
mv \
nice \
nohup \
pathchk \
stdbuf \
timeout \
touch \
@ -156,6 +157,7 @@ TEST_PROGS := \
nl \
od \
paste \
pathchk \
printf \
ptx \
pwd \

17
src/pathchk/Cargo.toml Normal file
View 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
View 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
View 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
}

View file

@ -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_rules! assert_no_error(
($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_rules! path_concat {
($e:expr, ..$n:expr) => {{

25
tests/pathchk.rs Normal file
View 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);
}
}