/* * 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 * 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) */ #include /* EX_IOERR, EX_OK, EX_OSERR, EX_USAGE */ /* 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 */ char *program_name = "mm"; 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; } static int usage(char *argv0) { (void)fprintf( stderr, "Usage: %s [-aenu] [-i input]... [-o output]...\n", argv0 ); return EX_USAGE; } int main(int argc, char *argv[]) { int c; struct Files files[2]; /* {read, write} */ size_t i; size_t j; size_t k; /* loop index but also unbuffer status */ int retval; /* Initializes the files structs with their default values, standard * input and standard output. If an input or an output is specified * these initial values will be overwritten, so to, say, use mm(1) * equivalently to tee(1p), -o - will need to be specified before * additional files to ensure standard output is still written. */ for (i = 0; i < 2; ++i) { files[i].a = 0; files[i].s = 0; files[i].mode = fmode[i]; files[i].files = NULL; files[i].names = NULL; Files_append( &files[i], i == 0 ? stdin : stdout, i == 0 ? stdin_name : stdout_name ); files[i].s = 0; } k = 0; if (argc > 0) { program_name = argv[0]; 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; } return oserr(program_name, "-e"); case 'i': if ( (strcmp(optarg, "-") == 0 && Files_append(&files[0], stdin, stdin_name) != NULL) || Files_open(&files[0], optarg) != NULL ) { break; } return oserr(program_name, optarg); 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; } } return oserr(program_name, optarg); case 'n': if (signal(SIGINT, SIG_IGN) != SIG_ERR) { break; } return oserr(program_name, "-n"); case 'u': k = 1; break; default: return usage(program_name); } } } if (optind != argc) { return usage(program_name); } 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) { return retval; } } } } } return retval; }