From 79478626da91471db186b1a503ed4bb21c470304 Mon Sep 17 00:00:00 2001 From: Knight Date: Sat, 7 May 2016 14:57:47 +0800 Subject: [PATCH 1/5] add mknod --- Cargo.toml | 4 +++- Makefile | 15 ++++++++------- src/mknod/Cargo.toml | 17 +++++++++++++++++ src/mknod/main.rs | 5 +++++ 4 files changed, 33 insertions(+), 8 deletions(-) create mode 100644 src/mknod/Cargo.toml create mode 100644 src/mknod/main.rs diff --git a/Cargo.toml b/Cargo.toml index 5be88ea6c..7f398ae68 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,6 +16,7 @@ unix = [ "kill", "logname", "mkfifo", + "mknod", "mktemp", "mv", "nice", @@ -123,8 +124,9 @@ ln = { optional=true, path="src/ln" } ls = { optional=true, path="src/ls" } logname = { optional=true, path="src/logname" } mkdir = { optional=true, path="src/mkdir" } -mktemp = { optional=true, path="src/mktemp" } mkfifo = { optional=true, path="src/mkfifo" } +mknod = { optional=true, path="src/mknod" } +mktemp = { optional=true, path="src/mktemp" } mv = { optional=true, path="src/mv" } nice = { optional=true, path="src/nice" } nl = { optional=true, path="src/nl" } diff --git a/Makefile b/Makefile index 185f1fbeb..b31261214 100644 --- a/Makefile +++ b/Makefile @@ -16,7 +16,7 @@ CARGOFLAGS ?= # Install directories PREFIX ?= /usr/local -DESTDIR ?= +DESTDIR ?= BINDIR ?= /bin LIBDIR ?= /lib @@ -107,6 +107,7 @@ UNIX_PROGS := \ kill \ logname \ mkfifo \ + mknod \ mv \ nice \ nohup \ @@ -182,7 +183,7 @@ TESTS := \ $(sort $(filter $(UTILS),$(filter-out $(SKIP_UTILS),$(TEST_PROGS)))) TEST_NO_FAIL_FAST := -TEST_SPEC_FEATURE := +TEST_SPEC_FEATURE := ifneq ($(SPEC),) TEST_NO_FAIL_FAST :=--no-fail-fast TEST_SPEC_FEATURE := test_unimplemented @@ -236,7 +237,7 @@ $(foreach util,$(EXES),$(eval $(call BUILD_EXE,$(util)))) build-pkgs: $(addprefix build_exe_,$(EXES)) -build-uutils: +build-uutils: ${CARGO} build ${CARGOFLAGS} --features "${EXES}" ${PROFILE_CMD} --no-default-features build: build-uutils build-pkgs @@ -254,11 +255,11 @@ busybox-src: fi; \ # This is a busybox-specific config file their test suite wants to parse. -$(BUILDDIR)/.config: $(BASEDIR)/.busybox-config +$(BUILDDIR)/.config: $(BASEDIR)/.busybox-config cp $< $@ # Test under the busybox testsuite -$(BUILDDIR)/busybox: busybox-src build-uutils $(BUILDDIR)/.config +$(BUILDDIR)/busybox: busybox-src build-uutils $(BUILDDIR)/.config cp $(BUILDDIR)/uutils $(BUILDDIR)/busybox; \ chmod +x $@; @@ -269,12 +270,12 @@ busytest: $(BUILDDIR)/busybox $(addprefix test_busybox_,$(filter-out $(SKIP_UTIL endif clean: - $(RM) -rf $(BUILDDIR) + $(RM) -rf $(BUILDDIR) distclean: clean $(CARGO) clean $(CARGOFLAGS) && $(CARGO) update $(CARGOFLAGS) -install: build +install: build mkdir -p $(INSTALLDIR_BIN) ifeq (${MULTICALL}, y) install $(BUILDDIR)/uutils $(INSTALLDIR_BIN)/$(PROG_PREFIX)uutils diff --git a/src/mknod/Cargo.toml b/src/mknod/Cargo.toml new file mode 100644 index 000000000..47e8870c1 --- /dev/null +++ b/src/mknod/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "mknod" +version = "0.0.1" +authors = [] + +[lib] +name = "uu_mknod" +path = "mknod.rs" + +[dependencies] +getopts = "*" +libc = "*" +uucore = { path="../uucore" } + +[[bin]] +name = "mknod" +path = "main.rs" diff --git a/src/mknod/main.rs b/src/mknod/main.rs new file mode 100644 index 000000000..41cf1306d --- /dev/null +++ b/src/mknod/main.rs @@ -0,0 +1,5 @@ +extern crate uu_mknod; + +fn main() { + std::process::exit(uu_mknod::uumain(std::env::args().collect())); +} From 467561da362eb02d34b19c0f41459a7058767f40 Mon Sep 17 00:00:00 2001 From: Knight Date: Sat, 7 May 2016 14:58:06 +0800 Subject: [PATCH 2/5] mknod: implemented --- src/mknod/mknod.rs | 205 +++++++++++++++++++++++++++++++++++++++++ src/mknod/parsemode.rs | 136 +++++++++++++++++++++++++++ 2 files changed, 341 insertions(+) create mode 100644 src/mknod/mknod.rs create mode 100644 src/mknod/parsemode.rs diff --git a/src/mknod/mknod.rs b/src/mknod/mknod.rs new file mode 100644 index 000000000..47bdc6ce5 --- /dev/null +++ b/src/mknod/mknod.rs @@ -0,0 +1,205 @@ +#![crate_name = "uu_mknod"] + +// This file is part of the uutils coreutils package. +// +// (c) Jian Zeng +// +// 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; + +mod parsemode; + +#[macro_use] +extern crate uucore; + +use libc::{mode_t, dev_t}; +use libc::{S_IRUSR, S_IWUSR, S_IRGRP, S_IWGRP, S_IROTH, S_IWOTH, S_IFIFO, S_IFBLK, S_IFCHR}; + +use getopts::Options; +use std::io::Write; + +use std::ffi::CString; + +static NAME: &'static str = "mknod"; +static VERSION: &'static str = env!("CARGO_PKG_VERSION"); + +const MODE_RW_UGO: mode_t = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH; + +#[inline(always)] +fn makedev(maj: u64, min: u64) -> dev_t { + // pick up from + ((min & 0xff) | ((maj & 0xfff) << 8) | (((min & !0xff)) << 12) | + (((maj & !0xfff)) << 32)) as dev_t +} + +#[cfg(windows)] +fn _makenod(path: CString, mode: mode_t, dev: dev_t) -> i32 { + panic!("Unsupported for windows platform") +} + +#[cfg(unix)] +fn _makenod(path: CString, mode: mode_t, dev: dev_t) -> i32 { + unsafe { libc::mknod(path.as_ptr(), mode, dev) } +} + +macro_rules! disp_err { + ($($args:tt)+) => ({ + pipe_write!(&mut ::std::io::stderr(), "{}: ", NAME); + pipe_writeln!(&mut ::std::io::stderr(), $($args)+); + pipe_writeln!(&mut ::std::io::stderr(), "Try '{} --help' for more information.", NAME); + }) +} + +pub fn uumain(args: Vec) -> i32 { + let mut opts = Options::new(); + + // Linux-specific options, not implemented + // opts.optflag("Z", "", "set the SELinux security context to default type"); + // opts.optopt("", "context", "like -Z, or if CTX is specified then set the SELinux or SMACK security context to CTX"); + opts.optopt("m", + "mode", + "set file permission bits to MODE, not a=rw - umask", + "MODE"); + + opts.optflag("", "help", "display this help and exit"); + opts.optflag("", "version", "output version information and exit"); + + let matches = match opts.parse(&args[1..]) { + Ok(m) => m, + Err(f) => crash!(1, "{}\nTry '{} --help' for more information.", f, NAME), + }; + + if matches.opt_present("help") { + println!( +"Usage: {0} [OPTION]... NAME TYPE [MAJOR MINOR] + +Mandatory arguments to long options are mandatory for short options too. + -m, --mode=MODE set file permission bits to MODE, not a=rw - umask + -Z set the SELinux security context to default type + --context[=CTX] like -Z, or if CTX is specified then set the SELinux + or SMACK security context to CTX + --help display this help and exit + --version output version information and exit + +Both MAJOR and MINOR must be specified when TYPE is b, c, or u, and they +must be omitted when TYPE is p. If MAJOR or MINOR begins with 0x or 0X, +it is interpreted as hexadecimal; otherwise, if it begins with 0, as octal; +otherwise, as decimal. TYPE may be: + + b create a block (buffered) special file + c, u create a character (unbuffered) special file + p create a FIFO + +NOTE: your shell may have its own version of mknod, which usually supersedes +the version described here. Please refer to your shell's documentation +for details about the options it supports.", NAME); + return 0; + } + + if matches.opt_present("version") { + println!("{} {}", NAME, VERSION); + return 0; + } + + let mut last_umask: mode_t = 0; + let mut newmode: mode_t = MODE_RW_UGO; + if matches.opt_present("mode") { + match parsemode::parse_mode(matches.opt_str("mode")) { + Ok(parsed) => { + if parsed > 0o777 { + show_info!("mode must specify only file permission bits"); + return 1; + } + newmode = parsed; + } + Err(e) => { + show_info!("{}", e); + return 1; + } + } + unsafe { + last_umask = libc::umask(0); + } + } + + let mut ret = 0i32; + match matches.free.len() { + 0 => disp_err!("missing operand"), + 1 => disp_err!("missing operand after ‘{}’", matches.free[0]), + _ => { + let args = &matches.free; + let c_str = CString::new(args[0].as_str()).expect("Failed to convert to CString"); + + // Only check the first character, to allow mnemonic usage like + // 'mknod /dev/rst0 character 18 0'. + let ch = args[1].chars().nth(0).expect("Failed to get the first char"); + + if ch == 'p' { + if args.len() > 2 { + show_info!("{}: extra operand ‘{}’", NAME, args[2]); + if args.len() == 4 { + eprintln!("Fifos do not have major and minor device numbers."); + } + eprintln!("Try '{} --help' for more information.", NAME); + return 1; + } + + ret = _makenod(c_str, S_IFIFO | newmode, 0); + } else { + if args.len() < 4 { + show_info!("missing operand after ‘{}’", args[args.len() - 1]); + if args.len() == 2 { + eprintln!("Special files require major and minor device numbers."); + } + eprintln!("Try '{} --help' for more information.", NAME); + return 1; + } else if args.len() > 4 { + disp_err!("extra operand ‘{}’", args[4]); + return 1; + } else if !"bcu".contains(ch) { + disp_err!("invalid device type ‘{}’", args[1]); + return 1; + } + + let maj = args[2].parse::(); + let min = args[3].parse::(); + if maj.is_err() { + show_info!("invalid major device number ‘{}’", args[2]); + return 1; + } else if min.is_err() { + show_info!("invalid minor device number ‘{}’", args[3]); + return 1; + } + + let (maj, min) = (maj.unwrap(), min.unwrap()); + let dev = makedev(maj, min); + if ch == 'b' { + // block special file + ret = _makenod(c_str, S_IFBLK | newmode, dev); + } else { + // char special file + ret = _makenod(c_str, S_IFCHR | newmode, dev); + } + } + } + } + + if last_umask != 0 { + unsafe { + libc::umask(last_umask); + } + } + if ret == -1 { + let c_str = CString::new(format!("{}: {}", NAME, matches.free[0]).as_str()) + .expect("Failed to convert to CString"); + unsafe { + libc::perror(c_str.as_ptr()); + } + } + + ret +} diff --git a/src/mknod/parsemode.rs b/src/mknod/parsemode.rs new file mode 100644 index 000000000..ff14db9c7 --- /dev/null +++ b/src/mknod/parsemode.rs @@ -0,0 +1,136 @@ +extern crate libc; +use libc::{mode_t, S_IRGRP, S_IWGRP, S_IROTH, S_IWOTH, S_IRUSR, S_IWUSR}; + +fn parse_change(mode: &str, fperm: mode_t) -> (mode_t, usize) { + let mut srwx = fperm & 0o7000; + let mut pos = 0; + for ch in mode.chars() { + match ch { + 'r' => srwx |= 0o444, + 'w' => srwx |= 0o222, + 'x' => srwx |= 0o111, + 'X' => srwx |= 0o111, + 's' => srwx |= 0o4000 | 0o2000, + 't' => srwx |= 0o1000, + 'u' => srwx = (fperm & 0o700) | ((fperm >> 3) & 0o070) | ((fperm >> 6) & 0o007), + 'g' => srwx = ((fperm << 3) & 0o700) | (fperm & 0o070) | ((fperm >> 3) & 0o007), + 'o' => srwx = ((fperm << 6) & 0o700) | ((fperm << 3) & 0o070) | (fperm & 0o007), + _ => break, + }; + pos += 1; + } + if pos == 0 { + srwx = 0; + } + (srwx, pos) +} + +fn parse_levels(mode: &str) -> (mode_t, usize) { + let mut mask = 0; + let mut pos = 0; + for ch in mode.chars() { + mask |= match ch { + 'u' => 0o7700, + 'g' => 0o7070, + 'o' => 0o7007, + 'a' => 0o7777, + _ => break, + }; + pos += 1; + } + if pos == 0 { + mask = 0o7777; // default to 'a' + } + (mask, pos) +} + +fn parse_symbolic(mut fperm: mode_t, mut mode: &str) -> Result { + let (mask, pos) = parse_levels(mode); + if pos == mode.len() { + return Err("invalid mode".to_owned()); + } + mode = &mode[pos..]; + while mode.len() > 0 { + let (op, pos) = try!(parse_op(mode, None)); + mode = &mode[pos..]; + let (srwx, pos) = parse_change(mode, fperm); + mode = &mode[pos..]; + match op { + '+' => fperm |= srwx & mask, + '-' => fperm &= !(srwx & mask), + '=' => fperm = (fperm & !mask) | (srwx & mask), + _ => unreachable!(), + } + } + Ok(fperm) +} + +fn parse_op(mode: &str, default: Option) -> Result<(char, usize), String> { + match mode.chars().next() { + Some(ch) => { + match ch { + '+' | '-' | '=' => Ok((ch, 1)), + _ => { + match default { + Some(ch) => Ok((ch, 0)), + None => { + Err(format!("invalid operator (expected +, -, or =, but found {})", ch)) + } + } + } + } + } + None => Err("unexpected end of mode".to_owned()), + } +} + +fn parse_numeric(fperm: mode_t, mut mode: &str) -> Result { + let (op, pos) = try!(parse_op(mode, Some('='))); + mode = mode[pos..].trim_left_matches('0'); + match mode_t::from_str_radix(mode, 8) { + Ok(change) => { + let after = match op { + '+' => fperm | change, + '-' => fperm & !change, + '=' => change, + _ => unreachable!(), + }; + if after > 0o7777 { + return Err("invalid mode".to_owned()); + } + Ok(after) + } + Err(_) => Err("invalid mode".to_owned()), + } +} +pub fn parse_mode(mode: Option) -> Result { + let fperm = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH; + if let Some(mode) = mode { + let arr: &[char] = &['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']; + let result = if mode.contains(arr) { + parse_numeric(fperm, mode.as_str()) + } else { + parse_symbolic(fperm, mode.as_str()) + }; + result + } else { + Ok(fperm) + } +} + + +#[test] +fn symbolic_modes() { + assert_eq!(parse_mode(Some("u+x".to_owned())).unwrap(), 0o766); + assert_eq!(parse_mode(Some("+x".to_owned())).unwrap(), 0o777); + assert_eq!(parse_mode(Some("a-w".to_owned())).unwrap(), 0o444); + assert_eq!(parse_mode(Some("g-r".to_owned())).unwrap(), 0o626); +} + +#[test] +fn numeric_modes() { + assert_eq!(parse_mode(Some("644".to_owned())).unwrap(), 0o644); + assert_eq!(parse_mode(Some("+100".to_owned())).unwrap(), 0o766); + assert_eq!(parse_mode(Some("-4".to_owned())).unwrap(), 0o662); + assert_eq!(parse_mode(None).unwrap(), 0o666); +} From 948675b0e30622b5583ea24913a2676fba17d4ba Mon Sep 17 00:00:00 2001 From: Knight Date: Sat, 7 May 2016 16:10:48 +0800 Subject: [PATCH 3/5] README: remove mknod from TODO list --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index f8ec53e11..91a68a0f0 100644 --- a/README.md +++ b/README.md @@ -162,7 +162,6 @@ To do - install - join - ls -- mknod - mktemp (almost done, some options are not working) - mv (almost done, one more option) - numfmt From 78f9351e71248f93e7d9ad40d9d3466aa6d3ba45 Mon Sep 17 00:00:00 2001 From: Knight Date: Sat, 7 May 2016 16:12:01 +0800 Subject: [PATCH 4/5] mknod: correct the help --- src/mknod/mknod.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/mknod/mknod.rs b/src/mknod/mknod.rs index 47bdc6ce5..61f4f6ed6 100644 --- a/src/mknod/mknod.rs +++ b/src/mknod/mknod.rs @@ -79,9 +79,6 @@ pub fn uumain(args: Vec) -> i32 { Mandatory arguments to long options are mandatory for short options too. -m, --mode=MODE set file permission bits to MODE, not a=rw - umask - -Z set the SELinux security context to default type - --context[=CTX] like -Z, or if CTX is specified then set the SELinux - or SMACK security context to CTX --help display this help and exit --version output version information and exit From 6592e0308c2c5a602afa2effa9e64eebdeb26e3d Mon Sep 17 00:00:00 2001 From: Knight Date: Sun, 8 May 2016 16:21:20 +0800 Subject: [PATCH 5/5] mknod: use libc@0.2.4 to avoid unresolved name --- src/mknod/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mknod/Cargo.toml b/src/mknod/Cargo.toml index 47e8870c1..e6e4036cc 100644 --- a/src/mknod/Cargo.toml +++ b/src/mknod/Cargo.toml @@ -9,7 +9,7 @@ path = "mknod.rs" [dependencies] getopts = "*" -libc = "*" +libc = "^0.2.4" uucore = { path="../uucore" } [[bin]]