diff --git a/src/cmd/b2sum/mod.rs b/src/cmd/b2sum/mod.rs index ad76675..87290f4 100644 --- a/src/cmd/b2sum/mod.rs +++ b/src/cmd/b2sum/mod.rs @@ -23,7 +23,7 @@ impl Cmd for B2sum { Arg::new("length") .help( "digest length in bits; must not exceed the max for the \ - blake2 algorithm and must be a multiple of 8" + blake2 algorithm and must be a multiple of 8", ) .short('l') .long("length") diff --git a/src/cmd/ln/mod.rs b/src/cmd/ln/mod.rs index 8b13789..fabbf61 100644 --- a/src/cmd/ln/mod.rs +++ b/src/cmd/ln/mod.rs @@ -1 +1,136 @@ +use super::Cmd; +use crate::unistd; +use clap::{Arg, ArgAction, Command}; +use std::{error::Error, fs, io, os, path::PathBuf}; +#[derive(Debug, Default)] +pub struct Ln; + +impl Cmd for Ln { + fn cli(&self) -> clap::Command { + Command::new("ln") + .about("make links between files") + .author("Nathan Fisher") + .version(env!("CARGO_PKG_VERSION")) + .args([ + Arg::new("force") + .help("remove existing destination files") + .short('f') + .long("force") + .action(ArgAction::SetTrue), + Arg::new("symbolic") + .help("make symbolic links instead of hard links") + .short('s') + .long("symbolic") + .action(ArgAction::SetTrue), + Arg::new("verbose") + .help("print name of each linked file") + .short('v') + .long("verbose") + .action(ArgAction::SetTrue), + Arg::new("to-target") + .help( + "For each source_file operand that names a file of type \ + symbolic link, create a (hard) link to the file referenced \ + by the symbolic link.", + ) + .short('L') + .overrides_with("to-link") + .action(ArgAction::SetTrue), + Arg::new("to-link") + .help( + "For each source_file operand that names a file of type \ + symbolic link, create a (hard) link to the symbolic link itself.", + ) + .short('P') + .conflicts_with("to-target") + .action(ArgAction::SetTrue), + Arg::new("file") + .value_name("source") + .num_args(1..) + .required(true), + Arg::new("dest").num_args(1).required(true), + ]) + } + + fn run(&self, matches: &clap::ArgMatches) -> Result<(), Box> { + let files = matches + .get_many::("file") + .unwrap() + .collect::>(); + let dest = matches.get_one::("dest").unwrap(); + let dest = PathBuf::from(dest); + let sym_target = if matches.get_flag("to-target") { + SymTarget::ToTarget + } else { + SymTarget::ToLink + }; + if files.len() > 1 { + if !dest.is_dir() { + return Err(io::Error::new( + io::ErrorKind::Other, + "destination must be a directory", + ) + .into()); + } + } + for f in files { + let source = PathBuf::from(f); + do_link( + source, + dest.clone(), + sym_target, + matches.get_flag("force"), + matches.get_flag("symbolic"), + matches.get_flag("verbose"), + )?; + } + Ok(()) + } + + fn path(&self) -> Option { + Some(crate::Path::Bin) + } +} + +#[derive(Clone, Copy, PartialEq)] +enum SymTarget { + ToTarget, + ToLink, +} + +fn do_link( + source: PathBuf, + mut dest: PathBuf, + sym_target: SymTarget, + force: bool, + symbolic: bool, + verbose: bool, +) -> Result<(), Box> { + let source = if source.is_symlink() && sym_target == SymTarget::ToTarget { + fs::canonicalize(&source)? + } else { + source + }; + if dest.is_dir() { + let name = source.file_name().unwrap(); + dest.push(name); + } + if dest.exists() { + if force { + unistd::unlink( + dest.to_str() + .ok_or(io::Error::new(io::ErrorKind::Other, "invalid utf8"))?, + )?; + } + } + if symbolic { + os::unix::fs::symlink(&source, &dest)?; + } else { + fs::hard_link(&source, &dest)?; + } + if verbose { + println!("{} -> {}", dest.display(), source.display()); + } + Ok(()) +} diff --git a/src/cmd/mod.rs b/src/cmd/mod.rs index 7e8fa94..e239b6a 100644 --- a/src/cmd/mod.rs +++ b/src/cmd/mod.rs @@ -100,6 +100,7 @@ pub fn get(name: &str) -> Option> { "hostid" => Some(Box::new(hostid::Hostid::default())), "hostname" => Some(Box::new(hostname::Hostname::default())), "link" => Some(Box::new(link::Link::default())), + "ln" => Some(Box::new(ln::Ln::default())), "logname" => Some(Box::new(logname::Logname::default())), "md5sum" => Some(Box::new(md5sum::Md5sum::default())), "mkfifo" => Some(Box::new(mkfifo::MkFifo::default())), @@ -133,7 +134,7 @@ pub fn get(name: &str) -> Option> { } } -pub static COMMANDS: [&str; 50] = [ +pub static COMMANDS: [&str; 51] = [ "b2sum", "base32", "base64", @@ -155,6 +156,7 @@ pub static COMMANDS: [&str; 50] = [ "hostid", "hostname", "link", + "ln", "logname", "md5sum", "mkfifo", diff --git a/src/cmd/rev/mod.rs b/src/cmd/rev/mod.rs index 06d5b94..c767263 100644 --- a/src/cmd/rev/mod.rs +++ b/src/cmd/rev/mod.rs @@ -1,6 +1,6 @@ use super::Cmd; use crate::args; -use clap::{Arg, Command}; +use clap::Command; use std::{ fs::File, io::{self, BufRead, BufReader, Write}, @@ -15,11 +15,7 @@ impl Cmd for Rev { Command::new("rev") .about("reverse lines characterwise") .author("Nathan Fisher") - .args([ - args::header(), - args::color(), - args::file(), - ]) + .args([args::header(), args::color(), args::file()]) } fn run(&self, matches: &clap::ArgMatches) -> Result<(), Box> { diff --git a/src/stat/mod.rs b/src/stat/mod.rs index 1b0030c..887da4b 100644 --- a/src/stat/mod.rs +++ b/src/stat/mod.rs @@ -3,7 +3,15 @@ use std::{ffi::CString, io}; #[inline(always)] pub fn mknod(path: &str, mode: u32, dev: u64) -> io::Result<()> { - let ret = unsafe { syscall!(MKNOD, CString::new(path)?.as_ptr(), mode, dev) }; + let ret = unsafe { + syscall!( + MKNODAT, + libc::AT_FDCWD, + CString::new(path)?.as_ptr(), + mode, + dev + ) + }; if ret == 0 { Ok(()) } else { @@ -13,7 +21,15 @@ pub fn mknod(path: &str, mode: u32, dev: u64) -> io::Result<()> { #[inline(always)] pub fn mkfifo(path: &str, mode: u32) -> io::Result<()> { - let ret = unsafe { syscall!(MKNOD, CString::new(path)?.as_ptr(), mode | libc::S_IFIFO, 0) }; + let ret = unsafe { + syscall!( + MKNODAT, + libc::AT_FDCWD, + CString::new(path)?.as_ptr(), + mode | libc::S_IFIFO, + 0 + ) + }; if ret == 0 { Ok(()) } else { diff --git a/src/unistd/mod.rs b/src/unistd/mod.rs index 565c36c..183f862 100644 --- a/src/unistd/mod.rs +++ b/src/unistd/mod.rs @@ -45,9 +45,12 @@ pub fn sethostname(name: &str) -> io::Result<()> { pub fn link(source: &str, dest: &str) -> io::Result<()> { let ret = unsafe { syscall!( - LINK, + LINKAT, + libc::AT_FDCWD, CString::new(source)?.as_ptr(), - CString::new(dest)?.as_ptr() + libc::AT_FDCWD, + CString::new(dest)?.as_ptr(), + 0 ) }; if ret == 0 { @@ -59,7 +62,7 @@ pub fn link(source: &str, dest: &str) -> io::Result<()> { #[inline(always)] pub fn unlink(name: &str) -> io::Result<()> { - let ret = unsafe { syscall!(UNLINK, CString::new(name)?.as_ptr()) }; + let ret = unsafe { syscall!(UNLINKAT, libc::AT_FDCWD, CString::new(name)?.as_ptr(), 0) }; if ret == 0 { Ok(()) } else { @@ -72,7 +75,8 @@ pub fn readlink(name: &str) -> io::Result { let mut buf = Vec::::with_capacity(1024); let ret = unsafe { syscall!( - READLINK, + READLINKAT, + libc::AT_FDCWD, CString::new(name)?.as_ptr(), buf.as_mut_ptr(), 1024 @@ -129,8 +133,9 @@ pub fn sync() { pub fn symlink(source: &str, dest: &str) -> io::Result<()> { let ret = unsafe { syscall!( - SYMLINK, + SYMLINKAT, CString::new(source)?.as_ptr(), + libc::AT_FDCWD, CString::new(dest)?.as_ptr() ) };