Replace scrut(1) with fileis(1) #146
12
Makefile
@ -32,7 +32,7 @@ RUSTLIBS = --extern getopt=build/o/libgetopt.rlib \
|
|||||||
CFLAGS += -I$(SYSEXITS)
|
CFLAGS += -I$(SYSEXITS)
|
||||||
|
|
||||||
.PHONY: all
|
.PHONY: all
|
||||||
all: dj false fop hru intcmp mm npc rpn scrut str strcmp swab true
|
all: dj false fileis fop hru intcmp mm npc rpn scrut str strcmp swab true
|
||||||
|
|
||||||
# keep build/include until bindgen(1) has stdin support
|
# keep build/include until bindgen(1) has stdin support
|
||||||
# https://github.com/rust-lang/rust-bindgen/issues/2703
|
# https://github.com/rust-lang/rust-bindgen/issues/2703
|
||||||
@ -97,6 +97,11 @@ false: build/bin/false
|
|||||||
build/bin/false: src/false.c build
|
build/bin/false: src/false.c build
|
||||||
$(CC) $(CFLAGS) -o $@ src/false.c
|
$(CC) $(CFLAGS) -o $@ src/false.c
|
||||||
|
|
||||||
|
.PHONY: fileis
|
||||||
|
fileis: build/bin/fileis
|
||||||
|
build/bin/fileis: src/fileis.rs build
|
||||||
|
$(RUSTC) $(RUSTFLAGS) $(RUSTLIBS) -o $@ src/fileis.rs
|
||||||
|
|
||||||
.PHONY: fop
|
.PHONY: fop
|
||||||
fop: build/bin/fop
|
fop: build/bin/fop
|
||||||
build/bin/fop: src/fop.rs build rustlibs
|
build/bin/fop: src/fop.rs build rustlibs
|
||||||
@ -127,11 +132,6 @@ rpn: build/bin/rpn
|
|||||||
build/bin/rpn: src/rpn.rs build rustlibs
|
build/bin/rpn: src/rpn.rs build rustlibs
|
||||||
$(RUSTC) $(RUSTFLAGS) $(RUSTLIBS) -o $@ src/rpn.rs
|
$(RUSTC) $(RUSTFLAGS) $(RUSTLIBS) -o $@ src/rpn.rs
|
||||||
|
|
||||||
.PHONY: scrut
|
|
||||||
scrut: build/bin/scrut
|
|
||||||
build/bin/scrut: src/scrut.c build
|
|
||||||
$(CC) $(CFLAGS) -o $@ src/scrut.c
|
|
||||||
|
|
||||||
.PHONY: str
|
.PHONY: str
|
||||||
str: build/bin/str
|
str: build/bin/str
|
||||||
build/bin/str: src/str.c build
|
build/bin/str: src/str.c build
|
||||||
|
@ -4,12 +4,12 @@
|
|||||||
.\" This work is licensed under CC BY-SA 4.0. To see a copy of this license,
|
.\" This work is licensed under CC BY-SA 4.0. To see a copy of this license,
|
||||||
.\" visit <http://creativecommons.org/licenses/by-sa/4.0/>.
|
.\" visit <http://creativecommons.org/licenses/by-sa/4.0/>.
|
||||||
.\"
|
.\"
|
||||||
.TH SCRUT 1 2024-06-06 "Harakit X.X.X"
|
.TH SCRUT 1 2024-07-18 "Harakit X.X.X"
|
||||||
.SH NAME
|
.SH NAME
|
||||||
scrut \(en scrutinize file properties
|
fileis \(en scrutinize file properties
|
||||||
.SH SYNOPSIS
|
.SH SYNOPSIS
|
||||||
|
|
||||||
scrut
|
fileis
|
||||||
.RB [ -LSbcdefgkprsuwx ]
|
.RB [ -LSbcdefgkprsuwx ]
|
||||||
.B file...
|
.B file...
|
||||||
.\"
|
.\"
|
||||||
@ -82,5 +82,6 @@ Copyright \(co 2024 DTB. License AGPLv3+: GNU AGPL version 3 or later
|
|||||||
.\"
|
.\"
|
||||||
.SH SEE ALSO
|
.SH SEE ALSO
|
||||||
.BR access (3p),
|
.BR access (3p),
|
||||||
|
.BR chown (1p),
|
||||||
.BR lstat (3p),
|
.BR lstat (3p),
|
||||||
.BR test (1p)
|
.BR test (1p)
|
91
src/fileis.rs
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2024 DTB <trinity@trinity.moe>
|
||||||
|
* Copyright (c) 2024 Emma Tebibyte <emma@tebibyte.media>
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify it under
|
||||||
|
* the terms of the GNU Affero General Public License as published by the Free
|
||||||
|
* Software Foundation, either version 3 of the License, or (at your option) any
|
||||||
|
* later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||||
|
* details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see https://www.gnu.org/licenses/.
|
||||||
|
*/
|
||||||
|
|
||||||
|
use std::{
|
||||||
|
env::args,
|
||||||
|
fs::metadata,
|
||||||
|
os::unix::fs::{ FileTypeExt, MetadataExt },
|
||||||
|
process::ExitCode,
|
||||||
|
};
|
||||||
|
|
||||||
|
extern crate getopt;
|
||||||
|
extern crate strerror;
|
||||||
|
extern crate sysexits;
|
||||||
|
|
||||||
|
use getopt::GetOpt;
|
||||||
|
use strerror::StrError;
|
||||||
|
use sysexits::EX_USAGE;
|
||||||
|
|
||||||
|
const OPTS: &str = "bcdefgkprsuwxLS";
|
||||||
|
|
||||||
|
fn usage(argv0: &str) -> ExitCode {
|
||||||
|
eprintln!("Usage: {} [-{}] file...", argv0, OPTS);
|
||||||
|
ExitCode::from(EX_USAGE)
|
||||||
|
}
|
||||||
emma marked this conversation as resolved
Outdated
|
|||||||
|
|
||||||
|
fn main() -> ExitCode {
|
||||||
|
let argv = args().collect::<Vec<_>>();
|
||||||
|
|
||||||
|
let mut sel = String::with_capacity(OPTS.len()); // selected options
|
||||||
|
let mut optind: usize = 1; // argv[0]
|
||||||
|
|
||||||
|
while let Some(opt) = argv.getopt(OPTS) {
|
||||||
|
if let Ok(optchr) = opt.opt() { sel.push_str(optchr); }
|
||||||
|
else { return usage(&argv[0]); }
|
||||||
|
|
||||||
|
optind = opt.ind();
|
||||||
|
}
|
||||||
|
|
||||||
|
if optind == argv.len() { return usage(&argv[0]); }
|
||||||
|
|
||||||
|
for arg in argv.iter().skip(optind) {
|
||||||
|
let fmeta = match metadata(arg) {
|
||||||
trinity marked this conversation as resolved
trinity
commented
I would rather have declarations precede assignments here. I understand assignment on declaration is idiomatic in Rust. Python, in fact, due to its type system, mandated it. But I've been forced to separate them (often) in C, and, having used both styles, prefer the separation for clarity of code. I would rather have declarations precede assignments here. I understand assignment on declaration is idiomatic in Rust. Python, in fact, due to its type system, mandated it. But I've been forced to separate them (often) in C, and, having used both styles, prefer the separation for clarity of code.
emma
commented
The separation decreases clarity, as it separates the context from the declaration of the variable. Separation should occur only when necessary, in my opinion. The separation decreases clarity, as it separates the context from the declaration of the variable. Separation should occur only when necessary, in my opinion.
silt
commented
firmly siding with emma on this. declaring variables at the top of the scope is an archaic convention imposed by the technical limitations of a bygone era. variables should, for readability's sake, generally be declared either near or at the site of their assignment, not way up at the top of the function (and in an entirely different scope!!). firmly siding with emma on this. declaring variables at the top of the scope is an archaic convention imposed by the technical limitations of a bygone era. variables should, for readability's sake, generally be declared either near or at the site of their assignment, not way up at the top of the function (and in an entirely different scope!!).
the note about scoping is actually more significant now that we're arguing about rust instead of c. the rust compiler is able to do a far better job at optimization if you stick to sane conventions and, as far as i'm aware (and please do correct me if i'm mistaken), this is one of those cases where things end up better-optimized if you write normal rust code instead of fortran.
trinity
commented
I disagree with the form but can't help agreeing with the compilation benefits. I disagree with the form but can't help agreeing with the compilation benefits.
|
|||||||
|
Ok(m) => m,
|
||||||
|
Err(e) => { // no perms or nonexistent
|
||||||
|
eprintln!("{}: {}: {}", argv[0], arg, e.strerror());
|
||||||
|
return ExitCode::FAILURE;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
let fmode = fmeta.mode();
|
||||||
|
let ftype = fmeta.file_type();
|
||||||
|
|
||||||
|
for selection in sel.chars() { // run all selected tests
|
||||||
|
match selection {
|
||||||
|
'b' if ftype.is_block_device() => (),
|
||||||
|
'c' if ftype.is_char_device() => (),
|
||||||
|
'e' => (), // exists or metadata would have errored
|
||||||
|
'd' if fmeta.is_dir() => (),
|
||||||
|
'f' if fmeta.is_file() => (),
|
||||||
|
'g' if fmode & 0o2000 /* S_ISGID */ != 0 => (), // setgid
|
||||||
|
'k' if fmode & 0o1000 /* S_ISVTX */ != 0 => (), // setvtx
|
||||||
|
'p' if ftype.is_fifo() => (),
|
||||||
|
'r' if fmode & 0o0400 /* S_IRUSR */ != 0 => (), // read access
|
||||||
|
'u' if fmode & 0o4000 /* S_ISUID */ != 0 => (), // setuid
|
||||||
|
'w' if fmode & 0o0200 /* S_IWUSR */ != 0 => (), // write access
|
||||||
|
'x' if fmode & 0o0100 /* S_IXUSR */ != 0 => (), // exec access
|
||||||
|
'L' if fmeta.is_symlink() => (),
|
||||||
|
'S' if ftype.is_socket() => (),
|
||||||
|
_ => { return ExitCode::FAILURE; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ExitCode::SUCCESS
|
||||||
|
}
|
107
src/scrut.c
@ -1,107 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2023–2024 DTB <trinity@trinity.moe>
|
|
||||||
* Copyright (c) 2024 Emma Tebibyte <emma@tebibyte.media>
|
|
||||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify it under
|
|
||||||
* the terms of the GNU Affero General Public License as published by the Free
|
|
||||||
* Software Foundation, either version 3 of the License, or (at your option) any
|
|
||||||
* later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
|
||||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
|
||||||
* FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
|
||||||
* details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU Affero General Public License
|
|
||||||
* along with this program. If not, see https://www.gnu.org/licenses/.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <stdio.h> /* fprintf(3), stderr, NULL */
|
|
||||||
#include <stdlib.h> /* EXIT_FAILURE, EXIT_SUCCESS */
|
|
||||||
#include <string.h> /* memset(3), strchr(3) */
|
|
||||||
#ifndef EX_USAGE
|
|
||||||
# include <sysexits.h>
|
|
||||||
#endif
|
|
||||||
#include <unistd.h> /* access(3), getopt(3), F_OK, R_OK, W_OK, X_OK */
|
|
||||||
#include <sys/stat.h> /* lstat(3), stat struct, S_ISBLK, S_ISCHR, S_ISDIR,
|
|
||||||
* S_ISFIFO, S_ISGID, S_ISREG, S_ISLNK, S_ISSOCK,
|
|
||||||
* S_ISUID, S_ISVTX */
|
|
||||||
|
|
||||||
static char args[] = "bcdefghkprsuwxLS";
|
|
||||||
static char ops[(sizeof args) / (sizeof *args)];
|
|
||||||
static char *program_name = "scrut";
|
|
||||||
|
|
||||||
int main(int argc, char *argv[]){
|
|
||||||
struct stat buf;
|
|
||||||
int c;
|
|
||||||
size_t i;
|
|
||||||
char *p;
|
|
||||||
|
|
||||||
if(argc < 2)
|
|
||||||
goto usage;
|
|
||||||
|
|
||||||
memset(ops, '\0', sizeof ops);
|
|
||||||
while((c = getopt(argc, argv, args)) != -1)
|
|
||||||
if((p = strchr(args, c)) == NULL)
|
|
||||||
goto usage;
|
|
||||||
else
|
|
||||||
ops[p - args] = c;
|
|
||||||
/* straighten out ops */
|
|
||||||
for(i = 0, p = ops; i < (sizeof ops) / (sizeof *ops); ++i)
|
|
||||||
if(ops[i] != '\0'){
|
|
||||||
*p = ops[i];
|
|
||||||
if(&ops[i] != p++)
|
|
||||||
ops[i] = '\0';
|
|
||||||
}
|
|
||||||
|
|
||||||
if(optind == argc)
|
|
||||||
goto usage;
|
|
||||||
|
|
||||||
argv += optind;
|
|
||||||
do{ if(access(*argv, F_OK) != 0 || lstat(*argv, &buf) == -1)
|
|
||||||
return EXIT_FAILURE; /* doesn't exist or isn't stattable */
|
|
||||||
|
|
||||||
for(i = 0; ops[i] != '\0'; ++i)
|
|
||||||
if(ops[i] == 'e')
|
|
||||||
continue;
|
|
||||||
else if(ops[i] == 'h'){
|
|
||||||
usage: fprintf(stderr, "Usage: %s [-%s] file...\n",
|
|
||||||
argv[0] == NULL
|
|
||||||
? program_name
|
|
||||||
: argv[0],
|
|
||||||
args);
|
|
||||||
|
|
||||||
return EX_USAGE;
|
|
||||||
}else if(
|
|
||||||
(ops[i] == 'b'
|
|
||||||
&& !S_ISBLK(buf.st_mode))
|
|
||||||
|| (ops[i] == 'c'
|
|
||||||
&& !S_ISCHR(buf.st_mode))
|
|
||||||
|| (ops[i] == 'd'
|
|
||||||
&& !S_ISDIR(buf.st_mode))
|
|
||||||
|| (ops[i] == 'f'
|
|
||||||
&& !S_ISREG(buf.st_mode))
|
|
||||||
|| (ops[i] == 'g'
|
|
||||||
&& !(buf.st_mode & S_ISGID))
|
|
||||||
|| (ops[i] == 'k'
|
|
||||||
&& !(buf.st_mode & S_ISVTX))
|
|
||||||
|| (ops[i] == 'p'
|
|
||||||
&& !S_ISFIFO(buf.st_mode))
|
|
||||||
|| (ops[i] == 'r'
|
|
||||||
&& access(*argv, R_OK) != 0)
|
|
||||||
|| (ops[i] == 'u'
|
|
||||||
&& !(buf.st_mode & S_ISUID))
|
|
||||||
|| (ops[i] == 'w'
|
|
||||||
&& access(*argv, W_OK) != 0)
|
|
||||||
|| (ops[i] == 'x'
|
|
||||||
&& access(*argv, X_OK) != 0)
|
|
||||||
|| (ops[i] == 'L'
|
|
||||||
&& !S_ISLNK(buf.st_mode))
|
|
||||||
|| (ops[i] == 'S'
|
|
||||||
&& !S_ISSOCK(buf.st_mode)))
|
|
||||||
return EXIT_FAILURE;
|
|
||||||
}while(*++argv != NULL);
|
|
||||||
|
|
||||||
return EXIT_SUCCESS;
|
|
||||||
}
|
|
let mut sel = String::new();