diff --git a/STYLE b/STYLE new file mode 100644 index 0000000..5322661 --- /dev/null +++ b/STYLE @@ -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 /* 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] + +-- +Copyright © 2024 Emma Tebibyte +Copyright © Wikipedia contributors + +This work is licensed under CC BY-SA 4.0. To view a copy of this license, visit +. diff --git a/docs/dj.1 b/docs/dj.1 index d358e3f..79096d5 100644 --- a/docs/dj.1 +++ b/docs/dj.1 @@ -4,7 +4,7 @@ .\" This work is licensed under CC BY-SA 4.0. To see a copy of this license, .\" visit . .\" -.TH DJ 1 2024-07-03 "Harakit X.X.X" +.TH DJ 1 2024-07-14 "Harakit X.X.X" .SH NAME dj \(en disk jockey .\" @@ -56,9 +56,9 @@ bytes read to this point are discarded. .IP \fB-o\fP Takes a file path as an argument and opens it for use as an output. .IP \fB-B\fP\ \fIblock_size\fP -Does the same as -.B -b -but for the output buffer. +Takes a numeric argument as the size in bytes of the output buffer, the default +being 1024. Note that this option only affects the size of output writes and not +the amount of output data itself. See the CAVEATS section. .IP \fB-S\fP 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, @@ -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 null byte is used. .IP \fB-c\fP -Specifies a number of reads to make. The default is 0, in which case the -input is read until a partial or empty read is made. +Specifies a number of blocks to read. The default is 0, in which case the input +is read until a partial or empty read is made. .IP \fB-H\fP Prints diagnostic messages in a human-readable manner as described in the 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. -The options -.B -b -and +Option variants that have lowercase and uppercase forms could be confused for +each other. The former affects input and the latter affects output. + +The .B -B -could be confused for each other, and so could -.B -s -and -.BR -S . -The lowercase option affects input and the capitalized option affects output. +option could be mistaken for the count in bytes of data written to the output. +This conception is intuitive but incorrect, as the +.B -c +option controls the number of blocks to read and the +.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, 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 .BR dd (1p) .BR lseek (3p) +.BR mm (1) diff --git a/src/dj.c b/src/dj.c index 3b5bc5f..d6f5fde 100644 --- a/src/dj.c +++ b/src/dj.c @@ -1,5 +1,6 @@ /* * Copyright (c) 2024 DTB + * Copyright (c) 2024 Emma Tebibyte * SPDX-License-Identifier: AGPL-3.0-or-later * * This program is free software: you can redistribute it and/or modify it under @@ -19,26 +20,23 @@ #include /* assert(3) */ #include /* errno */ #include /* open(2) */ +#include /* bool */ #include /* fprintf(3), stderr */ #include /* malloc(3), strtol(3), size_t */ #include /* memcpy(3), memmove(3), memset(3) */ -#if !defined EX_OK || !defined EX_OSERR || !defined EX_USAGE -# include -#endif +#include /* EX_OK, EX_OSERR, EX_USAGE */ #include /* close(2), getopt(3), lseek(2), read(2), write(2), * optarg, optind, STDIN_FILENO, STDOUT_FILENO */ -#include /* S_IRGRP, S_IROTH, S_IRUSR, S_IWGRP, S_IWOTH, - S_IWUSR */ -extern int errno; +#include /* S_IRGRP, S_IROTH, S_IRUSR, S_IWGRP, S_IWOTH, S_IWUSR */ char *program_name = "dj"; /* dj uses two structures that respectively correspond to the reading and * writing ends of its jockeyed "pipe". User-configurable members are noted * with their relevant options. */ -struct Io{ - char *buf; /* buffer */ - char *fn; /* file name (-io) */ +struct Io { + char *buf; /* buffer */ + char *fn; /* file name (-io) */ size_t bs; /* buffer size (-bB) */ size_t bufuse; /* buffer usage */ size_t bytes; /* bytes processed */ @@ -46,14 +44,10 @@ struct Io{ size_t rec; /* records processed */ long seek; /* remaining bytes to seek/skip (-sS) */ int error; /* errno */ - int fd; /* file descriptor */ - int fl; /* file opening flags */ + int fd; /* file descriptor */ + 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 = ""; static char *stdout_name = ""; @@ -67,14 +61,15 @@ static int write_flags = O_WRONLY | O_CREAT; /* Macro to check if fd is stdin or stdout */ #define fdisstd(fd) ((fd) == STDIN_FILENO || (fd) == STDOUT_FILENO) +/* Completes one Io block read */ static struct Io * -Io_read(struct Io *io){ +Io_read(struct Io *io) { int t; assert(io->bs > 0); 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; t = 0; } @@ -89,18 +84,20 @@ Io_read(struct Io *io){ return io; } +/* Completes one Io block write */ static struct Io * -Io_write(struct Io *io){ +Io_write(struct Io *io) { int t; assert(io->bufuse > 0); 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; t = 0; - }else if(t > 0) - memmove(io->buf, &(io->buf)[t], (io->bufuse -= t)); + } else if (t > 0) { + (void)memmove(io->buf, &(io->buf)[t], (io->bufuse -= t)); + } io->bytes += t; io->prec += (t > 0 && io->bufuse > 0); @@ -110,62 +107,79 @@ Io_write(struct Io *io){ } static int -oserr(char *e, int n){ - fprintf(stderr, "%s: %s: %s\n", program_name, e, strerror(n)); +oserr(char *e, int n) { + (void)fprintf(stderr, "%s: %s: %s\n", program_name, e, strerror(n)); + return EX_OSERR; } /* Prints statistics regarding the use of dj, particularly partially and * completely read and written records. */ -static void -fprintio(FILE *stream, char *fmt, struct Io io[2]){ - - fprintf(stream, fmt, - io[0].rec, io[0].prec, io[1].rec, io[1].prec, - io[0].bytes, io[1].bytes); - - return; +static int +fprintio(FILE *stream, char *fmt, struct Io io[2]) { + return fprintf( + stream, + fmt, + io[0].rec, + io[0].prec, + 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 * 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. */ static long -parse(char *s){ +parse(char *s) { long r; errno = 0; r = strtol(s, &s, 0); - return (*s == '\0' /* no chars left unparsed */ && errno == 0) - ? r - : -1; + return (*s == '\0' /* no chars left unparsed */ && errno == 0) ? r : -1; } static int -usage(char *s){ - - fprintf(stderr, "Usage: %s [-Hn] [-a byte] [-c count]\n" +usage(char *argv0) { + (void)fprintf( + stderr, + "Usage: %s [-Hn] [-a byte] [-c count]\n" "\t[-i file] [-b block_size] [-s offset]\n" "\t[-o file] [-B block_size] [-S offset]\n", - program_name); + argv0 + ); return EX_USAGE; } -int main(int argc, char *argv[]){ - 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 */ - char *fmt; /* == fmt_asv (default) or fmt_human (-H) */ - size_t i; /* side of io being modified */ - char noerror; /* 0=exits (default) 1=retries on partial reads or writes */ +int main(int argc, char *argv[]) { + int align; /* low 8b used, negative if no alignment is being done */ + int count; /* -1 if dj(1) runs until no more reads are possible */ + char *fmt; /* set to fmt_asv (default) or fmt_human (-H) */ + size_t i; /* side of io (in or out) being modified */ + bool retry; /* false if exits on partial reads or writes */ struct Io io[2 /* { in, out } */]; /* Set defaults. */ align = -1; - count = 0; + count = -1; fmt = fmt_asv; - noerror = 0; - for(i = 0; i < (sizeof io) / (sizeof *io); ++i){ + retry = 0; + for (i = 0; i < (sizeof io) / (sizeof *io); ++i) { io[i].bs = 1024 /* 1 KiB */; /* GNU dd(1) default; POSIX says 512B */ io[i].bufuse = 0; io[i].bytes = 0; @@ -178,88 +192,113 @@ int main(int argc, char *argv[]){ io[i].seek = 0; } - if(argc > 0){ + if (argc > 0) { int c; program_name = argv[0]; - while((c = getopt(argc, argv, ":a:b:B:c:i:hHns:S:o:")) != -1) - switch(c){ - case 'i': case 'o': i = (c == 'o'); - if(optarg[0] == '-' && optarg[1] == '\0'){ /* optarg == "-" */ - io[i].fd = i == 0 ? STDIN_FILENO : STDOUT_FILENO; - io[i].fn = i == 0 ? stdin_name : stdout_name; - break; - }else{ - int fd; + while ((c = getopt(argc, argv, "a:b:B:c:i:hHns:S:o:")) != -1) { + switch (c) { + case 'i': case 'o': /* input, output */ + i = (c == 'o'); - 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; + /* optarg == "-" (stdin/stdout) */ + if (optarg[0] == '-' && optarg[1] == '\0') { + 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 + && (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; } - } - return oserr(optarg, errno); - case 'n': noerror = 1; break; - case 'H': fmt = fmt_human; break; - case 'a': - if(optarg[0] == '\0' || optarg[1] == '\0'){ - align = optarg[0]; - break; - } - /* FALLTHROUGH */ - case 'c': case 'b': case 's': case 'B': case 'S': /* numbers */ - if(c == 'c' && (count = parse(optarg)) >= 0) - break; - i = (c >= 'A' && c <= 'Z'); - c |= 0x20 /* 0b 0010 0000 */; /* (ASCII) make lowercase */ - if((c == 'b' && (io[i].bs = parse(optarg)) > 0) - || (c == 's' && (io[i].seek = parse(optarg)) >= 0)) - break; - /* FALLTHROUGH */ - default: - return usage(program_name); + /* FALLTHROUGH */ + case 'c': /* number of reads */ + case 'b': case 'B': /* input/output block size */ + case 's': case 'S': /* (s)kip/(S)eek in input/output */ + if (c == 'c' && (count = parse(optarg)) >= 0) { break; } + + i = (c >= 'A' && c <= 'Z'); + c |= 0x20; /* 0b 0010 0000 (ASCII) make lowercase */ + + if ( /* if -b or -s is parsed out correctly */ + (c == 'b' && (io[i].bs = parse(optarg)) > 0) + || (c == 's' && (io[i].seek = parse(optarg)) >= 0) + ) { break; } /* don't error */ + + /* FALLTHROUGH */ + default: + return usage(program_name); } + } } assert(io->fd != STDIN_FILENO || io->fl == read_flags); assert(io->fd != STDOUT_FILENO || io->fl == write_flags); - if(argc > optind) - return usage(program_name); + if (argc > optind) { return usage(program_name); } - for(i = 0; i < (sizeof io) / (sizeof *io); ++i){ + for (i = 0; i < (sizeof io) / (sizeof *io); ++i) { /* buffer allocation */ - if((io[i].buf = malloc(io[i].bs * (sizeof *(io[i].buf)))) == NULL){ - fprintf(stderr, "%s: Failed to allocate %zd bytes\n", - program_name, io[i].bs); + if ((io[i].buf = malloc(io[i].bs * (sizeof *(io[i].buf)))) == NULL) { + (void)fprintf( + stderr, "%s: Failed to allocate %zd bytes\n", + program_name, io[i].bs + ); return EX_OSERR; } + /* 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; + } } - /* hard seeking */ - if(io[1].seek > 0){ - size_t t; - do{ - memset(io[1].buf, '\0', - (t = io[1].bufuse = MIN(io[1].bs, io[1].seek))); - if(Io_write(&io[1])->bufuse == t && !noerror && io[1].error == 0) - Io_write(&io[1]); /* second chance */ - if(io[1].error != 0) - return oserr(io[1].fn, io[1].error); - }while((io[1].seek -= (t - io[1].bufuse)) > 0 && io[1].bufuse != t); - io[1].bufuse = 0; + assert(io[1].bufuse == 0); /* requirement for hard seeking */ + + /* hard seeking; t is io[1].bufuse, before Io_write subtracts from it */ + for(size_t t; io[1].seek > 0; io[1].seek -= (t - io[1].bufuse)) { + (void)memset( + io[1].buf, '\0', /* set buf to all nulls */ + (t = io[1].bufuse = MIN(io[1].bs, io[1].seek)) /* saturate block */ + ); + + if (Io_write(&io[1])->bufuse == t && !retry && io[1].error == 0) { + (void)Io_write(&io[1]); /* second chance */ + } + + 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){ - fprintio(stderr, fmt, io); + io[1].bufuse = 0; /* reset after hard seek */ + + if (io[1].seek > 0) { /* hard seeking failed */ + (void)fprintio(stderr, fmt, io); return oserr(io[1].fn, errno); } - do{ + for ( ; + count == -1 || count > 0; + count -= (count != -1) /* decrement if counting */ + ) { assert(io[0].bufuse == 0); { /* read */ @@ -267,87 +306,114 @@ int main(int argc, char *argv[]){ size_t t; /* 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; + } t = io[0].bufuse; - if(Io_read(&io[0])->bufuse == t && !noerror && io[0].error == 0) - Io_read(&io[0]); /* second chance */ - assert(io[0].bufuse >= t); - if(io[0].bufuse == t) /* that's all she wrote */ - break; + if (Io_read(&io[0])->bufuse == t && !retry && io[0].error == 0) { + (void)Io_read(&io[0]); /* second chance */ + } - if(/* t < io[0].bufuse && */ io[0].bufuse < io[0].bs){ - fprintf(stderr, "%s: Partial read:\n\t", program_name); - fprintio(stderr, fmt, io); - if(!noerror) - count = 1; - if(align >= 0){ + assert(io[0].bufuse >= t); + + if (io[0].bufuse == t) { break; } /* that's all she wrote */ + + if (/* t < io[0].bufuse && */ io[0].bufuse < io[0].bs) { + (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 */ - memset(&(io[0].buf)[io[0].bufuse], align, - io[0].bs - io[0].bufuse); + (void)memset( + &(io[0].buf)[io[0].bufuse], + align, + io[0].bs - io[0].bufuse + ); + io->bufuse = io->bs; } } - if(skipping > 0){ + if (skipping > 0) { io[0].seek -= skipping; io[0].bufuse = 0; - count += (count != 0); + count += (count != -1); /* increment if counting */ continue; } } - /* write */ - do{ - int t; + assert(io[0].bufuse > 0); - if(io[0].bs <= io[1].bs){ + while (io[0].bufuse > 0) { /* write */ + if (io[0].bs <= io[1].bs) { int n; - /* saturate obuf */ - memcpy(io[1].buf, io[0].buf, - (io[1].bufuse = (n = MIN(io[0].bufuse, io[1].bs)))); + (void)memcpy( /* saturate obuf */ + io[1].buf, io[0].buf, + (io[1].bufuse = (n = MIN(io[0].bufuse, io[1].bs))) + ); + /* permute the copied units out of ibuf */ - memmove(io[0].buf, &(io[0].buf)[n], (io[0].bufuse -= n)); - }else /* if(io[0].bs < io[1].bs) */ { + (void)memmove(io[0].buf, &(io[0].buf)[n], (io[0].bufuse -= n)); + } else /* if(io[0].bs > io[1].bs) */ { int n; /* drain what we can from ibuf */ - memcpy(&(io[1].buf)[io[1].bufuse], io[0].buf, - (n = MIN(io[0].bufuse, io[1].bs - io[1].bufuse))); + (void)memcpy( + &(io[1].buf)[io[1].bufuse], io[0].buf, + (n = MIN(io[0].bufuse, io[1].bs - io[1].bufuse)) + ); io[1].bufuse += n; + /* 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; - 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 */ + } } - t = io[1].bufuse; - if(Io_write(&io[1])->bufuse == t && !noerror && io[1].error == 0) - Io_write(&io[1]); /* second chance */ - assert(io[1].bufuse <= t); - if(io[1].bufuse == t){ /* no more love */ - count = 1; - break; - } + { /* writes actually happen, or die */ + size_t t; - if(0 < io[1].bufuse /* && io[1].bufuse < t */){ - fprintf(stderr, "%s: Partial write:\n\t", program_name); - fprintio(stderr, fmt, io); - if(!noerror) + t = io[1].bufuse; + if (Io_write(&io[1])->bufuse == t + && !retry + && 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; + 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(io[i].error) - return oserr(io[i].fn, io[i].error); + if(!retry) { count = 1; } + } + } + } + + (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; } diff --git a/src/fop.rs b/src/fop.rs index 91c8a72..9f547b1 100644 --- a/src/fop.rs +++ b/src/fop.rs @@ -32,8 +32,8 @@ use sysexits::{ EX_DATAERR, EX_IOERR, EX_UNAVAILABLE, EX_USAGE }; fn main() { let argv = args().collect::>(); - let mut d = '\u{1E}'.to_string(); - let mut index_arg = 0; + let mut d = '\u{1E}'.to_string(); /* ASCII record separator */ + let mut optind = 1; let usage = format!( "Usage: {} [-d delimiter] index command [args...]", @@ -42,51 +42,62 @@ fn main() { while let Some(opt) = argv.getopt("d:") { match opt.opt() { - Ok(_) => { - /* unwrap because Err(OptError::MissingArg) will be returned if - * opt.arg() is None */ + Ok("d") => { + /* delimiter */ d = opt.arg().unwrap(); - index_arg = opt.ind(); + optind = opt.ind(); }, - Err(_) => { + _ => { eprintln!("{}", usage); exit(EX_USAGE); } }; } - let command_arg = index_arg as usize + 1; - - argv.get(command_arg).unwrap_or_else(|| { - eprintln!("{}", usage); - exit(EX_USAGE); - }); - - let index = argv[index_arg].parse::().unwrap_or_else(|e| { + /* parse the specified index as a number we can use */ + let index = argv[optind].parse::().unwrap_or_else(|e| { eprintln!("{}: {}: {}", argv[0], argv[1], e); 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 _ = 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::>(); - let opts = argv + /* collect arguments for the operator command */ + let command_args = argv .iter() .clone() - .skip(command_arg + 1) + .skip(command_arg + 1) /* skip the command name */ .collect::>(); - let mut spawned = Command::new(argv.get(command_arg).unwrap()) - .args(opts) + /* spawn the command to operate on the field */ + let mut spawned = Command::new(operator) + .args(command_args) /* spawn with the specified arguments */ .stdin(Stdio::piped()) - .stdout(Stdio::piped()) + .stdout(Stdio::piped()) /* piped stdout to handle output ourselves */ .spawn() .unwrap_or_else( |e| { eprintln!("{}: {}: {}", argv[0], argv[command_arg], e.strerror()); exit(EX_UNAVAILABLE); }); + /* get field we want to pipe into spawned program */ let field = fields.get(index).unwrap_or_else(|| { eprintln!( "{}: {}: No such index in input", @@ -96,9 +107,10 @@ fn main() { 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() { 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| { @@ -106,17 +118,27 @@ fn main() { exit(EX_IOERR); }); + /* get the output with which the original field will be replaced */ 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| { eprintln!("{}: {}: {}", argv[0], argv[command_arg], e); exit(EX_IOERR); }); + /* store the new field in the old fields vector */ fields[index] = &new_field; + /* fop it */ stdout().write_all( fields.join(&d.to_string()).as_bytes() ).unwrap_or_else(|e| { diff --git a/src/hru.rs b/src/hru.rs index b7937f7..c02d4c1 100644 --- a/src/hru.rs +++ b/src/hru.rs @@ -29,40 +29,45 @@ extern crate sysexits; use strerror::StrError; use sysexits::{ EX_DATAERR, EX_IOERR, EX_SOFTWARE }; +/* list of SI prefixes */ const LIST: [(u32, &str); 10] = [ - (3, "k"), - (6, "M"), - (9, "G"), - (12, "T"), - (15, "P"), - (18, "E"), - (21, "Z"), - (24, "Y"), - (27, "R"), - (30, "Q") + (3, "k"), /* kilo */ + (6, "M"), /* mega */ + (9, "G"), /* giga */ + (12, "T"), /* tera */ + (15, "P"), /* peta */ + (18, "E"), /* exa */ + (21, "Z"), /* zetta */ + (24, "Y"), /* yotta */ + (27, "R"), /* ronna */ + (30, "Q"), /* quetta */ ]; 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); } + if input < 1000 { return Ok(out); } /* too low to convert */ for (n, p) in LIST { let c = match 10_u128.checked_pow(n) { Some(c) => c, - None => { + None => { /* too big for the laws of computing :( */ return Err(format!("10^{}: Integer overflow", n.to_string())); }, }; 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)); }, - Ordering::Equal => { + Ordering::Equal => { /* c == input */ 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 { let argv = args().collect::>(); let mut buf = String::new(); + while let Ok(_) = stdin().read_line(&mut buf) { if buf.is_empty() { return ExitCode::SUCCESS; } @@ -96,6 +102,7 @@ fn main() -> ExitCode { let si_prefix = format!("{}B", prefix.1); + /* round output number to one decimal place */ let out = ((number * 10.0).round() / 10.0).to_string(); stdout().write_all(format!("{} {}\n", out, si_prefix).as_bytes()) diff --git a/src/npc.c b/src/npc.c index 1f96668..7ef760a 100644 --- a/src/npc.c +++ b/src/npc.c @@ -1,5 +1,6 @@ /* * Copyright (c) 2023 DTB + * Copyright (c) 2024 Emma Tebibyte * SPDX-License-Identifier: AGPL-3.0-or-later * * 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/. */ -#include /* fprintf(3), fputs(3), getc(3), putc(3), stdin, stdout, - * EOF */ -#include /* EXIT_FAILURE, EXIT_SUCCESS */ +#include /* fprintf(3), fputs(3), getc(3), perror(3), putc(3), stdin, + * stdout, EOF */ +#include /* EX_IOERR, EX_OK, EX_USAGE */ #include /* getopt(3) */ -#include -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; - char showend; - char showtab; + char showend = 0; /* print a dollar sign before each newline */ + char showtab = 0; /* prints tab characters in caret notation */ - showend = 0; - showtab = 0; + if (argc > 0) { + program_name = argv[0]; - if(argc > 0) - while((c = getopt(argc, argv, "et")) != -1) - switch(c){ - case 'e': showend = 1; break; - case 't': showtab = 1; break; - default: goto usage; + while ((c = getopt(argc, argv, "et")) != -1) { + switch (c){ + case 'e': showend = 1; break; + case 't': showtab = 1; break; + default: return usage(program_name); } - - if(argc > optind){ -usage: fprintf(stderr, "Usage: %s [-et]\n", argv[0]); - return EX_USAGE; + } } - while((c = getc(stdin)) != EOF){ - if((c & 0x80) != 0) - fputs("M-", stdout); - switch(c ^ 0x80 /* 0b 1000 0000 */){ - case 0x7f: fputs("^?", stdout); + if (argc > optind) { return usage(program_name); } + + while ((c = getc(stdin)) != EOF) { + if ((c & 0x80) != 0 && fputs("M-", stdout) == EOF) { + return ioerr(argv[0]); + } + + switch (c ^ 0x80 /* 0b 1000 0000 */) { + case 0x7f: /* ASCII DEL (127d) */ + if(fputs("^?", stdout) == EOF) { return ioerr(argv[0]); } break; - case '\n': if(showend) - putc('$', stdout); - default: - if(c >= ' ' || c == '\n' || (!showtab && c == '\t')) - putc(c, stdout); - else - fprintf(stdout, "^%c", c + '@'); + case '\n': + if (showend && fputc('$', stdout) == EOF) { + return ioerr(argv[0]); + } + default: + if (c >= ' ' || c == '\n' || (!showtab && c == '\t')) { + if (fputc(c, stdout) == EOF) { return ioerr(argv[0]); } + } else if (fprintf(stdout, "^%c", c + '@') < 0) { + return ioerr(argv[0]); + } } } diff --git a/src/rpn.rs b/src/rpn.rs index 2bfbbf5..784e323 100644 --- a/src/rpn.rs +++ b/src/rpn.rs @@ -57,7 +57,7 @@ extern crate sysexits; use sysexits::EX_DATAERR; #[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 { Add, Subtract, @@ -117,8 +117,8 @@ struct EvaluationError { code: i32, } -// I’m no math nerd but I want the highest possible approximation of 0.9 -// repeating and it seems this can give it to me +/* I’m no math nerd but I want the highest possible approximation of 0.9 + * repeating and it seems this can give it to me */ const PRECISION_MOD: f64 = 0.9 + f64::EPSILON * 100.0; fn eval( @@ -133,7 +133,7 @@ fn eval( return Ok((stack, oper)); } - // Split the input into tokens. + /* Split the input into tokens. */ let mut toks: VecDeque = input .split_whitespace() .rev() @@ -183,7 +183,7 @@ fn eval( 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 { let multiplier = 10_f64.powi(precision as i32); (value * multiplier).round() / multiplier @@ -193,11 +193,11 @@ fn main() -> ExitCode { let argv = args().collect::>(); let mut stack = VecDeque::new(); let mut buf = String::new(); - // Set floating-point precision for correcting rounding errors based on - // machine epsilon + /* Set floating-point precision for correcting rounding errors based on + * machine epsilon */ 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) { match eval(&buf.trim(), stack) { 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 .iter() .skip(1) .map(|x| x.to_owned()) - .collect::>() + .collect::>() .join(" "); match eval(&input, stack) { @@ -233,7 +234,7 @@ fn main() -> ExitCode { let val = match stack.iter().last() { Some(v) => v, - None => return ExitCode::from(0), + None => return ExitCode::SUCCESS, }; println!("{}", round_precise(val, precision).to_string()) @@ -244,5 +245,5 @@ fn main() -> ExitCode { }, }; } - ExitCode::from(0) + ExitCode::SUCCESS } diff --git a/src/scrut.c b/src/scrut.c index d85d243..39fa698 100644 --- a/src/scrut.c +++ b/src/scrut.c @@ -20,88 +20,75 @@ #include /* fprintf(3), stderr, NULL */ #include /* EXIT_FAILURE, EXIT_SUCCESS */ #include /* memset(3), strchr(3) */ -#ifndef EX_USAGE -# include -#endif +#include /* EX_USAGE */ #include /* access(3), getopt(3), F_OK, R_OK, W_OK, X_OK */ #include /* 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"; +char *program_name = "scrut"; +#define OPTS "bcdefgkprsuwxLS" +static char *opts = OPTS; -int main(int argc, char *argv[]){ - struct stat buf; - int c; - size_t i; - char *p; +static int +usage(char *argv0) { + (void)fprintf(stderr, "Usage: %s [-" OPTS "] file...\n", argv0); - if(argc < 2) - goto usage; + return EX_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'; +int main(int argc, char *argv[]) { + char sel[(sizeof opts) / (sizeof *opts)]; + + if (argc < 2) { return usage(argv[0] == NULL ? program_name : argv[0]); } + + { /* option parsing */ + char *p; + + memset(sel, '\0', sizeof sel); + for (int c; (c = getopt(argc, argv, opts)) != -1;) { + if ((p = strchr(opts, c)) == NULL) { return usage(argv[0]); } + else { sel[p - opts] = c; } } - if(optind == argc) - goto usage; + /* straighten out selections; permute out nulls */ + 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; - do{ if(access(*argv, F_OK) != 0 || lstat(*argv, &buf) == -1) + if (optind == argc) { return usage(argv[0]); } + + 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 */ + } - 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); + for (size_t i = 0; sel[i] != '\0'; ++i) { + if ( + (sel[i] == 'b' && !S_ISBLK(buf.st_mode)) + || (sel[i] == 'c' && !S_ISCHR(buf.st_mode)) + || (sel[i] == 'd' && !S_ISDIR(buf.st_mode)) + || (sel[i] == 'e' && 0) + || (sel[i] == 'f' && !S_ISREG(buf.st_mode)) + || (sel[i] == 'g' && !(buf.st_mode & S_ISGID)) + || (sel[i] == 'k' && !(buf.st_mode & S_ISVTX)) + || (sel[i] == 'p' && !S_ISFIFO(buf.st_mode)) + || (sel[i] == 'r' && access(*argv, R_OK) != 0) + || (sel[i] == 'u' && !(buf.st_mode & S_ISUID)) + || (sel[i] == 'w' && access(*argv, W_OK) != 0) + || (sel[i] == 'x' && access(*argv, X_OK) != 0) + || (sel[i] == 'L' && !S_ISLNK(buf.st_mode)) + || (sel[i] == 'S' && !S_ISSOCK(buf.st_mode)) + ) { return EXIT_FAILURE; } + } + } return EXIT_SUCCESS; } diff --git a/src/str.c b/src/str.c index b4725eb..9f7b351 100644 --- a/src/str.c +++ b/src/str.c @@ -20,16 +20,16 @@ #include #include /* NULL */ #include /* fprintf(3) */ -#include /* EXIT_FAILURE */ +#include /* size_t, EXIT_FAILURE */ #include /* strcmp(3) */ -#include +#include /* EX_USAGE */ -static char *program_name = "str"; +char *program_name = "str"; static struct { char *name; int (*f)(int); -}ctypes[] = { +} ctypes[] = { { "isalnum", isalnum }, { "isalpha", isalpha }, { "isblank", isblank }, @@ -41,35 +41,44 @@ static struct { { "isprint", isprint }, { "ispunct", ispunct }, { "isspace", isspace }, - { "isupper", isupper } + { "isupper", isupper }, + { NULL, NULL } /* marks end */ }; -int main(int argc, char *argv[]){ - int ctype; - int i; - 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]); +static int +usage(char *argv0) { + (void)fprintf(stderr, "Usage: %s type string...\n", argv0); return EX_USAGE; - -pass: for(argv += 2, r = 1; *argv != NULL; ++argv) - for(i = 0; argv[0][i] != '\0'; ++i) - /* 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 && !ctypes[ctype].f(argv[0][i])) - return 1; - else - r = 0; - - return r; +} + +int main(int argc, char *argv[]) { + size_t ctype; // selected from ctypes.h; index of ctype + int retval; // initially fail but becomes success on the first valid char + + if (argc < 3) { return usage(argv[0] == NULL ? program_name : argv[0]); } + + for ( /* iterate ctypes */ + ctype = 0; + ctypes[ctype].f != NULL /* break at the end of ctypes */ + && 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; } diff --git a/src/strcmp.c b/src/strcmp.c index 33eab10..f3dc25f 100644 --- a/src/strcmp.c +++ b/src/strcmp.c @@ -1,5 +1,6 @@ /* - * Copyright (c) 2022–2024 DTB + * Copyright (c) 2023 DTB + * Copyright (c) 2023–2024 Emma Tebibyte * SPDX-License-Identifier: AGPL-3.0-or-later * * 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 * along with this program. If not, see https://www.gnu.org/licenses/. */ - #include /* fprintf(3), stderr */ -#include /* size_t */ -#include /* EX_USAGE */ +#include /* EX_OK, EX_USAGE */ char *program_name = "strcmp"; -int main(int argc, char *argv[]){ +int main(int argc, char *argv[]) { + int i; + 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] ); + return EX_USAGE; } - /* This compares the Nth character of arg[2] onward with argv[1]'s Nth - * character, rather than comparing each arg with argv[1] sequentially. */ - for (; *argv[1] != '\0'; ++argv[1]) { /* iterate chars in argv[1] */ - for (size_t i = 2; i < argc; ++argv[i], ++i) { /* iterate &argv[2] */ - /* this never overruns because of nul termination */ - if (*argv[i-1] != *argv[i]) { return *argv[i-1] - *argv[i]; } + for (; *argv[1] != '\0'; ++argv[1]) { + for (i = 2; i < argc; ++i) { + /* a former string has a greater byte value */ + if (*argv[i-1] > *argv[i]) { + return 1; + /* a latter string has a greater byte value */ + } else if (*argv[i-1] < *argv[i]++) { + return -1; /* actually 255 */ + } } } - return 0; + return EX_OK; } diff --git a/src/swab.rs b/src/swab.rs index dd5b464..671f485 100644 --- a/src/swab.rs +++ b/src/swab.rs @@ -25,19 +25,23 @@ use std::{ }; extern crate getopt; -use getopt::GetOpt; - extern crate sysexits; -use sysexits::{ EX_OK, EX_OSERR, EX_USAGE }; - extern crate strerror; + +use getopt::GetOpt; +use sysexits::{ EX_IOERR, EX_OK, EX_OSERR, EX_USAGE }; use strerror::StrError; -fn oserr(s: &str, e: Error) -> ExitCode { - eprintln!("{}: {}", s, e.strerror()); +fn oserr(argv0: &str, e: Error) -> ExitCode { + eprintln!("{}: {}", argv0, e.strerror()); 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 { eprintln!("Usage: {} [-w word_size]", s); ExitCode::from(EX_USAGE as u8) @@ -45,12 +49,11 @@ fn usage(s: &str) -> ExitCode { fn main() -> ExitCode { let argv = args().collect::>(); - let mut buf: Vec = Vec::new(); + let mut buf: Vec = Vec::new(); // holds the sequence getting swabbed let mut input = stdin(); let mut output = stdout().lock(); - 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:") { match opt.opt() { @@ -73,17 +76,19 @@ fn main() -> ExitCode { loop { match input.read(&mut buf) { - Ok(0) => break ExitCode::from(EX_OK as u8), - Ok(v) if v == wordsize => { + Ok(0) => break ExitCode::from(EX_OK as u8), // read nothing; bye + Ok(v) if v == wordsize => { // read full block; swab 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) + break ioerr(&argv[0], e); } + }, - Ok(v) => { + Ok(v) => { // partial read; partially write 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)