mm(1): add
This commit is contained in:
		
							parent
							
								
									abb8fdf935
								
							
						
					
					
						commit
						194b19f94b
					
				
							
								
								
									
										7
									
								
								Makefile
									
									
									
									
									
								
							
							
						
						
									
										7
									
								
								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 | ||||
|  | ||||
							
								
								
									
										76
									
								
								docs/mm.1
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										76
									
								
								docs/mm.1
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,76 @@ | ||||
| .\" Copyright (c) 2024 DTB <trinity@trinity.moe> | ||||
| .\" | ||||
| .\" This work is licensed under CC BY-SA 4.0. To see a copy of this license, | ||||
| .\" visit <http://creativecommons.org/licenses/by-sa/4.0/>. | ||||
| 
 | ||||
| .TH mm 1 | ||||
| 
 | ||||
| .SH NAME | ||||
| 
 | ||||
| mm \(en middleman | ||||
| 
 | ||||
| .SH SYNOPSIS | ||||
| 
 | ||||
| mm | ||||
| .RB ( -aenu ) | ||||
| .RB ( -i | ||||
| .RB [ input ]) | ||||
| .RB ( -o | ||||
| .RB [ output ]) | ||||
| 
 | ||||
| .SH DESCRIPTION | ||||
| 
 | ||||
| Mm catenates input files and writes them to the start of each output file. | ||||
| 
 | ||||
| .SH OPTIONS | ||||
| 
 | ||||
| Mm, upon receiving the | ||||
| .B -a | ||||
| option, will open subsequent outputs for appending rather than updating. | ||||
| .PP | ||||
| The | ||||
| .B -i | ||||
| option opens a path as an input. Without any inputs specified mm will use | ||||
| standard input. Standard input itself can be specified by giving the path '-'. | ||||
| .PP | ||||
| The | ||||
| .B -o | ||||
| option opens a path as an output. Without any outputs specified mm will use | ||||
| standard output. Standard output itself can be specified by giving the | ||||
| path '-'. Standard error itself can be specified with the | ||||
| .B -e | ||||
| option. | ||||
| .PP | ||||
| The | ||||
| .B -u | ||||
| option ensures neither input or output will be buffered. | ||||
| .PP | ||||
| The | ||||
| .B -n | ||||
| option tells mm to ignore SIGINT signals. | ||||
| 
 | ||||
| .SH DIAGNOSTICS | ||||
| 
 | ||||
| If an output can no longer be written mm prints a diagnostic message, ceases | ||||
| writing to that particular output, and if there are more outputs specified, | ||||
| continues, eventually exiting unsuccessfully. | ||||
| .PP | ||||
| On error mm prints a diagnostic message and exits with the appropriate | ||||
| sysexits.h(3) status.  | ||||
| 
 | ||||
| .SH BUGS | ||||
| 
 | ||||
| Mm does not truncate existing files, which may lead to unexpected results. | ||||
| 
 | ||||
| .SH RATIONALE | ||||
| 
 | ||||
| Mm was modeled after the cat and tee utilities specified in POSIX. | ||||
| 
 | ||||
| .SH COPYRIGHT | ||||
| 
 | ||||
| Copyright (C) 2024 DTB. License AGPLv3+: GNU AGPL version 3 or later | ||||
| <https://gnu.org/licenses/agpl.html>. | ||||
| 
 | ||||
| .SH SEE ALSO | ||||
| 
 | ||||
| cat(1p), dd(1), dj(1), tee(1p) | ||||
							
								
								
									
										217
									
								
								src/mm.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										217
									
								
								src/mm.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,217 @@ | ||||
| /*
 | ||||
|  * Copyright (c) 2024 DTB <trinity@trinity.moe> | ||||
|  * SPDX-License-Identifier: AGPL-3.0-or-later | ||||
|  * | ||||
|  * This program is free software: you can redistribute it and/or modify it under | ||||
|  * the terms of the GNU Affero General Public License as published by the Free | ||||
|  * Software Foundation, either version 3 of the License, or (at your option) any | ||||
|  * later version. | ||||
|  * | ||||
|  * This program is distributed in the hope that it will be useful, but WITHOUT | ||||
|  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS | ||||
|  * FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more | ||||
|  * details. | ||||
|  * | ||||
|  * You should have received a copy of the GNU Affero General Public License | ||||
|  * along with this program. If not, see https://www.gnu.org/licenses/.
 | ||||
|  */ | ||||
| 
 | ||||
| #include <errno.h> /* errno */ | ||||
| #include <signal.h> /* signal(2), SIG_ERR, SIG_IGN, SIGINT */ | ||||
| #include <stdio.h> /* fclose(3), fopen(3), fprintf(3), getc(3), putc(3), | ||||
|                     * setvbuf(3), size_t, _IONBF, NULL */ | ||||
| #include <stdlib.h> /* free(3), realloc(3) */ | ||||
| #include <string.h> /* strcmp(3), strerror(3) */ | ||||
| #include <unistd.h> /* getopt(3) */ | ||||
| #if !defined EX_OSERR || !defined EX_USAGE | ||||
| #	include <sysexits.h> | ||||
| #endif | ||||
| extern int errno; | ||||
| 
 | ||||
| /* This structure is how open files are tracked. */ | ||||
| struct Files{ | ||||
| 	size_t a;	/* allocation */ | ||||
| 	size_t s;	/* used size */ | ||||
| 	char *mode;	/* file opening mode */ | ||||
| 	char **names;	/* file names */ | ||||
| 	FILE **files;	/* file pointers */ | ||||
| }; | ||||
| 
 | ||||
| /* How much to grow the allocation when it's saturated. */ | ||||
| #ifndef ALLOC_INCREMENT | ||||
| #	define ALLOC_INCREMENT 1 | ||||
| #endif | ||||
| 
 | ||||
| /* How much to grow the allocation at program start. */ | ||||
| #ifndef ALLOC_INITIAL | ||||
| #	define ALLOC_INITIAL 10 | ||||
| #endif | ||||
| 
 | ||||
| /* pre-allocated strings */ | ||||
| static char *program_name = "<no argv[0]>"; | ||||
| static char *stdin_name = "<stdin>"; | ||||
| static char *stdout_name = "<stdout>"; | ||||
| static char *stderr_name = "<stderr>"; | ||||
| static char *(fmode[]) = { (char []){"rb"}, (char []){"rb+"} }; | ||||
| static char *wharsh = "wb"; | ||||
| 
 | ||||
| /* Adds the open FILE pointer for the file at the path s to the files struct,
 | ||||
|  * returning the FILE if successful and NULL if not, allocating more memory in | ||||
|  * the files buffers as needed. */ | ||||
| static FILE * | ||||
| Files_append(struct Files *files, FILE *file, char *name){ | ||||
| 
 | ||||
| 	if(file == NULL || (files->s == files->a | ||||
| 			&& ((files->files = realloc(files->files, | ||||
| 					(files->a += (files->a == 0) | ||||
| 						? ALLOC_INITIAL | ||||
| 						: ALLOC_INCREMENT) | ||||
| 					* sizeof *(files->files))) == NULL | ||||
| 				|| (files->names = realloc(files->names, | ||||
| 					files->a * sizeof *(files->names))) == NULL))) | ||||
| 		return NULL; | ||||
| 
 | ||||
| 	files->names[files->s] = name; | ||||
| 	return files->files[files->s++] = file; | ||||
| } | ||||
| 
 | ||||
| /* Opens the file at the path p and puts it in the files struct, returning NULL
 | ||||
|  * if either the opening or the placement of the open FILE pointer fail. */ | ||||
| #define Files_open(files, p) \ | ||||
| 	Files_append((files), fopen((p), (files)->mode), (p)) | ||||
| 
 | ||||
| /* Prints a diagnostic message based on errno and returns an exit status
 | ||||
|  * appropriate for an OS error. */ | ||||
| static int | ||||
| oserr(char *s, char *r){ | ||||
| 
 | ||||
| 	fprintf(stderr, "%s: %s: %s\n", s, r, strerror(errno)); | ||||
| 
 | ||||
| 	return EX_OSERR; | ||||
| } | ||||
| 
 | ||||
| /* Hijacks i and j from main and destructs the files[2] struct used by main by
 | ||||
|  * closing its files and freeing its files and names arrays, returning retval | ||||
|  * from main. */ | ||||
| #define terminate \ | ||||
| 	for(i = 0; i < 2; ++i){ \ | ||||
| 		for(j = 0; j < files[i].s; ++j) \ | ||||
| 			if(files[i].files[j] != stdin \ | ||||
| 					&& files[i].files[j] != stdout \ | ||||
| 					&& files[i].files[j] != stderr) \ | ||||
| 				fclose(files[i].files[j]); \ | ||||
| 		free(files[i].files); \ | ||||
| 		free(files[i].names); \ | ||||
| 	} \ | ||||
| 	return retval | ||||
| 
 | ||||
| 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; | ||||
| } | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user