Merge branch 'dj-formatting'

This commit is contained in:
Emma Tebibyte 2024-07-29 22:40:18 -06:00
commit 6a5739ea9d
Signed by: emma
GPG Key ID: 06FA419A1698C270
11 changed files with 631 additions and 375 deletions

124
STYLE Normal file
View File

@ -0,0 +1,124 @@
The following guidelines are conducive to clear and readable code that is
consistent with the style of the rest of the Bonsai Computer System.
0. Braces are mandatory for all control flow.
1. Nested indentation should be kept to a minimum.
2. Empty lines should be placed between different kinds of statements:
int t;
assert(io->bufuse > 0);
assert(io->bufuse <= io->bs);
if ((t = write(io->fd, io->buf, io->bufuse)) < 0) {
io->error = errno;
t = 0;
} else if (t > 0) {
memmove(io->buf, &(io->buf)[t], (io->bufuse -= t));
}
io->bytes += t;
io->prec += (t > 0 && io->bufuse > 0);
io->rec += (t > 0 && io->bufuse == 0);
return io;
3. Each block of code should be indented once more than the keyword which
initiated the block:
switch (c) {
case 'e': mode |= EQUAL; break;
case 'g': mode |= GREATER; break;
case 'l': mode |= LESS; break;
default: return usage(s);
}
4. In C, spaces should be placed in control flow statements after the keyword
and before the opening brace:
for (i = 2; i < argc; ++i) {
5. If a function, a C control flow statement, or a Rust macro has arguments that
cause the statement to be broken into multiple lines, this should be done by
placing the arguments on a new line inside the parentheses:
let usage = format!(
"Usage: {} [-d delimiter] index command [args...]",
argv[0],
);
6. If Rust function arguments or fields are on their own lines, they should
always have a trailing comma:
return Err(EvaluationError {
message: format!("{}: Invalid token", i),
code: EX_DATAERR,
})
7. If text is on the same line as a brace, spaces should be placed after an
opening curly brace and before a closing one:
use sysexits::{ EX_DATAERR, EX_IOERR, EX_UNAVAILABLE, EX_USAGE };
8. If a control flow statement is short enough to be easily understood in a
glance, it may be placed on a single line:
if !(argc < 0) { usage(program_name); }
9. In C, note everything you use from a library in a comment subsequent to its
#include statement:
#include <unistd.h> /* close(2), getopt(3), lseek(2), read(2), write(2),
* optarg, optind, STDIN_FILENO, STDOUT_FILENO */
10. In Rust, place extern statements after use statements that include standard
library crates. Group alike statements:
use std::fs::Path;
extern crate strerror;
extern crate sysexits;
use strerror::StrError;
use sysexits::{ EX_OSERR, EX_USAGE };
11. Do not use do while loops in C.
12. Adhere to the following rules from the paper The Power of 10: Rules for
Developing Safety-Critical Code [0]:
1. Avoid complex flow constructs, such as goto and recursion.
2. All loops must have fixed bounds. This prevents runaway code.
3. Avoid heap memory allocation.
4. Restrict functions to the length of a single printed page.
6. Restrict the scope of data to the smallest possible.
7. Check the return value of all non-void functions, or cast to void to
indicate the return value is useless (such as in the case of using
fprintf(3p) to print to the standard error).
8. Use the preprocessor sparingly.
9. Limit pointer use to a single dereference, and do not use function
pointers.
10. Compile with all possible warnings active; all warnings should then be
addressed before release of the software (for C compilers, compile with
-Wpedantic).
13. Remember this quote from The Elements of Programming Style by Brian
Kernighan:
Everyone knows that debugging is twice as hard as writing a program in the
first place. So if you're as clever as you can be when you write it, how
will you ever debug it?
References
==========
[0] <https://web.eecs.umich.edu/~imarkov/10rules.pdf>
--
Copyright © 2024 Emma Tebibyte <emma@tebibyte.media>
Copyright © Wikipedia contributors
This work is licensed under CC BY-SA 4.0. To view a copy of this license, visit
<http://creativecommons.org/licenses/by-sa/4.0/>.

View File

@ -4,7 +4,7 @@
.\" 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 DJ 1 2024-07-03 "Harakit X.X.X" .TH DJ 1 2024-07-14 "Harakit X.X.X"
.SH NAME .SH NAME
dj \(en disk jockey dj \(en disk jockey
.\" .\"
@ -56,9 +56,9 @@ bytes read to this point are discarded.
.IP \fB-o\fP .IP \fB-o\fP
Takes a file path as an argument and opens it for use as an output. Takes a file path as an argument and opens it for use as an output.
.IP \fB-B\fP\ \fIblock_size\fP .IP \fB-B\fP\ \fIblock_size\fP
Does the same as Takes a numeric argument as the size in bytes of the output buffer, the default
.B -b being 1024. Note that this option only affects the size of output writes and not
but for the output buffer. the amount of output data itself. See the CAVEATS section.
.IP \fB-S\fP .IP \fB-S\fP
Takes a numeric argument as the index of the byte at which writing will Takes a numeric argument as the index of the byte at which writing will
commence; \(lqseeks\(rq that number of bytes. If the standard output is used, commence; \(lqseeks\(rq that number of bytes. If the standard output is used,
@ -68,8 +68,8 @@ Accepts a single literal byte with which the input buffer is padded in the event
of an incomplete read from the input file. If the option argument is empty, the of an incomplete read from the input file. If the option argument is empty, the
null byte is used. null byte is used.
.IP \fB-c\fP .IP \fB-c\fP
Specifies a number of reads to make. The default is 0, in which case the Specifies a number of blocks to read. The default is 0, in which case the input
input is read until a partial or empty read is made. is read until a partial or empty read is made.
.IP \fB-H\fP .IP \fB-H\fP
Prints diagnostic messages in a human-readable manner as described in the Prints diagnostic messages in a human-readable manner as described in the
DIAGNOSTICS section. DIAGNOSTICS section.
@ -181,15 +181,22 @@ option is specified, this could make written data nonsensical.
Existing files are not truncated on ouput and are instead overwritten. Existing files are not truncated on ouput and are instead overwritten.
The options Option variants that have lowercase and uppercase forms could be confused for
.B -b each other. The former affects input and the latter affects output.
and
The
.B -B .B -B
could be confused for each other, and so could option could be mistaken for the count in bytes of data written to the output.
.B -s This conception is intuitive but incorrect, as the
and .B -c
.BR -S . option controls the number of blocks to read and the
The lowercase option affects input and the capitalized option affects output. .B -b
option sets the size of the blocks. The
.B -B
option is similar to the latter but sets the size of blocks to be written,
regardless of the amount of data that will actually be written. In practice,
this means the input buffer should be very large to make use of modern hardware
input and output speeds.
The skipped or sought bytes while processing irregular files, such as streams, The skipped or sought bytes while processing irregular files, such as streams,
are reported in the diagnostic output, because they were actually read or are reported in the diagnostic output, because they were actually read or
@ -216,3 +223,4 @@ Copyright \(co 2023 DTB. License AGPLv3+: GNU AGPL version 3 or later
.SH SEE ALSO .SH SEE ALSO
.BR dd (1p) .BR dd (1p)
.BR lseek (3p) .BR lseek (3p)
.BR mm (1)

370
src/dj.c
View File

@ -1,5 +1,6 @@
/* /*
* Copyright (c) 2024 DTB <trinity@trinity.moe> * Copyright (c) 2024 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
@ -19,26 +20,23 @@
#include <assert.h> /* assert(3) */ #include <assert.h> /* assert(3) */
#include <errno.h> /* errno */ #include <errno.h> /* errno */
#include <fcntl.h> /* open(2) */ #include <fcntl.h> /* open(2) */
#include <stdbool.h> /* bool */
#include <stdio.h> /* fprintf(3), stderr */ #include <stdio.h> /* fprintf(3), stderr */
#include <stdlib.h> /* malloc(3), strtol(3), size_t */ #include <stdlib.h> /* malloc(3), strtol(3), size_t */
#include <string.h> /* memcpy(3), memmove(3), memset(3) */ #include <string.h> /* memcpy(3), memmove(3), memset(3) */
#if !defined EX_OK || !defined EX_OSERR || !defined EX_USAGE #include <sysexits.h> /* EX_OK, EX_OSERR, EX_USAGE */
# include <sysexits.h>
#endif
#include <unistd.h> /* close(2), getopt(3), lseek(2), read(2), write(2), #include <unistd.h> /* close(2), getopt(3), lseek(2), read(2), write(2),
* optarg, optind, STDIN_FILENO, STDOUT_FILENO */ * optarg, optind, STDIN_FILENO, STDOUT_FILENO */
#include <sys/stat.h> /* S_IRGRP, S_IROTH, S_IRUSR, S_IWGRP, S_IWOTH, #include <sys/stat.h> /* S_IRGRP, S_IROTH, S_IRUSR, S_IWGRP, S_IWOTH, S_IWUSR */
S_IWUSR */
extern int errno;
char *program_name = "dj"; char *program_name = "dj";
/* dj uses two structures that respectively correspond to the reading and /* dj uses two structures that respectively correspond to the reading and
* writing ends of its jockeyed "pipe". User-configurable members are noted * writing ends of its jockeyed "pipe". User-configurable members are noted
* with their relevant options. */ * with their relevant options. */
struct Io{ struct Io {
char *buf; /* buffer */ char *buf; /* buffer */
char *fn; /* file name (-io) */ char *fn; /* file name (-io) */
size_t bs; /* buffer size (-bB) */ size_t bs; /* buffer size (-bB) */
size_t bufuse; /* buffer usage */ size_t bufuse; /* buffer usage */
size_t bytes; /* bytes processed */ size_t bytes; /* bytes processed */
@ -46,14 +44,10 @@ struct Io{
size_t rec; /* records processed */ size_t rec; /* records processed */
long seek; /* remaining bytes to seek/skip (-sS) */ long seek; /* remaining bytes to seek/skip (-sS) */
int error; /* errno */ int error; /* errno */
int fd; /* file descriptor */ int fd; /* file descriptor */
int fl; /* file opening flags */ int fl; /* file opening flags */
}; };
/* To be assigned to main:fmt and used with printio(). */
static char *fmt_asv = "%d\037%d\036%d\037%d\035%d\036%d\034";
static char *fmt_human = "%d+%d > %d+%d; %d > %d\n";
static char *stdin_name = "<stdin>"; static char *stdin_name = "<stdin>";
static char *stdout_name = "<stdout>"; static char *stdout_name = "<stdout>";
@ -67,14 +61,15 @@ static int write_flags = O_WRONLY | O_CREAT;
/* Macro to check if fd is stdin or stdout */ /* Macro to check if fd is stdin or stdout */
#define fdisstd(fd) ((fd) == STDIN_FILENO || (fd) == STDOUT_FILENO) #define fdisstd(fd) ((fd) == STDIN_FILENO || (fd) == STDOUT_FILENO)
/* Completes one Io block read */
static struct Io * static struct Io *
Io_read(struct Io *io){ Io_read(struct Io *io) {
int t; int t;
assert(io->bs > 0); assert(io->bs > 0);
assert(io->bufuse < io->bs); assert(io->bufuse < io->bs);
if((t = read(io->fd, &(io->buf)[io->bufuse], io->bs - io->bufuse)) < 0){ if ((t = read(io->fd, &(io->buf)[io->bufuse], io->bs - io->bufuse)) < 0) {
io->error = errno; io->error = errno;
t = 0; t = 0;
} }
@ -89,18 +84,20 @@ Io_read(struct Io *io){
return io; return io;
} }
/* Completes one Io block write */
static struct Io * static struct Io *
Io_write(struct Io *io){ Io_write(struct Io *io) {
int t; int t;
assert(io->bufuse > 0); assert(io->bufuse > 0);
assert(io->bufuse <= io->bs); assert(io->bufuse <= io->bs);
if((t = write(io->fd, io->buf, io->bufuse)) < 0){ if ((t = write(io->fd, io->buf, io->bufuse)) < 0) {
io->error = errno; io->error = errno;
t = 0; t = 0;
}else if(t > 0) } else if (t > 0) {
memmove(io->buf, &(io->buf)[t], (io->bufuse -= t)); (void)memmove(io->buf, &(io->buf)[t], (io->bufuse -= t));
}
io->bytes += t; io->bytes += t;
io->prec += (t > 0 && io->bufuse > 0); io->prec += (t > 0 && io->bufuse > 0);
@ -110,62 +107,79 @@ Io_write(struct Io *io){
} }
static int static int
oserr(char *e, int n){ oserr(char *e, int n) {
fprintf(stderr, "%s: %s: %s\n", program_name, e, strerror(n)); (void)fprintf(stderr, "%s: %s: %s\n", program_name, e, strerror(n));
return EX_OSERR; return EX_OSERR;
} }
/* Prints statistics regarding the use of dj, particularly partially and /* Prints statistics regarding the use of dj, particularly partially and
* completely read and written records. */ * completely read and written records. */
static void static int
fprintio(FILE *stream, char *fmt, struct Io io[2]){ fprintio(FILE *stream, char *fmt, struct Io io[2]) {
return fprintf(
fprintf(stream, fmt, stream,
io[0].rec, io[0].prec, io[1].rec, io[1].prec, fmt,
io[0].bytes, io[1].bytes); io[0].rec,
io[0].prec,
return; io[1].rec,
io[1].prec,
io[0].bytes,
io[1].bytes
);
} }
/* To be assigned to main:fmt and used with printio(). */
static char *fmt_asv =
"%d" /* io[0].rec */ "\037" /* ASCII US */
"%d" /* io[0].prec */ "\036" /* ASCII RS */
"%d" /* io[1].rec */ "\037" /* ASCII US */
"%d" /* io[1].prec */ "\035" /* ASCII GS */
"%d" /* io[0].bytes */ "\036" /* ASCII RS */
"%d" /* io[1].bytes */ "\034" /* ASCII FS */
"\n"
;
static char *fmt_human = "%d+%d > %d+%d; %d > %d\n";
/* Parses the string s to an integer, returning either the integer or in the /* Parses the string s to an integer, returning either the integer or in the
* case of an error a negative integer. This is used for argument parsing * case of an error a negative integer. This is used for argument parsing
* (e.g. -B [int]) in dj and no negative integer would be valid anyway. */ * (e.g. -B [int]) in dj and no negative integer would be valid anyway. */
static long static long
parse(char *s){ parse(char *s) {
long r; long r;
errno = 0; errno = 0;
r = strtol(s, &s, 0); r = strtol(s, &s, 0);
return (*s == '\0' /* no chars left unparsed */ && errno == 0) return (*s == '\0' /* no chars left unparsed */ && errno == 0) ? r : -1;
? r
: -1;
} }
static int static int
usage(char *s){ usage(char *argv0) {
(void)fprintf(
fprintf(stderr, "Usage: %s [-Hn] [-a byte] [-c count]\n" stderr,
"Usage: %s [-Hn] [-a byte] [-c count]\n"
"\t[-i file] [-b block_size] [-s offset]\n" "\t[-i file] [-b block_size] [-s offset]\n"
"\t[-o file] [-B block_size] [-S offset]\n", "\t[-o file] [-B block_size] [-S offset]\n",
program_name); argv0
);
return EX_USAGE; return EX_USAGE;
} }
int main(int argc, char *argv[]){ int main(int argc, char *argv[]) {
int align; /* low 8b used, negative if no alignment is being done */ int align; /* low 8b used, negative if no alignment is being done */
int count; /* 0 if dj(1) runs until no more reads are possible */ int count; /* -1 if dj(1) runs until no more reads are possible */
char *fmt; /* == fmt_asv (default) or fmt_human (-H) */ char *fmt; /* set to fmt_asv (default) or fmt_human (-H) */
size_t i; /* side of io being modified */ size_t i; /* side of io (in or out) being modified */
char noerror; /* 0=exits (default) 1=retries on partial reads or writes */ bool retry; /* false if exits on partial reads or writes */
struct Io io[2 /* { in, out } */]; struct Io io[2 /* { in, out } */];
/* Set defaults. */ /* Set defaults. */
align = -1; align = -1;
count = 0; count = -1;
fmt = fmt_asv; fmt = fmt_asv;
noerror = 0; retry = 0;
for(i = 0; i < (sizeof io) / (sizeof *io); ++i){ for (i = 0; i < (sizeof io) / (sizeof *io); ++i) {
io[i].bs = 1024 /* 1 KiB */; /* GNU dd(1) default; POSIX says 512B */ io[i].bs = 1024 /* 1 KiB */; /* GNU dd(1) default; POSIX says 512B */
io[i].bufuse = 0; io[i].bufuse = 0;
io[i].bytes = 0; io[i].bytes = 0;
@ -178,88 +192,113 @@ int main(int argc, char *argv[]){
io[i].seek = 0; io[i].seek = 0;
} }
if(argc > 0){ if (argc > 0) {
int c; int c;
program_name = argv[0]; program_name = argv[0];
while((c = getopt(argc, argv, ":a:b:B:c:i:hHns:S:o:")) != -1) while ((c = getopt(argc, argv, "a:b:B:c:i:hHns:S:o:")) != -1) {
switch(c){ switch (c) {
case 'i': case 'o': i = (c == 'o'); case 'i': case 'o': /* input, output */
if(optarg[0] == '-' && optarg[1] == '\0'){ /* optarg == "-" */ i = (c == 'o');
io[i].fd = i == 0 ? STDIN_FILENO : STDOUT_FILENO;
io[i].fn = i == 0 ? stdin_name : stdout_name;
break;
}else{
int fd;
if((fd = open(optarg, io[i].fl, creat_mode)) != -1 /* optarg == "-" (stdin/stdout) */
&& (fdisstd(io[i].fd) || close(io[i].fd) == 0)){ if (optarg[0] == '-' && optarg[1] == '\0') {
io[i].fd = fd; io[i].fd = i == 0 ? STDIN_FILENO : STDOUT_FILENO;
io[i].fn = optarg; io[i].fn = i == 0 ? stdin_name : stdout_name;
break;
} else {
int fd;
if (
(fd = open(optarg, io[i].fl, creat_mode)) != -1
&& (fdisstd(io[i].fd) || close(io[i].fd) == 0)
) {
io[i].fd = fd;
io[i].fn = optarg;
break;
}
}
return oserr(optarg, errno);
/* UNREACHABLE */
case 'n': retry = 1; break; /* retry failed reads once */
case 'H': fmt = fmt_human; break; /* human-readable output */
case 'a': /* input buffer padding */
if (optarg[0] == '\0' || optarg[1] == '\0') {
align = optarg[0];
break; break;
} }
} /* FALLTHROUGH */
return oserr(optarg, errno); case 'c': /* number of reads */
case 'n': noerror = 1; break; case 'b': case 'B': /* input/output block size */
case 'H': fmt = fmt_human; break; case 's': case 'S': /* (s)kip/(S)eek in input/output */
case 'a': if (c == 'c' && (count = parse(optarg)) >= 0) { break; }
if(optarg[0] == '\0' || optarg[1] == '\0'){
align = optarg[0]; i = (c >= 'A' && c <= 'Z');
break; c |= 0x20; /* 0b 0010 0000 (ASCII) make lowercase */
}
/* FALLTHROUGH */ if ( /* if -b or -s is parsed out correctly */
case 'c': case 'b': case 's': case 'B': case 'S': /* numbers */ (c == 'b' && (io[i].bs = parse(optarg)) > 0)
if(c == 'c' && (count = parse(optarg)) >= 0) || (c == 's' && (io[i].seek = parse(optarg)) >= 0)
break; ) { break; } /* don't error */
i = (c >= 'A' && c <= 'Z');
c |= 0x20 /* 0b 0010 0000 */; /* (ASCII) make lowercase */ /* FALLTHROUGH */
if((c == 'b' && (io[i].bs = parse(optarg)) > 0) default:
|| (c == 's' && (io[i].seek = parse(optarg)) >= 0)) return usage(program_name);
break;
/* FALLTHROUGH */
default:
return usage(program_name);
} }
}
} }
assert(io->fd != STDIN_FILENO || io->fl == read_flags); assert(io->fd != STDIN_FILENO || io->fl == read_flags);
assert(io->fd != STDOUT_FILENO || io->fl == write_flags); assert(io->fd != STDOUT_FILENO || io->fl == write_flags);
if(argc > optind) if (argc > optind) { return usage(program_name); }
return usage(program_name);
for(i = 0; i < (sizeof io) / (sizeof *io); ++i){ for (i = 0; i < (sizeof io) / (sizeof *io); ++i) {
/* buffer allocation */ /* buffer allocation */
if((io[i].buf = malloc(io[i].bs * (sizeof *(io[i].buf)))) == NULL){ if ((io[i].buf = malloc(io[i].bs * (sizeof *(io[i].buf)))) == NULL) {
fprintf(stderr, "%s: Failed to allocate %zd bytes\n", (void)fprintf(
program_name, io[i].bs); stderr, "%s: Failed to allocate %zd bytes\n",
program_name, io[i].bs
);
return EX_OSERR; return EX_OSERR;
} }
/* easy seeking */ /* easy seeking */
if(!fdisstd(io[i].fd) && lseek(io[i].fd, io[i].seek, SEEK_SET) != -1) if (!fdisstd(io[i].fd) && lseek(io[i].fd, io[i].seek, SEEK_SET) != -1) {
io[i].seek = 0; io[i].seek = 0;
}
} }
/* hard seeking */ assert(io[1].bufuse == 0); /* requirement for hard seeking */
if(io[1].seek > 0){
size_t t; /* hard seeking; t is io[1].bufuse, before Io_write subtracts from it */
do{ for(size_t t; io[1].seek > 0; io[1].seek -= (t - io[1].bufuse)) {
memset(io[1].buf, '\0', (void)memset(
(t = io[1].bufuse = MIN(io[1].bs, io[1].seek))); io[1].buf, '\0', /* set buf to all nulls */
if(Io_write(&io[1])->bufuse == t && !noerror && io[1].error == 0) (t = io[1].bufuse = MIN(io[1].bs, io[1].seek)) /* saturate block */
Io_write(&io[1]); /* second chance */ );
if(io[1].error != 0)
return oserr(io[1].fn, io[1].error); if (Io_write(&io[1])->bufuse == t && !retry && io[1].error == 0) {
}while((io[1].seek -= (t - io[1].bufuse)) > 0 && io[1].bufuse != t); (void)Io_write(&io[1]); /* second chance */
io[1].bufuse = 0; }
if (io[1].error != 0) { return oserr(io[1].fn, io[1].error); }
if (io[1].bufuse == t) { break; } /* all writes failed! */
} }
if(io[1].seek > 0){ io[1].bufuse = 0; /* reset after hard seek */
fprintio(stderr, fmt, io);
if (io[1].seek > 0) { /* hard seeking failed */
(void)fprintio(stderr, fmt, io);
return oserr(io[1].fn, errno); return oserr(io[1].fn, errno);
} }
do{ for ( ;
count == -1 || count > 0;
count -= (count != -1) /* decrement if counting */
) {
assert(io[0].bufuse == 0); assert(io[0].bufuse == 0);
{ /* read */ { /* read */
@ -267,87 +306,114 @@ int main(int argc, char *argv[]){
size_t t; size_t t;
/* hack to intentionally get a partial read from Io_read */ /* hack to intentionally get a partial read from Io_read */
if((skipping = MIN(io[0].seek, io[0].bs)) > 0) if ((skipping = MIN(io[0].seek, io[0].bs)) > 0) {
io[0].bufuse = io[0].bs - (size_t)skipping; io[0].bufuse = io[0].bs - (size_t)skipping;
}
t = io[0].bufuse; t = io[0].bufuse;
if(Io_read(&io[0])->bufuse == t && !noerror && io[0].error == 0) if (Io_read(&io[0])->bufuse == t && !retry && io[0].error == 0) {
Io_read(&io[0]); /* second chance */ (void)Io_read(&io[0]); /* second chance */
assert(io[0].bufuse >= t); }
if(io[0].bufuse == t) /* that's all she wrote */
break;
if(/* t < io[0].bufuse && */ io[0].bufuse < io[0].bs){ assert(io[0].bufuse >= t);
fprintf(stderr, "%s: Partial read:\n\t", program_name);
fprintio(stderr, fmt, io); if (io[0].bufuse == t) { break; } /* that's all she wrote */
if(!noerror)
count = 1; if (/* t < io[0].bufuse && */ io[0].bufuse < io[0].bs) {
if(align >= 0){ (void)fprintf(stderr, "%s: Partial read:\n\t", program_name);
(void)fprintio(stderr, fmt, io);
if (!retry) { count = 1; }
if (align >= 0) {
/* fill the rest of the ibuf with padding */ /* fill the rest of the ibuf with padding */
memset(&(io[0].buf)[io[0].bufuse], align, (void)memset(
io[0].bs - io[0].bufuse); &(io[0].buf)[io[0].bufuse],
align,
io[0].bs - io[0].bufuse
);
io->bufuse = io->bs; io->bufuse = io->bs;
} }
} }
if(skipping > 0){ if (skipping > 0) {
io[0].seek -= skipping; io[0].seek -= skipping;
io[0].bufuse = 0; io[0].bufuse = 0;
count += (count != 0); count += (count != -1); /* increment if counting */
continue; continue;
} }
} }
/* write */ assert(io[0].bufuse > 0);
do{
int t;
if(io[0].bs <= io[1].bs){ while (io[0].bufuse > 0) { /* write */
if (io[0].bs <= io[1].bs) {
int n; int n;
/* saturate obuf */ (void)memcpy( /* saturate obuf */
memcpy(io[1].buf, io[0].buf, io[1].buf, io[0].buf,
(io[1].bufuse = (n = MIN(io[0].bufuse, io[1].bs)))); (io[1].bufuse = (n = MIN(io[0].bufuse, io[1].bs)))
);
/* permute the copied units out of ibuf */ /* permute the copied units out of ibuf */
memmove(io[0].buf, &(io[0].buf)[n], (io[0].bufuse -= n)); (void)memmove(io[0].buf, &(io[0].buf)[n], (io[0].bufuse -= n));
}else /* if(io[0].bs < io[1].bs) */ { } else /* if(io[0].bs > io[1].bs) */ {
int n; int n;
/* drain what we can from ibuf */ /* drain what we can from ibuf */
memcpy(&(io[1].buf)[io[1].bufuse], io[0].buf, (void)memcpy(
(n = MIN(io[0].bufuse, io[1].bs - io[1].bufuse))); &(io[1].buf)[io[1].bufuse], io[0].buf,
(n = MIN(io[0].bufuse, io[1].bs - io[1].bufuse))
);
io[1].bufuse += n; io[1].bufuse += n;
/* permute out the copied units */ /* permute out the copied units */
memmove(io[0].buf, &(io[0].buf)[n], io[0].bs - n); (void)memmove(io[0].buf, &(io[0].buf)[n], io[0].bs - n);
io[0].bufuse -= n; io[0].bufuse -= n;
if(io[0].bs + io[1].bufuse <= io[1].bs && count != 1) if(io[0].bs + io[1].bufuse <= io[1].bs && count != 1) {
continue; /* obuf not saturated - we could write more */ continue; /* obuf not saturated - we could write more */
}
} }
t = io[1].bufuse; { /* writes actually happen, or die */
if(Io_write(&io[1])->bufuse == t && !noerror && io[1].error == 0) size_t t;
Io_write(&io[1]); /* second chance */
assert(io[1].bufuse <= t);
if(io[1].bufuse == t){ /* no more love */
count = 1;
break;
}
if(0 < io[1].bufuse /* && io[1].bufuse < t */){ t = io[1].bufuse;
fprintf(stderr, "%s: Partial write:\n\t", program_name); if (Io_write(&io[1])->bufuse == t
fprintio(stderr, fmt, io); && !retry
if(!noerror) && io[1].error == 0) {
(void)Io_write(&io[1]); /* second chance */
}
assert(io[1].error == 0 || io[1].bufuse == t);
/* if the Io_writes errored, bufuse wouldn't have changed, and
* the error will be reported at the end of the read/write
* loop */
assert(io[1].bufuse <= t);
if (io[1].bufuse == t) { /* no more love */
count = 1; count = 1;
break;
}
} }
}while(io[0].bufuse > 0);
}while(count == 0 || --count > 0);
fprintio(stderr, fmt, io); if (0 < io[1].bufuse /* && io[1].bufuse < t */) {
(void)fprintf(stderr, "%s: Partial write:\n\t", program_name);
(void)fprintio(stderr, fmt, io);
for(i = 0; i < (sizeof io) / (sizeof *io); ++i) if(!retry) { count = 1; }
if(io[i].error) }
return oserr(io[i].fn, io[i].error); }
}
(void)fprintio(stderr, fmt, io);
for (i = 0; i < (sizeof io) / (sizeof *io); ++i) {
if (io[i].error) { return oserr(io[i].fn, io[i].error); }
}
return EX_OK; return EX_OK;
} }

View File

@ -32,8 +32,8 @@ 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 = '\u{1E}'.to_string(); let mut d = '\u{1E}'.to_string(); /* ASCII record separator */
let mut index_arg = 0; let mut optind = 1;
let usage = format!( let usage = format!(
"Usage: {} [-d delimiter] index command [args...]", "Usage: {} [-d delimiter] index command [args...]",
@ -42,51 +42,62 @@ fn main() {
while let Some(opt) = argv.getopt("d:") { while let Some(opt) = argv.getopt("d:") {
match opt.opt() { match opt.opt() {
Ok(_) => { Ok("d") => {
/* unwrap because Err(OptError::MissingArg) will be returned if /* delimiter */
* opt.arg() is None */
d = opt.arg().unwrap(); d = opt.arg().unwrap();
index_arg = opt.ind(); optind = opt.ind();
}, },
Err(_) => { _ => {
eprintln!("{}", usage); eprintln!("{}", usage);
exit(EX_USAGE); exit(EX_USAGE);
} }
}; };
} }
let command_arg = index_arg as usize + 1; /* parse the specified index as a number we can use */
let index = argv[optind].parse::<usize>().unwrap_or_else(|e| {
argv.get(command_arg).unwrap_or_else(|| {
eprintln!("{}", usage);
exit(EX_USAGE);
});
let index = argv[index_arg].parse::<usize>().unwrap_or_else(|e| {
eprintln!("{}: {}: {}", argv[0], argv[1], e); eprintln!("{}: {}: {}", argv[0], argv[1], e);
exit(EX_DATAERR); exit(EX_DATAERR);
}); });
/* index of the argv[0] for the operator command */
let command_arg = optind as usize + 1;
/* argv[0] of the operator command */
let operator = argv.get(command_arg).unwrap_or_else(|| {
eprintln!("{}", usage);
exit(EX_USAGE);
});
/* read entire standard input into memory */
let mut buf = String::new(); let mut buf = String::new();
let _ = stdin().read_to_string(&mut buf); if let Err(e) = stdin().read_to_string(&mut buf) {
eprintln!("{}: {}", argv[0], e.strerror());
exit(EX_IOERR);
};
/* split the buffer by the delimiter (by default, '\u{1E}') */
let mut fields = buf.split(&d).collect::<Vec<&str>>(); let mut fields = buf.split(&d).collect::<Vec<&str>>();
let opts = argv /* collect arguments for the operator command */
let command_args = argv
.iter() .iter()
.clone() .clone()
.skip(command_arg + 1) .skip(command_arg + 1) /* skip the command name */
.collect::<Vec<&String>>(); .collect::<Vec<&String>>();
let mut spawned = Command::new(argv.get(command_arg).unwrap()) /* spawn the command to operate on the field */
.args(opts) let mut spawned = Command::new(operator)
.args(command_args) /* spawn with the specified arguments */
.stdin(Stdio::piped()) .stdin(Stdio::piped())
.stdout(Stdio::piped()) .stdout(Stdio::piped()) /* piped stdout to handle output ourselves */
.spawn() .spawn()
.unwrap_or_else( |e| { .unwrap_or_else( |e| {
eprintln!("{}: {}: {}", argv[0], argv[command_arg], e.strerror()); eprintln!("{}: {}: {}", argv[0], argv[command_arg], e.strerror());
exit(EX_UNAVAILABLE); exit(EX_UNAVAILABLE);
}); });
/* get field we want to pipe into spawned program */
let field = fields.get(index).unwrap_or_else(|| { let field = fields.get(index).unwrap_or_else(|| {
eprintln!( eprintln!(
"{}: {}: No such index in input", "{}: {}: No such index in input",
@ -96,9 +107,10 @@ fn main() {
exit(EX_DATAERR); exit(EX_DATAERR);
}); });
/* get the stdin of the newly spawned program and feed it the field val */
if let Some(mut child_stdin) = spawned.stdin.take() { if let Some(mut child_stdin) = spawned.stdin.take() {
let _ = child_stdin.write_all(field.as_bytes()); let _ = child_stdin.write_all(field.as_bytes());
drop(child_stdin); drop(child_stdin); /* stay safe! drop your children! */
} }
let output = spawned.wait_with_output().unwrap_or_else(|e| { let output = spawned.wait_with_output().unwrap_or_else(|e| {
@ -106,17 +118,27 @@ fn main() {
exit(EX_IOERR); exit(EX_IOERR);
}); });
/* get the output with which the original field will be replaced */
let mut replace = output.stdout.clone(); let mut replace = output.stdout.clone();
if replace.pop() != Some(b'\n') { replace = output.stdout; } /* pop trailing newline out if the input did not contain it */
if fields[index].chars().last() != Some('\n') /* no newline */
&& replace.pop() != Some(b'\n') { /* pop last char of replacement */
/* restore replacement to original command output if popped char was not
* a newline */
replace = output.stdout;
}
/* convert the output of the program to UTF-8 */
let new_field = String::from_utf8(replace).unwrap_or_else(|e| { let new_field = String::from_utf8(replace).unwrap_or_else(|e| {
eprintln!("{}: {}: {}", argv[0], argv[command_arg], e); eprintln!("{}: {}: {}", argv[0], argv[command_arg], e);
exit(EX_IOERR); exit(EX_IOERR);
}); });
/* store the new field in the old fields vector */
fields[index] = &new_field; fields[index] = &new_field;
/* fop it */
stdout().write_all( stdout().write_all(
fields.join(&d.to_string()).as_bytes() fields.join(&d.to_string()).as_bytes()
).unwrap_or_else(|e| { ).unwrap_or_else(|e| {

View File

@ -29,40 +29,45 @@ extern crate sysexits;
use strerror::StrError; use strerror::StrError;
use sysexits::{ EX_DATAERR, EX_IOERR, EX_SOFTWARE }; use sysexits::{ EX_DATAERR, EX_IOERR, EX_SOFTWARE };
/* list of SI prefixes */
const LIST: [(u32, &str); 10] = [ const LIST: [(u32, &str); 10] = [
(3, "k"), (3, "k"), /* kilo */
(6, "M"), (6, "M"), /* mega */
(9, "G"), (9, "G"), /* giga */
(12, "T"), (12, "T"), /* tera */
(15, "P"), (15, "P"), /* peta */
(18, "E"), (18, "E"), /* exa */
(21, "Z"), (21, "Z"), /* zetta */
(24, "Y"), (24, "Y"), /* yotta */
(27, "R"), (27, "R"), /* ronna */
(30, "Q") (30, "Q"), /* quetta */
]; ];
fn convert(input: u128) -> Result<(f64, (u32, &'static str)), String> { fn convert(input: u128) -> Result<(f64, (u32, &'static str)), String> {
/* preserve decimal places in output by casting to a float */
let mut out = (input as f64, (0_u32, ""));
let mut out = (input as f64, (0_u32, "")); if input < 1000 { return Ok(out); } /* too low to convert */
if input < 1000 { return Ok(out); }
for (n, p) in LIST { for (n, p) in LIST {
let c = match 10_u128.checked_pow(n) { let c = match 10_u128.checked_pow(n) {
Some(c) => c, Some(c) => c,
None => { None => { /* too big for the laws of computing :( */
return Err(format!("10^{}: Integer overflow", n.to_string())); return Err(format!("10^{}: Integer overflow", n.to_string()));
}, },
}; };
match c.cmp(&input) { match c.cmp(&input) {
Ordering::Less => { Ordering::Less => { /* c < input */
/* the program will keep assigning out every loop until either
* the list runs out of higher prefix bases or the input is
* greater than the prefix base */
out = (input as f64 / c as f64, (n, p)); out = (input as f64 / c as f64, (n, p));
}, },
Ordering::Equal => { Ordering::Equal => { /* c == input */
return Ok((input as f64 / c as f64, (n, p))); return Ok((input as f64 / c as f64, (n, p)));
}, },
Ordering::Greater => {}, Ordering::Greater => {}, /* c > input */
}; };
} }
@ -72,6 +77,7 @@ fn convert(input: u128) -> Result<(f64, (u32, &'static str)), String> {
fn main() -> ExitCode { fn main() -> ExitCode {
let argv = args().collect::<Vec<String>>(); let argv = args().collect::<Vec<String>>();
let mut buf = String::new(); let mut buf = String::new();
while let Ok(_) = stdin().read_line(&mut buf) { while let Ok(_) = stdin().read_line(&mut buf) {
if buf.is_empty() { return ExitCode::SUCCESS; } if buf.is_empty() { return ExitCode::SUCCESS; }
@ -96,6 +102,7 @@ fn main() -> ExitCode {
let si_prefix = format!("{}B", prefix.1); let si_prefix = format!("{}B", prefix.1);
/* round output number to one decimal place */
let out = ((number * 10.0).round() / 10.0).to_string(); let out = ((number * 10.0).round() / 10.0).to_string();
stdout().write_all(format!("{} {}\n", out, si_prefix).as_bytes()) stdout().write_all(format!("{} {}\n", out, si_prefix).as_bytes())

View File

@ -1,5 +1,6 @@
/* /*
* Copyright (c) 2023 DTB <trinity@trinity.moe> * Copyright (c) 2023 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
@ -16,46 +17,65 @@
* along with this program. If not, see https://www.gnu.org/licenses/. * along with this program. If not, see https://www.gnu.org/licenses/.
*/ */
#include <stdio.h> /* fprintf(3), fputs(3), getc(3), putc(3), stdin, stdout, #include <stdio.h> /* fprintf(3), fputs(3), getc(3), perror(3), putc(3), stdin,
* EOF */ * stdout, EOF */
#include <stdlib.h> /* EXIT_FAILURE, EXIT_SUCCESS */ #include <sysexits.h> /* EX_IOERR, EX_OK, EX_USAGE */
#include <unistd.h> /* getopt(3) */ #include <unistd.h> /* getopt(3) */
#include <sysexits.h>
int main(int argc, char *argv[]){ char *program_name = "npc";
static int
ioerr(char *argv0) {
perror(argv0);
return EX_IOERR;
}
static int
usage(char *argv0) {
(void)fprintf(stderr, "Usage: %s [-et]\n", argv0);
return EX_USAGE;
}
int main(int argc, char *argv[]) {
int c; int c;
char showend; char showend = 0; /* print a dollar sign before each newline */
char showtab; char showtab = 0; /* prints tab characters in caret notation */
showend = 0; if (argc > 0) {
showtab = 0; program_name = argv[0];
if(argc > 0) while ((c = getopt(argc, argv, "et")) != -1) {
while((c = getopt(argc, argv, "et")) != -1) switch (c){
switch(c){ case 'e': showend = 1; break;
case 'e': showend = 1; break; case 't': showtab = 1; break;
case 't': showtab = 1; break; default: return usage(program_name);
default: goto usage;
} }
}
if(argc > optind){
usage: fprintf(stderr, "Usage: %s [-et]\n", argv[0]);
return EX_USAGE;
} }
while((c = getc(stdin)) != EOF){ if (argc > optind) { return usage(program_name); }
if((c & 0x80) != 0)
fputs("M-", stdout); while ((c = getc(stdin)) != EOF) {
switch(c ^ 0x80 /* 0b 1000 0000 */){ if ((c & 0x80) != 0 && fputs("M-", stdout) == EOF) {
case 0x7f: fputs("^?", stdout); return ioerr(argv[0]);
}
switch (c ^ 0x80 /* 0b 1000 0000 */) {
case 0x7f: /* ASCII DEL (127d) */
if(fputs("^?", stdout) == EOF) { return ioerr(argv[0]); }
break; break;
case '\n': if(showend) case '\n':
putc('$', stdout); if (showend && fputc('$', stdout) == EOF) {
default: return ioerr(argv[0]);
if(c >= ' ' || c == '\n' || (!showtab && c == '\t')) }
putc(c, stdout); default:
else if (c >= ' ' || c == '\n' || (!showtab && c == '\t')) {
fprintf(stdout, "^%c", c + '@'); if (fputc(c, stdout) == EOF) { return ioerr(argv[0]); }
} else if (fprintf(stdout, "^%c", c + '@') < 0) {
return ioerr(argv[0]);
}
} }
} }

View File

@ -57,7 +57,7 @@ extern crate sysexits;
use sysexits::EX_DATAERR; use sysexits::EX_DATAERR;
#[derive(Clone, PartialEq, PartialOrd, Debug)] #[derive(Clone, PartialEq, PartialOrd, Debug)]
// enum CalcType is a type containing operations used in the calculator /* enum CalcType is a type containing operations used in the calculator */
enum CalcType { enum CalcType {
Add, Add,
Subtract, Subtract,
@ -117,8 +117,8 @@ struct EvaluationError {
code: i32, code: i32,
} }
// Im no math nerd but I want the highest possible approximation of 0.9 /* Im no math nerd but I want the highest possible approximation of 0.9
// repeating and it seems this can give it to me * repeating and it seems this can give it to me */
const PRECISION_MOD: f64 = 0.9 + f64::EPSILON * 100.0; const PRECISION_MOD: f64 = 0.9 + f64::EPSILON * 100.0;
fn eval( fn eval(
@ -133,7 +133,7 @@ fn eval(
return Ok((stack, oper)); return Ok((stack, oper));
} }
// Split the input into tokens. /* Split the input into tokens. */
let mut toks: VecDeque<CalcType> = input let mut toks: VecDeque<CalcType> = input
.split_whitespace() .split_whitespace()
.rev() .rev()
@ -183,7 +183,7 @@ fn eval(
Ok((stack, oper)) Ok((stack, oper))
} }
// Round a float to the given precision level /* Round a float to the given precision level */
fn round_precise(value: &f64, precision: usize) -> f64 { fn round_precise(value: &f64, precision: usize) -> f64 {
let multiplier = 10_f64.powi(precision as i32); let multiplier = 10_f64.powi(precision as i32);
(value * multiplier).round() / multiplier (value * multiplier).round() / multiplier
@ -193,11 +193,11 @@ fn main() -> ExitCode {
let argv = args().collect::<Vec<String>>(); let argv = args().collect::<Vec<String>>();
let mut stack = VecDeque::new(); let mut stack = VecDeque::new();
let mut buf = String::new(); let mut buf = String::new();
// Set floating-point precision for correcting rounding errors based on /* Set floating-point precision for correcting rounding errors based on
// machine epsilon * machine epsilon */
let precision = (-f64::EPSILON.log10() * PRECISION_MOD).ceil() as usize; let precision = (-f64::EPSILON.log10() * PRECISION_MOD).ceil() as usize;
if argv.get(1).is_none() { if argv.get(1).is_none() { /* read from stdin */
while let Ok(_) = stdin().read_line(&mut buf) { while let Ok(_) = stdin().read_line(&mut buf) {
match eval(&buf.trim(), stack) { match eval(&buf.trim(), stack) {
Ok(s) => { Ok(s) => {
@ -219,12 +219,13 @@ fn main() -> ExitCode {
}, },
}; };
} }
} else { } else { /* read from argv */
/* join argv into an owned String joined by spaces minus argv[0] */
let input = argv let input = argv
.iter() .iter()
.skip(1) .skip(1)
.map(|x| x.to_owned()) .map(|x| x.to_owned())
.collect::<Vec<String>>() .collect::<Vec<_>>()
.join(" "); .join(" ");
match eval(&input, stack) { match eval(&input, stack) {
@ -233,7 +234,7 @@ fn main() -> ExitCode {
let val = match stack.iter().last() { let val = match stack.iter().last() {
Some(v) => v, Some(v) => v,
None => return ExitCode::from(0), None => return ExitCode::SUCCESS,
}; };
println!("{}", round_precise(val, precision).to_string()) println!("{}", round_precise(val, precision).to_string())
@ -244,5 +245,5 @@ fn main() -> ExitCode {
}, },
}; };
} }
ExitCode::from(0) ExitCode::SUCCESS
} }

View File

@ -20,88 +20,75 @@
#include <stdio.h> /* fprintf(3), stderr, NULL */ #include <stdio.h> /* fprintf(3), stderr, NULL */
#include <stdlib.h> /* EXIT_FAILURE, EXIT_SUCCESS */ #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> /* 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 */
static char args[] = "bcdefghkprsuwxLS"; char *program_name = "scrut";
static char ops[(sizeof args) / (sizeof *args)]; #define OPTS "bcdefgkprsuwxLS"
static char *program_name = "scrut"; static char *opts = OPTS;
int main(int argc, char *argv[]){ static int
struct stat buf; usage(char *argv0) {
int c; (void)fprintf(stderr, "Usage: %s [-" OPTS "] file...\n", argv0);
size_t i;
char *p;
if(argc < 2) return EX_USAGE;
goto usage; }
memset(ops, '\0', sizeof ops); int main(int argc, char *argv[]) {
while((c = getopt(argc, argv, args)) != -1) char sel[(sizeof opts) / (sizeof *opts)];
if((p = strchr(args, c)) == NULL)
goto usage; if (argc < 2) { return usage(argv[0] == NULL ? program_name : argv[0]); }
else
ops[p - args] = c; { /* option parsing */
/* straighten out ops */ char *p;
for(i = 0, p = ops; i < (sizeof ops) / (sizeof *ops); ++i)
if(ops[i] != '\0'){ memset(sel, '\0', sizeof sel);
*p = ops[i]; for (int c; (c = getopt(argc, argv, opts)) != -1;) {
if(&ops[i] != p++) if ((p = strchr(opts, c)) == NULL) { return usage(argv[0]); }
ops[i] = '\0'; else { sel[p - opts] = c; }
} }
if(optind == argc) /* straighten out selections; permute out nulls */
goto usage; p = sel;
for (size_t i = 0; i < (sizeof sel) / (sizeof *sel); ++i) {
if (sel[i] != '\0') {
*p = sel[i];
if (&sel[i] != p++) { sel[i] = '\0'; }
}
}
}
argv += optind; if (optind == argc) { return usage(argv[0]); }
do{ if(access(*argv, F_OK) != 0 || lstat(*argv, &buf) == -1)
for (argv += optind ; *argv != NULL; ++argv) {
struct stat buf;
if(access(*argv, F_OK) != 0 || lstat(*argv, &buf) == -1) {
return EXIT_FAILURE; /* 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 (size_t i = 0; sel[i] != '\0'; ++i) {
if(ops[i] == 'e') if (
continue; (sel[i] == 'b' && !S_ISBLK(buf.st_mode))
else if(ops[i] == 'h'){ || (sel[i] == 'c' && !S_ISCHR(buf.st_mode))
usage: fprintf(stderr, "Usage: %s [-%s] file...\n", || (sel[i] == 'd' && !S_ISDIR(buf.st_mode))
argv[0] == NULL || (sel[i] == 'e' && 0)
? program_name || (sel[i] == 'f' && !S_ISREG(buf.st_mode))
: argv[0], || (sel[i] == 'g' && !(buf.st_mode & S_ISGID))
args); || (sel[i] == 'k' && !(buf.st_mode & S_ISVTX))
|| (sel[i] == 'p' && !S_ISFIFO(buf.st_mode))
return EX_USAGE; || (sel[i] == 'r' && access(*argv, R_OK) != 0)
}else if( || (sel[i] == 'u' && !(buf.st_mode & S_ISUID))
(ops[i] == 'b' || (sel[i] == 'w' && access(*argv, W_OK) != 0)
&& !S_ISBLK(buf.st_mode)) || (sel[i] == 'x' && access(*argv, X_OK) != 0)
|| (ops[i] == 'c' || (sel[i] == 'L' && !S_ISLNK(buf.st_mode))
&& !S_ISCHR(buf.st_mode)) || (sel[i] == 'S' && !S_ISSOCK(buf.st_mode))
|| (ops[i] == 'd' ) { return EXIT_FAILURE; }
&& !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; return EXIT_SUCCESS;
} }

View File

@ -20,16 +20,16 @@
#include <ctype.h> #include <ctype.h>
#include <stddef.h> /* NULL */ #include <stddef.h> /* NULL */
#include <stdio.h> /* fprintf(3) */ #include <stdio.h> /* fprintf(3) */
#include <stdlib.h> /* EXIT_FAILURE */ #include <stdlib.h> /* size_t, EXIT_FAILURE */
#include <string.h> /* strcmp(3) */ #include <string.h> /* strcmp(3) */
#include <sysexits.h> #include <sysexits.h> /* EX_USAGE */
static char *program_name = "str"; char *program_name = "str";
static struct { static struct {
char *name; char *name;
int (*f)(int); int (*f)(int);
}ctypes[] = { } ctypes[] = {
{ "isalnum", isalnum }, { "isalnum", isalnum },
{ "isalpha", isalpha }, { "isalpha", isalpha },
{ "isblank", isblank }, { "isblank", isblank },
@ -41,35 +41,44 @@ static struct {
{ "isprint", isprint }, { "isprint", isprint },
{ "ispunct", ispunct }, { "ispunct", ispunct },
{ "isspace", isspace }, { "isspace", isspace },
{ "isupper", isupper } { "isupper", isupper },
{ NULL, NULL } /* marks end */
}; };
int main(int argc, char *argv[]){ static int
int ctype; usage(char *argv0) {
int i; (void)fprintf(stderr, "Usage: %s type string...\n", argv0);
int r;
if(argc >= 3){
for(ctype = 0; ctype < (sizeof ctypes) / (sizeof *ctypes);
++ctype)
if(strcmp(argv[1], ctypes[ctype].name) == 0)
goto pass;
}
fprintf(stderr, "Usage: %s type string...\n",
argv[0] == NULL ? program_name : argv[0]);
return EX_USAGE; return EX_USAGE;
}
pass: for(argv += 2, r = 1; *argv != NULL; ++argv)
for(i = 0; argv[0][i] != '\0'; ++i) int main(int argc, char *argv[]) {
/* First checks if argv[0][i] is valid ASCII; ctypes(3) size_t ctype; // selected from ctypes.h; index of ctype
* don't handle non-ASCII. int retval; // initially fail but becomes success on the first valid char
* This is bad. */
if((unsigned char)argv[0][i] < 0x80 && !ctypes[ctype].f(argv[0][i])) if (argc < 3) { return usage(argv[0] == NULL ? program_name : argv[0]); }
return 1;
else for ( /* iterate ctypes */
r = 0; ctype = 0;
ctypes[ctype].f != NULL /* break at the end of ctypes */
return r; && strcmp(argv[1], ctypes[ctype].name) != 0; /* break at match */
++ctype
);
if (ctypes[ctype].f == NULL) { return usage(argv[0]); }
/* iterate args */
for (argv += 2, retval = EXIT_FAILURE; *argv != NULL; ++argv) {
for (size_t i = 0; argv[0][i] != '\0'; ++i) { /* iterate arg bytes */
/* First checks if argv[0][i] is valid ASCII; ctypes(3) don't
* handle non-ASCII. This is bad. */
if(
(unsigned char)argv[0][i] < 0x80 // argv[0][i] is ASCII,
&& !ctypes[ctype].f(argv[0][i]) // so use ctypes(3)
) { return EXIT_FAILURE; }
else { retval = EXIT_SUCCESS; }
}
}
return retval;
} }

View File

@ -1,5 +1,6 @@
/* /*
* Copyright (c) 20222024 DTB <trinity@trinity.moe> * Copyright (c) 2023 DTB <trinity@trinity.moe>
* Copyright (c) 20232024 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
@ -15,29 +16,35 @@
* You should have received a copy of the GNU Affero General Public License * 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/. * along with this program. If not, see https://www.gnu.org/licenses/.
*/ */
#include <stdio.h> /* fprintf(3), stderr */ #include <stdio.h> /* fprintf(3), stderr */
#include <stdlib.h> /* size_t */ #include <sysexits.h> /* EX_OK, EX_USAGE */
#include <sysexits.h> /* EX_USAGE */
char *program_name = "strcmp"; char *program_name = "strcmp";
int main(int argc, char *argv[]){ int main(int argc, char *argv[]) {
int i;
if (argc < 3) { if (argc < 3) {
fprintf(stderr, "Usage: %s string string...\n", (void)fprintf(
stderr,
"Usage: %s string string...\n",
argv[0] == NULL ? program_name : argv[0] argv[0] == NULL ? program_name : argv[0]
); );
return EX_USAGE; return EX_USAGE;
} }
/* This compares the Nth character of arg[2] onward with argv[1]'s Nth for (; *argv[1] != '\0'; ++argv[1]) {
* character, rather than comparing each arg with argv[1] sequentially. */ for (i = 2; i < argc; ++i) {
for (; *argv[1] != '\0'; ++argv[1]) { /* iterate chars in argv[1] */ /* a former string has a greater byte value */
for (size_t i = 2; i < argc; ++argv[i], ++i) { /* iterate &argv[2] */ if (*argv[i-1] > *argv[i]) {
/* this never overruns because of nul termination */ return 1;
if (*argv[i-1] != *argv[i]) { return *argv[i-1] - *argv[i]; } /* a latter string has a greater byte value */
} else if (*argv[i-1] < *argv[i]++) {
return -1; /* actually 255 */
}
} }
} }
return 0; return EX_OK;
} }

View File

@ -25,19 +25,23 @@ use std::{
}; };
extern crate getopt; extern crate getopt;
use getopt::GetOpt;
extern crate sysexits; extern crate sysexits;
use sysexits::{ EX_OK, EX_OSERR, EX_USAGE };
extern crate strerror; extern crate strerror;
use getopt::GetOpt;
use sysexits::{ EX_IOERR, EX_OK, EX_OSERR, EX_USAGE };
use strerror::StrError; use strerror::StrError;
fn oserr(s: &str, e: Error) -> ExitCode { fn oserr(argv0: &str, e: Error) -> ExitCode {
eprintln!("{}: {}", s, e.strerror()); eprintln!("{}: {}", argv0, e.strerror());
ExitCode::from(EX_OSERR as u8) ExitCode::from(EX_OSERR as u8)
} }
fn ioerr(argv0: &str, e: Error) -> ExitCode {
eprintln!("{}: {}", argv0, e.strerror());
ExitCode::from(EX_IOERR as u8)
}
fn usage(s: &str) -> ExitCode { fn usage(s: &str) -> ExitCode {
eprintln!("Usage: {} [-w word_size]", s); eprintln!("Usage: {} [-w word_size]", s);
ExitCode::from(EX_USAGE as u8) ExitCode::from(EX_USAGE as u8)
@ -45,12 +49,11 @@ fn usage(s: &str) -> ExitCode {
fn main() -> ExitCode { fn main() -> ExitCode {
let argv = args().collect::<Vec<String>>(); let argv = args().collect::<Vec<String>>();
let mut buf: Vec<u8> = Vec::new(); let mut buf: Vec<u8> = Vec::new(); // holds the sequence getting swabbed
let mut input = stdin(); let mut input = stdin();
let mut output = stdout().lock(); let mut output = stdout().lock();
let mut optind: usize = 1; // argv[0] let mut optind: usize = 1; // argv[0]
let mut wordsize: usize = 2; // Equivalent to dd(1p). let mut wordsize: usize = 2; // default; mimics dd(1p) conv=swab
while let Some(opt) = argv.getopt("w:") { while let Some(opt) = argv.getopt("w:") {
match opt.opt() { match opt.opt() {
@ -73,17 +76,19 @@ fn main() -> ExitCode {
loop { loop {
match input.read(&mut buf) { match input.read(&mut buf) {
Ok(0) => break ExitCode::from(EX_OK as u8), Ok(0) => break ExitCode::from(EX_OK as u8), // read nothing; bye
Ok(v) if v == wordsize => { Ok(v) if v == wordsize => { // read full block; swab
let (left, right) = buf.split_at(v/2); let (left, right) = buf.split_at(v/2);
if let Err(e) = output.write(&right) if let Err(e) = output.write(&right)
.and_then(|_| output.write(&left)) { .and_then(|_| output.write(&left)) {
break oserr(&argv[0], e) break ioerr(&argv[0], e);
} }
}, },
Ok(v) => { Ok(v) => { // partial read; partially write
if let Err(e) = output.write(&buf[..v]) { if let Err(e) = output.write(&buf[..v]) {
break oserr(&argv[0], e) break ioerr(&argv[0], e);
} }
}, },
Err(e) => break oserr(&argv[0], e) Err(e) => break oserr(&argv[0], e)