Add WIP truncate applet

This commit is contained in:
Nathan Fisher 2023-04-17 22:11:02 -04:00
parent 8c2f7f39e4
commit 8e302f3f85
5 changed files with 269 additions and 87 deletions

113
Cargo.lock generated
View File

@ -208,18 +208,18 @@ dependencies = [
[[package]]
name = "clap"
version = "4.2.1"
version = "4.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "046ae530c528f252094e4a77886ee1374437744b2bff1497aa898bbddbbb29b3"
checksum = "9b802d85aaf3a1cdb02b224ba472ebdea62014fccfcb269b95a4d76443b5ee5a"
dependencies = [
"clap_builder",
]
[[package]]
name = "clap_builder"
version = "4.2.1"
version = "4.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "223163f58c9a40c3b0a43e1c4b50a9ce09f007ea2cb1ec258a687945b4b7929f"
checksum = "14a1a858f532119338887a4b8e1af9c60de8249cd7bafd68036a489e261e37b6"
dependencies = [
"bitflags",
"clap_lex",
@ -231,7 +231,7 @@ version = "4.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "01c22dcfb410883764b29953103d9ef7bb8fe21b3fa1158bc99986c2067294bd"
dependencies = [
"clap 4.2.1",
"clap 4.2.2",
]
[[package]]
@ -240,7 +240,7 @@ version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c7fa41f5e6aa83bd151b70fd0ceaee703d68cd669522795dc812df9edad1252c"
dependencies = [
"clap 4.2.1",
"clap 4.2.2",
"clap_complete",
]
@ -256,7 +256,7 @@ version = "0.2.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4237e29de9c6949982ba87d51709204504fb8ed2fd38232fcb1e5bf7d4ba48c8"
dependencies = [
"clap 4.2.1",
"clap 4.2.2",
"roff",
]
@ -325,7 +325,7 @@ dependencies = [
"proc-macro2",
"quote",
"scratch",
"syn 2.0.13",
"syn 2.0.15",
]
[[package]]
@ -342,7 +342,7 @@ checksum = "2345488264226bf682893e25de0769f3360aac9957980ec49361b083ddaa5bc5"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.13",
"syn 2.0.15",
]
[[package]]
@ -380,9 +380,9 @@ version = "0.1.0"
[[package]]
name = "filetime"
version = "0.2.20"
version = "0.2.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a3de6e8d11b22ff9edc6d916f890800597d60f8b2da1caf2955c274638d6412"
checksum = "5cbc844cecaee9d4443931972e1289c8ff485cb4cc2767cb03ca139ed6885153"
dependencies = [
"cfg-if",
"libc",
@ -672,9 +672,9 @@ checksum = "1792db035ce95be60c3f8853017b3999209281c24e2ba5bc8e59bf97a0c590c1"
[[package]]
name = "serde"
version = "1.0.159"
version = "1.0.160"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c04e8343c3daeec41f58990b9d77068df31209f2af111e059e9fe9646693065"
checksum = "bb2f3770c8bce3bcda7e149193a069a0f4365bda1fa5cd88e03bca26afc1216c"
[[package]]
name = "sha1"
@ -706,7 +706,7 @@ dependencies = [
"bitflags-mini",
"blake2b_simd",
"chrono",
"clap 4.2.1",
"clap 4.2.2",
"clap_complete",
"clap_complete_nushell",
"clap_mangen",
@ -788,9 +788,9 @@ dependencies = [
[[package]]
name = "syn"
version = "2.0.13"
version = "2.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c9da457c5285ac1f936ebd076af6dac17a61cfe7826f2076b4d015cf47bc8ec"
checksum = "a34fcf3e8b60f57e6a14301a2e916d323af98b0ea63c599441eec8558660c822"
dependencies = [
"proc-macro2",
"quote",
@ -841,7 +841,7 @@ checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.13",
"syn 2.0.15",
]
[[package]]
@ -1056,31 +1056,16 @@ version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f"
dependencies = [
"windows-targets 0.48.0",
"windows-targets",
]
[[package]]
name = "windows-sys"
version = "0.45.0"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0"
checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
dependencies = [
"windows-targets 0.42.2",
]
[[package]]
name = "windows-targets"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071"
dependencies = [
"windows_aarch64_gnullvm 0.42.2",
"windows_aarch64_msvc 0.42.2",
"windows_i686_gnu 0.42.2",
"windows_i686_msvc 0.42.2",
"windows_x86_64_gnu 0.42.2",
"windows_x86_64_gnullvm 0.42.2",
"windows_x86_64_msvc 0.42.2",
"windows-targets",
]
[[package]]
@ -1089,93 +1074,51 @@ version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5"
dependencies = [
"windows_aarch64_gnullvm 0.48.0",
"windows_aarch64_msvc 0.48.0",
"windows_i686_gnu 0.48.0",
"windows_i686_msvc 0.48.0",
"windows_x86_64_gnu 0.48.0",
"windows_x86_64_gnullvm 0.48.0",
"windows_x86_64_msvc 0.48.0",
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8"
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc"
[[package]]
name = "windows_aarch64_msvc"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43"
[[package]]
name = "windows_aarch64_msvc"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3"
[[package]]
name = "windows_i686_gnu"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f"
[[package]]
name = "windows_i686_gnu"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241"
[[package]]
name = "windows_i686_msvc"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060"
[[package]]
name = "windows_i686_msvc"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00"
[[package]]
name = "windows_x86_64_gnu"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36"
[[package]]
name = "windows_x86_64_gnu"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953"
[[package]]
name = "windows_x86_64_msvc"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0"
[[package]]
name = "windows_x86_64_msvc"
version = "0.48.0"

View File

@ -47,6 +47,7 @@ mod sleep;
mod sync;
mod touch;
mod r#true;
mod truncate;
mod unlink;
mod wc;
mod which;
@ -98,6 +99,7 @@ pub fn get(name: &str) -> Option<Box<dyn Cmd>> {
"sync" => Some(Box::new(sync::Sync::default())),
"touch" => Some(Box::new(touch::Touch::default())),
"true" => Some(Box::new(r#true::True::default())),
"truncate" => Some(Box::new(truncate::Truncate)),
"unlink" => Some(Box::new(unlink::Unlink::default())),
"wc" => Some(Box::new(wc::Wc::default())),
"which" => Some(Box::new(which::Which::default())),
@ -108,7 +110,7 @@ pub fn get(name: &str) -> Option<Box<dyn Cmd>> {
}
}
pub static COMMANDS: [&str; 45] = [
pub static COMMANDS: [&str; 46] = [
"base32",
"base64",
"basename",
@ -148,6 +150,7 @@ pub static COMMANDS: [&str; 45] = [
"sync",
"touch",
"true",
"truncate",
"unlink",
"wc",
"which",

View File

@ -0,0 +1,213 @@
use {
super::Cmd,
clap::{Arg, ArgAction, ArgGroup, Command, ValueHint},
std::{error::Error, fmt, fs, num::ParseIntError},
};
#[derive(Debug, Default)]
pub struct Truncate;
impl Cmd for Truncate {
fn cli(&self) -> clap::Command {
Command::new("truncate")
.about("truncate or extend the length of files")
.author("Nathan Fisher")
.version(env!("CARGO_PKG_VERSION"))
.args([
Arg::new("create")
.help("do not create files if they do not exist")
.long_help(
"Do not create files if they do not exist. The truncate \
utility does not treat this as an error. No error messages are \
displayed and the exit value is not affected.",
)
.short('c')
.long("no-create")
.action(ArgAction::SetFalse),
Arg::new("reference")
.help("truncate or extend files to the length of RFILE")
.short('r')
.long("reference")
.value_name("RFILE")
.value_hint(ValueHint::FilePath)
.num_args(1),
Arg::new("size")
.help("set or adjust the file size by SIZE bytes")
.long_help(
"If the size argument is preceded by a plus sign (+), files will \
be extended by this number of bytes. If the size argument is \
preceded by a dash (-), file lengths will be reduced by no more \
than this number of bytes, to a minimum length of zero bytes. If \
the size argument is preceded by a percent sign (%), files will be \
round up to a multiple of this number of bytes. If the size argument \
is preceded by a slash sign (/), files will be round down to a \
multiple of this number of bytes, to a minimum length of zero bytes. \
Otherwise, the size argument specifies an absolute length to which all \
files should be extended or reduced as appropriate.\n\nThe size argument \
may be suffixed with one of K, M, G or T (either upper or lower case) to \
indicate a multiple of Kilobytes, Megabytes, Gigabytes or Terabytes \
respectively.",
)
.short('s')
.long("size")
.allow_hyphen_values(true)
.value_name("SIZE")
.num_args(1),
Arg::new("file").value_name("FILE").num_args(1..),
])
.group(
ArgGroup::new("args")
.args(["reference", "size"])
.required(true),
)
}
fn run(&self, matches: &clap::ArgMatches) -> Result<(), Box<dyn std::error::Error>> {
let size = if let Some(file) = matches.get_one::<String>("reference") {
let num: i64 = fs::metadata(file)?.len().try_into()?;
Size { operator: Operator::Equal, num }
} else if let Some(s) = matches.get_one::<String>("size") {
parse_size(s)?
} else {
unreachable!();
};
Ok(())
}
fn path(&self) -> Option<shitbox::Path> {
Some(shitbox::Path::UsrBin)
}
}
enum Operator {
Equal,
Add,
Remove,
ModUp,
ModDown,
}
struct Size {
operator: Operator,
num: i64,
}
#[repr(i64)]
enum Multiplier {
Kilo = 1024,
Mega = 1024 * 1024,
Giga = 1024 * 1024 * 1024,
Tera = 1024 * 1024 * 1024 * 1024,
}
#[derive(Debug)]
pub enum ParseSizeError {
EmptySize,
InvalidChar,
ParseIntError,
}
impl fmt::Display for ParseSizeError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{self:?}")
}
}
impl From<ParseIntError> for ParseSizeError {
fn from(_value: ParseIntError) -> Self {
Self::ParseIntError
}
}
impl Error for ParseSizeError {}
fn parse_size(size: &str) -> Result<Size, ParseSizeError> {
if size.is_empty() {
return Err(ParseSizeError::EmptySize);
}
let mut operator: Option<Operator> = None;
let mut num = vec![];
let mut multiplier: Option<Multiplier> = None;
size.chars().try_for_each(|c| {
match c {
'+' => {
if operator.is_some() || !num.is_empty() || multiplier.is_some() {
return Err(ParseSizeError::InvalidChar);
} else {
operator = Some(Operator::Add);
}
}
'-' => {
if operator.is_some() || !num.is_empty() || multiplier.is_some() {
return Err(ParseSizeError::InvalidChar);
} else {
operator = Some(Operator::Remove);
}
}
'%' => {
if operator.is_some() || !num.is_empty() || multiplier.is_some() {
return Err(ParseSizeError::InvalidChar);
} else {
operator = Some(Operator::ModUp);
}
}
'/' => {
if operator.is_some() || !num.is_empty() || multiplier.is_some() {
return Err(ParseSizeError::InvalidChar);
} else {
operator = Some(Operator::ModDown);
}
}
'k' | 'K' => {
if multiplier.is_some() || num.is_empty() {
return Err(ParseSizeError::InvalidChar);
} else {
multiplier = Some(Multiplier::Kilo);
}
}
'm' | 'M' => {
if multiplier.is_some() || num.is_empty() {
return Err(ParseSizeError::InvalidChar);
} else {
multiplier = Some(Multiplier::Mega);
}
}
'g' | 'G' => {
if multiplier.is_some() || num.is_empty() {
return Err(ParseSizeError::InvalidChar);
} else {
multiplier = Some(Multiplier::Giga);
}
}
't' | 'T' => {
if multiplier.is_some() || num.is_empty() {
return Err(ParseSizeError::InvalidChar);
} else {
multiplier = Some(Multiplier::Tera);
}
}
ch if ch.is_digit(10) => {
if multiplier.is_some() {
return Err(ParseSizeError::InvalidChar);
} else {
num.push(ch as u8);
}
}
_ => return Err(ParseSizeError::InvalidChar),
}
Ok(())
})?;
let mut num: i64 = String::from_utf8(num).unwrap().parse()?;
if let Some(m) = multiplier {
match m {
Multiplier::Kilo => num *= 1024,
Multiplier::Mega => num *= (1024 * 1024),
Multiplier::Giga => num *= (1024 * 1024 * 1024),
Multiplier::Tera => num *= (1024 * 1024 * 1024 * 1024),
}
}
match operator {
Some(operator) => Ok(Size { operator, num }),
None => Ok(Size { operator: Operator::Equal, num }),
}
}

View File

@ -35,7 +35,12 @@ impl Cmd for Who {
session: _,
time,
} => {
println!("{user:9}{line:13}{} {}:{}", time.date(), time.hour(), time.minute());
println!(
"{user:9}{line:13}{} {}:{}",
time.date(),
time.hour(),
time.minute()
);
}
_ => {}
}

View File

@ -16,6 +16,24 @@ fn new_utsname() -> utsname {
}
}
pub fn ftruncate(fd: &File, len: i64) -> Result<(), Box<dyn Error>> {
let ret = unsafe { syscall!(FTRUNCATE, fd.as_raw_fd(), len) };
if ret == 0 {
Ok(())
} else {
Err(Errno::from(ret).into())
}
}
pub fn truncate(path: &str, len: i64) -> Result<(), Box<dyn Error>> {
let ret = unsafe { syscall!(TRUNCATE, CString::new(path)?.as_ptr(), len) };
if ret == 0 {
Ok(())
} else {
Err(Errno::from(ret).into())
}
}
#[allow(clippy::cast_sign_loss)]
pub fn gethostname() -> Result<String, Box<dyn Error>> {
let mut uts = new_utsname();