1 /* vi: set sw=4 ts=4: */
5 * Copyright (C) 2000 by Lineo, inc.
6 * Written by Erik Andersen <andersen@lineo.com>, <andersee@debian.org>
8 * Based in part on ladsh.c by Michael K. Johnson and Erik W. Troan, which is
9 * under the following liberal license: "We have placed this source code in the
10 * public domain. Use it in any project, free or commercial."
12 * This program is free software; you can redistribute it and/or modify
13 * it under the terms of the GNU General Public License as published by
14 * the Free Software Foundation; either version 2 of the License, or
15 * (at your option) any later version.
17 * This program is distributed in the hope that it will be useful,
18 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
20 * General Public License for more details.
22 * You should have received a copy of the GNU General Public License
23 * along with this program; if not, write to the Free Software
24 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
37 #include <sys/ioctl.h>
42 #define JOB_STATUS_FORMAT "[%d] %-22s %.40s\n"
45 enum redirectionType { REDIRECT_INPUT, REDIRECT_OVERWRITE,
50 struct job *head; /* head of list of running jobs */
51 struct job *fg; /* current foreground job */
54 struct redirectionSpecifier {
55 enum redirectionType type; /* type of redirection */
56 int fd; /* file descriptor being redirected */
57 char *filename; /* file to redirect fd to */
61 pid_t pid; /* 0 if exited */
62 char **argv; /* program name and arguments */
63 int numRedirections; /* elements in redirection array */
64 struct redirectionSpecifier *redirections; /* I/O redirections */
65 glob_t globResult; /* result of parameter globbing */
66 int freeGlob; /* should we globfree(&globResult)? */
67 int isStopped; /* is the program currently running? */
71 int jobId; /* job number */
72 int numProgs; /* total number of programs in job */
73 int runningProgs; /* number of programs running */
74 char *text; /* name of job */
75 char *cmdBuf; /* buffer various argv's point into */
76 pid_t pgrp; /* process group ID for the job */
77 struct childProgram *progs; /* array of programs in job */
78 struct job *next; /* to track background commands */
79 int stoppedProgs; /* number of programs alive, but stopped */
82 struct builtInCommand {
84 char *descr; /* description */
85 char *usage; /* usage */
86 int (*function) (struct job *, struct jobSet * jobList); /* function ptr */
89 /* Some function prototypes */
90 static int shell_cd(struct job *cmd, struct jobSet *junk);
91 static int shell_env(struct job *dummy, struct jobSet *junk);
92 static int shell_exit(struct job *cmd, struct jobSet *junk);
93 static int shell_fg_bg(struct job *cmd, struct jobSet *jobList);
94 static int shell_help(struct job *cmd, struct jobSet *junk);
95 static int shell_jobs(struct job *dummy, struct jobSet *jobList);
96 static int shell_pwd(struct job *dummy, struct jobSet *junk);
97 static int shell_export(struct job *cmd, struct jobSet *junk);
98 static int shell_source(struct job *cmd, struct jobSet *jobList);
99 static int shell_unset(struct job *cmd, struct jobSet *junk);
101 static void checkJobs(struct jobSet *jobList);
102 static int getCommand(FILE * source, char *command);
103 static int parseCommand(char **commandPtr, struct job *job, int *isBg);
104 static int setupRedirections(struct childProgram *prog);
105 static int runCommand(struct job newJob, struct jobSet *jobList, int inBg);
106 static int busy_loop(FILE * input);
109 /* Table of built-in functions */
110 static struct builtInCommand bltins[] = {
111 {"bg", "Resume a job in the background", "bg [%%job]", shell_fg_bg},
112 {"cd", "Change working directory", "cd [dir]", shell_cd},
113 //{"echo", "Echo arguments on stdout", "echo arg1 [...]", shell_echo},
114 {"env", "Print all environment variables", "env", shell_env},
115 {"exit", "Exit from shell()", "exit", shell_exit},
116 {"fg", "Bring job into the foreground", "fg [%%job]", shell_fg_bg},
117 {"jobs", "Lists the active jobs", "jobs", shell_jobs},
118 {"pwd", "Print current directory", "pwd", shell_pwd},
119 {"export", "Set environment variable", "export [VAR=value]", shell_export},
120 {"unset", "Unset environment variable", "unset VAR", shell_unset},
122 {".", "Source-in and run commands in a file", ". filename", shell_source},
123 {"help", "List shell built-in commands", "help", shell_help},
124 {NULL, NULL, NULL, NULL}
127 static const char shell_usage[] =
129 "sh [FILE]...\n\n" "The BusyBox command interpreter (shell).\n\n";
132 static char cwd[1024];
133 static char *prompt = "# ";
137 /* built-in 'cd <path>' handler */
138 static int shell_cd(struct job *cmd, struct jobSet *junk)
142 if (!cmd->progs[0].argv[1] == 1)
143 newdir = getenv("HOME");
145 newdir = cmd->progs[0].argv[1];
147 printf("cd: %s: %s\n", newdir, strerror(errno));
150 getcwd(cwd, sizeof(cwd));
155 /* built-in 'env' handler */
156 static int shell_env(struct job *dummy, struct jobSet *junk)
160 for (e = environ; *e; e++) {
161 fprintf(stdout, "%s\n", *e);
166 /* built-in 'exit' handler */
167 static int shell_exit(struct job *cmd, struct jobSet *junk)
169 if (!cmd->progs[0].argv[1] == 1)
173 exit(atoi(cmd->progs[0].argv[1]));
176 /* built-in 'fg' and 'bg' handler */
177 static int shell_fg_bg(struct job *cmd, struct jobSet *jobList)
180 struct job *job=NULL;
182 if (!jobList->head) {
183 if (!cmd->progs[0].argv[1] || cmd->progs[0].argv[2]) {
184 fprintf(stderr, "%s: exactly one argument is expected\n",
185 cmd->progs[0].argv[0]);
188 if (sscanf(cmd->progs[0].argv[1], "%%%d", &jobNum) != 1) {
189 fprintf(stderr, "%s: bad argument '%s'\n",
190 cmd->progs[0].argv[0], cmd->progs[0].argv[1]);
192 for (job = jobList->head; job; job = job->next) {
193 if (job->jobId == jobNum) {
203 fprintf(stderr, "%s: unknown job %d\n",
204 cmd->progs[0].argv[0], jobNum);
208 if (*cmd->progs[0].argv[0] == 'f') {
209 /* Make this job the foreground job */
210 if (tcsetpgrp(0, job->pgrp))
215 /* Restart the processes in the job */
216 for (i = 0; i < job->numProgs; i++)
217 job->progs[i].isStopped = 0;
219 kill(-job->pgrp, SIGCONT);
221 job->stoppedProgs = 0;
226 /* built-in 'help' handler */
227 static int shell_help(struct job *cmd, struct jobSet *junk)
229 struct builtInCommand *x;
231 fprintf(stdout, "\nBuilt-in commands:\n");
232 fprintf(stdout, "-------------------\n");
233 for (x = bltins; x->cmd; x++) {
234 fprintf(stdout, "%s\t%s\n", x->cmd, x->descr);
236 fprintf(stdout, "\n\n");
240 /* built-in 'jobs' handler */
241 static int shell_jobs(struct job *dummy, struct jobSet *jobList)
246 for (job = jobList->head; job; job = job->next) {
247 if (job->runningProgs == job->stoppedProgs)
248 statusString = "Stopped";
250 statusString = "Running";
252 printf(JOB_STATUS_FORMAT, job->jobId, statusString, job->text);
258 /* built-in 'pwd' handler */
259 static int shell_pwd(struct job *dummy, struct jobSet *junk)
261 getcwd(cwd, sizeof(cwd));
262 fprintf(stdout, "%s\n", cwd);
266 /* built-in 'export VAR=value' handler */
267 static int shell_export(struct job *cmd, struct jobSet *junk)
271 if (!cmd->progs[0].argv[1] == 1) {
272 return (shell_env(cmd, junk));
274 res = putenv(cmd->progs[0].argv[1]);
276 fprintf(stdout, "export: %s\n", strerror(errno));
280 /* Built-in '.' handler (read-in and execute commands from file) */
281 static int shell_source(struct job *cmd, struct jobSet *junk)
286 if (!cmd->progs[0].argv[1] == 1)
289 input = fopen(cmd->progs[0].argv[1], "r");
291 fprintf(stdout, "Couldn't open file '%s'\n",
292 cmd->progs[0].argv[1]);
296 /* Now run the file */
297 status = busy_loop(input);
301 /* built-in 'unset VAR' handler */
302 static int shell_unset(struct job *cmd, struct jobSet *junk)
304 if (!cmd->progs[0].argv[1] == 1) {
305 fprintf(stdout, "unset: parameter required.\n");
308 unsetenv(cmd->progs[0].argv[1]);
312 /* free up all memory from a job */
313 static void freeJob(struct job *cmd)
317 for (i = 0; i < cmd->numProgs; i++) {
318 free(cmd->progs[i].argv);
319 if (cmd->progs[i].redirections)
320 free(cmd->progs[i].redirections);
321 if (cmd->progs[i].freeGlob)
322 globfree(&cmd->progs[i].globResult);
330 /* remove a job from the jobList */
331 static void removeJob(struct jobSet *jobList, struct job *job)
336 if (job == jobList->head) {
337 jobList->head = job->next;
339 prevJob = jobList->head;
340 while (prevJob->next != job)
341 prevJob = prevJob->next;
342 prevJob->next = job->next;
348 /* Checks to see if any background processes have exited -- if they
349 have, figure out why and see if a job has completed */
350 static void checkJobs(struct jobSet *jobList)
357 while ((childpid = waitpid(-1, &status, WNOHANG | WUNTRACED)) > 0) {
358 for (job = jobList->head; job; job = job->next) {
360 while (progNum < job->numProgs &&
361 job->progs[progNum].pid != childpid) progNum++;
362 if (progNum < job->numProgs)
366 if (WIFEXITED(status) || WIFSIGNALED(status)) {
369 job->progs[progNum].pid = 0;
371 if (!job->runningProgs) {
372 printf(JOB_STATUS_FORMAT, job->jobId, "Done", job->text);
373 removeJob(jobList, job);
378 job->progs[progNum].isStopped = 1;
380 if (job->stoppedProgs == job->numProgs) {
381 printf(JOB_STATUS_FORMAT, job->jobId, "Stopped",
387 if (childpid == -1 && errno != ECHILD)
391 static int getCommand(FILE * source, char *command)
393 if (source == stdin) {
394 #ifdef BB_FEATURE_SH_COMMAND_EDITING
397 len=fprintf(stdout, "BBSHELL %s %s", cwd, prompt);
399 promptStr=(char*)malloc(sizeof(char)*(len+1));
400 sprintf(promptStr, "BBSHELL %s %s", cwd, prompt);
401 cmdedit_read_input(promptStr, fileno(stdin), fileno(stdout), command);
405 fprintf(stdout, "%s %s", cwd, prompt);
410 if (!fgets(command, BUFSIZ - 2, source)) {
416 /* remove trailing newline */
417 command[strlen(command) - 1] = '\0';
422 static void globLastArgument(struct childProgram *prog, int *argcPtr,
426 int argcAlloced = *argcAllocedPtr;
432 if (argc > 1) { /* cmd->globResult is already initialized */
434 i = prog->globResult.gl_pathc;
441 rc = glob(prog->argv[argc - 1], flags, NULL, &prog->globResult);
442 if (rc == GLOB_NOSPACE) {
443 fprintf(stderr, "out of space during glob operation\n");
445 } else if (rc == GLOB_NOMATCH ||
446 (!rc && (prog->globResult.gl_pathc - i) == 1 &&
447 !strcmp(prog->argv[argc - 1],
448 prog->globResult.gl_pathv[i]))) {
449 /* we need to remove whatever \ quoting is still present */
450 src = dst = prog->argv[argc - 1];
458 argcAlloced += (prog->globResult.gl_pathc - i);
460 realloc(prog->argv, argcAlloced * sizeof(*prog->argv));
461 memcpy(prog->argv + (argc - 1), prog->globResult.gl_pathv + i,
462 sizeof(*(prog->argv)) * (prog->globResult.gl_pathc - i));
463 argc += (prog->globResult.gl_pathc - i - 1);
466 *argcAllocedPtr = argcAlloced;
470 /* Return cmd->numProgs as 0 if no command is present (e.g. an empty
471 line). If a valid command is found, commandPtr is set to point to
472 the beginning of the next command (if the original command had more
473 then one job associated with it) or NULL if no more commands are
475 static int parseCommand(char **commandPtr, struct job *job, int *isBg)
478 char *returnCommand = NULL;
479 char *src, *buf, *chptr;
486 struct childProgram *prog;
488 /* skip leading white space */
489 while (**commandPtr && isspace(**commandPtr))
492 /* this handles empty lines or leading '#' characters */
493 if (!**commandPtr || (**commandPtr == '#')) {
501 job->progs = malloc(sizeof(*job->progs));
503 /* We set the argv elements to point inside of this string. The
504 memory is freed by freeJob().
506 Getting clean memory relieves us of the task of NULL
507 terminating things and makes the rest of this look a bit
508 cleaner (though it is, admittedly, a tad less efficient) */
509 job->cmdBuf = command = calloc(1, strlen(*commandPtr) + 1);
513 prog->numRedirections = 0;
514 prog->redirections = NULL;
519 prog->argv = malloc(sizeof(*prog->argv) * argvAlloced);
520 prog->argv[0] = job->cmdBuf;
524 while (*src && !done) {
531 fprintf(stderr, "character expected after \\\n");
536 /* in shell, "\'" should yield \' */
539 } else if (*src == '*' || *src == '?' || *src == '[' ||
540 *src == ']') *buf++ = '\\';
542 } else if (isspace(*src)) {
543 if (*prog->argv[argc]) {
545 /* +1 here leaves room for the NULL which ends argv */
546 if ((argc + 1) == argvAlloced) {
548 prog->argv = realloc(prog->argv,
549 sizeof(*prog->argv) *
552 prog->argv[argc] = buf;
554 globLastArgument(prog, &argc, &argvAlloced);
563 case '#': /* comment */
567 case '>': /* redirections */
569 i = prog->numRedirections++;
570 prog->redirections = realloc(prog->redirections,
571 sizeof(*prog->redirections) *
574 prog->redirections[i].fd = -1;
575 if (buf != prog->argv[argc]) {
576 /* the stuff before this character may be the file number
578 prog->redirections[i].fd =
579 strtol(prog->argv[argc], &chptr, 10);
581 if (*chptr && *prog->argv[argc]) {
583 globLastArgument(prog, &argc, &argvAlloced);
587 if (prog->redirections[i].fd == -1) {
589 prog->redirections[i].fd = 1;
591 prog->redirections[i].fd = 0;
596 prog->redirections[i].type =
597 REDIRECT_APPEND, src++;
599 prog->redirections[i].type = REDIRECT_OVERWRITE;
601 prog->redirections[i].type = REDIRECT_INPUT;
604 /* This isn't POSIX sh compliant. Oh well. */
606 while (isspace(*chptr))
610 fprintf(stderr, "file name expected after %c\n", *src);
615 prog->redirections[i].filename = buf;
616 while (*chptr && !isspace(*chptr))
619 src = chptr - 1; /* we src++ later */
620 prog->argv[argc] = ++buf;
624 /* finish this command */
625 if (*prog->argv[argc])
628 fprintf(stderr, "empty command in pipe\n");
632 prog->argv[argc] = NULL;
634 /* and start the next */
636 job->progs = realloc(job->progs,
637 sizeof(*job->progs) * job->numProgs);
638 prog = job->progs + (job->numProgs - 1);
639 prog->numRedirections = 0;
640 prog->redirections = NULL;
645 prog->argv = malloc(sizeof(*prog->argv) * argvAlloced);
646 prog->argv[0] = ++buf;
649 while (*src && isspace(*src))
653 fprintf(stderr, "empty command in pipe\n");
656 src--; /* we'll ++ it at the end of the loop */
660 case '&': /* background */
662 case ';': /* multiple commands */
664 returnCommand = *commandPtr + (src - *commandPtr) + 1;
671 fprintf(stderr, "character expected after \\\n");
674 if (*src == '*' || *src == '[' || *src == ']'
675 || *src == '?') *buf++ = '\\';
684 if (*prog->argv[argc]) {
686 globLastArgument(prog, &argc, &argvAlloced);
692 prog->argv[argc] = NULL;
694 if (!returnCommand) {
695 job->text = malloc(strlen(*commandPtr) + 1);
696 strcpy(job->text, *commandPtr);
698 /* This leaves any trailing spaces, which is a bit sloppy */
700 count = returnCommand - *commandPtr;
701 job->text = malloc(count + 1);
702 strncpy(job->text, *commandPtr, count);
703 job->text[count] = '\0';
706 *commandPtr = returnCommand;
711 static int runCommand(struct job newJob, struct jobSet *jobList, int inBg)
716 int pipefds[2]; /* pipefd[0] is for reading */
717 struct builtInCommand *x;
719 /* handle built-ins here -- we don't fork() so we can't background
721 for (x = bltins; x->cmd; x++) {
722 if (!strcmp(newJob.progs[0].argv[0], x->cmd)) {
723 return (x->function(&newJob, jobList));
727 nextin = 0, nextout = 1;
728 for (i = 0; i < newJob.numProgs; i++) {
729 if ((i + 1) < newJob.numProgs) {
731 nextout = pipefds[1];
736 if (!(newJob.progs[i].pid = fork())) {
737 signal(SIGTTOU, SIG_DFL);
749 /* explicit redirections override pipes */
750 setupRedirections(newJob.progs + i);
752 execvp(newJob.progs[i].argv[0], newJob.progs[i].argv);
753 fatalError("sh: %s: %s\n", newJob.progs[i].argv[0],
757 /* put our child in the process group whose leader is the
758 first process in this pipe */
759 setpgid(newJob.progs[i].pid, newJob.progs[0].pid);
766 /* If there isn't another process, nextin is garbage
767 but it doesn't matter */
771 newJob.pgrp = newJob.progs[0].pid;
773 /* find the ID for the job to use */
775 for (job = jobList->head; job; job = job->next)
776 if (job->jobId >= newJob.jobId)
777 newJob.jobId = job->jobId + 1;
779 /* add the job to the list of running jobs */
780 if (!jobList->head) {
781 job = jobList->head = malloc(sizeof(*job));
783 for (job = jobList->head; job->next; job = job->next);
784 job->next = malloc(sizeof(*job));
790 job->runningProgs = job->numProgs;
791 job->stoppedProgs = 0;
794 /* we don't wait for background jobs to return -- append it
795 to the list of backgrounded jobs and leave it alone */
797 printf("[%d] %d\n", job->jobId,
798 newJob.progs[newJob.numProgs - 1].pid);
802 /* move the new process group into the foreground */
804 if (tcsetpgrp(0, newJob.pgrp))
811 static int setupRedirections(struct childProgram *prog)
816 struct redirectionSpecifier *redir = prog->redirections;
818 for (i = 0; i < prog->numRedirections; i++, redir++) {
819 switch (redir->type) {
823 case REDIRECT_OVERWRITE:
824 mode = O_RDWR | O_CREAT | O_TRUNC;
826 case REDIRECT_APPEND:
827 mode = O_RDWR | O_CREAT | O_APPEND;
831 openfd = open(redir->filename, mode, 0666);
833 /* this could get lost if stderr has been redirected, but
834 bash and ash both lose it as well (though zsh doesn't!) */
835 fprintf(stderr, "error opening %s: %s\n", redir->filename,
840 if (openfd != redir->fd) {
841 dup2(openfd, redir->fd);
850 static int busy_loop(FILE * input)
853 char *nextCommand = NULL;
854 struct jobSet jobList = { NULL, NULL };
860 command = (char *) calloc(BUFSIZ, sizeof(char));
862 /* don't pay any attention to this signal; it just confuses
863 things and isn't really meant for shells anyway */
864 signal(SIGTTOU, SIG_IGN);
868 /* no job is in the foreground */
870 /* see if any background processes have exited */
874 if (getCommand(input, command))
876 nextCommand = command;
879 if (!parseCommand(&nextCommand, &newJob, &inBg) &&
881 runCommand(newJob, &jobList, inBg);
884 /* a job is running in the foreground; wait for it */
886 while (!jobList.fg->progs[i].pid ||
887 jobList.fg->progs[i].isStopped) i++;
889 waitpid(jobList.fg->progs[i].pid, &status, WUNTRACED);
891 if (WIFEXITED(status) || WIFSIGNALED(status)) {
892 /* the child exited */
893 jobList.fg->runningProgs--;
894 jobList.fg->progs[i].pid = 0;
896 if (!jobList.fg->runningProgs) {
899 removeJob(&jobList, jobList.fg);
902 /* move the shell to the foreground */
903 if (tcsetpgrp(0, getpid()))
907 /* the child was stopped */
908 jobList.fg->stoppedProgs++;
909 jobList.fg->progs[i].isStopped = 1;
911 if (jobList.fg->stoppedProgs == jobList.fg->runningProgs) {
912 printf("\n" JOB_STATUS_FORMAT, jobList.fg->jobId,
913 "Stopped", jobList.fg->text);
919 /* move the shell to the foreground */
920 if (tcsetpgrp(0, getpid()))
931 int shell_main(int argc, char **argv)
938 /* initialize the cwd */
939 getcwd(cwd, sizeof(cwd));
942 //if (argv[0] && argv[0][0] == '-') {
943 // shell_source("/etc/profile");
947 fprintf(stdout, "\n\nBusyBox v%s (%s) Built-in shell\n", BB_VER,
950 "Enter 'help' for a list of built-in commands.\n\n");
952 input = fopen(argv[1], "r");
954 fatalError("A: Couldn't open file '%s': %s\n", argv[1],
957 // fatalError("Got it.\n");
958 //exit(shell_source(argv[1]));
960 /* Set terminal IO to canonical mode, and save old term settings. */
961 #ifdef BB_FEATURE_SH_COMMAND_EDITING
966 return (busy_loop(input));