simplify and comment
This commit is contained in:
parent
8ae20904b6
commit
0624c3561b
277
mm/mm.c
277
mm/mm.c
@ -1,3 +1,21 @@
|
|||||||
|
/*
|
||||||
|
* 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 <errno.h> /* errno */
|
||||||
#include <signal.h> /* signal(2), SIG_ERR, SIG_IGN, SIGINT */
|
#include <signal.h> /* signal(2), SIG_ERR, SIG_IGN, SIGINT */
|
||||||
#include <stdio.h> /* fclose(3), fopen(3), fprintf(3), getc(3), putc(3),
|
#include <stdio.h> /* fclose(3), fopen(3), fprintf(3), getc(3), putc(3),
|
||||||
@ -5,11 +23,29 @@
|
|||||||
#include <stdlib.h> /* free(3), realloc(3) */
|
#include <stdlib.h> /* free(3), realloc(3) */
|
||||||
#include <string.h> /* strerror(3) */
|
#include <string.h> /* strerror(3) */
|
||||||
#include <unistd.h> /* getopt(3) */
|
#include <unistd.h> /* getopt(3) */
|
||||||
#if !defined EX_OK || !defined EX_USAGE
|
#if !defined EX_OSERR || !defined EX_USAGE
|
||||||
# include <sysexits.h>
|
# include <sysexits.h>
|
||||||
#endif
|
#endif
|
||||||
extern int errno;
|
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{
|
struct Io_ex{
|
||||||
size_t s;
|
size_t s;
|
||||||
FILE **files;
|
FILE **files;
|
||||||
@ -24,30 +60,26 @@ struct Io{
|
|||||||
struct Io_ex *ex;
|
struct Io_ex *ex;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/* pre-allocated strings */
|
||||||
static char *program_name = "<no argv[0]>";
|
static char *program_name = "<no argv[0]>";
|
||||||
static char *stdin_name = "<stdin>";
|
static char *stdin_name = "<stdin>";
|
||||||
static char *stdout_name = "<stdout>";
|
static char *stdout_name = "<stdout>";
|
||||||
static char *stderr_name = "<stderr>";
|
static char *stderr_name = "<stderr>";
|
||||||
static char *amode = "ab";
|
static char *(fmode[]) = { (char []){"rb"}, (char []){"rb+"} };
|
||||||
static char *rmode = "rb";
|
|
||||||
static char *wmode = "rb+";
|
|
||||||
static char *wharsh = "wb";
|
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))
|
||||||
|
|
||||||
|
/* Destructs the io[2] used by main. */
|
||||||
#define terminate(io) do{ \
|
#define terminate(io) do{ \
|
||||||
Io_destruct(&(io)[0]); \
|
Io_destruct(&(io)[0]); \
|
||||||
Io_destruct(&(io)[1]); }while(0)
|
Io_destruct(&(io)[1]); }while(0)
|
||||||
|
|
||||||
static struct Io *
|
static struct Io *Io_initialize(struct Io *io, struct Io_ex *io_ex);
|
||||||
Io_construct(struct Io *io){
|
/* Destructs an io struct by closing its handled files and freeing its extended
|
||||||
|
* file and name arrays. Returns the destructed io. */
|
||||||
io->s = 0;
|
|
||||||
io->ex->s = 0;
|
|
||||||
io->ex->files = NULL;
|
|
||||||
io->ex->names = NULL;
|
|
||||||
|
|
||||||
return io;
|
|
||||||
}
|
|
||||||
|
|
||||||
static struct Io *
|
static struct Io *
|
||||||
Io_destruct(struct Io *io){
|
Io_destruct(struct Io *io){
|
||||||
size_t i;
|
size_t i;
|
||||||
@ -55,36 +87,46 @@ Io_destruct(struct Io *io){
|
|||||||
if(io->s == -1)
|
if(io->s == -1)
|
||||||
io->s = 0;
|
io->s = 0;
|
||||||
|
|
||||||
for(i = 0; i < io->s; ++i)
|
for(i = 0; i < io->s; fclose(io->files[i++]));
|
||||||
fclose(io->files[i]);
|
for(i = 0; i < io->ex->s; fclose(io->ex->files[i++]));
|
||||||
|
|
||||||
for(i = 0; i < io->ex->s; ++i)
|
|
||||||
fclose(io->ex->files[i]);
|
|
||||||
free(io->ex->files);
|
free(io->ex->files);
|
||||||
free(io->ex->names);
|
free(io->ex->names);
|
||||||
|
|
||||||
return Io_construct(io); /* counter-intuitive but NULLs and 0s */
|
return Io_initialize(io, io->ex);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Adds the open FILE pointer for the file at the path s to the io struct,
|
||||||
|
* returning the FILE if successful and NULL if not. */
|
||||||
static FILE *
|
static FILE *
|
||||||
Io_fappend(struct Io *io, FILE *f, char *s){
|
Io_fappend(struct Io *io, FILE *f, char *s){
|
||||||
|
|
||||||
|
if(f == NULL)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
if(io->s == -1)
|
if(io->s == -1)
|
||||||
io->s = 0;
|
io->s = 0;
|
||||||
|
|
||||||
if(io->s < (sizeof (io->files)) / (sizeof *(io->files))){
|
if(io->s >= (sizeof io->files) / (sizeof *io->files)){
|
||||||
io->names[io->s] = s;
|
if((io->ex->files = realloc(io->ex->files,
|
||||||
return io->files[io->s++] = f;
|
(sizeof *(io->ex->files))
|
||||||
}else{
|
* ++io->ex->s))
|
||||||
io->ex->files = realloc(io->ex->files,
|
== NULL
|
||||||
(sizeof *(io->ex->files)) * ++io->ex->s);
|
|| (io->ex->names = realloc(io->ex->names,
|
||||||
io->ex->names = realloc(io->ex->names,
|
(sizeof *(io->ex->names))
|
||||||
(sizeof *(io->ex->names)) * io->ex->s);
|
* io->ex->s))
|
||||||
|
== NULL)
|
||||||
|
return NULL;
|
||||||
io->ex->names[io->ex->s] = s;
|
io->ex->names[io->ex->s] = s;
|
||||||
return io->ex->files[io->ex->s++] = f;
|
return io->ex->files[io->ex->s++] = f;
|
||||||
}
|
}
|
||||||
|
io->names[io->s] = s;
|
||||||
|
return io->files[io->s++] = f;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 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. */
|
||||||
static int
|
static int
|
||||||
Io_fileindex(struct Io *io, FILE *f){
|
Io_fileindex(struct Io *io, FILE *f){
|
||||||
size_t i;
|
size_t i;
|
||||||
@ -100,6 +142,8 @@ Io_fileindex(struct Io *io, FILE *f){
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Returns the corresponding file name in the io struct to the FILE pointer f,
|
||||||
|
* or NULL if f isn't in io->files or io->ex->files. */
|
||||||
static char *
|
static char *
|
||||||
Io_filename(struct Io *io, FILE *f){
|
Io_filename(struct Io *io, FILE *f){
|
||||||
int i;
|
int i;
|
||||||
@ -112,19 +156,11 @@ Io_filename(struct Io *io, FILE *f){
|
|||||||
return io->ex->names[i - io->s];
|
return io->ex->names[i - io->s];
|
||||||
}
|
}
|
||||||
|
|
||||||
static FILE *
|
|
||||||
Io_fopen(struct Io *io, char *s){
|
|
||||||
FILE *f;
|
|
||||||
|
|
||||||
if((f = fopen(s, io->fmode)) == NULL)
|
|
||||||
return NULL;
|
|
||||||
else
|
|
||||||
return Io_fappend(io, f, s);
|
|
||||||
}
|
|
||||||
|
|
||||||
static struct Io *Io_fremove(struct Io *io, FILE *f);
|
static struct Io *Io_fremove(struct Io *io, FILE *f);
|
||||||
static FILE *Io_nextfile(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
|
||||||
|
* message is printed to standard error and the stream is closed and removed
|
||||||
|
* from io. If no stream can be written, returns EOF. */
|
||||||
static int
|
static int
|
||||||
Io_fputc(struct Io *io, int c){
|
Io_fputc(struct Io *io, int c){
|
||||||
FILE *f;
|
FILE *f;
|
||||||
@ -133,25 +169,24 @@ Io_fputc(struct Io *io, int c){
|
|||||||
|
|
||||||
f = NULL;
|
f = NULL;
|
||||||
lf = NULL;
|
lf = NULL;
|
||||||
while((f = Io_nextfile(io, f)) != NULL){
|
for(f = NULL; (f = Io_nextfile(io, f)) != NULL;){
|
||||||
if(lf != NULL){
|
if(lf != NULL){
|
||||||
if(fclose(lf) == EOF)
|
if(fclose(lf) == EOF)
|
||||||
fprintf(stderr, "%s: %s: %s\n",
|
fprintf(stderr, "%s: %s: %s\n",
|
||||||
program_name, ln, strerror(errno));
|
program_name, ln, strerror(errno));
|
||||||
Io_fremove(io, lf);
|
if(Io_fremove(io, lf)->s == 0)
|
||||||
if(io->s == 0)
|
|
||||||
return EOF;
|
return EOF;
|
||||||
lf = NULL;
|
lf = NULL;
|
||||||
}
|
}
|
||||||
if(putc(c, f) == EOF){
|
if(putc(c, f) == EOF)
|
||||||
lf = f;
|
ln = Io_filename(io, lf = f);
|
||||||
ln = Io_filename(io, lf);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return c;
|
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 *
|
static struct Io *
|
||||||
Io_fremove(struct Io *io, FILE *f){
|
Io_fremove(struct Io *io, FILE *f){
|
||||||
size_t i;
|
size_t i;
|
||||||
@ -185,46 +220,49 @@ Io_fremove(struct Io *io, FILE *f){
|
|||||||
return io;
|
return io;
|
||||||
}
|
}
|
||||||
|
|
||||||
static FILE *
|
/* Initializes the members of the given io and io_ex structs (including setting
|
||||||
Io_nextfile(struct Io *io, FILE *f){
|
* io->ex to io_ex) and returns io. */
|
||||||
size_t i;
|
|
||||||
|
|
||||||
if(f == NULL)
|
|
||||||
return io->files[0];
|
|
||||||
|
|
||||||
for(i = 0; i < io->s; ++i)
|
|
||||||
if(io->files[i] == f){
|
|
||||||
if(i == io->s - 1 && io->ex->s == 0)
|
|
||||||
return NULL;
|
|
||||||
else if(i == io->s - 1)
|
|
||||||
return io->ex->files[0];
|
|
||||||
else
|
|
||||||
return io->files[i + 1];
|
|
||||||
}
|
|
||||||
for(i = 0; i < io->ex->s; ++i)
|
|
||||||
if(io->ex->files[i] == f){
|
|
||||||
if(i == io->ex->s - 1)
|
|
||||||
break;
|
|
||||||
else
|
|
||||||
return io->ex->files[i + 1];
|
|
||||||
}
|
|
||||||
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
static struct Io *
|
static struct Io *
|
||||||
Io_unbuffer(struct Io *io){
|
Io_initialize(struct Io *io, struct Io_ex *io_ex){
|
||||||
size_t i;
|
|
||||||
|
|
||||||
for(i = 0; i < io->s; ++i)
|
io->s = 0;
|
||||||
setvbuf(io->files[i], NULL, _IONBF, 0);
|
io->ex = io_ex;
|
||||||
|
io->ex->s = 0;
|
||||||
for(i = 0; i < io->ex->s; ++i)
|
io->ex->files = NULL;
|
||||||
setvbuf(io->ex->files[i], NULL, _IONBF, 0);
|
io->ex->names = NULL;
|
||||||
|
|
||||||
return io;
|
return io;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 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. */
|
||||||
|
static FILE *
|
||||||
|
Io_nextfile(struct Io *io, FILE *f){
|
||||||
|
int i;
|
||||||
|
|
||||||
|
return (f == NULL)
|
||||||
|
? io->files[0]
|
||||||
|
: ((i = Io_fileindex(io, f)) == -1
|
||||||
|
|| i == io->s + io->ex->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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Prints a diagnostic message based on errno and returns an exit status
|
||||||
|
* appropriate for an OS error. */
|
||||||
static int
|
static int
|
||||||
oserr(char *s, char *r){
|
oserr(char *s, char *r){
|
||||||
|
|
||||||
@ -233,6 +271,8 @@ oserr(char *s, char *r){
|
|||||||
return EX_OSERR;
|
return EX_OSERR;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Prints a diagnostic message synopsizing usage and returns an exit status
|
||||||
|
* appropriate for a usage error. */
|
||||||
static int
|
static int
|
||||||
usage(char *s){
|
usage(char *s){
|
||||||
|
|
||||||
@ -245,47 +285,59 @@ usage(char *s){
|
|||||||
int main(int argc, char *argv[]){
|
int main(int argc, char *argv[]){
|
||||||
int c;
|
int c;
|
||||||
size_t i;
|
size_t i;
|
||||||
struct Io io[2]; /* {read, write, error} */
|
struct Io io[2]; /* {read, write} */
|
||||||
struct Io_ex io_ex[2];
|
struct Io_ex io_ex[2];
|
||||||
int o;
|
int s; /* scratch variable */
|
||||||
char unbuffered;
|
|
||||||
|
|
||||||
|
/* The simple invocation (without given arguments) requires no memory
|
||||||
|
* allocations or use of the io structs and is therefore treated as a
|
||||||
|
* special case. To do the same with all bells and whistles call
|
||||||
|
* $ mm -i - -o - */
|
||||||
if(argc < 2){ /* simple invocation */
|
if(argc < 2){ /* simple invocation */
|
||||||
while((c = getc(stdin)) != EOF)
|
while((c = getc(stdin)) != EOF)
|
||||||
if(putc(c, stdout) == EOF)
|
if(putc(c, stdout) == EOF)
|
||||||
break;
|
return 1;
|
||||||
return EX_OK;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
io[0].ex = &io_ex[0];
|
/* Initializes the io structs with their default values, standard input
|
||||||
io[1].ex = &io_ex[1];
|
* and standard output. Both io->s are set to -1, a special value
|
||||||
Io_construct(&io[0]);
|
* indicating that they're at their defaults and that their FILE
|
||||||
Io_construct(&io[1]);
|
* pointers won't need to be closed. If an input or an output is
|
||||||
io[0].fmode = rmode;
|
* specified these initial values will be overwritten, so to, say, use
|
||||||
io[1].fmode = wmode;
|
* 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,
|
||||||
|
i == 0 ? stdin_name : stdout_name);
|
||||||
|
io[i].s = -1;
|
||||||
|
}
|
||||||
|
|
||||||
Io_fappend(&io[0], stdin, stdin_name);
|
s = 0; /* Refers to whether or not io will be unbuffered. */
|
||||||
Io_fappend(&io[1], stdout, stdout_name);
|
|
||||||
io[0].s = -1;
|
|
||||||
io[1].s = -1;
|
|
||||||
|
|
||||||
unbuffered = 0;
|
|
||||||
|
|
||||||
while((c = getopt(argc, argv, "aehi:no:u")) != -1)
|
while((c = getopt(argc, argv, "aehi:no:u")) != -1)
|
||||||
switch(c){
|
switch(c){
|
||||||
case 'a':
|
case 'a': /* "rb+" -> "ab" */
|
||||||
io[1].fmode = amode;
|
io[1].fmode[0] = 'a';
|
||||||
|
io[1].fmode[2] = '\0';
|
||||||
break;
|
break;
|
||||||
case 'e':
|
case 'e':
|
||||||
Io_fappend(&io[1], stderr, stderr_name);
|
if(Io_fappend(&io[1], stderr, stderr_name) != NULL)
|
||||||
break;
|
break;
|
||||||
|
terminate(io);
|
||||||
|
return oserr(argv[0], "-e");
|
||||||
case 'i': case 'o':
|
case 'i': case 'o':
|
||||||
if(optarg[0] == '-' && optarg[1] == '\0'){
|
if(optarg[0] == '-' && optarg[1] == '\0'){
|
||||||
/* "-" */
|
/* "-" */
|
||||||
Io_fappend(&io[c == 'o'],
|
if(Io_fappend(&io[c == 'o'],
|
||||||
c == 'i' ? stdin : stdout,
|
c == 'i' ? stdin : stdout,
|
||||||
c == 'i' ? stdin_name : stdout_name);
|
c == 'i'
|
||||||
|
? stdin_name
|
||||||
|
: stdout_name) != NULL)
|
||||||
break;
|
break;
|
||||||
|
terminate(io);
|
||||||
|
return oserr(argv[0], optarg);
|
||||||
}else if(Io_fopen(&io[c == 'o'], optarg)
|
}else if(Io_fopen(&io[c == 'o'], optarg)
|
||||||
!= NULL)
|
!= NULL)
|
||||||
break;
|
break;
|
||||||
@ -293,7 +345,7 @@ int main(int argc, char *argv[]){
|
|||||||
if(c == 'o' && errno == ENOENT){
|
if(c == 'o' && errno == ENOENT){
|
||||||
io[1].fmode = wharsh;
|
io[1].fmode = wharsh;
|
||||||
if(Io_fopen(&io[1], optarg) != NULL){
|
if(Io_fopen(&io[1], optarg) != NULL){
|
||||||
io[1].fmode = wmode;
|
io[1].fmode = fmode[1];
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -305,14 +357,14 @@ int main(int argc, char *argv[]){
|
|||||||
terminate(io);
|
terminate(io);
|
||||||
return oserr(argv[0], "-n");
|
return oserr(argv[0], "-n");
|
||||||
case 'u':
|
case 'u':
|
||||||
unbuffered = 1;
|
s = 1;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
terminate(io);
|
terminate(io);
|
||||||
return usage(argv[0]);
|
return usage(argv[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(unbuffered){
|
if(s){
|
||||||
Io_unbuffer(&io[0]);
|
Io_unbuffer(&io[0]);
|
||||||
Io_unbuffer(&io[1]);
|
Io_unbuffer(&io[1]);
|
||||||
}
|
}
|
||||||
@ -321,7 +373,10 @@ int main(int argc, char *argv[]){
|
|||||||
io[0].s = 1;
|
io[0].s = 1;
|
||||||
if(io[1].s == -1)
|
if(io[1].s == -1)
|
||||||
io[1].s = 1;
|
io[1].s = 1;
|
||||||
o = io[1].s;
|
|
||||||
|
/* Refers to the amount of outputs prior to writing; Io_fputc will
|
||||||
|
* remove any to which it can't write. */
|
||||||
|
s = io[1].s;
|
||||||
|
|
||||||
for(i = 0; i < io[0].s; ++i){
|
for(i = 0; i < io[0].s; ++i){
|
||||||
while((c = getc(io[0].files[i])) != EOF)
|
while((c = getc(io[0].files[i])) != EOF)
|
||||||
@ -331,8 +386,12 @@ int main(int argc, char *argv[]){
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
o = io[1].s != o;
|
/* 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;
|
||||||
|
|
||||||
terminate(io);
|
terminate(io);
|
||||||
|
|
||||||
return o;
|
return s;
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user