From 194b19f94b9e3dd01408bfcf22fbe6717636c818 Mon Sep 17 00:00:00 2001 From: DTB Date: Sat, 17 Feb 2024 23:43:22 -0700 Subject: [PATCH 01/12] mm(1): add --- Makefile | 7 +- docs/mm.1 | 76 +++++++++++++++++++ src/mm.c | 217 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 299 insertions(+), 1 deletion(-) create mode 100644 docs/mm.1 create mode 100644 src/mm.c diff --git a/Makefile b/Makefile index 0f269f7..bf55ae7 100644 --- a/Makefile +++ b/Makefile @@ -17,7 +17,7 @@ CC=cc RUSTC=rustc .PHONY: all -all: dj false fop intcmp rpn scrut str strcmp true +all: dj false fop intcmp mm rpn scrut str strcmp true build: # keep build/include until bindgen(1) has stdin support @@ -79,6 +79,11 @@ intcmp: build/bin/intcmp build/bin/intcmp: src/intcmp.c build $(CC) $(CFLAGS) -o $@ src/intcmp.c +.PHONY: mm +mm: build/bin/mm +build/bin/mm: src/mm.c build + $(CC) $(CFLAGS) -o $@ src/mm.c + .PHONY: rpn rpn: build/bin/rpn build/bin/rpn: src/rpn.rs build build/o/libsysexits.rlib diff --git a/docs/mm.1 b/docs/mm.1 new file mode 100644 index 0000000..36fb1ba --- /dev/null +++ b/docs/mm.1 @@ -0,0 +1,76 @@ +.\" Copyright (c) 2024 DTB +.\" +.\" This work is licensed under CC BY-SA 4.0. To see a copy of this license, +.\" visit . + +.TH mm 1 + +.SH NAME + +mm \(en middleman + +.SH SYNOPSIS + +mm +.RB ( -aenu ) +.RB ( -i +.RB [ input ]) +.RB ( -o +.RB [ output ]) + +.SH DESCRIPTION + +Mm catenates input files and writes them to the start of each output file. + +.SH OPTIONS + +Mm, upon receiving the +.B -a +option, will open subsequent outputs for appending rather than updating. +.PP +The +.B -i +option opens a path as an input. Without any inputs specified mm will use +standard input. Standard input itself can be specified by giving the path '-'. +.PP +The +.B -o +option opens a path as an output. Without any outputs specified mm will use +standard output. Standard output itself can be specified by giving the +path '-'. Standard error itself can be specified with the +.B -e +option. +.PP +The +.B -u +option ensures neither input or output will be buffered. +.PP +The +.B -n +option tells mm to ignore SIGINT signals. + +.SH DIAGNOSTICS + +If an output can no longer be written mm prints a diagnostic message, ceases +writing to that particular output, and if there are more outputs specified, +continues, eventually exiting unsuccessfully. +.PP +On error mm prints a diagnostic message and exits with the appropriate +sysexits.h(3) status. + +.SH BUGS + +Mm does not truncate existing files, which may lead to unexpected results. + +.SH RATIONALE + +Mm was modeled after the cat and tee utilities specified in POSIX. + +.SH COPYRIGHT + +Copyright (C) 2024 DTB. License AGPLv3+: GNU AGPL version 3 or later +. + +.SH SEE ALSO + +cat(1p), dd(1), dj(1), tee(1p) diff --git a/src/mm.c b/src/mm.c new file mode 100644 index 0000000..6e53c8d --- /dev/null +++ b/src/mm.c @@ -0,0 +1,217 @@ +/* + * Copyright (c) 2024 DTB + * SPDX-License-Identifier: AGPL-3.0-or-later + * + * This program is free software: you can redistribute it and/or modify it under + * the terms of the GNU Affero General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) any + * later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more + * details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see https://www.gnu.org/licenses/. + */ + +#include /* errno */ +#include /* signal(2), SIG_ERR, SIG_IGN, SIGINT */ +#include /* fclose(3), fopen(3), fprintf(3), getc(3), putc(3), + * setvbuf(3), size_t, _IONBF, NULL */ +#include /* free(3), realloc(3) */ +#include /* strcmp(3), strerror(3) */ +#include /* getopt(3) */ +#if !defined EX_OSERR || !defined EX_USAGE +# include +#endif +extern int errno; + +/* This structure is how open files are tracked. */ +struct Files{ + size_t a; /* allocation */ + size_t s; /* used size */ + char *mode; /* file opening mode */ + char **names; /* file names */ + FILE **files; /* file pointers */ +}; + +/* How much to grow the allocation when it's saturated. */ +#ifndef ALLOC_INCREMENT +# define ALLOC_INCREMENT 1 +#endif + +/* How much to grow the allocation at program start. */ +#ifndef ALLOC_INITIAL +# define ALLOC_INITIAL 10 +#endif + +/* pre-allocated strings */ +static char *program_name = ""; +static char *stdin_name = ""; +static char *stdout_name = ""; +static char *stderr_name = ""; +static char *(fmode[]) = { (char []){"rb"}, (char []){"rb+"} }; +static char *wharsh = "wb"; + +/* Adds the open FILE pointer for the file at the path s to the files struct, + * returning the FILE if successful and NULL if not, allocating more memory in + * the files buffers as needed. */ +static FILE * +Files_append(struct Files *files, FILE *file, char *name){ + + if(file == NULL || (files->s == files->a + && ((files->files = realloc(files->files, + (files->a += (files->a == 0) + ? ALLOC_INITIAL + : ALLOC_INCREMENT) + * sizeof *(files->files))) == NULL + || (files->names = realloc(files->names, + files->a * sizeof *(files->names))) == NULL))) + return NULL; + + files->names[files->s] = name; + return files->files[files->s++] = file; +} + +/* Opens the file at the path p and puts it in the files struct, returning NULL + * if either the opening or the placement of the open FILE pointer fail. */ +#define Files_open(files, p) \ + Files_append((files), fopen((p), (files)->mode), (p)) + +/* Prints a diagnostic message based on errno and returns an exit status + * appropriate for an OS error. */ +static int +oserr(char *s, char *r){ + + fprintf(stderr, "%s: %s: %s\n", s, r, strerror(errno)); + + return EX_OSERR; +} + +/* Hijacks i and j from main and destructs the files[2] struct used by main by + * closing its files and freeing its files and names arrays, returning retval + * from main. */ +#define terminate \ + for(i = 0; i < 2; ++i){ \ + for(j = 0; j < files[i].s; ++j) \ + if(files[i].files[j] != stdin \ + && files[i].files[j] != stdout \ + && files[i].files[j] != stderr) \ + fclose(files[i].files[j]); \ + free(files[i].files); \ + free(files[i].names); \ + } \ + return retval + +int main(int argc, char *argv[]){ + int c; + struct Files files[2]; /* {read, write} */ + size_t i; + size_t j; + size_t k; /* loop index but also unbuffer status */ + int retval; + + /* Initializes the files structs with their default values, standard + * input and standard output. If an input or an output is specified + * these initial values will be overwritten, so to, say, use mm(1) + * equivalently to tee(1p), -o - will need to be specified before + * additional files to ensure standard output is still written. */ + for(i = 0; i < 2; ++i){ + files[i].a = 0; + files[i].s = 0; + files[i].mode = fmode[i]; + files[i].files = NULL; + files[i].names = NULL; + Files_append(&files[i], i == 0 ? stdin : stdout, + i == 0 ? stdin_name : stdout_name); + files[i].s = 0; + } + + k = 0; + + while((c = getopt(argc, argv, "aehi:no:u")) != -1) + switch(c){ + case 'a': /* "rb+" -> "ab" */ + files[1].mode[0] = 'a'; + files[1].mode[2] = '\0'; + break; + case 'e': + if(Files_append(&files[1], stderr, stderr_name) != NULL) + break; + retval = oserr(argv[0], "-e"); + terminate; + case 'i': + if((strcmp(optarg, "-") == 0 && Files_append(&files[0], + stdin, stdin_name) != NULL) + || Files_open(&files[0], optarg) != NULL) + break; + retval = oserr(argv[0], optarg); + terminate; + case 'o': + if((strcmp(optarg, "-") == 0 && Files_append(&files[1], + stdout, stdout_name) != NULL) + || Files_open(&files[1], optarg) != NULL) + break; + /* does not exist, so try to create it */ + if(errno == ENOENT){ + files[1].mode = wharsh; + if(Files_open(&files[1], optarg) != NULL){ + files[1].mode = fmode[1]; + break; + } + } + retval = oserr(argv[0], optarg); + terminate; + case 'n': + if(signal(SIGINT, SIG_IGN) != SIG_ERR) + break; + retval = oserr(argv[0], "-n"); + terminate; + case 'u': + k = 1; + break; + default: + fprintf(stderr, "Usage: %s (-aenu) (-i [input])..." + " (-o [output])...\n", argv[0]); + retval = EX_USAGE; + terminate; + } + + files[0].s += files[0].s == 0; + files[1].s += files[1].s == 0; + + /* Unbuffer files. */ + if(k){ + for(i = 0; + i < files[0].s; + setvbuf(files[0].files[i++], NULL, _IONBF, 0)); + for(i = 0; + i < files[1].s; + setvbuf(files[1].files[i++], NULL, _IONBF, 0)); + } + + retval = 0; + + /* Actual program loop. */ + for(i = 0; i < files[0].s; ++i) /* iterate ins */ + while((c = getc(files[0].files[i])) != EOF) /* iterate chars */ + for(j = 0; j < files[1].s; ++j) /* iterate outs */ + if(putc(c, files[1].files[j]) == EOF){ + /* notebook's full */ + retval = 1; + if(fclose(files[1].files[j]) == EOF) + fprintf(stderr, "%s: %s: %s\n", + program_name, files[1].names[j], strerror(errno)); + /* massage out the tense muscle */ + for(k = j; k < files[1].s - 1; ++k){ + files[1].files[k] = files[1].files[k+1]; + files[1].names[k] = files[1].names[k+1]; + } + if(--files[i].s == 0) + terminate; + } + + terminate; +} -- 2.46.1 From 395205d4c61cf94be98dda93af6a9289a9d21e4a Mon Sep 17 00:00:00 2001 From: DTB Date: Thu, 22 Feb 2024 19:14:38 -0700 Subject: [PATCH 02/12] mm.1: fix case in copyright thingy --- docs/mm.1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/mm.1 b/docs/mm.1 index 36fb1ba..e84ac66 100644 --- a/docs/mm.1 +++ b/docs/mm.1 @@ -68,7 +68,7 @@ Mm was modeled after the cat and tee utilities specified in POSIX. .SH COPYRIGHT -Copyright (C) 2024 DTB. License AGPLv3+: GNU AGPL version 3 or later +Copyright (c) 2024 DTB. License AGPLv3+: GNU AGPL version 3 or later . .SH SEE ALSO -- 2.46.1 From 58245c9484ca74ad2d71d0dbe63b861820c65db9 Mon Sep 17 00:00:00 2001 From: DTB Date: Thu, 22 Feb 2024 19:27:14 -0700 Subject: [PATCH 03/12] dj(1): remove function pointer hijinks (fixes #66) --- src/dj.c | 42 +++++++++++++++++++++--------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/src/dj.c b/src/dj.c index d5bd59c..8a6732c 100644 --- a/src/dj.c +++ b/src/dj.c @@ -203,28 +203,28 @@ Io_fdseek(struct Io *io){ if(!fdisstd(io->fd) && lseek(io->fd, io->seek, SEEK_SET) != -1) return -1; - else if(io->fl == write_flags){ - memset(io->buf, '\0', io->bs); - /* This is a dirty trick; rather than testing conditions and operating - * likewise, because the parameters to read or write are going to be - * the same either way, just use a function pointer to keep track of - * the intended operation. */ - op = (int (*)(int, void *, size_t))&write; - /* Function pointer casts are risky; this works because the difference - * is in the second parameter and only that write(2) makes the buffer - * const whereas read(2) does not. To avoid even the slightest - * undefined behavior comment out the cast, just be ready for a - * -Wincompatible-function-pointer-types if your compiler notices it. - */ - }else - op = &read; - /* We're going to cheat and use bufuse as the retval for write(2), which is - * fine because it'll be zeroed as this function returns anyway. */ - do{ if( (io->bufuse = (*op)(io->fd, io->buf, MIN(io->bs, io->seek))) == 0) - /* second chance */ - io->bufuse = (*op)(io->fd, io->buf, MIN(io->bs, io->seek)); - }while((io->seek -= io->bufuse) > 0 && io->bufuse != 0); + /* repeated code to get the condition out of the loop */ + if(io->fl == write_flags){ + memset(io->buf, '\0', io->bs); + /* We're going to cheat and use bufuse as the retval for write(2), + * which is fine because it'll be zeroed as this function returns + * anyway. */ + do{ + if((io->bufuse = write(io->fd, io->buf, MIN(io->bs, io->seek))) + == 0) + /* second chance */ + io->bufuse = write(io->fd, io->buf, MIN(io->bs, io->seek)); + }while((io->seek -= io->bufuse) > 0 && io->bufuse != 0); + }else if(io->fl == read_flags){ + do{ + if((io->bufuse = read(io->fd, io->buf, MIN(io->bs, io->seek))) + == 0) + /* second chance */ + io->bufuse = read(io->fd, io->buf, MIN(io->bs, io->seek)); + }while((io->seek -= io->bufuse) > 0 && io->bufuse != 0); + }else + return EX_SOFTWARE; io->bufuse = 0; -- 2.46.1 From 3f0d95fe8fe87cd3b3c6ba766e1ec643d6f7faa6 Mon Sep 17 00:00:00 2001 From: DTB Date: Fri, 23 Feb 2024 20:49:24 -0700 Subject: [PATCH 04/12] swab(1): minimum viable program --- Makefile | 7 +++++++ src/swab.rs | 47 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+) create mode 100644 src/swab.rs diff --git a/Makefile b/Makefile index 46fd88e..d8c0c59 100644 --- a/Makefile +++ b/Makefile @@ -101,6 +101,13 @@ strcmp: build/bin/strcmp build/bin/strcmp: src/strcmp.c build $(CC) $(CFLAGS) -o $@ src/strcmp.c +.PHONY: swab +swab: build/bin/swab +build/bin/swab: src/swab.rs build build/o/libsysexits.rlib + $(RUSTC) $(RUSTFLAGS) \ + --extern sysexits=build/o/libsysexits.rlib \ + -o $@ src/swab.rs + .PHONY: true true: build/bin/true build/bin/true: src/true.c build diff --git a/src/swab.rs b/src/swab.rs new file mode 100644 index 0000000..26104eb --- /dev/null +++ b/src/swab.rs @@ -0,0 +1,47 @@ +use std::{ + env::args, + io::{ stdin, stdout, Error, ErrorKind, Read, Write }, + process::ExitCode, + vec::Vec +}; + +extern crate sysexits; + +use sysexits::{ EX_OK, EX_OSERR }; + +fn oserr(s: &str, e: Error) -> ExitCode { + eprintln!("{}: {}", s, e); + ExitCode::from(EX_OSERR as u8) +} + +fn main() -> ExitCode { + let argv = args().collect::>(); + let mut buf: Vec = Vec::new(); + let mut input = stdin(); + let mut output = stdout().lock(); + + let wordsize: usize = 2; + let force = false; + + buf.resize(wordsize, 0); + + loop { + match input.read(&mut buf) { + Ok(0) => break ExitCode::from(EX_OK as u8), + Ok(v) if v == wordsize => { + let (left, right) = buf.split_at(v/2); + if let Err(e) = output.write(&right) + .and_then(|_| output.write(&left)) { + break oserr(&argv[0], e) + } + }, + Ok(v) => { + if let Err(e) = output.write(&buf[..v]) { + break oserr(&argv[0], e) + } + }, + Err(e) if e.kind() == ErrorKind::Interrupted && force => continue, + Err(e) => break oserr(&argv[0], e) + } + } +} -- 2.46.1 From e788947fc4adfd2084b7fb1af0cf7aee7acbee0a Mon Sep 17 00:00:00 2001 From: DTB Date: Sat, 24 Feb 2024 03:04:05 -0700 Subject: [PATCH 05/12] swab(1): add argument parsing --- Makefile | 2 +- src/swab.rs | 33 +++++++++++++++++++++++++++++---- 2 files changed, 30 insertions(+), 5 deletions(-) diff --git a/Makefile b/Makefile index d8c0c59..3c4f52e 100644 --- a/Makefile +++ b/Makefile @@ -104,7 +104,7 @@ build/bin/strcmp: src/strcmp.c build .PHONY: swab swab: build/bin/swab build/bin/swab: src/swab.rs build build/o/libsysexits.rlib - $(RUSTC) $(RUSTFLAGS) \ + $(RUSTC) $(RUSTFLAGS) --extern getopt=build/o/libgetopt.rlib \ --extern sysexits=build/o/libsysexits.rlib \ -o $@ src/swab.rs diff --git a/src/swab.rs b/src/swab.rs index 26104eb..f48842c 100644 --- a/src/swab.rs +++ b/src/swab.rs @@ -5,23 +5,48 @@ use std::{ vec::Vec }; -extern crate sysexits; +extern crate getopt; +use getopt::{ Opt, Parser }; -use sysexits::{ EX_OK, EX_OSERR }; +extern crate sysexits; +use sysexits::{ EX_OK, EX_OSERR, EX_USAGE }; fn oserr(s: &str, e: Error) -> ExitCode { eprintln!("{}: {}", s, e); ExitCode::from(EX_OSERR as u8) } +fn usage(s: &str) -> ExitCode { + eprintln!("Usage: {} (-f) (-w [wordsize])", s); + ExitCode::from(EX_USAGE as u8) +} + fn main() -> ExitCode { let argv = args().collect::>(); let mut buf: Vec = Vec::new(); let mut input = stdin(); let mut output = stdout().lock(); - let wordsize: usize = 2; - let force = false; + let mut opts = Parser::new(&argv, "fw:"); + let mut force = false; + let mut wordsize: usize = 2; + + loop { + match opts.next() { + None => break, + Some(opt) => + match opt { + Ok(Opt('f', None)) => force = true, + Ok(Opt('w', Some(arg))) => { + match arg.parse::() { + Ok(w) if w % 2 == 0 => { wordsize = w; () }, + _ => { return usage(&argv[0]); }, + } + }, + _ => { return usage(&argv[0]); } + } + } + } buf.resize(wordsize, 0); -- 2.46.1 From 1e041a52a2eab165af03a157c83ce388c05bd73c Mon Sep 17 00:00:00 2001 From: DTB Date: Sat, 24 Feb 2024 03:21:20 -0700 Subject: [PATCH 06/12] swab.1: add swab(1) man page --- docs/swab.1 | 71 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 docs/swab.1 diff --git a/docs/swab.1 b/docs/swab.1 new file mode 100644 index 0000000..a0c1be3 --- /dev/null +++ b/docs/swab.1 @@ -0,0 +1,71 @@ +.\" Copyright (c) 2024 DTB +.\" +.\" This work is licensed under CC BY-SA 4.0. To see a copy of this license, +.\" visit . + +.TH swab 1 + +.SH NAME + +swab \(en swap bytes + +.SH SYNOPSIS + +swab +.RB ( -f ) +.RB ( -w +.R [ +.B word size +.R ]) + +.SH USAGE + +Swab swaps the latter and former halves of a block of bytes. + +.SH EXAMPLES + +The following sh(1p) line: + +.R printf 'hello world!\n' | swab + +Produces the following output: + +.R ehll oowlr!d + +.SH OPTIONS + +The +.B -f +option ignores system call interruptions. +.PP +The +.B -w +option configures the word size; that is, the size in bytes of the block size +on which to operate. By default the word size is 2. The word size must be +cleanly divisible by 2, otherwise the block of bytes being processed can't be +halved. + +.SH DIAGNOSTICS + +If an error is encountered in input, output, or invocation, a diagnostic +message will be written to standard error and swab will exit with the +appropriate status from sysexits.h(3). + +.SH RATIONALE + +Swab was modeled after the +.R conv=swab +functionality specified in the POSIX dd utility but additionally allows the +word size to be configured. +.PP +Swab is useful for fixing the endianness of binary files produced on other +machines. + +.SH COPYRIGHT + +Copyright (c) 2024 DTB. License AGPLv3+: GNU AGPL version 3 or later +. + +.SH SEE ALSO + +dd(1p) -- 2.46.1 From bbac85daf8439c5b1ea9aebb6fe018066817cc12 Mon Sep 17 00:00:00 2001 From: DTB Date: Mon, 26 Feb 2024 08:42:06 -0700 Subject: [PATCH 07/12] swab(1): add copyright notice --- src/swab.rs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/swab.rs b/src/swab.rs index f48842c..ca944d9 100644 --- a/src/swab.rs +++ b/src/swab.rs @@ -1,3 +1,21 @@ +/* + * Copyright (c) 2024 DTB + * SPDX-License-Identifier: AGPL-3.0-or-later + * + * This program is free software: you can redistribute it and/or modify it under + * the terms of the GNU Affero General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) any + * later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more + * details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see https://www.gnu.org/licenses/. + */ + use std::{ env::args, io::{ stdin, stdout, Error, ErrorKind, Read, Write }, -- 2.46.1 From f14877118dd2206ab3d7d0914ba3a8a41c73a3cc Mon Sep 17 00:00:00 2001 From: DTB Date: Mon, 26 Feb 2024 08:43:03 -0700 Subject: [PATCH 08/12] Makefile: add swab(1) --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 3c4f52e..a630e09 100644 --- a/Makefile +++ b/Makefile @@ -17,7 +17,7 @@ CC=cc RUSTC=rustc .PHONY: all -all: dj false fop intcmp rpn scrut str strcmp true +all: dj false fop intcmp rpn scrut str strcmp swab true build: # keep build/include until bindgen(1) has stdin support -- 2.46.1 From d74bc715cf951352ae66d65403ebff55ab4e53c4 Mon Sep 17 00:00:00 2001 From: DTB Date: Mon, 26 Feb 2024 09:02:58 -0700 Subject: [PATCH 09/12] mm(1): fix full file handling --- src/mm.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/mm.c b/src/mm.c index 6e53c8d..5e81f7f 100644 --- a/src/mm.c +++ b/src/mm.c @@ -23,7 +23,8 @@ #include /* free(3), realloc(3) */ #include /* strcmp(3), strerror(3) */ #include /* getopt(3) */ -#if !defined EX_OSERR || !defined EX_USAGE +#if !defined EX_IOERR || !defined EX_OK || !defined EX_OSERR \ + || !defined EX_USAGE # include #endif extern int errno; @@ -192,7 +193,7 @@ int main(int argc, char *argv[]){ setvbuf(files[1].files[i++], NULL, _IONBF, 0)); } - retval = 0; + retval = EX_OK; /* Actual program loop. */ for(i = 0; i < files[0].s; ++i) /* iterate ins */ @@ -200,16 +201,16 @@ int main(int argc, char *argv[]){ for(j = 0; j < files[1].s; ++j) /* iterate outs */ if(putc(c, files[1].files[j]) == EOF){ /* notebook's full */ - retval = 1; + retval = EX_IOERR; if(fclose(files[1].files[j]) == EOF) fprintf(stderr, "%s: %s: %s\n", program_name, files[1].names[j], strerror(errno)); /* massage out the tense muscle */ - for(k = j; k < files[1].s - 1; ++k){ + for(k = j--; k < files[1].s - 1; ++k){ files[1].files[k] = files[1].files[k+1]; files[1].names[k] = files[1].names[k+1]; } - if(--files[i].s == 0) + if(--files[1].s == 0) terminate; } -- 2.46.1 From 135bf2a8ebf7b08bf10acb16e2ebb51d2395a682 Mon Sep 17 00:00:00 2001 From: DTB Date: Mon, 26 Feb 2024 09:11:47 -0700 Subject: [PATCH 10/12] mm(1), mm.1: bring source in line with documentation --- docs/mm.1 | 2 +- src/mm.c | 92 +++++++++++++++++++++++++++++-------------------------- 2 files changed, 50 insertions(+), 44 deletions(-) diff --git a/docs/mm.1 b/docs/mm.1 index e84ac66..2244588 100644 --- a/docs/mm.1 +++ b/docs/mm.1 @@ -56,7 +56,7 @@ writing to that particular output, and if there are more outputs specified, continues, eventually exiting unsuccessfully. .PP On error mm prints a diagnostic message and exits with the appropriate -sysexits.h(3) status. +sysexits.h(3) status. .SH BUGS diff --git a/src/mm.c b/src/mm.c index 5e81f7f..ff62148 100644 --- a/src/mm.c +++ b/src/mm.c @@ -132,53 +132,57 @@ int main(int argc, char *argv[]){ k = 0; - while((c = getopt(argc, argv, "aehi:no:u")) != -1) - switch(c){ - case 'a': /* "rb+" -> "ab" */ - files[1].mode[0] = 'a'; - files[1].mode[2] = '\0'; - break; - case 'e': - if(Files_append(&files[1], stderr, stderr_name) != NULL) + if(argc > 0) + program_name = argv[0]; + + if(argc > 1) + while((c = getopt(argc, argv, "aehi:no:u")) != -1) + switch(c){ + case 'a': /* "rb+" -> "ab" */ + files[1].mode[0] = 'a'; + files[1].mode[2] = '\0'; break; - retval = oserr(argv[0], "-e"); - terminate; - case 'i': - if((strcmp(optarg, "-") == 0 && Files_append(&files[0], - stdin, stdin_name) != NULL) - || Files_open(&files[0], optarg) != NULL) - break; - retval = oserr(argv[0], optarg); - terminate; - case 'o': - if((strcmp(optarg, "-") == 0 && Files_append(&files[1], - stdout, stdout_name) != NULL) - || Files_open(&files[1], optarg) != NULL) - break; - /* does not exist, so try to create it */ - if(errno == ENOENT){ - files[1].mode = wharsh; - if(Files_open(&files[1], optarg) != NULL){ - files[1].mode = fmode[1]; + case 'e': + if(Files_append(&files[1], stderr, stderr_name) != NULL) break; + retval = oserr(argv[0], "-e"); + terminate; + case 'i': + if((strcmp(optarg, "-") == 0 && Files_append(&files[0], + stdin, stdin_name) != NULL) + || Files_open(&files[0], optarg) != NULL) + break; + retval = oserr(argv[0], optarg); + terminate; + case 'o': + if((strcmp(optarg, "-") == 0 && Files_append(&files[1], + stdout, stdout_name) != NULL) + || Files_open(&files[1], optarg) != NULL) + break; + /* does not exist, so try to create it */ + if(errno == ENOENT){ + files[1].mode = wharsh; + if(Files_open(&files[1], optarg) != NULL){ + files[1].mode = fmode[1]; + break; + } } - } - retval = oserr(argv[0], optarg); - terminate; - case 'n': - if(signal(SIGINT, SIG_IGN) != SIG_ERR) + retval = oserr(argv[0], optarg); + terminate; + case 'n': + if(signal(SIGINT, SIG_IGN) != SIG_ERR) + break; + retval = oserr(argv[0], "-n"); + terminate; + case 'u': + k = 1; break; - retval = oserr(argv[0], "-n"); - terminate; - case 'u': - k = 1; - break; - default: - fprintf(stderr, "Usage: %s (-aenu) (-i [input])..." - " (-o [output])...\n", argv[0]); - retval = EX_USAGE; - terminate; - } + default: + fprintf(stderr, "Usage: %s (-aenu) (-i [input])..." + " (-o [output])...\n", argv[0]); + retval = EX_USAGE; + terminate; + } files[0].s += files[0].s == 0; files[1].s += files[1].s == 0; @@ -202,6 +206,8 @@ int main(int argc, char *argv[]){ if(putc(c, files[1].files[j]) == EOF){ /* notebook's full */ retval = EX_IOERR; + fprintf(stderr, "%s: %s: %s\n", + program_name, files[1].names[j], strerror(errno)); if(fclose(files[1].files[j]) == EOF) fprintf(stderr, "%s: %s: %s\n", program_name, files[1].names[j], strerror(errno)); -- 2.46.1 From 31ae3d1997fa30b916313baf74ada5db66dc084a Mon Sep 17 00:00:00 2001 From: emma Date: Sun, 24 Mar 2024 13:15:55 -0600 Subject: [PATCH 11/12] Revert "Merge branch 'main' into strerror (not PGP signed)" This reverts commit 7e45af1bcaadf15bc9d7d2d6e6fd68542f2f6ea1, reversing changes made to d87b5d095870cb0874629ebcda13f069e1c4014f. --- Makefile | 14 +--- docs/mm.1 | 76 ------------------ docs/swab.1 | 71 ----------------- src/dj.c | 40 +++++----- src/mm.c | 224 ---------------------------------------------------- src/swab.rs | 90 --------------------- 6 files changed, 21 insertions(+), 494 deletions(-) delete mode 100644 docs/mm.1 delete mode 100644 docs/swab.1 delete mode 100644 src/mm.c delete mode 100644 src/swab.rs diff --git a/Makefile b/Makefile index 6e6ff4e..dd77985 100644 --- a/Makefile +++ b/Makefile @@ -27,7 +27,7 @@ RUSTLIBS=--extern getopt=build/o/libgetopt.rlib \ CFLAGS+=-I$(SYSEXITS) .PHONY: all -all: dj false fop hru intcmp mm rpn scrut str strcmp swab true +all: dj false fop hru intcmp rpn scrut str strcmp true build: # keep build/include until bindgen(1) has stdin support @@ -96,11 +96,6 @@ intcmp: build/bin/intcmp build/bin/intcmp: src/intcmp.c build $(CC) $(CFLAGS) -o $@ src/intcmp.c -.PHONY: mm -mm: build/bin/mm -build/bin/mm: src/mm.c build - $(CC) $(CFLAGS) -o $@ src/mm.c - .PHONY: rpn rpn: build/bin/rpn build/bin/rpn: src/rpn.rs build rustlibs @@ -121,13 +116,6 @@ strcmp: build/bin/strcmp build/bin/strcmp: src/strcmp.c build $(CC) $(CFLAGS) -o $@ src/strcmp.c -.PHONY: swab -swab: build/bin/swab -build/bin/swab: src/swab.rs build build/o/libsysexits.rlib - $(RUSTC) $(RUSTFLAGS) --extern getopt=build/o/libgetopt.rlib \ - --extern sysexits=build/o/libsysexits.rlib \ - -o $@ src/swab.rs - .PHONY: true true: build/bin/true build/bin/true: src/true.c build diff --git a/docs/mm.1 b/docs/mm.1 deleted file mode 100644 index 2244588..0000000 --- a/docs/mm.1 +++ /dev/null @@ -1,76 +0,0 @@ -.\" Copyright (c) 2024 DTB -.\" -.\" This work is licensed under CC BY-SA 4.0. To see a copy of this license, -.\" visit . - -.TH mm 1 - -.SH NAME - -mm \(en middleman - -.SH SYNOPSIS - -mm -.RB ( -aenu ) -.RB ( -i -.RB [ input ]) -.RB ( -o -.RB [ output ]) - -.SH DESCRIPTION - -Mm catenates input files and writes them to the start of each output file. - -.SH OPTIONS - -Mm, upon receiving the -.B -a -option, will open subsequent outputs for appending rather than updating. -.PP -The -.B -i -option opens a path as an input. Without any inputs specified mm will use -standard input. Standard input itself can be specified by giving the path '-'. -.PP -The -.B -o -option opens a path as an output. Without any outputs specified mm will use -standard output. Standard output itself can be specified by giving the -path '-'. Standard error itself can be specified with the -.B -e -option. -.PP -The -.B -u -option ensures neither input or output will be buffered. -.PP -The -.B -n -option tells mm to ignore SIGINT signals. - -.SH DIAGNOSTICS - -If an output can no longer be written mm prints a diagnostic message, ceases -writing to that particular output, and if there are more outputs specified, -continues, eventually exiting unsuccessfully. -.PP -On error mm prints a diagnostic message and exits with the appropriate -sysexits.h(3) status. - -.SH BUGS - -Mm does not truncate existing files, which may lead to unexpected results. - -.SH RATIONALE - -Mm was modeled after the cat and tee utilities specified in POSIX. - -.SH COPYRIGHT - -Copyright (c) 2024 DTB. License AGPLv3+: GNU AGPL version 3 or later -. - -.SH SEE ALSO - -cat(1p), dd(1), dj(1), tee(1p) diff --git a/docs/swab.1 b/docs/swab.1 deleted file mode 100644 index a0c1be3..0000000 --- a/docs/swab.1 +++ /dev/null @@ -1,71 +0,0 @@ -.\" Copyright (c) 2024 DTB -.\" -.\" This work is licensed under CC BY-SA 4.0. To see a copy of this license, -.\" visit . - -.TH swab 1 - -.SH NAME - -swab \(en swap bytes - -.SH SYNOPSIS - -swab -.RB ( -f ) -.RB ( -w -.R [ -.B word size -.R ]) - -.SH USAGE - -Swab swaps the latter and former halves of a block of bytes. - -.SH EXAMPLES - -The following sh(1p) line: - -.R printf 'hello world!\n' | swab - -Produces the following output: - -.R ehll oowlr!d - -.SH OPTIONS - -The -.B -f -option ignores system call interruptions. -.PP -The -.B -w -option configures the word size; that is, the size in bytes of the block size -on which to operate. By default the word size is 2. The word size must be -cleanly divisible by 2, otherwise the block of bytes being processed can't be -halved. - -.SH DIAGNOSTICS - -If an error is encountered in input, output, or invocation, a diagnostic -message will be written to standard error and swab will exit with the -appropriate status from sysexits.h(3). - -.SH RATIONALE - -Swab was modeled after the -.R conv=swab -functionality specified in the POSIX dd utility but additionally allows the -word size to be configured. -.PP -Swab is useful for fixing the endianness of binary files produced on other -machines. - -.SH COPYRIGHT - -Copyright (c) 2024 DTB. License AGPLv3+: GNU AGPL version 3 or later -. - -.SH SEE ALSO - -dd(1p) diff --git a/src/dj.c b/src/dj.c index 8a6732c..d5bd59c 100644 --- a/src/dj.c +++ b/src/dj.c @@ -203,28 +203,28 @@ Io_fdseek(struct Io *io){ if(!fdisstd(io->fd) && lseek(io->fd, io->seek, SEEK_SET) != -1) return -1; - - /* repeated code to get the condition out of the loop */ - if(io->fl == write_flags){ + else if(io->fl == write_flags){ memset(io->buf, '\0', io->bs); - /* We're going to cheat and use bufuse as the retval for write(2), - * which is fine because it'll be zeroed as this function returns - * anyway. */ - do{ - if((io->bufuse = write(io->fd, io->buf, MIN(io->bs, io->seek))) - == 0) - /* second chance */ - io->bufuse = write(io->fd, io->buf, MIN(io->bs, io->seek)); - }while((io->seek -= io->bufuse) > 0 && io->bufuse != 0); - }else if(io->fl == read_flags){ - do{ - if((io->bufuse = read(io->fd, io->buf, MIN(io->bs, io->seek))) - == 0) - /* second chance */ - io->bufuse = read(io->fd, io->buf, MIN(io->bs, io->seek)); - }while((io->seek -= io->bufuse) > 0 && io->bufuse != 0); + /* This is a dirty trick; rather than testing conditions and operating + * likewise, because the parameters to read or write are going to be + * the same either way, just use a function pointer to keep track of + * the intended operation. */ + op = (int (*)(int, void *, size_t))&write; + /* Function pointer casts are risky; this works because the difference + * is in the second parameter and only that write(2) makes the buffer + * const whereas read(2) does not. To avoid even the slightest + * undefined behavior comment out the cast, just be ready for a + * -Wincompatible-function-pointer-types if your compiler notices it. + */ }else - return EX_SOFTWARE; + op = &read; + + /* We're going to cheat and use bufuse as the retval for write(2), which is + * fine because it'll be zeroed as this function returns anyway. */ + do{ if( (io->bufuse = (*op)(io->fd, io->buf, MIN(io->bs, io->seek))) == 0) + /* second chance */ + io->bufuse = (*op)(io->fd, io->buf, MIN(io->bs, io->seek)); + }while((io->seek -= io->bufuse) > 0 && io->bufuse != 0); io->bufuse = 0; diff --git a/src/mm.c b/src/mm.c deleted file mode 100644 index ff62148..0000000 --- a/src/mm.c +++ /dev/null @@ -1,224 +0,0 @@ -/* - * Copyright (c) 2024 DTB - * SPDX-License-Identifier: AGPL-3.0-or-later - * - * This program is free software: you can redistribute it and/or modify it under - * the terms of the GNU Affero General Public License as published by the Free - * Software Foundation, either version 3 of the License, or (at your option) any - * later version. - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more - * details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see https://www.gnu.org/licenses/. - */ - -#include /* errno */ -#include /* signal(2), SIG_ERR, SIG_IGN, SIGINT */ -#include /* fclose(3), fopen(3), fprintf(3), getc(3), putc(3), - * setvbuf(3), size_t, _IONBF, NULL */ -#include /* free(3), realloc(3) */ -#include /* strcmp(3), strerror(3) */ -#include /* getopt(3) */ -#if !defined EX_IOERR || !defined EX_OK || !defined EX_OSERR \ - || !defined EX_USAGE -# include -#endif -extern int errno; - -/* This structure is how open files are tracked. */ -struct Files{ - size_t a; /* allocation */ - size_t s; /* used size */ - char *mode; /* file opening mode */ - char **names; /* file names */ - FILE **files; /* file pointers */ -}; - -/* How much to grow the allocation when it's saturated. */ -#ifndef ALLOC_INCREMENT -# define ALLOC_INCREMENT 1 -#endif - -/* How much to grow the allocation at program start. */ -#ifndef ALLOC_INITIAL -# define ALLOC_INITIAL 10 -#endif - -/* pre-allocated strings */ -static char *program_name = ""; -static char *stdin_name = ""; -static char *stdout_name = ""; -static char *stderr_name = ""; -static char *(fmode[]) = { (char []){"rb"}, (char []){"rb+"} }; -static char *wharsh = "wb"; - -/* Adds the open FILE pointer for the file at the path s to the files struct, - * returning the FILE if successful and NULL if not, allocating more memory in - * the files buffers as needed. */ -static FILE * -Files_append(struct Files *files, FILE *file, char *name){ - - if(file == NULL || (files->s == files->a - && ((files->files = realloc(files->files, - (files->a += (files->a == 0) - ? ALLOC_INITIAL - : ALLOC_INCREMENT) - * sizeof *(files->files))) == NULL - || (files->names = realloc(files->names, - files->a * sizeof *(files->names))) == NULL))) - return NULL; - - files->names[files->s] = name; - return files->files[files->s++] = file; -} - -/* Opens the file at the path p and puts it in the files struct, returning NULL - * if either the opening or the placement of the open FILE pointer fail. */ -#define Files_open(files, p) \ - Files_append((files), fopen((p), (files)->mode), (p)) - -/* Prints a diagnostic message based on errno and returns an exit status - * appropriate for an OS error. */ -static int -oserr(char *s, char *r){ - - fprintf(stderr, "%s: %s: %s\n", s, r, strerror(errno)); - - return EX_OSERR; -} - -/* Hijacks i and j from main and destructs the files[2] struct used by main by - * closing its files and freeing its files and names arrays, returning retval - * from main. */ -#define terminate \ - for(i = 0; i < 2; ++i){ \ - for(j = 0; j < files[i].s; ++j) \ - if(files[i].files[j] != stdin \ - && files[i].files[j] != stdout \ - && files[i].files[j] != stderr) \ - fclose(files[i].files[j]); \ - free(files[i].files); \ - free(files[i].names); \ - } \ - return retval - -int main(int argc, char *argv[]){ - int c; - struct Files files[2]; /* {read, write} */ - size_t i; - size_t j; - size_t k; /* loop index but also unbuffer status */ - int retval; - - /* Initializes the files structs with their default values, standard - * input and standard output. If an input or an output is specified - * these initial values will be overwritten, so to, say, use mm(1) - * equivalently to tee(1p), -o - will need to be specified before - * additional files to ensure standard output is still written. */ - for(i = 0; i < 2; ++i){ - files[i].a = 0; - files[i].s = 0; - files[i].mode = fmode[i]; - files[i].files = NULL; - files[i].names = NULL; - Files_append(&files[i], i == 0 ? stdin : stdout, - i == 0 ? stdin_name : stdout_name); - files[i].s = 0; - } - - k = 0; - - if(argc > 0) - program_name = argv[0]; - - if(argc > 1) - while((c = getopt(argc, argv, "aehi:no:u")) != -1) - switch(c){ - case 'a': /* "rb+" -> "ab" */ - files[1].mode[0] = 'a'; - files[1].mode[2] = '\0'; - break; - case 'e': - if(Files_append(&files[1], stderr, stderr_name) != NULL) - break; - retval = oserr(argv[0], "-e"); - terminate; - case 'i': - if((strcmp(optarg, "-") == 0 && Files_append(&files[0], - stdin, stdin_name) != NULL) - || Files_open(&files[0], optarg) != NULL) - break; - retval = oserr(argv[0], optarg); - terminate; - case 'o': - if((strcmp(optarg, "-") == 0 && Files_append(&files[1], - stdout, stdout_name) != NULL) - || Files_open(&files[1], optarg) != NULL) - break; - /* does not exist, so try to create it */ - if(errno == ENOENT){ - files[1].mode = wharsh; - if(Files_open(&files[1], optarg) != NULL){ - files[1].mode = fmode[1]; - break; - } - } - retval = oserr(argv[0], optarg); - terminate; - case 'n': - if(signal(SIGINT, SIG_IGN) != SIG_ERR) - break; - retval = oserr(argv[0], "-n"); - terminate; - case 'u': - k = 1; - break; - default: - fprintf(stderr, "Usage: %s (-aenu) (-i [input])..." - " (-o [output])...\n", argv[0]); - retval = EX_USAGE; - terminate; - } - - files[0].s += files[0].s == 0; - files[1].s += files[1].s == 0; - - /* Unbuffer files. */ - if(k){ - for(i = 0; - i < files[0].s; - setvbuf(files[0].files[i++], NULL, _IONBF, 0)); - for(i = 0; - i < files[1].s; - setvbuf(files[1].files[i++], NULL, _IONBF, 0)); - } - - retval = EX_OK; - - /* Actual program loop. */ - for(i = 0; i < files[0].s; ++i) /* iterate ins */ - while((c = getc(files[0].files[i])) != EOF) /* iterate chars */ - for(j = 0; j < files[1].s; ++j) /* iterate outs */ - if(putc(c, files[1].files[j]) == EOF){ - /* notebook's full */ - retval = EX_IOERR; - fprintf(stderr, "%s: %s: %s\n", - program_name, files[1].names[j], strerror(errno)); - if(fclose(files[1].files[j]) == EOF) - fprintf(stderr, "%s: %s: %s\n", - program_name, files[1].names[j], strerror(errno)); - /* massage out the tense muscle */ - for(k = j--; k < files[1].s - 1; ++k){ - files[1].files[k] = files[1].files[k+1]; - files[1].names[k] = files[1].names[k+1]; - } - if(--files[1].s == 0) - terminate; - } - - terminate; -} diff --git a/src/swab.rs b/src/swab.rs deleted file mode 100644 index ca944d9..0000000 --- a/src/swab.rs +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Copyright (c) 2024 DTB - * SPDX-License-Identifier: AGPL-3.0-or-later - * - * This program is free software: you can redistribute it and/or modify it under - * the terms of the GNU Affero General Public License as published by the Free - * Software Foundation, either version 3 of the License, or (at your option) any - * later version. - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more - * details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see https://www.gnu.org/licenses/. - */ - -use std::{ - env::args, - io::{ stdin, stdout, Error, ErrorKind, Read, Write }, - process::ExitCode, - vec::Vec -}; - -extern crate getopt; -use getopt::{ Opt, Parser }; - -extern crate sysexits; -use sysexits::{ EX_OK, EX_OSERR, EX_USAGE }; - -fn oserr(s: &str, e: Error) -> ExitCode { - eprintln!("{}: {}", s, e); - ExitCode::from(EX_OSERR as u8) -} - -fn usage(s: &str) -> ExitCode { - eprintln!("Usage: {} (-f) (-w [wordsize])", s); - ExitCode::from(EX_USAGE as u8) -} - -fn main() -> ExitCode { - let argv = args().collect::>(); - let mut buf: Vec = Vec::new(); - let mut input = stdin(); - let mut output = stdout().lock(); - - let mut opts = Parser::new(&argv, "fw:"); - let mut force = false; - let mut wordsize: usize = 2; - - loop { - match opts.next() { - None => break, - Some(opt) => - match opt { - Ok(Opt('f', None)) => force = true, - Ok(Opt('w', Some(arg))) => { - match arg.parse::() { - Ok(w) if w % 2 == 0 => { wordsize = w; () }, - _ => { return usage(&argv[0]); }, - } - }, - _ => { return usage(&argv[0]); } - } - } - } - - buf.resize(wordsize, 0); - - loop { - match input.read(&mut buf) { - Ok(0) => break ExitCode::from(EX_OK as u8), - Ok(v) if v == wordsize => { - let (left, right) = buf.split_at(v/2); - if let Err(e) = output.write(&right) - .and_then(|_| output.write(&left)) { - break oserr(&argv[0], e) - } - }, - Ok(v) => { - if let Err(e) = output.write(&buf[..v]) { - break oserr(&argv[0], e) - } - }, - Err(e) if e.kind() == ErrorKind::Interrupted && force => continue, - Err(e) => break oserr(&argv[0], e) - } - } -} -- 2.46.1 From c75bb930018bc1d39b01e1ae622b22b0e419fcaa Mon Sep 17 00:00:00 2001 From: emma Date: Sun, 24 Mar 2024 13:28:42 -0600 Subject: [PATCH 12/12] Makefile: directory specification changes --- Makefile | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/Makefile b/Makefile index dd77985..3a9c844 100644 --- a/Makefile +++ b/Makefile @@ -13,18 +13,18 @@ .PRAGMA: posix_202x # future POSIX standard support à la pdpmake(1) .PRAGMA: command_comment # breaks without this? -DESTDIR=./dist -PREFIX=/usr/local +DESTDIR ?= dist +PREFIX ?= /usr/local -SYSEXITS!=printf '\043include \n' | cpp -M - | sed 's/ /\n/g' \ - | sed -n 's/sysexits\.h//p' || printf 'include/\n' +SYSEXITS != printf '\043include \n' | cpp -M - | sed 's/ /\n/g' \ + | sed -n 's/sysexits\.h//p' || printf 'include\n' -CC?=cc -RUSTC?=rustc -RUSTLIBS=--extern getopt=build/o/libgetopt.rlib \ +CC ?= cc +RUSTC ?= rustc +RUSTLIBS = --extern getopt=build/o/libgetopt.rlib \ --extern sysexits=build/o/libsysexits.rlib \ --extern strerror=build/o/libstrerror.rlib -CFLAGS+=-I$(SYSEXITS) +CFLAGS += -I$(SYSEXITS) .PHONY: all all: dj false fop hru intcmp rpn scrut str strcmp true @@ -36,12 +36,12 @@ build: .PHONY: clean clean: - rm -rf build/ dist/ + rm -rf build dist dist: all - mkdir -p $(DESTDIR)$(PREFIX)/bin $(DESTDIR)$(PREFIX)/share/man/man1 - cp build/bin/* $(DESTDIR)$(PREFIX)/bin/ - cp docs/*.1 $(DESTDIR)$(PREFIX)/share/man/man1/ + mkdir -p $(DESTDIR)/$(PREFIX)/bin $(DESTDIR)/$(PREFIX)/share/man/man1 + cp build/bin/* $(DESTDIR)/$(PREFIX)/bin + cp docs/*.1 $(DESTDIR)/$(PREFIX)/share/man/man1 .PHONY: install install: dist -- 2.46.1