diff --git a/Cargo.toml b/Cargo.toml index 653866415..85254dd82 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -183,6 +183,10 @@ path = "tail/tail.rs" name = "tee" path = "tee/tee.rs" +[[bin]] +name = "test" +path = "test/test.rs" + [[bin]] name = "touch" path = "touch/touch.rs" diff --git a/Makefile b/Makefile index cc9b91999..439b13765 100644 --- a/Makefile +++ b/Makefile @@ -41,6 +41,7 @@ PROGS := \ sync \ tac \ tee \ + test \ touch \ tr \ true \ diff --git a/README.md b/README.md index 335496c63..1d9c79b77 100644 --- a/README.md +++ b/README.md @@ -159,7 +159,7 @@ To do - stdbuf - stty - tail (not all features implemented) -- test +- test (not all features implemented) - timeout - tsort - unexpand diff --git a/mkmain.rs b/mkmain.rs index ad522c116..3f5d89bfa 100644 --- a/mkmain.rs +++ b/mkmain.rs @@ -23,6 +23,7 @@ fn main() { } let crat = match args.get(1).as_slice() { + "test" => "uutest", "true" => "uutrue", "false" => "uufalse", "sync" => "uusync", diff --git a/mkuutils.rs b/mkuutils.rs index 78b35303b..0d3aa1a8a 100644 --- a/mkuutils.rs +++ b/mkuutils.rs @@ -22,6 +22,10 @@ fn main() { } util_map.push_str(format!("map.insert(\"{}\", hashsum::uumain);\n", prog).as_slice()); } + "test" => { + crates.push_str("extern crate uutest;\n"); + util_map.push_str("map.insert(\"test\", uutest::uumain);\n"); + } "true" => util_map.push_str("fn uutrue(_: Vec) -> int { 0 }\nmap.insert(\"true\", uutrue);\n"), "false" => util_map.push_str("fn uufalse(_: Vec) -> int { 1 }\nmap.insert(\"false\", uufalse);\n"), "sync" => { diff --git a/test/test.rs b/test/test.rs new file mode 100644 index 000000000..9e74faa3e --- /dev/null +++ b/test/test.rs @@ -0,0 +1,237 @@ +#![crate_name = "uutest"] + +/* + * This file is part of the uutils coreutils package. + * + * (c) mahkoh (ju.orth [at] gmail [dot] com) + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +extern crate libc; +extern crate num; + +use std::os::{args_as_bytes}; +use std::str::{from_utf8}; +use num::bigint::{BigInt}; + +pub fn uumain(_: Vec) -> int { + let args = args_as_bytes(); + let args: Vec<&[u8]> = args.iter().map(|a| a.as_slice()).collect(); + let args = args.as_slice(); + if args.len() == 0 { + return 2; + } + let args = match args[0] { + b"[" => match args[args.len()-1] { + b"]" => args.slice(1, args.len()-1), + _ => return 2, + }, + _ => args.slice(1, args.len()), + }; + let rv = match args.len() { + 0 => false, + 1 => one(args), + 2 => two(args), + 3 => three(args), + 4 => four(args), + _ => return 2, + }; + 1 - rv as int +} + +fn one(args: &[&[u8]]) -> bool { + args[0].len() > 0 +} + +fn two(args: &[&[u8]]) -> bool { + match args[0] { + b"!" => !one(args.slice_from(1)), + b"-b" => path(args[1], BlockSpecial), + b"-c" => path(args[1], CharacterSpecial), + b"-d" => path(args[1], Directory), + b"-e" => path(args[1], Exists), + b"-f" => path(args[1], Regular), + b"-g" => path(args[1], GroupIDFlag), + b"-h" => path(args[1], SymLink), + b"-L" => path(args[1], SymLink), + b"-n" => one(args.slice_from(1)), + b"-p" => path(args[1], FIFO), + b"-r" => path(args[1], Readable), + b"-S" => path(args[1], Socket), + b"-s" => path(args[1], NonEmpty), + b"-t" => isatty(args[1]), + b"-u" => path(args[1], UserIDFlag), + b"-w" => path(args[1], Writable), + b"-x" => path(args[1], Executable), + b"-z" => !one(args.slice_from(1)), + _ => false, + } +} + +fn three(args: &[&[u8]]) -> bool { + match args[1] { + b"=" => args[0] == args[2], + b"!=" => args[0] != args[2], + b"-eq" => integers(args[0], args[2], Equal), + b"-ne" => integers(args[0], args[2], Unequal), + b"-gt" => integers(args[0], args[2], Greater), + b"-ge" => integers(args[0], args[2], GreaterEqual), + b"-lt" => integers(args[0], args[2], Less), + b"-le" => integers(args[0], args[2], LessEqual), + _ => match args[0] { + b"!" => !two(args.slice_from(1)), + _ => false, + } + } +} + +fn four(args: &[&[u8]]) -> bool { + match args[0] { + b"!" => !three(args.slice_from(1)), + _ => false, + } +} + +enum IntegerCondition { + Equal, + Unequal, + Greater, + GreaterEqual, + Less, + LessEqual, +} + +fn integers(a: &[u8], b: &[u8], cond: IntegerCondition) -> bool { + let (a, b): (&str, &str) = match (from_utf8(a), from_utf8(b)) { + (Some(a), Some(b)) => (a, b), + _ => return false, + }; + let (a, b): (BigInt, BigInt) = match (from_str(a), from_str(b)) { + (Some(a), Some(b)) => (a, b), + _ => return false, + }; + match cond { + Equal => a == b, + Unequal => a != b, + Greater => a > b, + GreaterEqual => a >= b, + Less => a < b, + LessEqual => a <= b, + } +} + +fn isatty(fd: &[u8]) -> bool { + use libc::{isatty}; + from_utf8(fd).and_then(|s| from_str(s)) + .map(|i| unsafe { isatty(i) == 1 }).unwrap_or(false) +} + +#[deriving(Eq, PartialEq)] +enum PathCondition { + BlockSpecial, + CharacterSpecial, + Directory, + Exists, + Regular, + GroupIDFlag, + SymLink, + FIFO, + Readable, + Socket, + NonEmpty, + UserIDFlag, + Writable, + Executable, +} + +#[cfg(not(windows))] +fn path(path: &[u8], cond: PathCondition) -> bool { + use libc::{stat, c_int, lstat, S_IFMT, S_IFLNK, S_IFBLK, S_IFCHR, S_IFDIR, S_IFREG}; + use libc::{S_IFIFO, mode_t}; + static S_ISUID: mode_t = 0o4000; + static S_ISGID: mode_t = 0o2000; + static S_IFSOCK: c_int = 0o140000; + + enum Permission { + Read = 0o4, + Write = 0o2, + Execute = 0o1, + } + let perm = |stat: stat, p: Permission| { + use libc::{getgid, getuid}; + let (uid, gid) = unsafe { (getuid(), getgid()) }; + if uid == stat.st_uid { + stat.st_mode & (p as mode_t << 6) != 0 + } else if gid == stat.st_gid { + stat.st_mode & (p as mode_t << 3) != 0 + } else { + stat.st_mode & (p as mode_t << 0) != 0 + } + }; + + let path = unsafe { path.to_c_str_unchecked() }; + let mut stat = unsafe { std::mem::zeroed() }; + if cond == SymLink { + if unsafe { lstat(path.as_ptr(), &mut stat) } == 0 { + if stat.st_mode as c_int & S_IFMT == S_IFLNK { + return true; + } + } + return false; + } + if unsafe { libc::stat(path.as_ptr(), &mut stat) } != 0 { + return false; + } + let file_type = stat.st_mode as c_int & S_IFMT; + match cond { + BlockSpecial => file_type == S_IFBLK, + CharacterSpecial => file_type == S_IFCHR, + Directory => file_type == S_IFDIR, + Exists => true, + Regular => file_type == S_IFREG, + GroupIDFlag => stat.st_mode & S_ISGID != 0, + SymLink => true, + FIFO => file_type == S_IFIFO, + Readable => perm(stat, Read), + Socket => file_type == S_IFSOCK, + NonEmpty => stat.st_size > 0, + UserIDFlag => stat.st_mode & S_ISUID != 0, + Writable => perm(stat, Write), + Executable => perm(stat, Execute), + } +} + +#[cfg(windows)] +fn path(path: &[u8], cond: PathCondition) -> bool { + use std::io::{TypeFile, TypeDirectory, TypeBlockSpecial, TypeNamedPipe}; + use std::io::fs::{stat}; + use std::path::{Path}; + + let path = match Path::new_opt(path) { + Some(p) => p, + None => return false, + }; + let stat = match stat(&path) { + Ok(s) => s, + _ => return false, + }; + match cond { + BlockSpecial => stat.kind == TypeBlockSpecial, + CharacterSpecial => false, + Directory => stat.kind == TypeDirectory, + Exists => true, + Regular => stat.kind == TypeFile, + GroupIDFlag => false, + SymLink => false, + FIFO => stat.kind == TypeNamedPipe, + Readable => false, // TODO + Socket => false, // TODO? + NonEmpty => stat.size > 0, + UserIDFlag => false, + Writable => false, // TODO + Executable => false, // TODO + } +} +