Revert -r362422.

While whimsical, there's too much negative energy around minion as well as the
positive.
This commit is contained in:
Warner Losh 2020-06-20 20:06:14 +00:00
parent bafd96b8dd
commit b1779ca056

View File

@ -75,19 +75,19 @@ static char *nexttape;
static FILE *popenfp = NULL; static FILE *popenfp = NULL;
static int atomic(ssize_t (*)(), int, char *, int); static int atomic(ssize_t (*)(), int, char *, int);
static void dominion(int, int); static void doslave(int, int);
static void enminion(void); static void enslave(void);
static void flushtape(void); static void flushtape(void);
static void killall(void); static void killall(void);
static void rollforward(void); static void rollforward(void);
/* /*
* Concurrent dump mods (Caltech) - disk block reading and tape writing * Concurrent dump mods (Caltech) - disk block reading and tape writing
* are exported to several minion processes. While one minion writes the * are exported to several slave processes. While one slave writes the
* tape, the others read disk blocks; they pass control of the tape in * tape, the others read disk blocks; they pass control of the tape in
* a ring via signals. The parent process traverses the file system and * a ring via signals. The parent process traverses the file system and
* sends writeheader()'s and lists of daddr's to the minions via pipes. * sends writeheader()'s and lists of daddr's to the slaves via pipes.
* The following structure defines the instruction packets sent to minions. * The following structure defines the instruction packets sent to slaves.
*/ */
struct req { struct req {
ufs2_daddr_t dblk; ufs2_daddr_t dblk;
@ -95,20 +95,20 @@ struct req {
}; };
static int reqsiz; static int reqsiz;
#define MINIONS 3 /* 1 minion writing, 1 reading, 1 for slack */ #define SLAVES 3 /* 1 slave writing, 1 reading, 1 for slack */
static struct minion { static struct slave {
int64_t tapea; /* header number at start of this chunk */ int64_t tapea; /* header number at start of this chunk */
int64_t firstrec; /* record number of this block */ int64_t firstrec; /* record number of this block */
int count; /* count to next header (used for TS_TAPE */ int count; /* count to next header (used for TS_TAPE */
/* after EOT) */ /* after EOT) */
int inode; /* inode that we are currently dealing with */ int inode; /* inode that we are currently dealing with */
int fd; /* FD for this minion */ int fd; /* FD for this slave */
int pid; /* PID for this minion */ int pid; /* PID for this slave */
int sent; /* 1 == we've sent this minion requests */ int sent; /* 1 == we've sent this slave requests */
char (*tblock)[TP_BSIZE]; /* buffer for data blocks */ char (*tblock)[TP_BSIZE]; /* buffer for data blocks */
struct req *req; /* buffer for requests */ struct req *req; /* buffer for requests */
} minions[MINIONS+1]; } slaves[SLAVES+1];
static struct minion *mlp; static struct slave *slp;
static char (*nextblock)[TP_BSIZE]; static char (*nextblock)[TP_BSIZE];
@ -116,9 +116,9 @@ static int master; /* pid of master, for sending error signals */
static int tenths; /* length of tape used per block written */ static int tenths; /* length of tape used per block written */
static volatile sig_atomic_t caught; /* have we caught the signal to proceed? */ static volatile sig_atomic_t caught; /* have we caught the signal to proceed? */
static volatile sig_atomic_t ready; /* reached the lock point without having */ static volatile sig_atomic_t ready; /* reached the lock point without having */
/* received the SIGUSR2 signal from the prev minion? */ /* received the SIGUSR2 signal from the prev slave? */
static jmp_buf jmpbuf; /* where to jump to if we are ready when the */ static jmp_buf jmpbuf; /* where to jump to if we are ready when the */
/* SIGUSR2 arrives from the previous minion */ /* SIGUSR2 arrives from the previous slave */
int int
alloctape(void) alloctape(void)
@ -143,20 +143,20 @@ alloctape(void)
* packets, so flushtape() can write them together with one write(). * packets, so flushtape() can write them together with one write().
* Align tape buffer on page boundary to speed up tape write(). * Align tape buffer on page boundary to speed up tape write().
*/ */
for (i = 0; i <= MINIONS; i++) { for (i = 0; i <= SLAVES; i++) {
buf = (char *) buf = (char *)
malloc((unsigned)(reqsiz + writesize + pgoff + TP_BSIZE)); malloc((unsigned)(reqsiz + writesize + pgoff + TP_BSIZE));
if (buf == NULL) if (buf == NULL)
return(0); return(0);
minions[i].tblock = (char (*)[TP_BSIZE]) slaves[i].tblock = (char (*)[TP_BSIZE])
(((long)&buf[ntrec + 1] + pgoff) &~ pgoff); (((long)&buf[ntrec + 1] + pgoff) &~ pgoff);
minions[i].req = (struct req *)minions[i].tblock - ntrec - 1; slaves[i].req = (struct req *)slaves[i].tblock - ntrec - 1;
} }
mlp = &minions[0]; slp = &slaves[0];
mlp->count = 1; slp->count = 1;
mlp->tapea = 0; slp->tapea = 0;
mlp->firstrec = 0; slp->firstrec = 0;
nextblock = mlp->tblock; nextblock = slp->tblock;
return(1); return(1);
} }
@ -164,8 +164,8 @@ void
writerec(char *dp, int isspcl) writerec(char *dp, int isspcl)
{ {
mlp->req[trecno].dblk = (ufs2_daddr_t)0; slp->req[trecno].dblk = (ufs2_daddr_t)0;
mlp->req[trecno].count = 1; slp->req[trecno].count = 1;
/* Can't do a structure assignment due to alignment problems */ /* Can't do a structure assignment due to alignment problems */
bcopy(dp, *(nextblock)++, sizeof (union u_spcl)); bcopy(dp, *(nextblock)++, sizeof (union u_spcl));
if (isspcl) if (isspcl)
@ -185,8 +185,8 @@ dumpblock(ufs2_daddr_t blkno, int size)
dblkno = fsbtodb(sblock, blkno); dblkno = fsbtodb(sblock, blkno);
tpblks = size >> tp_bshift; tpblks = size >> tp_bshift;
while ((avail = MIN(tpblks, ntrec - trecno)) > 0) { while ((avail = MIN(tpblks, ntrec - trecno)) > 0) {
mlp->req[trecno].dblk = dblkno; slp->req[trecno].dblk = dblkno;
mlp->req[trecno].count = avail; slp->req[trecno].count = avail;
trecno += avail; trecno += avail;
spcl.c_tapea += avail; spcl.c_tapea += avail;
if (trecno >= ntrec) if (trecno >= ntrec)
@ -232,27 +232,27 @@ flushtape(void)
int i, blks, got; int i, blks, got;
int64_t lastfirstrec; int64_t lastfirstrec;
int siz = (char *)nextblock - (char *)mlp->req; int siz = (char *)nextblock - (char *)slp->req;
mlp->req[trecno].count = 0; /* Sentinel */ slp->req[trecno].count = 0; /* Sentinel */
if (atomic(write, mlp->fd, (char *)mlp->req, siz) != siz) if (atomic(write, slp->fd, (char *)slp->req, siz) != siz)
quit("error writing command pipe: %s\n", strerror(errno)); quit("error writing command pipe: %s\n", strerror(errno));
mlp->sent = 1; /* we sent a request, read the response later */ slp->sent = 1; /* we sent a request, read the response later */
lastfirstrec = mlp->firstrec; lastfirstrec = slp->firstrec;
if (++mlp >= &minions[MINIONS]) if (++slp >= &slaves[SLAVES])
mlp = &minions[0]; slp = &slaves[0];
/* Read results back from next minion */ /* Read results back from next slave */
if (mlp->sent) { if (slp->sent) {
if (atomic(read, mlp->fd, (char *)&got, sizeof got) if (atomic(read, slp->fd, (char *)&got, sizeof got)
!= sizeof got) { != sizeof got) {
perror(" DUMP: error reading command pipe in master"); perror(" DUMP: error reading command pipe in master");
dumpabort(0); dumpabort(0);
} }
mlp->sent = 0; slp->sent = 0;
/* Check for end of tape */ /* Check for end of tape */
if (got < writesize) { if (got < writesize) {
@ -262,15 +262,15 @@ flushtape(void)
* Drain the results, don't care what the values were. * Drain the results, don't care what the values were.
* If we read them here then trewind won't... * If we read them here then trewind won't...
*/ */
for (i = 0; i < MINIONS; i++) { for (i = 0; i < SLAVES; i++) {
if (minions[i].sent) { if (slaves[i].sent) {
if (atomic(read, minions[i].fd, if (atomic(read, slaves[i].fd,
(char *)&got, sizeof got) (char *)&got, sizeof got)
!= sizeof got) { != sizeof got) {
perror(" DUMP: error reading command pipe in master"); perror(" DUMP: error reading command pipe in master");
dumpabort(0); dumpabort(0);
} }
minions[i].sent = 0; slaves[i].sent = 0;
} }
} }
@ -288,11 +288,11 @@ flushtape(void)
if (spcl.c_addr[i] != 0) if (spcl.c_addr[i] != 0)
blks++; blks++;
} }
mlp->count = lastspclrec + blks + 1 - spcl.c_tapea; slp->count = lastspclrec + blks + 1 - spcl.c_tapea;
mlp->tapea = spcl.c_tapea; slp->tapea = spcl.c_tapea;
mlp->firstrec = lastfirstrec + ntrec; slp->firstrec = lastfirstrec + ntrec;
mlp->inode = curino; slp->inode = curino;
nextblock = mlp->tblock; nextblock = slp->tblock;
trecno = 0; trecno = 0;
asize += tenths; asize += tenths;
blockswritten += ntrec; blockswritten += ntrec;
@ -312,7 +312,7 @@ trewind(void)
int f; int f;
int got; int got;
for (f = 0; f < MINIONS; f++) { for (f = 0; f < SLAVES; f++) {
/* /*
* Drain the results, but unlike EOT we DO (or should) care * Drain the results, but unlike EOT we DO (or should) care
* what the return values were, since if we detect EOT after * what the return values were, since if we detect EOT after
@ -321,22 +321,22 @@ trewind(void)
* *
* fixme: punt for now. * fixme: punt for now.
*/ */
if (minions[f].sent) { if (slaves[f].sent) {
if (atomic(read, minions[f].fd, (char *)&got, sizeof got) if (atomic(read, slaves[f].fd, (char *)&got, sizeof got)
!= sizeof got) { != sizeof got) {
perror(" DUMP: error reading command pipe in master"); perror(" DUMP: error reading command pipe in master");
dumpabort(0); dumpabort(0);
} }
minions[f].sent = 0; slaves[f].sent = 0;
if (got != writesize) { if (got != writesize) {
msg("EOT detected in last 2 tape records!\n"); msg("EOT detected in last 2 tape records!\n");
msg("Use a longer tape, decrease the size estimate\n"); msg("Use a longer tape, decrease the size estimate\n");
quit("or use no size estimate at all.\n"); quit("or use no size estimate at all.\n");
} }
} }
(void) close(minions[f].fd); (void) close(slaves[f].fd);
} }
while (wait((int *)NULL) >= 0) /* wait for any signals from minions */ while (wait((int *)NULL) >= 0) /* wait for any signals from slaves */
/* void */; /* void */;
if (pipeout) if (pipeout)
@ -396,29 +396,29 @@ void
rollforward(void) rollforward(void)
{ {
struct req *p, *q, *prev; struct req *p, *q, *prev;
struct minion *tmlp; struct slave *tslp;
int i, size, got; int i, size, got;
int64_t savedtapea; int64_t savedtapea;
union u_spcl *ntb, *otb; union u_spcl *ntb, *otb;
tmlp = &minions[MINIONS]; tslp = &slaves[SLAVES];
ntb = (union u_spcl *)tmlp->tblock[1]; ntb = (union u_spcl *)tslp->tblock[1];
/* /*
* Each of the N minions should have requests that need to * Each of the N slaves should have requests that need to
* be replayed on the next tape. Use the extra minion buffers * be replayed on the next tape. Use the extra slave buffers
* (minions[MINIONS]) to construct request lists to be sent to * (slaves[SLAVES]) to construct request lists to be sent to
* each minion in turn. * each slave in turn.
*/ */
for (i = 0; i < MINIONS; i++) { for (i = 0; i < SLAVES; i++) {
q = &tmlp->req[1]; q = &tslp->req[1];
otb = (union u_spcl *)mlp->tblock; otb = (union u_spcl *)slp->tblock;
/* /*
* For each request in the current minion, copy it to tmlp. * For each request in the current slave, copy it to tslp.
*/ */
prev = NULL; prev = NULL;
for (p = mlp->req; p->count > 0; p += p->count) { for (p = slp->req; p->count > 0; p += p->count) {
*q = *p; *q = *p;
if (p->dblk == 0) if (p->dblk == 0)
*ntb++ = *otb++; /* copy the datablock also */ *ntb++ = *otb++; /* copy the datablock also */
@ -433,26 +433,26 @@ rollforward(void)
ntb--; ntb--;
q -= 1; q -= 1;
q->count = 0; q->count = 0;
q = &tmlp->req[0]; q = &tslp->req[0];
if (i == 0) { if (i == 0) {
q->dblk = 0; q->dblk = 0;
q->count = 1; q->count = 1;
trecno = 0; trecno = 0;
nextblock = tmlp->tblock; nextblock = tslp->tblock;
savedtapea = spcl.c_tapea; savedtapea = spcl.c_tapea;
spcl.c_tapea = mlp->tapea; spcl.c_tapea = slp->tapea;
startnewtape(0); startnewtape(0);
spcl.c_tapea = savedtapea; spcl.c_tapea = savedtapea;
lastspclrec = savedtapea - 1; lastspclrec = savedtapea - 1;
} }
size = (char *)ntb - (char *)q; size = (char *)ntb - (char *)q;
if (atomic(write, mlp->fd, (char *)q, size) != size) { if (atomic(write, slp->fd, (char *)q, size) != size) {
perror(" DUMP: error writing command pipe"); perror(" DUMP: error writing command pipe");
dumpabort(0); dumpabort(0);
} }
mlp->sent = 1; slp->sent = 1;
if (++mlp >= &minions[MINIONS]) if (++slp >= &slaves[SLAVES])
mlp = &minions[0]; slp = &slaves[0];
q->count = 1; q->count = 1;
@ -464,34 +464,34 @@ rollforward(void)
*/ */
q->dblk = prev->dblk + q->dblk = prev->dblk +
prev->count * (TP_BSIZE / DEV_BSIZE); prev->count * (TP_BSIZE / DEV_BSIZE);
ntb = (union u_spcl *)tmlp->tblock; ntb = (union u_spcl *)tslp->tblock;
} else { } else {
/* /*
* It wasn't a disk block. Copy the data to its * It wasn't a disk block. Copy the data to its
* new location in the buffer. * new location in the buffer.
*/ */
q->dblk = 0; q->dblk = 0;
*((union u_spcl *)tmlp->tblock) = *ntb; *((union u_spcl *)tslp->tblock) = *ntb;
ntb = (union u_spcl *)tmlp->tblock[1]; ntb = (union u_spcl *)tslp->tblock[1];
} }
} }
mlp->req[0] = *q; slp->req[0] = *q;
nextblock = mlp->tblock; nextblock = slp->tblock;
if (q->dblk == 0) if (q->dblk == 0)
nextblock++; nextblock++;
trecno = 1; trecno = 1;
/* /*
* Clear the first minions' response. One hopes that it * Clear the first slaves' response. One hopes that it
* worked ok, otherwise the tape is much too short! * worked ok, otherwise the tape is much too short!
*/ */
if (mlp->sent) { if (slp->sent) {
if (atomic(read, mlp->fd, (char *)&got, sizeof got) if (atomic(read, slp->fd, (char *)&got, sizeof got)
!= sizeof got) { != sizeof got) {
perror(" DUMP: error reading command pipe in master"); perror(" DUMP: error reading command pipe in master");
dumpabort(0); dumpabort(0);
} }
mlp->sent = 0; slp->sent = 0;
if (got != writesize) { if (got != writesize) {
quit("EOT detected at start of the tape!\n"); quit("EOT detected at start of the tape!\n");
@ -634,7 +634,7 @@ restore_check_point:
} }
} }
enminion(); /* Share open tape file descriptor with minions */ enslave(); /* Share open tape file descriptor with slaves */
if (popenout) if (popenout)
close(tapefd); /* Give up our copy of it. */ close(tapefd); /* Give up our copy of it. */
signal(SIGINFO, infosch); signal(SIGINFO, infosch);
@ -643,18 +643,18 @@ restore_check_point:
blocksthisvol = 0; blocksthisvol = 0;
if (top) if (top)
newtape++; /* new tape signal */ newtape++; /* new tape signal */
spcl.c_count = mlp->count; spcl.c_count = slp->count;
/* /*
* measure firstrec in TP_BSIZE units since restore doesn't * measure firstrec in TP_BSIZE units since restore doesn't
* know the correct ntrec value... * know the correct ntrec value...
*/ */
spcl.c_firstrec = mlp->firstrec; spcl.c_firstrec = slp->firstrec;
spcl.c_volume++; spcl.c_volume++;
spcl.c_type = TS_TAPE; spcl.c_type = TS_TAPE;
writeheader((ino_t)mlp->inode); writeheader((ino_t)slp->inode);
if (tapeno > 1) if (tapeno > 1)
msg("Volume %d begins with blocks from inode %d\n", msg("Volume %d begins with blocks from inode %d\n",
tapeno, mlp->inode); tapeno, slp->inode);
} }
} }
@ -687,7 +687,7 @@ Exit(status)
} }
/* /*
* proceed - handler for SIGUSR2, used to synchronize IO between the minions. * proceed - handler for SIGUSR2, used to synchronize IO between the slaves.
*/ */
void void
proceed(int signo __unused) proceed(int signo __unused)
@ -699,45 +699,45 @@ proceed(int signo __unused)
} }
void void
enminion(void) enslave(void)
{ {
int cmd[2]; int cmd[2];
int i, j; int i, j;
master = getpid(); master = getpid();
signal(SIGTERM, dumpabort); /* Minion sends SIGTERM on dumpabort() */ signal(SIGTERM, dumpabort); /* Slave sends SIGTERM on dumpabort() */
signal(SIGPIPE, sigpipe); signal(SIGPIPE, sigpipe);
signal(SIGUSR1, tperror); /* Minion sends SIGUSR1 on tape errors */ signal(SIGUSR1, tperror); /* Slave sends SIGUSR1 on tape errors */
signal(SIGUSR2, proceed); /* Minion sends SIGUSR2 to next minion */ signal(SIGUSR2, proceed); /* Slave sends SIGUSR2 to next slave */
for (i = 0; i < MINIONS; i++) { for (i = 0; i < SLAVES; i++) {
if (i == mlp - &minions[0]) { if (i == slp - &slaves[0]) {
caught = 1; caught = 1;
} else { } else {
caught = 0; caught = 0;
} }
if (socketpair(AF_UNIX, SOCK_STREAM, 0, cmd) < 0 || if (socketpair(AF_UNIX, SOCK_STREAM, 0, cmd) < 0 ||
(minions[i].pid = fork()) < 0) (slaves[i].pid = fork()) < 0)
quit("too many minions, %d (recompile smaller): %s\n", quit("too many slaves, %d (recompile smaller): %s\n",
i, strerror(errno)); i, strerror(errno));
minions[i].fd = cmd[1]; slaves[i].fd = cmd[1];
minions[i].sent = 0; slaves[i].sent = 0;
if (minions[i].pid == 0) { /* Minion starts up here */ if (slaves[i].pid == 0) { /* Slave starts up here */
for (j = 0; j <= i; j++) for (j = 0; j <= i; j++)
(void) close(minions[j].fd); (void) close(slaves[j].fd);
signal(SIGINT, SIG_IGN); /* Master handles this */ signal(SIGINT, SIG_IGN); /* Master handles this */
dominion(cmd[0], i); doslave(cmd[0], i);
Exit(X_FINOK); Exit(X_FINOK);
} }
} }
for (i = 0; i < MINIONS; i++) for (i = 0; i < SLAVES; i++)
(void) atomic(write, minions[i].fd, (void) atomic(write, slaves[i].fd,
(char *) &minions[(i + 1) % MINIONS].pid, (char *) &slaves[(i + 1) % SLAVES].pid,
sizeof minions[0].pid); sizeof slaves[0].pid);
master = 0; master = 0;
} }
@ -747,10 +747,10 @@ killall(void)
{ {
int i; int i;
for (i = 0; i < MINIONS; i++) for (i = 0; i < SLAVES; i++)
if (minions[i].pid > 0) { if (slaves[i].pid > 0) {
(void) kill(minions[i].pid, SIGKILL); (void) kill(slaves[i].pid, SIGKILL);
minions[i].sent = 0; slaves[i].sent = 0;
} }
} }
@ -762,42 +762,42 @@ killall(void)
* get the lock back for the next cycle by swapping descriptors. * get the lock back for the next cycle by swapping descriptors.
*/ */
static void static void
dominion(int cmd, int minion_number) doslave(int cmd, int slave_number)
{ {
int nread; int nread;
int nextminion, size, wrote, eot_count; int nextslave, size, wrote, eot_count;
/* /*
* Need our own seek pointer. * Need our own seek pointer.
*/ */
(void) close(diskfd); (void) close(diskfd);
if ((diskfd = open(disk, O_RDONLY)) < 0) if ((diskfd = open(disk, O_RDONLY)) < 0)
quit("minion couldn't reopen disk: %s\n", strerror(errno)); quit("slave couldn't reopen disk: %s\n", strerror(errno));
/* /*
* Need the pid of the next minion in the loop... * Need the pid of the next slave in the loop...
*/ */
if ((nread = atomic(read, cmd, (char *)&nextminion, sizeof nextminion)) if ((nread = atomic(read, cmd, (char *)&nextslave, sizeof nextslave))
!= sizeof nextminion) { != sizeof nextslave) {
quit("master/minion protocol botched - didn't get pid of next minion.\n"); quit("master/slave protocol botched - didn't get pid of next slave.\n");
} }
/* /*
* Get list of blocks to dump, read the blocks into tape buffer * Get list of blocks to dump, read the blocks into tape buffer
*/ */
while ((nread = atomic(read, cmd, (char *)mlp->req, reqsiz)) == reqsiz) { while ((nread = atomic(read, cmd, (char *)slp->req, reqsiz)) == reqsiz) {
struct req *p = mlp->req; struct req *p = slp->req;
for (trecno = 0; trecno < ntrec; for (trecno = 0; trecno < ntrec;
trecno += p->count, p += p->count) { trecno += p->count, p += p->count) {
if (p->dblk) { if (p->dblk) {
blkread(p->dblk, mlp->tblock[trecno], blkread(p->dblk, slp->tblock[trecno],
p->count * TP_BSIZE); p->count * TP_BSIZE);
} else { } else {
if (p->count != 1 || atomic(read, cmd, if (p->count != 1 || atomic(read, cmd,
(char *)mlp->tblock[trecno], (char *)slp->tblock[trecno],
TP_BSIZE) != TP_BSIZE) TP_BSIZE) != TP_BSIZE)
quit("master/minion protocol botched.\n"); quit("master/slave protocol botched.\n");
} }
} }
if (setjmp(jmpbuf) == 0) { if (setjmp(jmpbuf) == 0) {
@ -816,14 +816,14 @@ dominion(int cmd, int minion_number)
while (eot_count < 10 && size < writesize) { while (eot_count < 10 && size < writesize) {
#ifdef RDUMP #ifdef RDUMP
if (host) if (host)
wrote = rmtwrite(mlp->tblock[0]+size, wrote = rmtwrite(slp->tblock[0]+size,
writesize-size); writesize-size);
else else
#endif #endif
wrote = write(tapefd, mlp->tblock[0]+size, wrote = write(tapefd, slp->tblock[0]+size,
writesize-size); writesize-size);
#ifdef WRITEDEBUG #ifdef WRITEDEBUG
printf("minion %d wrote %d\n", minion_number, wrote); printf("slave %d wrote %d\n", slave_number, wrote);
#endif #endif
if (wrote < 0) if (wrote < 0)
break; break;
@ -834,8 +834,8 @@ dominion(int cmd, int minion_number)
#ifdef WRITEDEBUG #ifdef WRITEDEBUG
if (size != writesize) if (size != writesize)
printf("minion %d only wrote %d out of %d bytes and gave up.\n", printf("slave %d only wrote %d out of %d bytes and gave up.\n",
minion_number, size, writesize); slave_number, size, writesize);
#endif #endif
/* /*
@ -862,10 +862,10 @@ dominion(int cmd, int minion_number)
} }
/* /*
* If partial write, don't want next minion to go. * If partial write, don't want next slave to go.
* Also jolts him awake. * Also jolts him awake.
*/ */
(void) kill(nextminion, SIGUSR2); (void) kill(nextslave, SIGUSR2);
} }
if (nread != 0) if (nread != 0)
quit("error reading command pipe: %s\n", strerror(errno)); quit("error reading command pipe: %s\n", strerror(errno));