1
Fork 0
mirror of https://github.com/RGBCube/uutils-coreutils synced 2025-07-30 04:27:45 +00:00

Update readlink.

I updated to the nightly build, completed support for the verbose flag,
and refactored the canonicalization method to simplify and add support
for Windows paths.
This commit is contained in:
Joseph Crail 2015-05-12 17:07:31 -04:00
parent 57050517f9
commit b2063d6d73

View file

@ -1,5 +1,5 @@
#![crate_name = "readlink"] #![crate_name = "readlink"]
#![feature(collections, core, old_io, os, old_path, rustc_private)] #![feature(file_type, rustc_private)]
/* /*
* This file is part of the uutils coreutils package. * This file is part of the uutils coreutils package.
@ -12,22 +12,20 @@
extern crate getopts; extern crate getopts;
use getopts::{optflag, getopts, usage, OptGroup}; use getopts::{getopts, optflag, OptGroup, usage};
use std::old_io as io; use std::env;
use std::old_io::fs; use std::fs::{metadata, read_link};
use std::os; use std::io::{Error, ErrorKind, Result, Write};
use std::vec::Vec; use std::path::{Component, PathBuf};
use CanonicalizeMode::{None, Normal, Existing, Missing}; use CanonicalizeMode::{None, Normal, Existing, Missing};
#[path = "../common/util.rs"] #[path = "../common/util.rs"]
#[macro_use] #[macro_use]
mod util; mod util;
const NAME: &'static str = "readlink"; const NAME: &'static str = "readlink";
const VERSION: &'static str = "0.0.1"; const VERSION: &'static str = "0.0.1";
#[derive(PartialEq)] #[derive(PartialEq)]
enum CanonicalizeMode { enum CanonicalizeMode {
@ -37,22 +35,21 @@ enum CanonicalizeMode {
Missing, Missing,
} }
fn resolve(original: &PathBuf) -> Result<PathBuf> {
fn resolve(original: &Path) -> io::IoResult<Path> {
const MAX_LINKS_FOLLOWED: u32 = 255; const MAX_LINKS_FOLLOWED: u32 = 255;
let mut followed = 0; let mut followed = 0;
let mut result = original.clone(); let mut result = original.clone();
loop { loop {
if followed == MAX_LINKS_FOLLOWED { if followed == MAX_LINKS_FOLLOWED {
return Err(io::standard_error(io::InvalidInput)); return Err(Error::new(ErrorKind::InvalidInput, "maximum links followed"));
} }
match fs::lstat(&result) { match metadata(&result) {
Err(e) => return Err(e), Err(e) => return Err(e),
Ok(ref stat) if stat.kind != io::FileType::Symlink => break, Ok(ref m) if !m.file_type().is_symlink() => break,
Ok(..) => { Ok(..) => {
followed += 1; followed += 1;
match fs::readlink(&result) { match read_link(&result) {
Ok(path) => { Ok(path) => {
result.pop(); result.pop();
result.push(path); result.push(path);
@ -64,21 +61,41 @@ fn resolve(original: &Path) -> io::IoResult<Path> {
} }
} }
} }
return Ok(result); Ok(result)
} }
fn canonicalize(original: &PathBuf, can_mode: &CanonicalizeMode) -> Result<PathBuf> {
// Create an absolute path
let original = if original.as_path().is_absolute() {
original.clone()
} else {
env::current_dir().unwrap().join(original)
};
fn canonicalize(original: &Path, can_mode: &CanonicalizeMode) -> io::IoResult<Path> { let mut result = PathBuf::new();
let original = os::make_absolute(original).unwrap(); let mut parts = vec!();
let result = original.root_path();
let mut result = result.expect("make_absolute has no root_path"); // Split path by directory separator; add prefix (Windows-only) and root
let mut parts = vec![]; // directory to final path buffer; add remaining parts to temporary
// vector for canonicalization.
for part in original.components() { for part in original.components() {
parts.push(part); match part {
Component::Prefix(_) | Component::RootDir => {
result.push(part.as_os_str());
},
Component::CurDir => {},
Component::ParentDir => {
parts.pop();
},
Component::Normal(_) => {
parts.push(part.as_os_str());
}
}
} }
if parts.len() > 1 { // Resolve the symlinks where possible
for part in parts.init().iter() { if parts.len() > 0 {
for part in parts[..parts.len()-1].iter() {
result.push(part); result.push(part);
if *can_mode == None { if *can_mode == None {
@ -86,9 +103,9 @@ fn canonicalize(original: &Path, can_mode: &CanonicalizeMode) -> io::IoResult<Pa
} }
match resolve(&result) { match resolve(&result) {
Err(_) => match *can_mode { Err(e) => match *can_mode {
Missing => continue, Missing => continue,
_ => return Err(io::standard_error(io::InvalidInput)), _ => return Err(e)
}, },
Ok(path) => { Ok(path) => {
result.pop(); result.pop();
@ -96,22 +113,22 @@ fn canonicalize(original: &Path, can_mode: &CanonicalizeMode) -> io::IoResult<Pa
} }
} }
} }
}
result.push(parts.last().unwrap()); result.push(parts.last().unwrap());
match resolve(&result) {
Err(e) => { if *can_mode == Existing { return Err(e); } }, match resolve(&result) {
Ok(path) => { Err(e) => { if *can_mode == Existing { return Err(e); } },
result.pop(); Ok(path) => {
result.push(path); result.pop();
result.push(path);
}
} }
} }
return Ok(result); Ok(result)
} }
pub fn uumain(args: Vec<String>) -> i32 { pub fn uumain(args: Vec<String>) -> i32 {
let program = args[0].as_slice(); let program = &args[0];
let opts = [ let opts = [
optflag("f", "canonicalize", optflag("f", "canonicalize",
"canonicalize by following every symlink in every component of the \ "canonicalize by following every symlink in every component of the \
@ -131,7 +148,7 @@ pub fn uumain(args: Vec<String>) -> i32 {
optflag("", "version", "output version information and exit"), optflag("", "version", "output version information and exit"),
]; ];
let matches = match getopts(args.tail(), &opts) { let matches = match getopts(&args[1..], &opts) {
Ok(m) => m, Ok(m) => m,
Err(f) => crash!(1, "Invalid options\n{}", f) Err(f) => crash!(1, "Invalid options\n{}", f)
}; };
@ -148,6 +165,7 @@ pub fn uumain(args: Vec<String>) -> i32 {
let mut no_newline = matches.opt_present("no-newline"); let mut no_newline = matches.opt_present("no-newline");
let use_zero = matches.opt_present("zero"); let use_zero = matches.opt_present("zero");
let silent = matches.opt_present("silent") || matches.opt_present("quiet"); let silent = matches.opt_present("silent") || matches.opt_present("quiet");
let verbose = matches.opt_present("verbose");
let mut can_mode = None; let mut can_mode = None;
if matches.opt_present("canonicalize") { if matches.opt_present("canonicalize") {
@ -175,37 +193,46 @@ pub fn uumain(args: Vec<String>) -> i32 {
} }
for f in files.iter() { for f in files.iter() {
let p = Path::new(f); let p = PathBuf::from(f);
if can_mode == None { if can_mode == None {
match fs::readlink(&p) { match read_link(&p) {
Ok(path) => show(path.as_str().unwrap(), no_newline, use_zero), Ok(path) => show(&path, no_newline, use_zero),
Err(_) => return 1 Err(err) => {
if verbose {
eprintln!("{}: {}: errno {}", NAME, f, err.raw_os_error().unwrap());
}
return 1
}
} }
} else { } else {
match canonicalize(&p, &can_mode) { match canonicalize(&p, &can_mode) {
Ok(path) => show(path.as_str().unwrap(), no_newline, use_zero), Ok(path) => show(&path, no_newline, use_zero),
Err(_) => return 1 Err(err) => {
if verbose {
eprintln!("{}: {}: errno {:?}", NAME, f, err.raw_os_error().unwrap());
}
return 1
}
} }
} }
} }
return 0;
0
} }
fn show(path: &PathBuf, no_newline: bool, use_zero: bool) {
fn show(path: &str, no_newline: bool, use_zero: bool) { let path = path.as_path().to_str().unwrap();
if use_zero { if use_zero {
print!("{}\0", path); print!("{}\0", path);
} else if no_newline {
print!("{}", path);
} else { } else {
if no_newline { println!("{}", path);
io::print(path);
} else {
io::println(path);
}
} }
} }
fn show_usage(program: &str, opts: &[OptGroup]) { fn show_usage(program: &str, opts: &[OptGroup]) {
println!("Usage: {0} [OPTION]... [FILE]...", program); println!("Usage: {0} [OPTION]... [FILE]...", program);
print!("Print value of a symbolic link or canonical file name"); print!("Print value of a symbolic link or canonical file name");
io::print(usage("", opts).as_slice()); print!("{}", usage("", opts));
} }