swap io struct for a file struct
This commit is contained in:
parent
d46a189914
commit
cc943d045e
327
mm/mm.c
327
mm/mm.c
@ -28,37 +28,24 @@
|
||||
#endif
|
||||
extern int errno;
|
||||
|
||||
/* mm(1) works similarly to dj(1) in that, fundamentally, they both take bytes
|
||||
* from input and write them to output, but whereas dj(1)'s utility is in
|
||||
* controlling the reads and writes themselves across an individual input file
|
||||
* and individual output file - with regards to quantity of bytes read/written,
|
||||
* buffering of reads and writes, and where in the input to read and where in
|
||||
* the output to write - mm(1)'s utility is in handling arbitrary quantities of
|
||||
* input files and writing to arbitrary quantities of output files, without
|
||||
* the fine-grained control dj(1) offers. There are design decisions in mm(1)
|
||||
* that are repeated from dj(1) (the same author wrote each utility's initial
|
||||
* implementation) but, while dj(1)'s design is modeled around juggling text
|
||||
* within buffers, mm(1)'s design is modeled around juggling open files. */
|
||||
|
||||
/* mm(1) can take arbitrary files but for the sake of resiliency it has a
|
||||
* 10-file (for input and output independently) pre-allocated array and
|
||||
* additional dynamically allocated lists of FILE pointers for when those
|
||||
* arrays are full, with Io_ functions handling necessary operations on both
|
||||
* the Io struct and its nested Io_ex struct. */
|
||||
|
||||
struct Io_ex{
|
||||
size_t s;
|
||||
FILE **files;
|
||||
char **names;
|
||||
/* 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 */
|
||||
};
|
||||
|
||||
struct Io{
|
||||
size_t s;
|
||||
FILE *files[10];
|
||||
char *names[10];
|
||||
char *fmode;
|
||||
struct Io_ex *ex;
|
||||
};
|
||||
/* 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]>";
|
||||
@ -68,196 +55,150 @@ static char *stderr_name = "<stderr>";
|
||||
static char *(fmode[]) = { (char []){"rb"}, (char []){"rb+"} };
|
||||
static char *wharsh = "wb";
|
||||
|
||||
/* Opens the file at the path s and puts it in the io struct, returning NULL if
|
||||
* either the opening or the placement of the open FILE pointer fail. */
|
||||
#define Io_fopen(io, s) Io_fappend((io), fopen((s), (io)->fmode), (s))
|
||||
/* Grows the files allocations by ALLOC_INCREMENT. */
|
||||
#define Files_grow(files) (\
|
||||
(files->files = realloc(files->files, \
|
||||
(files->a += ALLOC_INCREMENT) \
|
||||
* sizeof *(files->files))) == NULL \
|
||||
|| (files->names = realloc(files->names, \
|
||||
files->a * sizeof *(files->names))) == NULL \
|
||||
? NULL \
|
||||
: files \
|
||||
)
|
||||
|
||||
/* Destructs the io[2] used by main. */
|
||||
#define terminate(io) do{ \
|
||||
Io_destruct(&(io)[0]); \
|
||||
Io_destruct(&(io)[1]); }while(0)
|
||||
/* 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_fopen(files, p) \
|
||||
Files_fappend((files), fopen((p), (files)->mode), (p))
|
||||
|
||||
static struct Io *Io_initialize(struct Io *io, struct Io_ex *io_ex);
|
||||
/* Destructs an io struct by closing its handled files and freeing its extended
|
||||
* file and name arrays. Returns the destructed io. */
|
||||
static struct Io *
|
||||
Io_destruct(struct Io *io){
|
||||
size_t i;
|
||||
/* Destructs the files[2] used by main. */
|
||||
#define terminate(files) do{ \
|
||||
Files_destruct(&(files)[0]); \
|
||||
Files_destruct(&(files)[1]); }while(0)
|
||||
|
||||
if(io->s == -1)
|
||||
io->s = 0;
|
||||
static struct Files *Files_initialize(struct Files *files);
|
||||
/* Destructs a files struct by closing its files and freeing its files and
|
||||
* names arrays. Returns the destructed files. */
|
||||
static struct Files *
|
||||
Files_destruct(struct Files *files){
|
||||
int i;
|
||||
|
||||
for(i = 0; i < io->s; fclose(io->files[i++]));
|
||||
for(i = 0; i < io->ex->s; fclose(io->ex->files[i++]));
|
||||
free(io->ex->files);
|
||||
free(io->ex->names);
|
||||
for(i = 0; i < files->s; fclose(files->files[i++]));
|
||||
free(files->files);
|
||||
free(files->names);
|
||||
|
||||
return Io_initialize(io, io->ex);
|
||||
return Files_initialize(files);
|
||||
}
|
||||
|
||||
/* Adds the open FILE pointer for the file at the path s to the io struct,
|
||||
/* 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. */
|
||||
static FILE *
|
||||
Io_fappend(struct Io *io, FILE *f, char *s){
|
||||
Files_fappend(struct Files *files, FILE *file, char *name){
|
||||
|
||||
if(f == NULL)
|
||||
if(files->s == -1)
|
||||
files->s = 0;
|
||||
|
||||
if(file == NULL || Files_grow(files) == NULL)
|
||||
return NULL;
|
||||
|
||||
if(io->s == -1)
|
||||
io->s = 0;
|
||||
|
||||
if(io->s >= (sizeof io->files) / (sizeof *io->files)){
|
||||
if((io->ex->files = realloc(io->ex->files,
|
||||
(sizeof *(io->ex->files))
|
||||
* ++io->ex->s))
|
||||
== NULL
|
||||
|| (io->ex->names = realloc(io->ex->names,
|
||||
(sizeof *(io->ex->names))
|
||||
* io->ex->s))
|
||||
== NULL)
|
||||
return NULL;
|
||||
io->ex->names[io->ex->s] = s;
|
||||
return io->ex->files[io->ex->s++] = f;
|
||||
}
|
||||
io->names[io->s] = s;
|
||||
return io->files[io->s++] = f;
|
||||
files->names[files->s] = name;
|
||||
return files->files[files->s++] = file;
|
||||
}
|
||||
|
||||
/* Returns the first index within io->files of the given FILE pointer f. If f
|
||||
* isn't within io->files but is within io->ex->files, returns the sum of io->s
|
||||
* and the index of the FILE pointer in io->ex->files. If f is within neither
|
||||
* io->files or io->ex->files, returns -1. */
|
||||
/* Returns the first index within files->files of the given FILE pointer f. If
|
||||
* f isn't within files->files, returns -1. */
|
||||
static int
|
||||
Io_fileindex(struct Io *io, FILE *f){
|
||||
Files_fileindex(struct Files *files, FILE *f){
|
||||
size_t i;
|
||||
|
||||
for(i = 0; i < io->s; ++i)
|
||||
if(io->files[i] == f)
|
||||
for(i = 0; i < files->s; ++i)
|
||||
if(files->files[i] == f)
|
||||
return i;
|
||||
|
||||
for(i = 0; i < io->ex->s; ++i)
|
||||
if(io->ex->files[i] == f)
|
||||
return i + io->s;
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Returns the corresponding file name in the io struct to the FILE pointer f,
|
||||
* or NULL if f is NULL or isn't in io->files or io->ex->files. */
|
||||
/* Returns the corresponding file name in the files struct to the FILE pointer
|
||||
* f, or NULL if f is NULL or isn't in files->files. */
|
||||
static char *
|
||||
Io_filename(struct Io *io, FILE *f){
|
||||
Files_filename(struct Files *files, FILE *f){
|
||||
int i;
|
||||
|
||||
return (f == NULL || (i = Io_fileindex(io, f)) == -1)
|
||||
return (f == NULL || (i = Files_fileindex(files, f)) == -1)
|
||||
? NULL
|
||||
: (i < io->s - 1)
|
||||
? io->names[i]
|
||||
: io->ex->names[i - io->s];
|
||||
: files->names[i];
|
||||
}
|
||||
|
||||
static struct Io *Io_fremove(struct Io *io, FILE *f);
|
||||
static FILE *Io_nextfile(struct Io *io, FILE *f);
|
||||
/* Prints c to all io->files, returning c. If a stream can't be written, a
|
||||
static struct Files *Files_fremove(struct Files *files, FILE *f);
|
||||
static FILE *Files_nextfile(struct Files *files, FILE *f);
|
||||
/* Prints c to all files, returning c. If a stream can't be written, a
|
||||
* message is printed to standard error and the stream is closed and removed
|
||||
* from io. If no stream can be written, returns EOF. */
|
||||
* from files. If no stream can be written, returns EOF. */
|
||||
static int
|
||||
Io_fputc(struct Io *io, int c){
|
||||
Io_fputc(struct Files *files, int c){
|
||||
FILE *f;
|
||||
FILE *lf;
|
||||
char *ln;
|
||||
|
||||
f = NULL;
|
||||
lf = NULL;
|
||||
for(f = NULL; (f = Io_nextfile(io, f)) != NULL;){
|
||||
for(f = NULL; (f = Files_nextfile(files, f)) != NULL;){
|
||||
if(lf != NULL){
|
||||
if(fclose(lf) == EOF)
|
||||
fprintf(stderr, "%s: %s: %s\n",
|
||||
program_name, ln, strerror(errno));
|
||||
if(Io_fremove(io, lf)->s == 0)
|
||||
if(Files_fremove(files, lf)->s == 0)
|
||||
return EOF;
|
||||
lf = NULL;
|
||||
}
|
||||
if(putc(c, f) == EOF)
|
||||
ln = Io_filename(io, lf = f);
|
||||
ln = Files_filename(files, lf = f);
|
||||
}
|
||||
|
||||
return c;
|
||||
}
|
||||
|
||||
/* Removes a given file pointer from the given io struct, returning NULL if the
|
||||
* file was already absent from io and otherwise io. */
|
||||
static struct Io *
|
||||
Io_fremove(struct Io *io, FILE *f){
|
||||
/* Removes a given file pointer f from the given files struct, returning NULL
|
||||
* if the file was already absent from files and otherwise files. */
|
||||
static struct Files *
|
||||
Files_fremove(struct Files *files, FILE *f){
|
||||
size_t i;
|
||||
|
||||
for(i = 0; i < io->s && io->files[i] != f; ++i);
|
||||
if(io->files[i] == f){
|
||||
for( ; i < io->s - 1; ++i){
|
||||
io->files[i] = io->files[i + 1];
|
||||
io->names[i] = io->names[i + 1];
|
||||
}
|
||||
if(io->ex->s > 0){
|
||||
io->files[io->s] = io->ex->files[0];
|
||||
io->names[io->s] = io->ex->names[0];
|
||||
}else{
|
||||
--io->s;
|
||||
return io;
|
||||
}
|
||||
i = 0;
|
||||
}else{
|
||||
for(i = 0; i < io->ex->s && io->ex->files[i] != f; ++i);
|
||||
if(io->ex->files[i] != f)
|
||||
return NULL;
|
||||
}
|
||||
if((i = Files_fileindex(files, f)) == -1)
|
||||
return NULL;
|
||||
|
||||
for( ; i < io->ex->s - 1; ++i){
|
||||
io->ex->files[i] = io->ex->files[i + 1];
|
||||
io->ex->names[i] = io->ex->names[i + 1];
|
||||
for(; i < files->s - 1; ++i){
|
||||
files->files[i] = files->files[i + 1];
|
||||
files->names[i] = files->names[i + 1];
|
||||
}
|
||||
--io->ex->s;
|
||||
--files->s;
|
||||
|
||||
return io;
|
||||
return files;
|
||||
}
|
||||
|
||||
/* Initializes the members of the given io and io_ex structs (including setting
|
||||
* io->ex to io_ex) and returns io. */
|
||||
static struct Io *
|
||||
Io_initialize(struct Io *io, struct Io_ex *io_ex){
|
||||
/* Initializes the members of the given files struct and returns files. */
|
||||
static struct Files *
|
||||
Files_initialize(struct Files *files){
|
||||
|
||||
io->s = 0;
|
||||
io->ex = io_ex;
|
||||
io->ex->s = 0;
|
||||
io->ex->files = NULL;
|
||||
io->ex->names = NULL;
|
||||
files->a = 0;
|
||||
files->s = 0;
|
||||
files->files = NULL;
|
||||
files->names = NULL;
|
||||
|
||||
return io;
|
||||
return files;
|
||||
}
|
||||
|
||||
/* Returns the first file in io if f is NULL, NULL if the file is the last in
|
||||
* or absent from io, or the file after f in io. */
|
||||
/* Returns the first file in files if f is NULL, NULL if the file is the last
|
||||
* in or absent from files, or the file after f in files. */
|
||||
static FILE *
|
||||
Io_nextfile(struct Io *io, FILE *f){
|
||||
Files_nextfile(struct Files *files, FILE *f){
|
||||
int i;
|
||||
|
||||
return (f == NULL)
|
||||
? io->files[0]
|
||||
: ((i = Io_fileindex(io, f)) == -1
|
||||
|| i == io->s + io->ex->s - 1)
|
||||
? files->files[0]
|
||||
: ((i = Files_fileindex(files, f)) == -1 || i == files->s - 1)
|
||||
? NULL
|
||||
: (i < io->s - 1)
|
||||
? io->files[i + 1]
|
||||
: io->ex->files[i - io->s + 1];
|
||||
}
|
||||
|
||||
/* Stops stdio from buffering io. Returns io. */
|
||||
static struct Io *
|
||||
Io_unbuffer(struct Io *io){
|
||||
size_t i;
|
||||
|
||||
for(i = 0; i < io->s; setvbuf(io->files[i++], NULL, _IONBF, 0));
|
||||
for(i = 0; i < io->ex->s; setvbuf(io->ex->files[i++], NULL,_IONBF, 0));
|
||||
|
||||
return io;
|
||||
: files->files[i + 1];
|
||||
}
|
||||
|
||||
/* Prints a diagnostic message based on errno and returns an exit status
|
||||
@ -284,8 +225,7 @@ usage(char *s){
|
||||
int main(int argc, char *argv[]){
|
||||
int c;
|
||||
size_t i;
|
||||
struct Io io[2]; /* {read, write} */
|
||||
struct Io_ex io_ex[2];
|
||||
struct Files files[2]; /* {read, write} */
|
||||
int s; /* scratch variable */
|
||||
|
||||
/* The simple invocation (without given arguments) requires no memory
|
||||
@ -299,98 +239,103 @@ int main(int argc, char *argv[]){
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Initializes the io structs with their default values, standard input
|
||||
* and standard output. Both io->s are set to -1, a special value
|
||||
* indicating that they're at their defaults and that their FILE
|
||||
/* Initializes the files structs with their default values, standard
|
||||
* input and standard output. Both files->s are set to -1, a special
|
||||
* value indicating that they're at their defaults and that their FILE
|
||||
* pointers won't need to be closed. 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){
|
||||
Io_initialize(&io[i], &io_ex[i])->fmode = fmode[i];
|
||||
Io_fappend(&io[i], i == 0 ? stdin : stdout,
|
||||
Files_initialize(&files[i])->mode = fmode[i];
|
||||
Files_fappend(&files[i], i == 0 ? stdin : stdout,
|
||||
i == 0 ? stdin_name : stdout_name);
|
||||
io[i].s = -1;
|
||||
files[i].s = -1;
|
||||
}
|
||||
|
||||
s = 0; /* Refers to whether or not io will be unbuffered. */
|
||||
s = 0; /* Refers to whether or not files will be unbuffered. */
|
||||
|
||||
while((c = getopt(argc, argv, "aehi:no:u")) != -1)
|
||||
switch(c){
|
||||
case 'a': /* "rb+" -> "ab" */
|
||||
io[1].fmode[0] = 'a';
|
||||
io[1].fmode[2] = '\0';
|
||||
files[1].mode[0] = 'a';
|
||||
files[1].mode[2] = '\0';
|
||||
break;
|
||||
case 'e':
|
||||
if(Io_fappend(&io[1], stderr, stderr_name) != NULL)
|
||||
if(Files_fappend(&files[1], stderr, stderr_name)
|
||||
!= NULL)
|
||||
break;
|
||||
terminate(io);
|
||||
terminate(files);
|
||||
return oserr(argv[0], "-e");
|
||||
case 'i': case 'o':
|
||||
if(optarg[0] == '-' && optarg[1] == '\0'){
|
||||
/* "-" */
|
||||
if(Io_fappend(&io[c == 'o'],
|
||||
if(Files_fappend(&files[c == 'o'],
|
||||
c == 'i' ? stdin : stdout,
|
||||
c == 'i'
|
||||
? stdin_name
|
||||
: stdout_name) != NULL)
|
||||
break;
|
||||
terminate(io);
|
||||
terminate(files);
|
||||
return oserr(argv[0], optarg);
|
||||
}else if(Io_fopen(&io[c == 'o'], optarg)
|
||||
}else if(Files_fopen(&files[c == 'o'], optarg)
|
||||
!= NULL)
|
||||
break;
|
||||
/* does not exist, so try to create it */
|
||||
if(c == 'o' && errno == ENOENT){
|
||||
io[1].fmode = wharsh;
|
||||
if(Io_fopen(&io[1], optarg) != NULL){
|
||||
io[1].fmode = fmode[1];
|
||||
files[1].mode = wharsh;
|
||||
if(Files_fopen(&files[1], optarg) != NULL){
|
||||
files[1].mode = fmode[1];
|
||||
break;
|
||||
}
|
||||
}
|
||||
terminate(io);
|
||||
terminate(files);
|
||||
return oserr(argv[0], optarg);;
|
||||
case 'n':
|
||||
if(signal(SIGINT, SIG_IGN) != SIG_ERR)
|
||||
break;
|
||||
terminate(io);
|
||||
terminate(files);
|
||||
return oserr(argv[0], "-n");
|
||||
case 'u':
|
||||
s = 1;
|
||||
break;
|
||||
default:
|
||||
terminate(io);
|
||||
terminate(files);
|
||||
return usage(argv[0]);
|
||||
}
|
||||
|
||||
if(s){
|
||||
Io_unbuffer(&io[0]);
|
||||
Io_unbuffer(&io[1]);
|
||||
/* Unbuffer files. */
|
||||
if(s){ 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));
|
||||
}
|
||||
|
||||
if(io[0].s == -1)
|
||||
io[0].s = 1;
|
||||
if(io[1].s == -1)
|
||||
io[1].s = 1;
|
||||
if(files[0].s == -1)
|
||||
files[0].s = 1;
|
||||
if(files[1].s == -1)
|
||||
files[1].s = 1;
|
||||
|
||||
/* Refers to the amount of outputs prior to writing; Io_fputc will
|
||||
* remove any to which it can't write. */
|
||||
s = io[1].s;
|
||||
s = files[1].s;
|
||||
|
||||
for(i = 0; i < io[0].s; ++i){
|
||||
while((c = getc(io[0].files[i])) != EOF)
|
||||
if(Io_fputc(&io[1], c) == EOF){ /* notebook's full */
|
||||
terminate(io);
|
||||
/* Actual program loop. */
|
||||
for(i = 0; i < files[0].s; ++i)
|
||||
while((c = getc(files[0].files[i])) != EOF)
|
||||
if(Io_fputc(&files[1], c) == EOF){
|
||||
terminate(files); /* notebook's full */
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* Reflects whether (s==0) or not (s==1) the full length of the inputs
|
||||
* could be written to all outputs, which is the return value for main
|
||||
* to maintain feature parity with tee(1p). */
|
||||
s = io[1].s != s;
|
||||
s = files[1].s != s;
|
||||
|
||||
terminate(io);
|
||||
terminate(files);
|
||||
|
||||
return s;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user