mm(1): rewritten in Rust #143
							
								
								
									
										4
									
								
								Makefile
									
									
									
									
									
								
							
							
						
						
									
										4
									
								
								Makefile
									
									
									
									
									
								
							@ -114,8 +114,8 @@ build/bin/intcmp: src/intcmp.c build
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
.PHONY: mm
 | 
					.PHONY: mm
 | 
				
			||||||
mm: build/bin/mm
 | 
					mm: build/bin/mm
 | 
				
			||||||
build/bin/mm: src/mm.c build
 | 
					build/bin/mm: src/mm.rs build rustlibs 
 | 
				
			||||||
	$(CC) $(CFLAGS) -o $@ src/mm.c
 | 
						$(RUSTC) $(RUSTFLAGS) $(RUSTLIBS) -o $@ src/mm.rs
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.PHONY: npc
 | 
					.PHONY: npc
 | 
				
			||||||
npc: build/bin/npc
 | 
					npc: build/bin/npc
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										32
									
								
								docs/mm.1
									
									
									
									
									
								
							
							
						
						
									
										32
									
								
								docs/mm.1
									
									
									
									
									
								
							@ -3,14 +3,14 @@
 | 
				
			|||||||
.\" This work is licensed under CC BY-SA 4.0. To see a copy of this license,
 | 
					.\" 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/>.
 | 
					.\" visit <http://creativecommons.org/licenses/by-sa/4.0/>.
 | 
				
			||||||
.\"
 | 
					.\"
 | 
				
			||||||
.TH MM 1 2024-06-17 "Harakit X.X.X"
 | 
					.TH MM 1 2024-07-14 "Harakit X.X.X"
 | 
				
			||||||
.SH NAME
 | 
					.SH NAME
 | 
				
			||||||
