mm(1): add
This commit is contained in:
		
							parent
							
								
									abb8fdf935
								
							
						
					
					
						commit
						194b19f94b
					
				
							
								
								
									
										7
									
								
								Makefile
									
									
									
									
									
								
							
							
						
						
									
										7
									
								
								Makefile
									
									
									
									
									
								
							| @ -17,7 +17,7 @@ CC=cc | |||||||
| RUSTC=rustc | RUSTC=rustc | ||||||
| 
 | 
 | ||||||
| .PHONY: all | .PHONY: all | ||||||
| all: dj false fop intcmp rpn scrut str strcmp true | all: dj false fop intcmp mm rpn scrut str strcmp true | ||||||
| 
 | 
 | ||||||
| build: | build: | ||||||
| 	# keep build/include until bindgen(1) has stdin support | 	# keep build/include until bindgen(1) has stdin support | ||||||
| @ -79,6 +79,11 @@ intcmp: build/bin/intcmp | |||||||
| build/bin/intcmp: src/intcmp.c build | build/bin/intcmp: src/intcmp.c build | ||||||
| 	$(CC) $(CFLAGS) -o $@ src/intcmp.c | 	$(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 | .PHONY: rpn | ||||||
| rpn: build/bin/rpn | rpn: build/bin/rpn | ||||||
| build/bin/rpn: src/rpn.rs build build/o/libsysexits.rlib | 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