Appendix B. ladsh Source Code

  1: /* ladsh4.c */
  2:
  3: #define _GNU_SOURCE
  4:
  5: #include <ctype.h>
  6: #include <errno.h>
  7: #include <fcntl.h>
  8: #include <glob.h>
  9: #include <signal.h>
 10: #include <stdio.h>
 11: #include <stdlib.h>
 12: #include <string.h>
 13: #include <sys/ioctl.h>
 14: #include <sys/wait.h>
 15: #include <unistd.h>
 16:
 17: #define MAX_COMMAND_LEN 250     /* max length of a single command
 18:                                    string */
 19: #define JOB_STATUS_FORMAT "[%d] %-22s %.40s
"
 20:
 21: struct jobSet {
 22:     struct job * head;      /* head of list of running jobs */
 23:     struct job * fg;        /* current foreground job */
 24: };
 25:
 26: enum redirectionType { REDIRECT_INPUT, REDIRECT_OVERWRITE,
 27:                        REDIRECT_APPEND };
 28:
 29: struct redirectionSpecifier {
 30:     enum redirectionType type;  /* type of redirection */
 31:     int fd;                 /* fd being redirected */
 32:     char * filename;        /* file to redirect fd to */
 33: };
 34:
 35: struct childProgram {
 36:     pid_t pid;              /* 0 if exited */
 37:     char ** argv;           /* program name and arguments */
 38:     int numRedirections;    /* elements in redirection array */
 39:     struct redirectionSpecifier * redirections; /* I/O redirs */
 40:     glob_t globResult;      /* result of parameter globbing */
 41:     int freeGlob;           /* should we free globResult? */
 42:     int isStopped;          /* is the program currently running? */
 43: };
 44:
 45: struct job {
 46:     int jobId;              /* job number */
 47:     int numProgs;           /* number of programs in job */
 48:     int runningProgs;       /* number of programs running */
 49:     char * text;            /* name of job */
 50:     char * cmdBuf;          /* buffer various argv's point to */
 51:     pid_t pgrp;             /* process group ID for the job */
 52:     struct childProgram * progs; /* array of programs in job */
 53:     struct job * next;      /* to track background commands */
 54:     int stoppedProgs;       /* num of programs alive, but stopped */
 55: };
 56:
 57: void freeJob(struct job * cmd) {
 58:     int i;
 59:
 60:     for (i = 0; i < cmd->numProgs; i++) {
 61:         free(cmd->progs[i].argv);
 62:         if (cmd->progs[i].redirections)
 63:             free(cmd->progs[i].redirections);
 64:         if (cmd->progs[i].freeGlob)
 65:             globfree(&cmd->progs[i].globResult);
 66:     }
 67:     free(cmd->progs);
 68:     if (cmd->text) free(cmd->text);
 69:     free(cmd->cmdBuf);
 70: }
 71:
 72: int getCommand(FILE * source, char * command) {
 73:     if (source == stdin) {
 74:         printf("# ");
 75:         fflush(stdout);
 76:     }
 77:
 78:     if (!fgets(command, MAX_COMMAND_LEN, source)) {
 79:         if (source == stdin) printf("
");
 80:         return 1;
 81:     }
 82:
 83:     /* remove trailing newline */
 84:     command[strlen(command) - 1] = '';
 85:
 86:     return 0;
 87: }
 88:
 89: void globLastArgument(struct childProgram * prog, int * argcPtr,
 90:                         int * argcAllocedPtr) {
 91:     int argc = *argcPtr;
 92:     int argcAlloced = *argcAllocedPtr;
 93:     int rc;
 94:     int flags;
 95:     int i;
 96:     char * src, * dst;
 97:
 98:     if (argc > 1) {       /* cmd->globResult already initialized */
 99:         flags = GLOB_APPEND;
100:         i = prog->globResult.gl_pathc;
101:     } else {
102:         prog->freeGlob = 1;
103:         flags = 0;
104:         i = 0;
105:     }
106:
107:     rc = glob(prog->argv[argc - 1], flags, NULL, &prog->globResult);
108:     if (rc == GLOB_NOSPACE) {
109:         fprintf(stderr, "out of space during glob operation
");
110:         return;
111:     } else if (rc == GLOB_NOMATCH ||
112:                (!rc && (prog->globResult.gl_pathc - i) == 1 &&
113:                 !strcmp(prog->argv[argc - 1],
114:                         prog->globResult.gl_pathv[i]))) {
115:         /* we need to remove whatever  quoting is still present */
116:         src = dst = prog->argv[argc - 1];
117:         while (*src) {
118:             if (*src != '') *dst++ = *src;
119:             src++;
120:         }
121:         *dst = '';
122:     } else if (!rc) {
123:         argcAlloced += (prog->globResult.gl_pathc - i);
124:         prog->argv = realloc(prog->argv,
125:                              argcAlloced * sizeof(*prog->argv));
126:         memcpy(prog->argv + (argc - 1),
127:                prog->globResult.gl_pathv + i,
128:                sizeof(*(prog->argv)) *
129:                       (prog->globResult.gl_pathc - i));
130:         argc += (prog->globResult.gl_pathc - i - 1);
131:     }
132:
133:     *argcAllocedPtr = argcAlloced;
134:     *argcPtr = argc;
135: }
136:
137: /* Return cmd->numProgs as 0 if no command is present (e.g. an empty
138:    line). If a valid command is found, commandPtr is set to point to
139:    the beginning of the next command (if the original command had
140:    more than one job associated with it) or NULL if no more
141:    commands are present. */
142: int parseCommand(char ** commandPtr, struct job * job, int * isBg) {
143:     char * command;
144:     char * returnCommand = NULL;
145:     char * src, * buf, * chptr;
146:     int argc = 0;
147:     int done = 0;
148:     int argvAlloced;
149:     int i;
150:     char quote = '';
151:     int count;
152:     struct childProgram * prog;
153:
154:     /* skip leading white space */
155:     while (**commandPtr && isspace(**commandPtr)) (*commandPtr)++;
156:
157:     /* this handles empty lines and leading '#' characters */
158:         if (!**commandPtr || (**commandPtr=='#')) {
159:         job->numProgs = 0;
160:         *commandPtr = NULL;
161:         return 0;
162:     }
163:
164:     *isBg = 0;
165:     job->numProgs = 1;
166:     job->progs = malloc(sizeof(*job->progs));
167:
168:     /* We set the argv elements to point inside of this string. The
169:        memory is freed by freeJob().
170:
171:        Getting clean memory relieves us of the task of NULL
172:        terminating things and makes the rest of this look a bit
173:        cleaner (though it is, admittedly, a tad less efficient) */
174:     job->cmdBuf = command = calloc(1, strlen(*commandPtr) + 1);
175:     job->text = NULL;
176:
177:     prog = job->progs;
178:     prog->numRedirections = 0;
179:     prog->redirections = NULL;
180:     prog->freeGlob = 0;
181:     prog->isStopped = 0;
182:
183:     argvAlloced = 5;
184:     prog->argv = malloc(sizeof(*prog->argv) * argvAlloced);
185:     prog->argv[0] = job->cmdBuf;
186:
187:     buf = command;
188:     src = *commandPtr;
189:     while (*src && !done) {
190:         if (quote == *src) {
191:             quote = '';
192:         } else if (quote) {
193:             if (*src == '') {
194:                 src++;
195:                 if (!*src) {
196:                     fprintf(stderr,
197:                         "character expected after \
");
198:                     freeJob(job);
199:                     return 1;
200:                 }
201:
202:                 /* in shell, "'" should yield ' */
203:                 if (*src != quote) *buf++ = '';
204:             } else if (*src == '*' || *src == '?' || *src == '[' ||
205:                        *src == ']')
206:                 *buf++ = '';
207:             *buf++ = *src;
208:         } else if (isspace(*src)) {
209:             if (*prog->argv[argc]) {
210:                 buf++, argc++;
211:                 /* +1 here leaves room for the NULL which
212:                    ends argv */
213:                 if ((argc + 1) == argvAlloced) {
214:                     argvAlloced += 5;
215:                     prog->argv = realloc(prog->argv,
216:                                 sizeof(*prog->argv) * argvAlloced);
217:                 }
218:                 prog->argv[argc] = buf;
219:
220:                 globLastArgument(prog, &argc, &argvAlloced);
221:             }
222:         } else switch (*src) {
223:         case '"':
224:         case ''':
225:             quote = *src;
226:             break;
227:
228:         case '#':                         /* comment */
229:             done = 1;
230:             break;
231:
232:         case '>':                         /* redirections */
233:         case '<':
234:             i = prog->numRedirections++;
235:             prog->redirections = realloc(prog->redirections,
236:                         sizeof(*prog->redirections) * (i + 1));
237:
238:             prog->redirections[i].fd = -1;
239:             if (buf != prog->argv[argc]) {
240:                 /* the stuff before this character may be
241:                    the file number being redirected */
242:                 prog->redirections[i].fd =
243:                     strtol(prog->argv[argc], &chptr, 10);
244:
245:                 if (*chptr && *prog->argv[argc]) {
246:                     buf++, argc++;
247:                     globLastArgument(prog, &argc, &argvAlloced);
248:                 }
249:             }
250:
251:             if (prog->redirections[i].fd == -1) {
252:                 if (*src == '>')
253:                     prog->redirections[i].fd = 1;
254:                 else
255:                     prog->redirections[i].fd = 0;
256:             }
257:
258:             if (*src++ == '>') {
259:                 if (*src == '>') {
260:                     prog->redirections[i].type = REDIRECT_APPEND;
261:                     src++;
262:                 } else {
263:                    prog->redirections[i].type = REDIRECT_OVERWRITE;
264:                 }
265:             } else {
266:                 prog->redirections[i].type = REDIRECT_INPUT;
267:             }
268:
269:             /* This isn't POSIX sh compliant. Oh well. */
270:             chptr = src;
271:             while (isspace(*chptr)) chptr++;
272:
273:             if (!*chptr) {
274:                 fprintf(stderr, "file name expected after %c
",
275:                         *src);
276:                 freeJob(job);
277:                 return 1;
278:             }
279:
280:             prog->redirections[i].filename = buf;
281:             while (*chptr && !isspace(*chptr))
282:                 *buf++ = *chptr++;
283:
284:             src = chptr - 1;               /* we src++ later */
285:             prog->argv[argc] = ++buf;
286:             break;
287:
288:         case '|':                         /* pipe */
289:             /* finish this command */
290:             if (*prog->argv[argc]) argc++;
291:             if (!argc) {
292:                 fprintf(stderr, "empty command in pipe
");
293:                 freeJob(job);
294:                 return 1;
295:             }
296:             prog->argv[argc] = NULL;
297:
298:             /* and start the next */
299:             job->numProgs++;
300:             job->progs = realloc(job->progs,
301:                                  sizeof(*job->progs) *
302:                                      job->numProgs);
303:             prog = job->progs + (job->numProgs - 1);
304:             prog->numRedirections = 0;
305:             prog->redirections = NULL;
306:             prog->freeGlob = 0;
307:             argc = 0;
308:
309:             argvAlloced = 5;
310:             prog->argv = malloc(sizeof(*prog->argv) *
311:                                     argvAlloced);
312:             prog->argv[0] = ++buf;
313:
314:             src++;
315:             while (*src && isspace(*src)) src++;
316:
317:             if (!*src) {
318:                 fprintf(stderr, "empty command in pipe
");
319:                 return 1;
320:             }
321:             src--;     /* we'll ++ it at the end of the loop */
322:
323:             break;
324:
325:         case '&':                         /* background */
326:             *isBg = 1;
327:         case ';':                         /* multiple commands */
328:             done = 1;
329:             returnCommand = *commandPtr + (src - *commandPtr) + 1;
330:             break;
331:
332:         case '':
333:             src++;
334:             if (!*src) {
335:                 freeJob(job);
336:                 fprintf(stderr, "character expected after \
");
337:                 return 1;
338:             }
339:             if (*src == '*' || *src == '[' || *src == ']'
340:                             || *src == '?')
341:                 *buf++ = '';
342:             /* fallthrough */
343:         default:
344:             *buf++ = *src;
345:         }
346:
347:         src++;
348:     }
349:
350:     if (*prog->argv[argc]) {
351:         argc++;
352:         globLastArgument(prog, &argc, &argvAlloced);
353:     }
354:     if (!argc) {
355:         freeJob(job);
356:         return 0;
357:     }
358:     prog->argv[argc] = NULL;
359:
360:     if (!returnCommand) {
361:         job->text = malloc(strlen(*commandPtr) + 1);
362:         strcpy(job->text, *commandPtr);
363:     } else {
364:         /* This leaves any trailing spaces, which is a bit sloppy */
365:
366:         count = returnCommand - *commandPtr;
367:         job->text = malloc(count + 1);
368:         strncpy(job->text, *commandPtr, count);
369:         job->text[count] = '';
370:     }
371:
372:     *commandPtr = returnCommand;
373:
374:     return 0;
375: }
376:
377: int setupRedirections(struct childProgram * prog) {
378:     int i;
379:     int openfd;
380:     int mode;
381:     struct redirectionSpecifier * redir = prog->redirections;
382:
383:     for (i = 0; i < prog->numRedirections; i++, redir++) {
384:         switch (redir->type) {
385:         case REDIRECT_INPUT:
386:             mode = O_RDONLY;
387:             break;
388:         case REDIRECT_OVERWRITE:
389:             mode = O_RDWR | O_CREAT | O_TRUNC;
390:             break;
391:         case REDIRECT_APPEND:
392:             mode = O_RDWR | O_CREAT | O_APPEND;
393:             break;
394:         }
395:
396:         openfd = open(redir->filename, mode, 0666);
397:         if (openfd < 0) {
398:             /* this could get lost if stderr has been redirected,
399:                but bash and ash both lose it as well (though zsh
400:                doesn't!) */
401:             fprintf(stderr, "error opening %s: %s
",
402:                     redir->filename, strerror(errno));
403:             return 1;
404:         }
405:
406:         if (openfd != redir->fd) {
407:             dup2(openfd, redir->fd);
408:             close(openfd);
409:         }
410:     }
411:
412:     return 0;
413: }
414:
415: int runCommand(struct job newJob, struct jobSet * jobList,
416:                int inBg) {
417:     struct job * job;
418:     char * newdir, * buf;
419:     int i, len;
420:     int nextin, nextout;
421:     int pipefds[2];            /* pipefd[0] is for reading */
422:     char * statusString;
423:     int jobNum;
424:     int controlfds[2];         /* a pipe to make the child pause */
425:
426:     /* handle built-ins here -- we don't fork() so we
427:        can't background these very easily */
428:     if (!strcmp(newJob.progs[0].argv[0], "exit")) {
429:         /* this should return a real exit code */
430:         exit(0);
431:     } else if (!strcmp(newJob.progs[0].argv[0], "pwd")) {
432:         len = 50;
433:         buf = malloc(len);
434:         while (!getcwd(buf, len) && errno == ERANGE) {
435:             len += 50;
436:             buf = realloc(buf, len);
437:         }
438:         printf("%s
", buf);
439:         free(buf);
440:         return 0;
441:     } else if (!strcmp(newJob.progs[0].argv[0], "cd")) {
442:         if (!newJob.progs[0].argv[1] == 1)
443:             newdir = getenv("HOME");
444:         else
445:             newdir = newJob.progs[0].argv[1];
446:         if (chdir(newdir))
447:             printf("failed to change current directory: %s
",
448:                     strerror(errno));
449:         return 0;
450:     } else if (!strcmp(newJob.progs[0].argv[0], "jobs")) {
451:         for (job = jobList->head; job; job = job->next) {
452:             if (job->runningProgs == job->stoppedProgs)
453:                 statusString = "Stopped";
454:             else
455:                 statusString = "Running";
456:
457:             printf(JOB_STATUS_FORMAT, job->jobId, statusString,
458:                     job->text);
459:         }
460:         return 0;
461:     } else if (!strcmp(newJob.progs[0].argv[0], "fg") ||
462:                !strcmp(newJob.progs[0].argv[0], "bg")) {
463:         if (!newJob.progs[0].argv[1] || newJob.progs[0].argv[2]) {
464:             fprintf(stderr,
465:                     "%s: exactly one argument is expected
",
466:                     newJob.progs[0].argv[0]);
467:             return 1;
468:         }
469:
470:        if (sscanf(newJob.progs[0].argv[1], "%%%d", &jobNum) != 1) {
471:             fprintf(stderr, "%s: bad argument '%s'
",
472:                     newJob.progs[0].argv[0],
473:                     newJob.progs[0].argv[1]);
474:             return 1;
475:         }
476:
477:         for (job = jobList->head; job; job = job->next)
478:             if (job->jobId == jobNum) break;
479:
480:         if (!job) {
481:             fprintf(stderr, "%s: unknown job %d
",
482:                     newJob.progs[0].argv[0], jobNum);
483:             return 1;
484:         }
485:
486:         if (*newJob.progs[0].argv[0] == 'f') {
487:             /* Make this job the foreground job */
488:
489:             if (tcsetpgrp(0, job->pgrp))
490:                 perror("tcsetpgrp");
491:             jobList->fg = job;
492:         }
493:
494:         /* Restart the processes in the job */
495:         for (i = 0; i < job->numProgs; i++)
496:             job->progs[i].isStopped = 0;
497:
498:         kill(-job->pgrp, SIGCONT);
499:
500:         job->stoppedProgs = 0;
501:
502:         return 0;
503:     }
504:
505:     nextin = 0, nextout = 1;
506:     for (i = 0; i < newJob.numProgs; i++) {
507:         if ((i + 1) < newJob.numProgs) {
508:             pipe(pipefds);
509:             nextout = pipefds[1];
510:         } else {
511:             nextout = 1;
512:         }
513:
514:         pipe(controlfds);
515:
516:         if (!(newJob.progs[i].pid = fork())) {
517:             signal(SIGTTOU, SIG_DFL);
518:
519:             close(controlfds[1]);
520:            /* this read will return 0 when the write side closes */
521:             read(controlfds[0], &len, 1);
522:             close(controlfds[0]);
523:
524:             if (nextin != 0) {
525:                 dup2(nextin, 0);
526:                 close(nextin);
527:             }
528:
529:             if (nextout != 1) {
530:                 dup2(nextout, 1);
531:                 close(nextout);
532:             }
533:
534:             /* explicit redirections override pipes */
535:             setupRedirections(newJob.progs + i);
536:
537:             execvp(newJob.progs[i].argv[0], newJob.progs[i].argv);
538:             fprintf(stderr, "exec() of %s failed: %s
",
539:                     newJob.progs[i].argv[0],
540:                     strerror(errno));
541:             exit(1);
542:         }
543:
544:         /* put our child in the process group whose leader is the
545:            first process in this pipe */
546:         setpgid(newJob.progs[i].pid, newJob.progs[0].pid);
547:
548:         /* close the control pipe so the child can continue */
549:         close(controlfds[0]);
550:         close(controlfds[1]);
551:
552:         if (nextin != 0) close(nextin);
553:         if (nextout != 1) close(nextout);
554:
555:         /* If there isn't another process, nextin is garbage
556:            but it doesn't matter */
557:         nextin = pipefds[0];
558:     }
559:
560:     newJob.pgrp = newJob.progs[0].pid;
561:
562:     /* find the ID for the job to use */
563:     newJob.jobId = 1;
564:     for (job = jobList->head; job; job = job->next)
565:         if (job->jobId >= newJob.jobId)
566:             newJob.jobId = job->jobId + 1;
567:
568:     /* add the job to the list of running jobs */
569:     if (!jobList->head) {
570:         job = jobList->head = malloc(sizeof(*job));
571:     } else {
572:         for (job = jobList->head; job->next; job = job->next);
573:         job->next = malloc(sizeof(*job));
574:         job = job->next;
575:     }
576:
577:     *job = newJob;
578:     job->next = NULL;
579:     job->runningProgs = job->numProgs;
580:     job->stoppedProgs = 0;
581:
582:     if (inBg) {
583:         /* we don't wait for background jobs to return -- append it
584:            to the list of backgrounded jobs and leave it alone */
585:
586:         printf("[%d] %d
", job->jobId,
587:                newJob.progs[newJob.numProgs - 1].pid);
588:     } else {
589:         jobList->fg = job;
590:
591:         /* move the new process group into the foreground */
592:
593:         if (tcsetpgrp(0, newJob.pgrp))
594:             perror("tcsetpgrp");
595:     }
596:
597:     return 0;
598: }
599:
600: void removeJob(struct jobSet * jobList, struct job * job) {
601:     struct job * prevJob;
602:
603:     freeJob(job);
604:     if (job == jobList->head) {
605:         jobList->head = job->next;
606:     } else {
607:         prevJob = jobList->head;
608:         while (prevJob->next != job) prevJob = prevJob->next;
609:         prevJob->next = job->next;
610:     }
611:
612:     free(job);
613: }
614:
615: /* Checks to see if any background processes have exited -- if they
616:    have, figure out why and see if a job has completed */
617: void checkJobs(struct jobSet * jobList) {
618:     struct job * job;
619:     pid_t childpid;
620:     int status;
621:     int progNum;
622:     char * msg;
623:
624:     while ((childpid = waitpid(-1, &status,
625:                                WNOHANG | WUNTRACED)) > 0) {
626:         for (job = jobList->head; job; job = job->next) {
627:             progNum = 0;
628:             while (progNum < job->numProgs &&
629:                         job->progs[progNum].pid != childpid)
630:                 progNum++;
631:             if (progNum < job->numProgs) break;
632:         }
633:
634:         if (WIFEXITED(status) || WIFSIGNALED(status)) {
635:             /* child exited */
636:             job->runningProgs--;
637:             job->progs[progNum].pid = 0;
638:
639:             if (!WIFSIGNALED(status))
640:                 msg = "Done";
641:             else
642:                 msg = strsignal(WTERMSIG(status));
643:
644:             if (!job->runningProgs) {
645:                 printf(JOB_STATUS_FORMAT, job->jobId,
646:                        msg, job->text);
647:                 removeJob(jobList, job);
648:             }
649:         } else {
650:             /* child stopped */
651:             job->stoppedProgs++;
652:             job->progs[progNum].isStopped = 1;
653:
654:             if (job->stoppedProgs == job->numProgs) {
655:                 printf(JOB_STATUS_FORMAT, job->jobId, "Stopped",
656:                        job->text);
657:             }
658:         }
659:     }
660:
661:     if (childpid == -1 && errno != ECHILD)
662:         perror("waitpid");
663: }
664:
665: int main(int argc, const char ** argv) {
666:     char command[MAX_COMMAND_LEN + 1];
667:     char * nextCommand = NULL;
668:     struct jobSet jobList = { NULL, NULL };
669:     struct job newJob;
670:     FILE * input = stdin;
671:     int i;
672:     int status;
673:     int inBg;
674:
675:     if (argc > 2) {
676:         fprintf(stderr, "unexpected arguments; usage: ladsh1 "
677:                         "<commands>
");
678:         exit(1);
679:     } else if (argc == 2) {
680:         input = fopen(argv[1], "r");
681:         if (!input) {
682:             perror("fopen");
683:             exit(1);
684:         }
685:     }
686:
687:     /* don't pay any attention to this signal; it just confuses
688:        things and isn't really meant for shells anyway */
689:     signal(SIGTTOU, SIG_IGN);
690:
691:     while (1) {
692:         if (!jobList.fg) {
693:             /* no job is in the foreground */
694:
695:             /* see if any background processes have exited */
696:             checkJobs(&jobList);
697:
698:             if (!nextCommand) {
699:                 if (getCommand(input, command)) break;
700:                 nextCommand = command;
701:             }
702:
703:             if (!parseCommand(&nextCommand, &newJob, &inBg) &&
704:                               newJob.numProgs) {
705:                 runCommand(newJob, &jobList, inBg);
706:             }
707:         } else {
708:             /* a job is running in the foreground; wait for it */
709:             i = 0;
710:             while (!jobList.fg->progs[i].pid ||
711:                    jobList.fg->progs[i].isStopped) i++;
712:
713:             waitpid(jobList.fg->progs[i].pid, &status, WUNTRACED);
714:
715:             if (WIFSIGNALED(status) &&
716:                     (WTERMSIG(status) != SIGINT)) {
717:                 printf("%s
", strsignal(status));
718:             }
719:
720:             if (WIFEXITED(status) || WIFSIGNALED(status)) {
721:                 /* the child exited */
722:                 jobList.fg->runningProgs--;
723:                 jobList.fg->progs[i].pid = 0;
724:
725:                 if (!jobList.fg->runningProgs) {
726:                     /* child exited */
727:
728:                     removeJob(&jobList, jobList.fg);
729:                     jobList.fg = NULL;
730:
731:                     /* move the shell to the foreground */
732:                     if (tcsetpgrp(0, getpid()))
733:                         perror("tcsetpgrp");
734:                 }
735:             } else {
736:                 /* the child was stopped */
737:                 jobList.fg->stoppedProgs++;
738:                 jobList.fg->progs[i].isStopped = 1;
739:
740:                 if (jobList.fg->stoppedProgs ==
741:                                     jobList.fg->runningProgs) {
742:                     printf("
" JOB_STATUS_FORMAT,
743:                            jobList.fg->jobId,
744:                            "Stopped", jobList.fg->text);
745:                     jobList.fg = NULL;
746:                 }
747:             }
748:
749:             if (!jobList.fg) {
750:                 /* move the shell to the foreground */
751:                 if (tcsetpgrp(0, getpid()))
752:                     perror("tcsetpgrp");
753:             }
754:         }
755:     }
756:
757:     return 0;
758: }
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset