mirror of
https://github.com/RGBCube/uutils-coreutils
synced 2025-07-27 19:17:43 +00:00
commit
9534bf3f17
7 changed files with 371 additions and 9 deletions
|
@ -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" }
|
||||
|
|
15
Makefile
15
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
|
||||
|
|
|
@ -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
|
||||
|
|
17
src/mknod/Cargo.toml
Normal file
17
src/mknod/Cargo.toml
Normal file
|
@ -0,0 +1,17 @@
|
|||
[package]
|
||||
name = "mknod"
|
||||
version = "0.0.1"
|
||||
authors = []
|
||||
|
||||
[lib]
|
||||
name = "uu_mknod"
|
||||
path = "mknod.rs"
|
||||
|
||||
[dependencies]
|
||||
getopts = "*"
|
||||
libc = "^0.2.4"
|
||||
uucore = { path="../uucore" }
|
||||
|
||||
[[bin]]
|
||||
name = "mknod"
|
||||
path = "main.rs"
|
5
src/mknod/main.rs
Normal file
5
src/mknod/main.rs
Normal file
|
@ -0,0 +1,5 @@
|
|||
extern crate uu_mknod;
|
||||
|
||||
fn main() {
|
||||
std::process::exit(uu_mknod::uumain(std::env::args().collect()));
|
||||
}
|
202
src/mknod/mknod.rs
Normal file
202
src/mknod/mknod.rs
Normal file
|
@ -0,0 +1,202 @@
|
|||
#![crate_name = "uu_mknod"]
|
||||
|
||||
// This file is part of the uutils coreutils package.
|
||||
//
|
||||
// (c) Jian Zeng <anonymousknight96@gmail.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;
|
||||
|
||||
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 <sys/sysmacros.h>
|
||||
((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<String>) -> 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
|
||||
--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::<u64>();
|
||||
let min = args[3].parse::<u64>();
|
||||
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
|
||||
}
|
136
src/mknod/parsemode.rs
Normal file
136
src/mknod/parsemode.rs
Normal file
|
@ -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<mode_t, String> {
|
||||
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<char>) -> 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<mode_t, String> {
|
||||
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<String>) -> Result<mode_t, String> {
|
||||
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);
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue