diff --git a/Cargo.toml b/Cargo.toml index fffbc9e84..fc003d5a1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -131,6 +131,14 @@ path = "printenv/printenv.rs" name = "pwd" path = "pwd/pwd.rs" +[[bin]] +name = "realpath" +path = "realpath/realpath.rs" + +[[bin]] +name = "relpath" +path = "relpath/relpath.rs" + [[bin]] name = "rm" path = "rm/rm.rs" diff --git a/Makefile b/Makefile index 25d383219..a5186626b 100644 --- a/Makefile +++ b/Makefile @@ -29,6 +29,8 @@ PROGS := \ paste \ printenv \ pwd \ + realpath \ + relpath \ rm \ rmdir \ sleep \ diff --git a/README.md b/README.md index 37cd36cd6..1568ee924 100644 --- a/README.md +++ b/README.md @@ -139,8 +139,6 @@ To do - printf - ptx - readlink -- realpath -- relpath - remove - runcon - setuidgid diff --git a/common/util.rs b/common/util.rs index 1accfbf71..ffd10be80 100644 --- a/common/util.rs +++ b/common/util.rs @@ -19,6 +19,16 @@ macro_rules! show_error( }) ) +#[macro_export] +macro_rules! eprint( + ($($args:expr),+) => (safe_write!(&mut ::std::io::stderr(), $($args),+)) +) + +#[macro_export] +macro_rules! eprintln( + ($($args:expr),+) => (safe_writeln!(&mut ::std::io::stderr(), $($args),+)) +) + #[macro_export] macro_rules! show_warning( ($($args:expr),+) => ({ diff --git a/realpath/realpath.rs b/realpath/realpath.rs new file mode 100644 index 000000000..a8b89401a --- /dev/null +++ b/realpath/realpath.rs @@ -0,0 +1,138 @@ +#![crate_id = "realpath#1.0.0"] + +/* + * This file is part of the uutils coreutils package. + * + * (c) 2014 Vsevolod Velichko + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +#![feature(macro_rules)] +extern crate getopts; +extern crate libc; + +use getopts::{optflag, getopts, usage}; + +#[path = "../common/util.rs"] mod util; + +static NAME: &'static str = "realpath"; +static VERSION: &'static str = "1.0.0"; + +pub fn uumain(args: Vec) -> int { + let program = args.get(0); + let options = [ + optflag("h", "help", "Show help and exit"), + optflag("V", "version", "Show version and exit"), + optflag("s", "strip", "Only strip '.' and '..' components, but don't resolve symbolic links"), + optflag("z", "zero", "Separate output filenames with \\0 rather than newline"), + optflag("q", "quiet", "Do not print warnings for invalid paths"), + ]; + + let opts = match getopts(args.tail(), options) { + Ok(m) => m, + Err(f) => { + show_error!("{}", f); + show_usage(program.as_slice(), options); + return 1 + } + }; + + if opts.opt_present("V") { version(); return 0 } + if opts.opt_present("h") { show_usage(program.as_slice(), options); return 0 } + + if opts.free.len() == 0 { + show_error!("Missing operand: FILENAME, at least one is required"); + println!("Try `{:s} --help` for more information.", program.as_slice()); + return 1 + } + + let strip = opts.opt_present("s"); + let zero = opts.opt_present("z"); + let quiet = opts.opt_present("q"); + let mut retcode = 0; + opts.free.iter().map(|x| + if !resolve_path(x.as_slice(), strip, zero, quiet) { + retcode = 1 + } + ).last(); + retcode +} + +fn resolve_path(path: &str, strip: bool, zero: bool, quiet: bool) -> bool { + let p = Path::new(path); + let abs = std::os::make_absolute(&p); + + if strip { + if zero { + print!("{}\0", abs.display()); + } else { + println!("{}", abs.display()) + } + return true + } + + let mut result = match abs.root_path() { + None => crash!(2, "Broken path parse! Report to developers: {}", path), + Some(x) => x, + }; + + let mut links_left = 256; + + for part in abs.components() { + result.push(part); + loop { + if links_left == 0 { + if !quiet { show_error!("Too many symbolic links: {}", path) }; + return false + } + match std::io::fs::lstat(&result) { + Err(_) => break, + Ok(ref s) if s.kind != std::io::TypeSymlink => break, + Ok(_) => { + links_left -= 1; + match std::io::fs::readlink(&result) { + Ok(x) => { + result.pop(); + result.push(x); + }, + _ => { + if !quiet { + show_error!("Invalid path: {}", path) + }; + return false + }, + } + } + } + } + } + + if zero { + print!("{}\0", result.display()); + } else { + println!("{}", result.display()); + } + + true +} + +fn version() { + println!("{} v{}", NAME, VERSION) +} + +fn show_usage(program: &str, options: &[getopts::OptGroup]) { + version(); + println!("Usage:"); + println!(" {:s} [-s|--strip] [-z|--zero] FILENAME…", program); + println!(" {:s} -V|--version", program); + println!(" {:s} -h|--help", program); + println!(""); + print!("{:s}", usage( + "Convert each FILENAME to the absolute path.\n\ + All the symbolic links will be resolved, resulting path will contain no special components like '.' or '..'.\n\ + Each path component must exist or resolution will fail and non-zero exit status returned.\n\ + Each resolved FILENAME will be written to the standard output, one per line.", options) + ); +} diff --git a/relpath/relpath.rs b/relpath/relpath.rs new file mode 100644 index 000000000..7ae9acd90 --- /dev/null +++ b/relpath/relpath.rs @@ -0,0 +1,100 @@ +#![crate_id = "relpath#1.0.0"] + +/* + * This file is part of the uutils coreutils package. + * + * (c) 2014 Vsevolod Velichko + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +#![feature(macro_rules)] +extern crate getopts; +extern crate libc; + +use getopts::{optflag, optopt, getopts, usage}; + +#[path = "../common/util.rs"] mod util; + +static NAME: &'static str = "relpath"; +static VERSION: &'static str = "1.0.0"; + +pub fn uumain(args: Vec) -> int { + let program = args.get(0); + let options = [ + optflag("h", "help", "Show help and exit"), + optflag("V", "version", "Show version and exit"), + optopt("d", "", "If any of FROM and TO is not subpath of DIR, output absolute path instead of relative", "DIR"), + ]; + + let opts = match getopts(args.tail(), options) { + Ok(m) => m, + Err(f) => { + show_error!("{}", f); + show_usage(program.as_slice(), options); + return 1 + } + }; + + if opts.opt_present("V") { version(); return 0 } + if opts.opt_present("h") { show_usage(program.as_slice(), options); return 0 } + + if opts.free.len() == 0 { + show_error!("Missing operand: TO"); + println!("Try `{:s} --help` for more information.", program.as_slice()); + return 1 + } + + let to = Path::new(opts.free.get(0).as_slice()); + let from = if opts.free.len() > 1 { + Path::new(opts.free.get(1).as_slice()) + } else { + std::os::getcwd() + }; + let absto = std::os::make_absolute(&to); + let absfrom = std::os::make_absolute(&from); + + if opts.opt_present("d") { + let base = Path::new(opts.opt_str("d").unwrap()); + let absbase = std::os::make_absolute(&base); + if !absbase.is_ancestor_of(&absto) || !absbase.is_ancestor_of(&absfrom) { + println!("{}", absto.display()); + return 0 + } + } + + let mut suffixPos = 0; + absfrom.components() + .zip(absto.components()) + .take_while( + |&(f, t)| if f == t { + suffixPos += 1; true + } else { + false + }).last(); + + let mut result = Path::new(""); + absfrom.components().skip(suffixPos).map(|_| result.push("..")).last(); + absto.components().skip(suffixPos).map(|x| result.push(x)).last(); + + println!("{}", result.display()); + 0 +} + +fn version() { + println!("{} v{}", NAME, VERSION) +} + +fn show_usage(program: &str, options: &[getopts::OptGroup]) { + version(); + println!("Usage:"); + println!(" {:s} [-d DIR] TO [FROM]", program); + println!(" {:s} -V|--version", program); + println!(" {:s} -h|--help", program); + println!(""); + print!("{:s}", usage( + "Convert TO destination to the relative path from the FROM dir.\n\ + If FROM path is omitted, current working dir will be used.", options) + ); +} diff --git a/uutils/uutils.rs b/uutils/uutils.rs index fd9243904..1c710a6aa 100644 --- a/uutils/uutils.rs +++ b/uutils/uutils.rs @@ -42,6 +42,8 @@ extern crate nohup; extern crate paste; extern crate printenv; extern crate pwd; +extern crate realpath; +extern crate relpath; extern crate rm; extern crate rmdir; extern crate seq; @@ -111,6 +113,8 @@ fn util_map() -> HashMap<&str, fn(Vec) -> int> { map.insert("paste", paste::uumain); map.insert("printenv", printenv::uumain); map.insert("pwd", pwd::uumain); + map.insert("realpath", realpath::uumain); + map.insert("relpath", relpath::uumain); map.insert("rm", rm::uumain); map.insert("rmdir", rmdir::uumain); map.insert("seq", seq::uumain);