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 #ifdef BB_FEATURE_SH_COMMAND_EDITING
46 #define JOB_STATUS_FORMAT "[%d] %-22s %.40s\n"
49 enum redirectionType { REDIRECT_INPUT, REDIRECT_OVERWRITE,
54 struct job *head; /* head of list of running jobs */
55 struct job *fg; /* current foreground job */
58 struct redirectionSpecifier {
59 enum redirectionType type; /* type of redirection */
60 int fd; /* file descriptor being redirected */
61 char *filename; /* file to redirect fd to */
65 pid_t pid; /* 0 if exited */
66 char **argv; /* program name and arguments */
67 int numRedirections; /* elements in redirection array */
68 struct redirectionSpecifier *redirections; /* I/O redirections */
69 glob_t globResult; /* result of parameter globbing */
70 int freeGlob; /* should we globfree(&globResult)? */
71 int isStopped; /* is the program currently running? */
75 int jobId; /* job number */
76 int numProgs; /* total number of programs in job */
77 int runningProgs; /* number of programs running */
78 char *text; /* name of job */
79 char *cmdBuf; /* buffer various argv's point into */
80 pid_t pgrp; /* process group ID for the job */
81 struct childProgram *progs; /* array of programs in job */
82 struct job *next; /* to track background commands */
83 int stoppedProgs; /* number of programs alive, but stopped */
86 struct builtInCommand {
88 char *descr; /* description */
89 char *usage; /* usage */
90 int (*function) (struct job *, struct jobSet * jobList); /* function ptr */
93 /* Some function prototypes */
94 static int shell_cd(struct job *cmd, struct jobSet *junk);
95 static int shell_env(struct job *dummy, struct jobSet *junk);
96 static int shell_exit(struct job *cmd, struct jobSet *junk);
97 static int shell_fg_bg(struct job *cmd, struct jobSet *jobList);
98 static int shell_help(struct job *cmd, struct jobSet *junk);
99 static int shell_jobs(struct job *dummy, struct jobSet *jobList);
100 static int shell_pwd(struct job *dummy, struct jobSet *junk);
101 static int shell_export(struct job *cmd, struct jobSet *junk);
102 static int shell_source(struct job *cmd, struct jobSet *jobList);
103 static int shell_unset(struct job *cmd, struct jobSet *junk);
105 static void checkJobs(struct jobSet *jobList);
106 static int getCommand(FILE * source, char *command);
107 static int parseCommand(char **commandPtr, struct job *job, int *isBg);
108 static int setupRedirections(struct childProgram *prog);
109 static int runCommand(struct job newJob, struct jobSet *jobList, int inBg);
110 static int busy_loop(FILE * input);
113 /* Table of built-in functions */
114 static struct builtInCommand bltins[] = {
115 {"bg", "Resume a job in the background", "bg [%%job]", shell_fg_bg},
116 {"cd", "Change working directory", "cd [dir]", shell_cd},
117 //{"echo", "Echo arguments on stdout", "echo arg1 [...]", shell_echo},
118 {"env", "Print all environment variables", "env", shell_env},
119 {"exit", "Exit from shell()", "exit", shell_exit},
120 {"fg", "Bring job into the foreground", "fg [%%job]", shell_fg_bg},
121 {"jobs", "Lists the active jobs", "jobs", shell_jobs},
122 {"pwd", "Print current directory", "pwd", shell_pwd},
123 {"export", "Set environment variable", "export [VAR=value]", shell_export},
124 {"unset", "Unset environment variable", "unset VAR", shell_unset},
126 {".", "Source-in and run commands in a file", ". filename",
128 {"help", "List shell built-in commands", "help", shell_help},
129 {NULL, NULL, NULL, NULL}
132 static const char shell_usage[] =
134 "sh [FILE]...\n\n" "The BusyBox command interpreter (shell).\n\n";
137 static char cwd[1024];
138 static char *prompt = "# ";
142 /* built-in 'cd <path>' handler */
143 static int shell_cd(struct job *cmd, struct jobSet *junk)
147 if (!cmd->progs[0].argv[1] == 1)
148 newdir = getenv("HOME");
150 newdir = cmd->progs[0].argv[1];
152 printf("cd: %s: %s\n", newdir, strerror(errno));
155 getcwd(cwd, sizeof(cwd));
160 /* built-in 'env' handler */
161 static int shell_env(struct job *dummy, struct jobSet *junk)
165 for (e = environ; *e; e++) {
166 fprintf(stdout, "%s\n", *e);
171 /* built-in 'exit' handler */
172 static int shell_exit(struct job *cmd, struct jobSet *junk)
174 if (!cmd->progs[0].argv[1] == 1)
178 exit(atoi(cmd->progs[0].argv[1]));
181 /* built-in 'fg' and 'bg' handler */
182 static int shell_fg_bg(struct job *cmd, struct jobSet *jobList)
185 struct job *job=NULL;
187 if (!jobList->head) {
188 if (!cmd->progs[0].argv[1] || cmd->progs[0].argv[2]) {
189 fprintf(stderr, "%s: exactly one argument is expected\n",
190 cmd->progs[0].argv[0]);
193 if (sscanf(cmd->progs[0].argv[1], "%%%d", &jobNum) != 1) {
194 fprintf(stderr, "%s: bad argument '%s'\n",
195 cmd->progs[0].argv[0], cmd->progs[0].argv[1]);
197 for (job = jobList->head; job; job = job->next) {
198 if (job->jobId == jobNum) {
208 fprintf(stderr, "%s: unknown job %d\n",
209 cmd->progs[0].argv[0], jobNum);
213 if (*cmd->progs[0].argv[0] == 'f') {
214 /* Make this job the foreground job */
215 if (tcsetpgrp(0, job->pgrp))
220 /* Restart the processes in the job */
221 for (i = 0; i < job->numProgs; i++)
222 job->progs[i].isStopped = 0;
224 kill(-job->pgrp, SIGCONT);
226 job->stoppedProgs = 0;
231 /* built-in 'help' handler */
232 static int shell_help(struct job *cmd, struct jobSet *junk)
234 struct builtInCommand *x;
236 fprintf(stdout, "\nBuilt-in commands:\n");
237 fprintf(stdout, "-------------------\n");
238 for (x = bltins; x->cmd; x++) {
239 fprintf(stdout, "%s\t%s\n", x->cmd, x->descr);
241 fprintf(stdout, "\n\n");
245 /* built-in 'jobs' handler */
246 static int shell_jobs(struct job *dummy, struct jobSet *jobList)
251 for (job = jobList->head; job; job = job->next) {
252 if (job->runningProgs == job->stoppedProgs)
253 statusString = "Stopped";
255 statusString = "Running";
257 printf(JOB_STATUS_FORMAT, job->jobId, statusString, job->text);
263 /* built-in 'pwd' handler */
264 static int shell_pwd(struct job *dummy, struct jobSet *junk)
266 getcwd(cwd, sizeof(cwd));
267 fprintf(stdout, "%s\n", cwd);
271 /* built-in 'export VAR=value' handler */
272 static int shell_export(struct job *cmd, struct jobSet *junk)
276 if (!cmd->progs[0].argv[1] == 1) {
277 return (shell_env(cmd, junk));
279 res = putenv(cmd->progs[0].argv[1]);
281 fprintf(stdout, "export: %s\n", strerror(errno));
285 /* Built-in '.' handler (read-in and execute commands from file) */
286 static int shell_source(struct job *cmd, struct jobSet *junk)
291 if (!cmd->progs[0].argv[1] == 1)
294 input = fopen(cmd->progs[0].argv[1], "r");
296 fprintf(stdout, "Couldn't open file '%s'\n",
297 cmd->progs[0].argv[1]);
301 /* Now run the file */
302 status = busy_loop(input);
306 /* built-in 'unset VAR' handler */
307 static int shell_unset(struct job *cmd, struct jobSet *junk)
309 if (!cmd->progs[0].argv[1] == 1) {
310 fprintf(stdout, "unset: parameter required.\n");
313 unsetenv(cmd->progs[0].argv[1]);
317 /* free up all memory from a job */
318 static void freeJob(struct job *cmd)
322 for (i = 0; i < cmd->numProgs; i++) {
323 free(cmd->progs[i].argv);
324 if (cmd->progs[i].redirections)
325 free(cmd->progs[i].redirections);
326 if (cmd->progs[i].freeGlob)
327 globfree(&cmd->progs[i].globResult);
335 /* remove a job from the jobList */
336 static void removeJob(struct jobSet *jobList, struct job *job)
341 if (job == jobList->head) {
342 jobList->head = job->next;
344 prevJob = jobList->head;
345 while (prevJob->next != job)
346 prevJob = prevJob->next;
347 prevJob->next = job->next;
353 /* Checks to see if any background processes have exited -- if they
354 have, figure out why and see if a job has completed */
355 static void checkJobs(struct jobSet *jobList)
362 while ((childpid = waitpid(-1, &status, WNOHANG | WUNTRACED)) > 0) {
363 for (job = jobList->head; job; job = job->next) {
365 while (progNum < job->numProgs &&
366 job->progs[progNum].pid != childpid) progNum++;
367 if (progNum < job->numProgs)
371 if (WIFEXITED(status) || WIFSIGNALED(status)) {
374 job->progs[progNum].pid = 0;
376 if (!job->runningProgs) {
377 printf(JOB_STATUS_FORMAT, job->jobId, "Done", job->text);
378 removeJob(jobList, job);
383 job->progs[progNum].isStopped = 1;
385 if (job->stoppedProgs == job->numProgs) {
386 printf(JOB_STATUS_FORMAT, job->jobId, "Stopped",
392 if (childpid == -1 && errno != ECHILD)
396 static int getCommand(FILE * source, char *command)
398 if (source == stdin) {
399 fprintf(stdout, "BBSHELL %s %s", cwd, prompt);
401 #ifdef BB_FEATURE_SH_COMMAND_EDITING
402 cmdedit_read_input(fileno(stdin), fileno(stdout), command);
407 if (!fgets(command, BUFSIZ - 2, source)) {
413 /* remove trailing newline */
414 command[strlen(command) - 1] = '\0';
419 static void globLastArgument(struct childProgram *prog, int *argcPtr,
423 int argcAlloced = *argcAllocedPtr;
429 if (argc > 1) { /* cmd->globResult is already initialized */
431 i = prog->globResult.gl_pathc;
438 rc = glob(prog->argv[argc - 1], flags, NULL, &prog->globResult);
439 if (rc == GLOB_NOSPACE) {
440 fprintf(stderr, "out of space during glob operation\n");
442 } else if (rc == GLOB_NOMATCH ||
443 (!rc && (prog->globResult.gl_pathc - i) == 1 &&
444 !strcmp(prog->argv[argc - 1],
445 prog->globResult.gl_pathv[i]))) {
446 /* we need to remove whatever \ quoting is still present */
447 src = dst = prog->argv[argc - 1];
455 argcAlloced += (prog->globResult.gl_pathc - i);
457 realloc(prog->argv, argcAlloced * sizeof(*prog->argv));
458 memcpy(prog->argv + (argc - 1), prog->globResult.gl_pathv + i,
459 sizeof(*(prog->argv)) * (prog->globResult.gl_pathc - i));
460 argc += (prog->globResult.gl_pathc - i - 1);
463 *argcAllocedPtr = argcAlloced;
467 /* Return cmd->numProgs as 0 if no command is present (e.g. an empty
468 line). If a valid command is found, commandPtr is set to point to
469 the beginning of the next command (if the original command had more
470 then one job associated with it) or NULL if no more commands are
472 static int parseCommand(char **commandPtr, struct job *job, int *isBg)
475 char *returnCommand = NULL;
476 char *src, *buf, *chptr;
483 struct childProgram *prog;
485 /* skip leading white space */
486 while (**commandPtr && isspace(**commandPtr))
489 /* this handles empty lines or leading '#' characters */
490 if (!**commandPtr || (**commandPtr == '#')) {
498 job->progs = malloc(sizeof(*job->progs));
500 /* We set the argv elements to point inside of this string. The
501 memory is freed by freeJob().
503 Getting clean memory relieves us of the task of NULL
504 terminating things and makes the rest of this look a bit
505 cleaner (though it is, admittedly, a tad less efficient) */
506 job->cmdBuf = command = calloc(1, strlen(*commandPtr) + 1);
510 prog->numRedirections = 0;
511 prog->redirections = NULL;
516 prog->argv = malloc(sizeof(*prog->argv) * argvAlloced);
517 prog->argv[0] = job->cmdBuf;
521 while (*src && !done) {
528 fprintf(stderr, "character expected after \\\n");
533 /* in shell, "\'" should yield \' */
536 } else if (*src == '*' || *src == '?' || *src == '[' ||
537 *src == ']') *buf++ = '\\';
539 } else if (isspace(*src)) {
540 if (*prog->argv[argc]) {
542 /* +1 here leaves room for the NULL which ends argv */
543 if ((argc + 1) == argvAlloced) {
545 prog->argv = realloc(prog->argv,
546 sizeof(*prog->argv) *
549 prog->argv[argc] = buf;
551 globLastArgument(prog, &argc, &argvAlloced);
560 case '#': /* comment */
564 case '>': /* redirections */
566 i = prog->numRedirections++;
567 prog->redirections = realloc(prog->redirections,
568 sizeof(*prog->redirections) *
571 prog->redirections[i].fd = -1;
572 if (buf != prog->argv[argc]) {
573 /* the stuff before this character may be the file number
575 prog->redirections[i].fd =
576 strtol(prog->argv[argc], &chptr, 10);
578 if (*chptr && *prog->argv[argc]) {
580 globLastArgument(prog, &argc, &argvAlloced);
584 if (prog->redirections[i].fd == -1) {
586 prog->redirections[i].fd = 1;
588 prog->redirections[i].fd = 0;
593 prog->redirections[i].type =
594 REDIRECT_APPEND, src++;
596 prog->redirections[i].type = REDIRECT_OVERWRITE;
598 prog->redirections[i].type = REDIRECT_INPUT;
601 /* This isn't POSIX sh compliant. Oh well. */
603 while (isspace(*chptr))
607 fprintf(stderr, "file name expected after %c\n", *src);
612 prog->redirections[i].filename = buf;
613 while (*chptr && !isspace(*chptr))
616 src = chptr - 1; /* we src++ later */
617 prog->argv[argc] = ++buf;
621 /* finish this command */
622 if (*prog->argv[argc])
625 fprintf(stderr, "empty command in pipe\n");
629 prog->argv[argc] = NULL;
631 /* and start the next */
633 job->progs = realloc(job->progs,
634 sizeof(*job->progs) * job->numProgs);
635 prog = job->progs + (job->numProgs - 1);
636 prog->numRedirections = 0;
637 prog->redirections = NULL;
642 prog->argv = malloc(sizeof(*prog->argv) * argvAlloced);
643 prog->argv[0] = ++buf;
646 while (*src && isspace(*src))
650 fprintf(stderr, "empty command in pipe\n");
653 src--; /* we'll ++ it at the end of the loop */
657 case '&': /* background */
659 case ';': /* multiple commands */
661 returnCommand = *commandPtr + (src - *commandPtr) + 1;
668 fprintf(stderr, "character expected after \\\n");
671 if (*src == '*' || *src == '[' || *src == ']'
672 || *src == '?') *buf++ = '\\';
681 if (*prog->argv[argc]) {
683 globLastArgument(prog, &argc, &argvAlloced);
689 prog->argv[argc] = NULL;
691 if (!returnCommand) {
692 job->text = malloc(strlen(*commandPtr) + 1);
693 strcpy(job->text, *commandPtr);
695 /* This leaves any trailing spaces, which is a bit sloppy */
697 count = returnCommand - *commandPtr;
698 job->text = malloc(count + 1);
699 strncpy(job->text, *commandPtr, count);
700 job->text[count] = '\0';
703 *commandPtr = returnCommand;
708 static int runCommand(struct job newJob, struct jobSet *jobList, int inBg)
713 int pipefds[2]; /* pipefd[0] is for reading */
714 struct builtInCommand *x;
716 /* handle built-ins here -- we don't fork() so we can't background
718 for (x = bltins; x->cmd; x++) {
719 if (!strcmp(newJob.progs[0].argv[0], x->cmd)) {
720 return (x->function(&newJob, jobList));
724 nextin = 0, nextout = 1;
725 for (i = 0; i < newJob.numProgs; i++) {
726 if ((i + 1) < newJob.numProgs) {
728 nextout = pipefds[1];
733 if (!(newJob.progs[i].pid = fork())) {
734 signal(SIGTTOU, SIG_DFL);
746 /* explicit redirections override pipes */
747 setupRedirections(newJob.progs + i);
749 execvp(newJob.progs[i].argv[0], newJob.progs[i].argv);
750 fatalError("sh: %s: %s\n", newJob.progs[i].argv[0],
754 /* put our child in the process group whose leader is the
755 first process in this pipe */
756 setpgid(newJob.progs[i].pid, newJob.progs[0].pid);
763 /* If there isn't another process, nextin is garbage
764 but it doesn't matter */
768 newJob.pgrp = newJob.progs[0].pid;
770 /* find the ID for the job to use */
772 for (job = jobList->head; job; job = job->next)
773 if (job->jobId >= newJob.jobId)
774 newJob.jobId = job->jobId + 1;
776 /* add the job to the list of running jobs */
777 if (!jobList->head) {
778 job = jobList->head = malloc(sizeof(*job));
780 for (job = jobList->head; job->next; job = job->next);
781 job->next = malloc(sizeof(*job));
787 job->runningProgs = job->numProgs;
788 job->stoppedProgs = 0;
791 /* we don't wait for background jobs to return -- append it
792 to the list of backgrounded jobs and leave it alone */
794 printf("[%d] %d\n", job->jobId,
795 newJob.progs[newJob.numProgs - 1].pid);
799 /* move the new process group into the foreground */
801 if (tcsetpgrp(0, newJob.pgrp))
808 static int setupRedirections(struct childProgram *prog)
813 struct redirectionSpecifier *redir = prog->redirections;
815 for (i = 0; i < prog->numRedirections; i++, redir++) {
816 switch (redir->type) {
820 case REDIRECT_OVERWRITE:
821 mode = O_RDWR | O_CREAT | O_TRUNC;
823 case REDIRECT_APPEND:
824 mode = O_RDWR | O_CREAT | O_APPEND;
828 openfd = open(redir->filename, mode, 0666);
830 /* this could get lost if stderr has been redirected, but
831 bash and ash both lose it as well (though zsh doesn't!) */
832 fprintf(stderr, "error opening %s: %s\n", redir->filename,
837 if (openfd != redir->fd) {
838 dup2(openfd, redir->fd);
847 static int busy_loop(FILE * input)
850 char *nextCommand = NULL;
851 struct jobSet jobList = { NULL, NULL };
857 command = (char *) calloc(BUFSIZ, sizeof(char));
859 /* don't pay any attention to this signal; it just confuses
860 things and isn't really meant for shells anyway */
861 signal(SIGTTOU, SIG_IGN);
865 /* no job is in the foreground */
867 /* see if any background processes have exited */
871 if (getCommand(input, command))
873 nextCommand = command;
876 if (!parseCommand(&nextCommand, &newJob, &inBg) &&
878 runCommand(newJob, &jobList, inBg);
881 /* a job is running in the foreground; wait for it */
883 while (!jobList.fg->progs[i].pid ||
884 jobList.fg->progs[i].isStopped) i++;
886 waitpid(jobList.fg->progs[i].pid, &status, WUNTRACED);
888 if (WIFEXITED(status) || WIFSIGNALED(status)) {
889 /* the child exited */
890 jobList.fg->runningProgs--;
891 jobList.fg->progs[i].pid = 0;
893 if (!jobList.fg->runningProgs) {
896 removeJob(&jobList, jobList.fg);
899 /* move the shell to the foreground */
900 if (tcsetpgrp(0, getpid()))
904 /* the child was stopped */
905 jobList.fg->stoppedProgs++;
906 jobList.fg->progs[i].isStopped = 1;
908 if (jobList.fg->stoppedProgs == jobList.fg->runningProgs) {
909 printf("\n" JOB_STATUS_FORMAT, jobList.fg->jobId,
910 "Stopped", jobList.fg->text);
916 /* move the shell to the foreground */
917 if (tcsetpgrp(0, getpid()))
928 int shell_main(int argc, char **argv)
935 /* initialize the cwd */
936 getcwd(cwd, sizeof(cwd));
939 //if (argv[0] && argv[0][0] == '-') {
940 // shell_source("/etc/profile");
944 fprintf(stdout, "\n\nBusyBox v%s (%s) Built-in shell\n", BB_VER,
947 "Enter 'help' for a list of built-in commands.\n\n");
949 input = fopen(argv[1], "r");
951 fatalError("A: Couldn't open file '%s': %s\n", argv[1],
954 // fatalError("Got it.\n");
955 //exit(shell_source(argv[1]));
957 /* Set terminal IO to canonical mode, and save old term settings. */
958 #ifdef BB_FEATURE_SH_COMMAND_EDITING
963 return (busy_loop(input));