1
0
forked from bonsai/harakit

Compare commits

...

28 Commits

Author SHA1 Message Date
3392d64e44 fop(1): literal record separator 2024-06-05 17:25:22 -06:00
2dd6c0ded7 fop(1): adds the ability to use any string as a delimiter 2024-06-02 19:05:30 -06:00
9093b06166 Merge branch 'manpage-install' 2024-04-27 12:58:50 -06:00
DTB
6132c9bf47 Merge branch 'main' into scrut-sysexits 2024-04-26 19:37:34 -06:00
DTB
919b171400 scrut(1): conditionally include non-POSIX sysexits.h 2024-04-26 19:35:33 -06:00
DTB
8e38db92c7 scrut(1): update copyright header 2024-04-26 19:33:45 -06:00
DTB
adc9dbded5 scrut(1): changed return status 1 to EXIT_FAILURE 2024-04-26 19:30:33 -06:00
488351da39 scrut(1): changed return status 0 to EXIT_SUCCESS 2024-04-25 16:46:05 -06:00
d8b54fdbf5 Makefile: fixes erroneous whitespace 2024-04-25 12:43:31 -06:00
f6aac60aee Makefile: fixed manpage install location 2024-04-25 12:40:20 -06:00
DTB
61382c34d9 mm(1): error out when given positional arguments 2024-03-31 22:54:03 -06:00
8d9ac33566 Merge branch 'strerror' 2024-03-29 19:23:27 -06:00
b503d97625 Makefile: directory specification changes 2024-03-29 19:22:55 -06:00
b562898a74 copyright 2024-03-27 09:21:19 +05:45
d05d2fae05 Makefile: add a missing build target for npc(1) 2024-03-26 17:19:21 +05:45
127192185f Merge branch 'dj-fix' (closes #66) 2024-03-18 21:01:48 -06:00
f81232685a Merge branch 'mm' 2024-03-18 20:56:06 -06:00
05b5a4480c Merge branch 'swab' (closes #22) 2024-03-18 20:53:08 -06:00
DTB
135bf2a8eb mm(1), mm.1: bring source in line with documentation 2024-02-26 09:11:47 -07:00
DTB
d74bc715cf mm(1): fix full file handling 2024-02-26 09:02:58 -07:00
DTB
f14877118d Makefile: add swab(1) 2024-02-26 08:43:03 -07:00
DTB
bbac85daf8 swab(1): add copyright notice 2024-02-26 08:42:06 -07:00
DTB
1e041a52a2 swab.1: add swab(1) man page 2024-02-24 03:21:20 -07:00
DTB
e788947fc4 swab(1): add argument parsing 2024-02-24 03:04:05 -07:00
DTB
3f0d95fe8f swab(1): minimum viable program 2024-02-23 20:49:24 -07:00
DTB
58245c9484 dj(1): remove function pointer hijinks (fixes #66) 2024-02-22 19:27:14 -07:00
DTB
395205d4c6 mm.1: fix case in copyright thingy 2024-02-22 19:14:38 -07:00
DTB
194b19f94b mm(1): add 2024-02-17 23:43:22 -07:00
8 changed files with 539 additions and 49 deletions

View File

@@ -1,6 +1,7 @@
# Copyright (c) 20232024 Emma Tebibyte <emma@tebibyte.media> # Copyright (c) 20232024 Emma Tebibyte <emma@tebibyte.media>
# Copyright (c) 20232024 DTB <trinity@trinity.moe> # Copyright (c) 20232024 DTB <trinity@trinity.moe>
# Copyright (c) 2023 Sasha Koshka <sashakoshka@tebibyte.media> # Copyright (c) 2023 Sasha Koshka <sashakoshka@tebibyte.media>
# Copyright (c) 2024 Aaditya Aryal <aryalaadi123@gmail.com>
# SPDX-License-Identifier: FSFAP # SPDX-License-Identifier: FSFAP
# #
# Copying and distribution of this file, with or without modification, are # Copying and distribution of this file, with or without modification, are
@@ -13,21 +14,23 @@
.PRAGMA: posix_202x # future POSIX standard support à la pdpmake(1) .PRAGMA: posix_202x # future POSIX standard support à la pdpmake(1)
.PRAGMA: command_comment # breaks without this? .PRAGMA: command_comment # breaks without this?
DESTDIR=./dist DESTDIR ?= dist
PREFIX=/usr/local PREFIX ?= /usr/local
SYSEXITS!=printf '\043include <sysexits.h>\n' | cpp -M - | sed 's/ /\n/g' \ MANDIR != [ $(PREFIX) = / ] && printf '/usr/share/man\n' \
| sed -n 's/sysexits\.h//p' || printf 'include/\n' || printf '/share/man\n'
SYSEXITS != printf '\043include <sysexits.h>\n' | cpp -M - | sed 's/ /\n/g' \
| sed -n 's/sysexits\.h//p' || printf 'include\n'
CC?=cc CC ?= cc
RUSTC?=rustc RUSTC ?= rustc
RUSTLIBS=--extern getopt=build/o/libgetopt.rlib \ RUSTLIBS = --extern getopt=build/o/libgetopt.rlib \
--extern sysexits=build/o/libsysexits.rlib \ --extern sysexits=build/o/libsysexits.rlib \
--extern strerror=build/o/libstrerror.rlib --extern strerror=build/o/libstrerror.rlib
CFLAGS+=-I$(SYSEXITS) CFLAGS += -I$(SYSEXITS)
.PHONY: all .PHONY: all
all: dj false fop hru intcmp rpn scrut str strcmp true all: dj false fop hru intcmp mm npc rpn scrut str strcmp swab true
build: build:
# keep build/include until bindgen(1) has stdin support # keep build/include until bindgen(1) has stdin support
@@ -36,12 +39,12 @@ build:
.PHONY: clean .PHONY: clean
clean: clean:
rm -rf build/ dist/ rm -rf build dist
dist: all dist: all
mkdir -p $(DESTDIR)$(PREFIX)/bin $(DESTDIR)$(PREFIX)/share/man/man1 mkdir -p $(DESTDIR)/$(PREFIX)/bin $(DESTDIR)/$(PREFIX)/share/man/man1
cp build/bin/* $(DESTDIR)$(PREFIX)/bin/ cp build/bin/* $(DESTDIR)/$(PREFIX)/bin
cp docs/*.1 $(DESTDIR)$(PREFIX)/share/man/man1/ cp docs/*.1 $(DESTDIR)/$(PREFIX)/$(MANDIR)/man1
.PHONY: install .PHONY: install
install: dist install: dist
@@ -96,6 +99,16 @@ intcmp: build/bin/intcmp
build/bin/intcmp: src/intcmp.c build build/bin/intcmp: src/intcmp.c build
$(CC) $(CFLAGS) -o $@ src/intcmp.c $(CC) $(CFLAGS) -o $@ src/intcmp.c
.PHONY: mm
mm: build/bin/mm
build/bin/mm: src/mm.c build
$(CC) $(CFLAGS) -o $@ src/mm.c
.PHONY: npc
npc: build/bin/npc
build/bin/npc: src/npc.c build
$(CC) $(CFLAGAS) -o $@ src/npc.c
.PHONY: rpn .PHONY: rpn
rpn: build/bin/rpn rpn: build/bin/rpn
build/bin/rpn: src/rpn.rs build rustlibs build/bin/rpn: src/rpn.rs build rustlibs
@@ -116,6 +129,13 @@ strcmp: build/bin/strcmp
build/bin/strcmp: src/strcmp.c build build/bin/strcmp: src/strcmp.c build
$(CC) $(CFLAGS) -o $@ src/strcmp.c $(CC) $(CFLAGS) -o $@ src/strcmp.c
.PHONY: swab
swab: build/bin/swab
build/bin/swab: src/swab.rs build build/o/libsysexits.rlib
$(RUSTC) $(RUSTFLAGS) --extern getopt=build/o/libgetopt.rlib \
--extern sysexits=build/o/libsysexits.rlib \
-o $@ src/swab.rs
.PHONY: true .PHONY: true
true: build/bin/true true: build/bin/true
build/bin/true: src/true.c build build/bin/true: src/true.c build

76
docs/mm.1 Normal file
View File

@@ -0,0 +1,76 @@
.\" Copyright (c) 2024 DTB <trinity@trinity.moe>
.\"
.\" 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/>.
.TH mm 1
.SH NAME
mm \(en middleman
.SH SYNOPSIS
mm
.RB ( -aenu )
.RB ( -i
.RB [ input ])
.RB ( -o
.RB [ output ])
.SH DESCRIPTION
Mm catenates input files and writes them to the start of each output file.
.SH OPTIONS
Mm, upon receiving the
.B -a
option, will open subsequent outputs for appending rather than updating.
.PP
The
.B -i
option opens a path as an input. Without any inputs specified mm will use
standard input. Standard input itself can be specified by giving the path '-'.
.PP
The
.B -o
option opens a path as an output. Without any outputs specified mm will use
standard output. Standard output itself can be specified by giving the
path '-'. Standard error itself can be specified with the
.B -e
option.
.PP
The
.B -u
option ensures neither input or output will be buffered.
.PP
The
.B -n
option tells mm to ignore SIGINT signals.
.SH DIAGNOSTICS
If an output can no longer be written mm prints a diagnostic message, ceases
writing to that particular output, and if there are more outputs specified,
continues, eventually exiting unsuccessfully.
.PP
On error mm prints a diagnostic message and exits with the appropriate
sysexits.h(3) status.
.SH BUGS
Mm does not truncate existing files, which may lead to unexpected results.
.SH RATIONALE
Mm was modeled after the cat and tee utilities specified in POSIX.
.SH COPYRIGHT
Copyright (c) 2024 DTB. License AGPLv3+: GNU AGPL version 3 or later
<https://gnu.org/licenses/agpl.html>.
.SH SEE ALSO
cat(1p), dd(1), dj(1), tee(1p)

71
docs/swab.1 Normal file
View File

@@ -0,0 +1,71 @@
.\" Copyright (c) 2024 DTB <trinity@trinity.moe>
.\"
.\" 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/>.
.TH swab 1
.SH NAME
swab \(en swap bytes
.SH SYNOPSIS
swab
.RB ( -f )
.RB ( -w
.R [
.B word size
.R ])
.SH USAGE
Swab swaps the latter and former halves of a block of bytes.
.SH EXAMPLES
The following sh(1p) line:
.R printf 'hello world!\n' | swab
Produces the following output:
.R ehll oowlr!d
.SH OPTIONS
The
.B -f
option ignores system call interruptions.
.PP
The
.B -w
option configures the word size; that is, the size in bytes of the block size
on which to operate. By default the word size is 2. The word size must be
cleanly divisible by 2, otherwise the block of bytes being processed can't be
halved.
.SH DIAGNOSTICS
If an error is encountered in input, output, or invocation, a diagnostic
message will be written to standard error and swab will exit with the
appropriate status from sysexits.h(3).
.SH RATIONALE
Swab was modeled after the
.R conv=swab
functionality specified in the POSIX dd utility but additionally allows the
word size to be configured.
.PP
Swab is useful for fixing the endianness of binary files produced on other
machines.
.SH COPYRIGHT
Copyright (c) 2024 DTB. License AGPLv3+: GNU AGPL version 3 or later
<https://gnu.org/licenses/agpl.html>.
.SH SEE ALSO
dd(1p)

View File

@@ -203,28 +203,28 @@ Io_fdseek(struct Io *io){
if(!fdisstd(io->fd) && lseek(io->fd, io->seek, SEEK_SET) != -1) if(!fdisstd(io->fd) && lseek(io->fd, io->seek, SEEK_SET) != -1)
return -1; return -1;
else if(io->fl == write_flags){
memset(io->buf, '\0', io->bs);
/* This is a dirty trick; rather than testing conditions and operating
* likewise, because the parameters to read or write are going to be
* the same either way, just use a function pointer to keep track of
* the intended operation. */
op = (int (*)(int, void *, size_t))&write;
/* Function pointer casts are risky; this works because the difference
* is in the second parameter and only that write(2) makes the buffer
* const whereas read(2) does not. To avoid even the slightest
* undefined behavior comment out the cast, just be ready for a
* -Wincompatible-function-pointer-types if your compiler notices it.
*/
}else
op = &read;
/* We're going to cheat and use bufuse as the retval for write(2), which is /* repeated code to get the condition out of the loop */
* fine because it'll be zeroed as this function returns anyway. */ if(io->fl == write_flags){
do{ if( (io->bufuse = (*op)(io->fd, io->buf, MIN(io->bs, io->seek))) == 0) memset(io->buf, '\0', io->bs);
/* second chance */ /* We're going to cheat and use bufuse as the retval for write(2),
io->bufuse = (*op)(io->fd, io->buf, MIN(io->bs, io->seek)); * which is fine because it'll be zeroed as this function returns
}while((io->seek -= io->bufuse) > 0 && io->bufuse != 0); * anyway. */
do{
if((io->bufuse = write(io->fd, io->buf, MIN(io->bs, io->seek)))
== 0)
/* second chance */
io->bufuse = write(io->fd, io->buf, MIN(io->bs, io->seek));
}while((io->seek -= io->bufuse) > 0 && io->bufuse != 0);
}else if(io->fl == read_flags){
do{
if((io->bufuse = read(io->fd, io->buf, MIN(io->bs, io->seek)))
== 0)
/* second chance */
io->bufuse = read(io->fd, io->buf, MIN(io->bs, io->seek));
}while((io->seek -= io->bufuse) > 0 && io->bufuse != 0);
}else
return EX_SOFTWARE;
io->bufuse = 0; io->bufuse = 0;

View File

@@ -32,18 +32,12 @@ use sysexits::{ EX_DATAERR, EX_IOERR, EX_UNAVAILABLE, EX_USAGE };
fn main() { fn main() {
let argv = args().collect::<Vec<String>>(); let argv = args().collect::<Vec<String>>();
let mut d = '␞'; let mut d = 0x1E.to_string();
let mut arg_parser = Parser::new(&argv, "d:"); let mut arg_parser = Parser::new(&argv, "d:");
while let Some(opt) = arg_parser.next() { while let Some(opt) = arg_parser.next() {
match opt { match opt {
Ok(Opt('d', Some(arg))) => { Ok(Opt('d', Some(arg))) => d = arg,
let arg_char = arg.chars().collect::<Vec<char>>();
if arg_char.len() > 1 {
eprintln!("{}: {}: Not a character.", argv[0], arg);
exit(EX_USAGE);
} else { d = arg_char[0]; }
},
_ => {}, _ => {},
}; };
} }
@@ -63,7 +57,7 @@ fn main() {
let mut buf = String::new(); let mut buf = String::new();
let _ = stdin().read_to_string(&mut buf); let _ = stdin().read_to_string(&mut buf);
let mut fields = buf.split(d).collect::<Vec<&str>>(); let mut fields = buf.split(&d).collect::<Vec<&str>>();
let opts = argv let opts = argv
.iter() .iter()

236
src/mm.c Normal file
View File

@@ -0,0 +1,236 @@
/*
* Copyright (c) 2024 DTB <trinity@trinity.moe>
* 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 <errno.h> /* errno */
#include <signal.h> /* signal(2), SIG_ERR, SIG_IGN, SIGINT */
#include <stdio.h> /* fclose(3), fopen(3), fprintf(3), getc(3), putc(3),
* setvbuf(3), size_t, _IONBF, NULL */
#include <stdlib.h> /* free(3), realloc(3) */
#include <string.h> /* strcmp(3), strerror(3) */
#include <unistd.h> /* getopt(3) */
#if !defined EX_IOERR || !defined EX_OK || !defined EX_OSERR \
|| !defined EX_USAGE
# include <sysexits.h>
#endif
extern int errno;
/* This structure is how open files are tracked. */
struct Files{
size_t a; /* allocation */
size_t s; /* used size */
char *mode; /* file opening mode */
char **names; /* file names */
FILE **files; /* file pointers */
};
/* How much to grow the allocation when it's saturated. */
#ifndef ALLOC_INCREMENT
# define ALLOC_INCREMENT 1
#endif
/* How much to grow the allocation at program start. */
#ifndef ALLOC_INITIAL
# define ALLOC_INITIAL 10
#endif
/* pre-allocated strings */
static char *program_name = "<no argv[0]>";
static char *stdin_name = "<stdin>";
static char *stdout_name = "<stdout>";
static char *stderr_name = "<stderr>";
static char *(fmode[]) = { (char []){"rb"}, (char []){"rb+"} };
static char *wharsh = "wb";
/* Adds the open FILE pointer for the file at the path s to the files struct,
* returning the FILE if successful and NULL if not, allocating more memory in
* the files buffers as needed. */
static FILE *
Files_append(struct Files *files, FILE *file, char *name){
if(file == NULL || (files->s == files->a
&& ((files->files = realloc(files->files,
(files->a += (files->a == 0)
? ALLOC_INITIAL
: ALLOC_INCREMENT)
* sizeof *(files->files))) == NULL
|| (files->names = realloc(files->names,
files->a * sizeof *(files->names))) == NULL)))
return NULL;
files->names[files->s] = name;
return files->files[files->s++] = file;
}
/* Opens the file at the path p and puts it in the files struct, returning NULL
* if either the opening or the placement of the open FILE pointer fail. */
#define Files_open(files, p) \
Files_append((files), fopen((p), (files)->mode), (p))
/* Prints a diagnostic message based on errno and returns an exit status
* appropriate for an OS error. */
static int
oserr(char *s, char *r){
fprintf(stderr, "%s: %s: %s\n", s, r, strerror(errno));
return EX_OSERR;
}
/* Hijacks i and j from main and destructs the files[2] struct used by main by
* closing its files and freeing its files and names arrays, returning retval
* from main. */
#define terminate \
for(i = 0; i < 2; ++i){ \
for(j = 0; j < files[i].s; ++j) \
if(files[i].files[j] != stdin \
&& files[i].files[j] != stdout \
&& files[i].files[j] != stderr) \
fclose(files[i].files[j]); \
free(files[i].files); \
free(files[i].names); \
} \
return retval
/* Prints a usage text, in which s is the program being run (i.e. argv[0]), and
* returns an exit status appropriate for a usage error. */
int usage(char *s){
fprintf(stderr, "Usage: %s (-aenu) (-i [input])... (-o [output])...\n", s);
return EX_USAGE;
}
int main(int argc, char *argv[]){
int c;
struct Files files[2]; /* {read, write} */
size_t i;
size_t j;
size_t k; /* loop index but also unbuffer status */
int retval;
/* Initializes the files structs with their default values, standard
* input and standard output. If an input or an output is specified
* these initial values will be overwritten, so to, say, use mm(1)
* equivalently to tee(1p), -o - will need to be specified before
* additional files to ensure standard output is still written. */
for(i = 0; i < 2; ++i){
files[i].a = 0;
files[i].s = 0;
files[i].mode = fmode[i];
files[i].files = NULL;
files[i].names = NULL;
Files_append(&files[i], i == 0 ? stdin : stdout,
i == 0 ? stdin_name : stdout_name);
files[i].s = 0;
}
k = 0;
if(argc > 0)
program_name = argv[0];
if(argc > 1)
while((c = getopt(argc, argv, "aehi:no:u")) != -1)
switch(c){
case 'a': /* "rb+" -> "ab" */
files[1].mode[0] = 'a';
files[1].mode[2] = '\0';
break;
case 'e':
if(Files_append(&files[1], stderr, stderr_name) != NULL)
break;
retval = oserr(argv[0], "-e");
terminate;
case 'i':
if((strcmp(optarg, "-") == 0 && Files_append(&files[0],
stdin, stdin_name) != NULL)
|| Files_open(&files[0], optarg) != NULL)
break;
retval = oserr(argv[0], optarg);
terminate;
case 'o':
if((strcmp(optarg, "-") == 0 && Files_append(&files[1],
stdout, stdout_name) != NULL)
|| Files_open(&files[1], optarg) != NULL)
break;
/* does not exist, so try to create it */
if(errno == ENOENT){
files[1].mode = wharsh;
if(Files_open(&files[1], optarg) != NULL){
files[1].mode = fmode[1];
break;
}
}
retval = oserr(argv[0], optarg);
terminate;
case 'n':
if(signal(SIGINT, SIG_IGN) != SIG_ERR)
break;
retval = oserr(argv[0], "-n");
terminate;
case 'u':
k = 1;
break;
default:
retval = usage(argv[0]);
terminate;
}
if(optind != argc){
retval = usage(argv[0]);
terminate;
}
files[0].s += files[0].s == 0;
files[1].s += files[1].s == 0;
/* Unbuffer files. */
if(k){
for(i = 0;
i < files[0].s;
setvbuf(files[0].files[i++], NULL, _IONBF, 0));
for(i = 0;
i < files[1].s;
setvbuf(files[1].files[i++], NULL, _IONBF, 0));
}
retval = EX_OK;
/* Actual program loop. */
for(i = 0; i < files[0].s; ++i) /* iterate ins */
while((c = getc(files[0].files[i])) != EOF) /* iterate chars */
for(j = 0; j < files[1].s; ++j) /* iterate outs */
if(putc(c, files[1].files[j]) == EOF){
/* notebook's full */
retval = EX_IOERR;
fprintf(stderr, "%s: %s: %s\n",
program_name, files[1].names[j], strerror(errno));
if(fclose(files[1].files[j]) == EOF)
fprintf(stderr, "%s: %s: %s\n",
program_name, files[1].names[j], strerror(errno));
/* massage out the tense muscle */
for(k = j--; k < files[1].s - 1; ++k){
files[1].files[k] = files[1].files[k+1];
files[1].names[k] = files[1].names[k+1];
}
if(--files[1].s == 0)
terminate;
}
terminate;
}

View File

@@ -1,5 +1,6 @@
/* /*
* Copyright (c) 2023 DTB <trinity@trinity.moe> * Copyright (c) 20232024 DTB <trinity@trinity.moe>
* Copyright (c) 2024 Emma Tebibyte <emma@tebibyte.media>
* SPDX-License-Identifier: AGPL-3.0-or-later * SPDX-License-Identifier: AGPL-3.0-or-later
* *
* This program is free software: you can redistribute it and/or modify it under * This program is free software: you can redistribute it and/or modify it under
@@ -17,13 +18,15 @@
*/ */
#include <stdio.h> /* fprintf(3), stderr, NULL */ #include <stdio.h> /* fprintf(3), stderr, NULL */
#include <stdlib.h> /* EXIT_FAILURE */ #include <stdlib.h> /* EXIT_FAILURE, EXIT_SUCCESS */
#include <string.h> /* memset(3), strchr(3) */ #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 <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, #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_ISFIFO, S_ISGID, S_ISREG, S_ISLNK, S_ISSOCK,
* S_ISUID, S_ISVTX */ * S_ISUID, S_ISVTX */
#include <sysexits.h>
static char args[] = "bcdefghkprsuwxLS"; static char args[] = "bcdefghkprsuwxLS";
static char ops[(sizeof args) / (sizeof *args)]; static char ops[(sizeof args) / (sizeof *args)];
@@ -57,7 +60,7 @@ int main(int argc, char *argv[]){
argv += optind; argv += optind;
do{ if(access(*argv, F_OK) != 0 || lstat(*argv, &buf) == -1) do{ if(access(*argv, F_OK) != 0 || lstat(*argv, &buf) == -1)
return 1; /* doesn't exist or isn't stattable */ return EXIT_FAILURE; /* doesn't exist or isn't stattable */
for(i = 0; ops[i] != '\0'; ++i) for(i = 0; ops[i] != '\0'; ++i)
if(ops[i] == 'e') if(ops[i] == 'e')
@@ -97,8 +100,8 @@ usage: fprintf(stderr, "Usage: %s (-%s) [file...]\n",
&& !S_ISLNK(buf.st_mode)) && !S_ISLNK(buf.st_mode))
|| (ops[i] == 'S' || (ops[i] == 'S'
&& !S_ISSOCK(buf.st_mode))) && !S_ISSOCK(buf.st_mode)))
return 1; return EXIT_FAILURE;
}while(*++argv != NULL); }while(*++argv != NULL);
return 0; return EXIT_SUCCESS;
} }

90
src/swab.rs Normal file
View File

@@ -0,0 +1,90 @@
/*
* Copyright (c) 2024 DTB <trinity@trinity.moe>
* 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,
io::{ stdin, stdout, Error, ErrorKind, Read, Write },
process::ExitCode,
vec::Vec
};
extern crate getopt;
use getopt::{ Opt, Parser };
extern crate sysexits;
use sysexits::{ EX_OK, EX_OSERR, EX_USAGE };
fn oserr(s: &str, e: Error) -> ExitCode {
eprintln!("{}: {}", s, e);
ExitCode::from(EX_OSERR as u8)
}
fn usage(s: &str) -> ExitCode {
eprintln!("Usage: {} (-f) (-w [wordsize])", s);
ExitCode::from(EX_USAGE as u8)
}
fn main() -> ExitCode {
let argv = args().collect::<Vec<String>>();
let mut buf: Vec<u8> = Vec::new();
let mut input = stdin();
let mut output = stdout().lock();
let mut opts = Parser::new(&argv, "fw:");
let mut force = false;
let mut wordsize: usize = 2;
loop {
match opts.next() {
None => break,
Some(opt) =>
match opt {
Ok(Opt('f', None)) => force = true,
Ok(Opt('w', Some(arg))) => {
match arg.parse::<usize>() {
Ok(w) if w % 2 == 0 => { wordsize = w; () },
_ => { return usage(&argv[0]); },
}
},
_ => { return usage(&argv[0]); }
}
}
}
buf.resize(wordsize, 0);
loop {
match input.read(&mut buf) {
Ok(0) => break ExitCode::from(EX_OK as u8),
Ok(v) if v == wordsize => {
let (left, right) = buf.split_at(v/2);
if let Err(e) = output.write(&right)
.and_then(|_| output.write(&left)) {
break oserr(&argv[0], e)
}
},
Ok(v) => {
if let Err(e) = output.write(&buf[..v]) {
break oserr(&argv[0], e)
}
},
Err(e) if e.kind() == ErrorKind::Interrupted && force => continue,
Err(e) => break oserr(&argv[0], e)
}
}
}