mm \(en middleman
 | 
					mm \(en middleman
 | 
				
			||||||
.\"
 | 
					.\"
 | 
				
			||||||
.SH SYNOPSIS
 | 
					.SH SYNOPSIS
 | 
				
			||||||
 | 
					
 | 
				
			||||||
mm
 | 
					mm
 | 
				
			||||||
.RB [ -aenu ]
 | 
					.RB [ -aetu ]
 | 
				
			||||||
.RB [ -i\ input ]
 | 
					.RB [ -i\ input ]
 | 
				
			||||||
.RB [ -o\ output ]
 | 
					.RB [ -o\ output ]
 | 
				
			||||||
.\"
 | 
					.\"
 | 
				
			||||||
@ -21,19 +21,25 @@ Catenate input files and write them to the start of each output file or stream.
 | 
				
			|||||||
.SH OPTIONS
 | 
					.SH OPTIONS
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.IP \fB-a\fP
 | 
					.IP \fB-a\fP
 | 
				
			||||||
Opens subsequent outputs for appending rather than updating.
 | 
					Opens outputs for appending rather than updating.
 | 
				
			||||||
.IP \fB-e\fP
 | 
					.IP \fB-e\fP
 | 
				
			||||||
Use the standard error as an output.
 | 
					Use the standard error as an output.
 | 
				
			||||||
.IP \fB-i\fP\ \fIinput\fP
 | 
					.IP \fB-t\fP
 | 
				
			||||||
Opens a path as an input. If one or more of the input files is \(lq-\(rq or if
 | 
					Causes outputs to be overwritten instead of being truncated.
 | 
				
			||||||
no inputs are specified, the standard input shall be used.
 | 
					 | 
				
			||||||
.IP \fB-o\fP\ \fIoutput\fP
 | 
					 | 
				
			||||||
Opens a path as an output. If one or more of the output files is \(lq-\(rq or if
 | 
					 | 
				
			||||||
no outputs are specified, the standard output shall be used.
 | 
					 | 
				
			||||||
.IP \fB-u\fP
 | 
					.IP \fB-u\fP
 | 
				
			||||||
Ensures neither input or output will be buffered.
 | 
					Ensures neither input or output will be buffered.
 | 
				
			||||||
.IP \fB-n\fP
 | 
					.IP \fB-i\fP\ \fIinput\fP
 | 
				
			||||||
Causes SIGINT signals to be ignored.
 | 
					Opens a path as an input. If one or more of the input files is \(lq-\(rq or if
 | 
				
			||||||
 | 
					no inputs are specified, the standard input shall be used. If specified as the
 | 
				
			||||||
 | 
					last option and if there are trailing arguments to the program, they shall be
 | 
				
			||||||
 | 
					appended to the list of files to use as inputs.
 | 
				
			||||||
 | 
					.IP \fB-o\fP\ \fIoutput\fP
 | 
				
			||||||
 | 
					Opens a path as an output. If one or more of the output files is \(lq-\(rq or if
 | 
				
			||||||
 | 
					no outputs are specified and the
 | 
				
			||||||
 | 
					.B -e
 | 
				
			||||||
 | 
					option is not specified, the standard output shall be used. If specified as the
 | 
				
			||||||
 | 
					last option and if there are trailing arguments to the program, they shall be
 | 
				
			||||||
 | 
					appended to the list of files to use as outputs.
 | 
				
			||||||
.\"
 | 
					.\"
 | 
				
			||||||
.SH DIAGNOSTICS
 | 
					.SH DIAGNOSTICS
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -45,10 +51,6 @@ exits with the appropriate
 | 
				
			|||||||
.BR sysexits.h (3)
 | 
					.BR sysexits.h (3)
 | 
				
			||||||
status.
 | 
					status.
 | 
				
			||||||
.\"
 | 
					.\"
 | 
				
			||||||
.SH CAVEATS
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
Existing files are not truncated on ouput and are instead overwritten.
 | 
					 | 
				
			||||||
.\"
 | 
					 | 
				
			||||||
.SH RATIONALE
 | 
					.SH RATIONALE
 | 
				
			||||||
 | 
					
 | 
				
			||||||
The
 | 
					The
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										236
									
								
								src/mm.c
									
									
									
									
									
								
							
							
						
						
									
										236
									
								
								src/mm.c
									
									
									
									
									
								
							@ -1,236 +0,0 @@
 | 
				
			|||||||
/*
 | 
					 | 
				
			||||||
 * 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_IOERR || !defined EX_OK || !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
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/* Prints a usage text, in which s is the program being run (i.e. argv[0]), and
 | 
					 | 
				
			||||||
 * returns an exit status appropriate for a usage error. */
 | 
					 | 
				
			||||||
int usage(char *s){
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	fprintf(stderr, "Usage: %s [-aenu] [-i input]... [-o output]...\n", s);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	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];
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	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:
 | 
					 | 
				
			||||||
				retval = usage(argv[0]);
 | 
					 | 
				
			||||||
				terminate;
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	if(optind != argc){
 | 
					 | 
				
			||||||
		retval = usage(argv[0]);
 | 
					 | 
				
			||||||
		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;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
							
								
								
									
										185
									
								
								src/mm.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										185
									
								
								src/mm.rs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,185 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * Copyright (c) 2024 Emma Tebibyte <emma@tebibyte.media>
 | 
				
			||||||
 | 
					 * 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/.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use std::{
 | 
				
			||||||
 | 
						env::args,
 | 
				
			||||||
 | 
						fs::File,
 | 
				
			||||||
 | 
						io::{ stdin, stdout, stderr, BufWriter, Read, Write },
 | 
				
			||||||
 | 
						os::fd::{ AsRawFd, FromRawFd },
 | 
				
			||||||
 | 
						process::{ exit, ExitCode },
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					extern crate getopt;
 | 
				
			||||||
 | 
					extern crate strerror;
 | 
				
			||||||
 | 
					extern crate sysexits;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use getopt::GetOpt;
 | 
				
			||||||
 | 
					use strerror::StrError;
 | 
				
			||||||
 | 
					use sysexits::{ EX_IOERR, EX_USAGE };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use ArgMode::*;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					enum ArgMode { In, Out }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn main() -> ExitCode {
 | 
				
			||||||
 | 
						let argv = args().collect::<Vec<_>>();
 | 
				
			||||||
 | 
						let usage = format!("Usage: {} [-aetu] [-i input] [-o output]", argv[0]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						let mut a = false; /* append to the file */
 | 
				
			||||||
 | 
						let mut e = false; /* use stderr as an output */
 | 
				
			||||||
 | 
						let mut t = true; /* do not truncate the file before writing */
 | 
				
			||||||
 | 
						let mut u = false; /* unbuffer i/o */
 | 
				
			||||||
 | 
						let mut ins = Vec::new(); /* initial input file path vector */
 | 
				
			||||||
 | 
						let mut outs = Vec::new(); /* initial output file path vector */
 | 
				
			||||||
 | 
						let mut mode: Option<ArgMode> = None; /* mode set by last-used option */
 | 
				
			||||||
 | 
						let mut optind = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						while let Some(opt) = argv.getopt("aei:o:tu") {
 | 
				
			||||||
 | 
							match opt.opt() {
 | 
				
			||||||
 | 
								Ok("a") => a = true,
 | 
				
			||||||
 | 
								Ok("e") => e = true, 
 | 
				
			||||||
 | 
								Ok("u") => u = true,
 | 
				
			||||||
 | 
								Ok("t") => t = false,
 | 
				
			||||||
 | 
								Ok("i") => { /* add inputs */
 | 
				
			||||||
 | 
									let input = opt.arg().unwrap();
 | 
				
			||||||
 | 
									ins.push(input);
 | 
				
			||||||
 | 
									mode = Some(In); /* latest argument == -i */
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								Ok("o") => { /* add output */
 | 
				
			||||||
 | 
									let output = opt.arg().unwrap(); 
 | 
				
			||||||
 | 
									outs.push(output);
 | 
				
			||||||
 | 
									mode = Some(Out); /* latest argument == -o */
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								Err(_) | Ok(_) => {
 | 
				
			||||||
 | 
									eprintln!("{}", usage);
 | 
				
			||||||
 | 
									return ExitCode::from(EX_USAGE as u8);
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							optind = opt.ind();
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						let remaining = argv.iter().skip(optind);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/* check the last flag specified */
 | 
				
			||||||
 | 
						if let Some(m) = mode {
 | 
				
			||||||
 | 
							for arg in remaining {
 | 
				
			||||||
 | 
								/* move the subsequent arguments to the list of inputs or outputs */
 | 
				
			||||||
 | 
								match m {
 | 
				
			||||||
 | 
									In => ins.push(arg.to_string()),
 | 
				
			||||||
 | 
									Out => outs.push(arg.to_string()),
 | 
				
			||||||
 | 
								};
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							eprintln!("{}", usage);
 | 
				
			||||||
 | 
							return ExitCode::from(EX_USAGE as u8);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/* use stdin if no inputs are specified */
 | 
				
			||||||
 | 
						if ins.is_empty() { ins.push("-".to_string()); }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/* use stdout if no outputs are specified */
 | 
				
			||||||
 | 
						if outs.is_empty() && !e { outs.push("-".to_string()); }
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						/* map all path strings to files */
 | 
				
			||||||
 | 
						let inputs = ins.iter().map(|file| {
 | 
				
			||||||
 | 
							/* if a file is “-”, it is stdin */
 | 
				
			||||||
 | 
							if *file == "-" {
 | 
				
			||||||
 | 
								/* portable way to access stdin as a file */
 | 
				
			||||||
 | 
								return unsafe { File::from_raw_fd(stdin().as_raw_fd()) };
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							match File::open(file) {
 | 
				
			||||||
 | 
								Ok(f) => f,
 | 
				
			||||||
 | 
								Err(e) => {
 | 
				
			||||||
 | 
									eprintln!("{}: {}: {}", argv[0], file, e.strerror());
 | 
				
			||||||
 | 
									exit(EX_IOERR);
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}).collect::<Vec<_>>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/* map all path strings to files */
 | 
				
			||||||
 | 
						let mut outputs = outs.iter().map(|file| {
 | 
				
			||||||
 | 
							/* of a file is “-”, it is stdout */
 | 
				
			||||||
 | 
							if *file == "-" {
 | 
				
			||||||
 | 
								/* portable way to access stdout as a file */
 | 
				
			||||||
 | 
								return unsafe { File::from_raw_fd(stdout().as_raw_fd()) }; 
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							let options = File::options()
 | 
				
			||||||
 | 
								/* don’t truncate if -t is specified, append if -a is specified */
 | 
				
			||||||
 | 
								.truncate(t)
 | 
				
			||||||
 | 
								.append(a)
 | 
				
			||||||
 | 
								/* enable the ability to create and write to files */
 | 
				
			||||||
 | 
								.create(true)
 | 
				
			||||||
 | 
								.write(true)
 | 
				
			||||||
 | 
								/* finally, open the file! */
 | 
				
			||||||
 | 
								.open(file);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							match options {
 | 
				
			||||||
 | 
								Ok(f) => return f,
 | 
				
			||||||
 | 
								Err(e) => {
 | 
				
			||||||
 | 
									eprintln!("{}: {}: {}", argv[0], file, e.strerror());
 | 
				
			||||||
 | 
									exit(EX_IOERR);
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							};
 | 
				
			||||||
 | 
						}).collect::<Vec<_>>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/* if -e is specified, use stderr */
 | 
				
			||||||
 | 
						if e {
 | 
				
			||||||
 | 
							/* portable way to access stderr as a file */
 | 
				
			||||||
 | 
							outputs.push(unsafe { File::from_raw_fd(stderr().as_raw_fd()) });
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						let mut outputs = outputs.iter().map(|o| {
 | 
				
			||||||
 | 
							if u {
 | 
				
			||||||
 | 
								/* unbuffered writing through a buffer of capacity 0 */
 | 
				
			||||||
 | 
								BufWriter::with_capacity(0, o)
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								/* theoretically buffered writing */
 | 
				
			||||||
 | 
								BufWriter::new(o)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}).collect::<Vec<_>>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for file in inputs {
 | 
				
			||||||
 | 
							for byte in file.bytes().map(|b| {
 | 
				
			||||||
 | 
								b.unwrap_or_else(|e| {
 | 
				
			||||||
 | 
									eprintln!("{}: {}", argv[0], e.strerror());
 | 
				
			||||||
 | 
									exit(EX_IOERR);
 | 
				
			||||||
 | 
								})
 | 
				
			||||||
 | 
							}) {
 | 
				
			||||||
 | 
								for out in &mut outputs {
 | 
				
			||||||
 | 
									if let Err(e) = out.write(&[byte]) {
 | 
				
			||||||
 | 
										eprintln!("{}: {}", argv[0], e.strerror());
 | 
				
			||||||
 | 
										return ExitCode::from(EX_IOERR as u8);
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									if u {
 | 
				
			||||||
 | 
										/* immediately flush the output for -u */
 | 
				
			||||||
 | 
										if let Err(e) = out.flush() { 
 | 
				
			||||||
 | 
											eprintln!("{}: {}", argv[0], e.strerror());
 | 
				
			||||||
 | 
											return ExitCode::from(EX_IOERR as u8);
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						ExitCode::SUCCESS
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user