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: }
3.147.238.1