1 /* vi: set sw=4 ts=4: */
3 * lash -- the BusyBox Lame-Ass SHell
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>
40 #ifdef BB_FEATURE_SH_COMMAND_EDITING
45 #define JOB_STATUS_FORMAT "[%d] %-22s %.40s\n"
48 enum redirectionType { REDIRECT_INPUT, REDIRECT_OVERWRITE,
53 struct job *head; /* head of list of running jobs */
54 struct job *fg; /* current foreground job */
57 struct redirectionSpecifier {
58 enum redirectionType type; /* type of redirection */
59 int fd; /* file descriptor being redirected */
60 char *filename; /* file to redirect fd to */
64 pid_t pid; /* 0 if exited */
65 char **argv; /* program name and arguments */
66 int numRedirections; /* elements in redirection array */
67 struct redirectionSpecifier *redirections; /* I/O redirections */
68 glob_t globResult; /* result of parameter globbing */
69 int freeGlob; /* should we globfree(&globResult)? */
70 int isStopped; /* is the program currently running? */
74 int jobId; /* job number */
75 int numProgs; /* total number of programs in job */
76 int runningProgs; /* number of programs running */
77 char *text; /* name of job */
78 char *cmdBuf; /* buffer various argv's point into */
79 pid_t pgrp; /* process group ID for the job */
80 struct childProgram *progs; /* array of programs in job */
81 struct job *next; /* to track background commands */
82 int stoppedProgs; /* number of programs alive, but stopped */
85 struct builtInCommand {
87 char *descr; /* description */
88 char *usage; /* usage */
89 int (*function) (struct job *, struct jobSet * jobList); /* function ptr */
92 /* Some function prototypes */
93 static int shell_cd(struct job *cmd, struct jobSet *junk);
94 static int shell_env(struct job *dummy, struct jobSet *junk);
95 static int shell_exit(struct job *cmd, struct jobSet *junk);
96 static int shell_fg_bg(struct job *cmd, struct jobSet *jobList);
97 static int shell_help(struct job *cmd, struct jobSet *junk);
98 static int shell_jobs(struct job *dummy, struct jobSet *jobList);
99 static int shell_pwd(struct job *dummy, struct jobSet *junk);
100 static int shell_export(struct job *cmd, struct jobSet *junk);
101 static int shell_source(struct job *cmd, struct jobSet *jobList);
102 static int shell_unset(struct job *cmd, struct jobSet *junk);
104 static void checkJobs(struct jobSet *jobList);
105 static int getCommand(FILE * source, char *command);
106 static int parseCommand(char **commandPtr, struct job *job, int *isBg);
107 static int setupRedirections(struct childProgram *prog);
108 static int runCommand(struct job newJob, struct jobSet *jobList, int inBg);
109 static int busy_loop(FILE * input);
112 /* Table of built-in functions */
113 static struct builtInCommand bltins[] = {
114 {"bg", "Resume a job in the background", "bg [%%job]", shell_fg_bg},
115 {"cd", "Change working directory", "cd [dir]", shell_cd},
116 {"env", "Print all environment variables", "env", shell_env},
117 {"exit", "Exit from shell()", "exit", shell_exit},
118 {"fg", "Bring job into the foreground", "fg [%%job]", shell_fg_bg},
119 {"jobs", "Lists the active jobs", "jobs", shell_jobs},
120 {"pwd", "Print current directory", "pwd", shell_pwd},
121 {"export", "Set environment variable", "export [VAR=value]", shell_export},
122 {"unset", "Unset environment variable", "unset VAR", shell_unset},
124 {".", "Source-in and run commands in a file", ". filename", shell_source},
125 {"help", "List shell built-in commands", "help", shell_help},
126 {NULL, NULL, NULL, NULL}
129 static const char shell_usage[] =
131 "sh [FILE]...\n\n" "The BusyBox command interpreter (shell).\n\n";
134 static char cwd[1024];
135 static char *prompt = "# ";
137 #ifdef BB_FEATURE_SH_COMMAND_EDITING
138 void win_changed(int sig)
140 struct winsize win = { 0, 0 };
141 ioctl(0, TIOCGWINSZ, &win);
142 if (win.ws_col > 0) {
143 cmdedit_setwidth( win.ws_col - 1);
149 /* built-in 'cd <path>' handler */
150 static int shell_cd(struct job *cmd, struct jobSet *junk)
154 if (!cmd->progs[0].argv[1] == 1)
155 newdir = getenv("HOME");
157 newdir = cmd->progs[0].argv[1];
159 printf("cd: %s: %s\n", newdir, strerror(errno));
162 getcwd(cwd, sizeof(cwd));
167 /* built-in 'env' handler */
168 static int shell_env(struct job *dummy, struct jobSet *junk)
172 for (e = environ; *e; e++) {
173 fprintf(stdout, "%s\n", *e);
178 /* built-in 'exit' handler */
179 static int shell_exit(struct job *cmd, struct jobSet *junk)
181 if (!cmd->progs[0].argv[1] == 1)
185 exit(atoi(cmd->progs[0].argv[1]));
188 /* built-in 'fg' and 'bg' handler */
189 static int shell_fg_bg(struct job *cmd, struct jobSet *jobList)
192 struct job *job=NULL;
194 if (!jobList->head) {
195 if (!cmd->progs[0].argv[1] || cmd->progs[0].argv[2]) {
196 fprintf(stderr, "%s: exactly one argument is expected\n",
197 cmd->progs[0].argv[0]);
200 if (sscanf(cmd->progs[0].argv[1], "%%%d", &jobNum) != 1) {
201 fprintf(stderr, "%s: bad argument '%s'\n",
202 cmd->progs[0].argv[0], cmd->progs[0].argv[1]);
204 for (job = jobList->head; job; job = job->next) {
205 if (job->jobId == jobNum) {
215 fprintf(stderr, "%s: unknown job %d\n",
216 cmd->progs[0].argv[0], jobNum);
220 if (*cmd->progs[0].argv[0] == 'f') {
221 /* Make this job the foreground job */
222 if (tcsetpgrp(0, job->pgrp))
227 /* Restart the processes in the job */
228 for (i = 0; i < job->numProgs; i++)
229 job->progs[i].isStopped = 0;
231 kill(-job->pgrp, SIGCONT);
233 job->stoppedProgs = 0;
238 /* built-in 'help' handler */
239 static int shell_help(struct job *cmd, struct jobSet *junk)
241 struct builtInCommand *x;
243 fprintf(stdout, "\nBuilt-in commands:\n");
244 fprintf(stdout, "-------------------\n");
245 for (x = bltins; x->cmd; x++) {
246 fprintf(stdout, "%s\t%s\n", x->cmd, x->descr);
248 fprintf(stdout, "\n\n");
252 /* built-in 'jobs' handler */
253 static int shell_jobs(struct job *dummy, struct jobSet *jobList)
258 for (job = jobList->head; job; job = job->next) {
259 if (job->runningProgs == job->stoppedProgs)
260 statusString = "Stopped";
262 statusString = "Running";
264 printf(JOB_STATUS_FORMAT, job->jobId, statusString, job->text);
270 /* built-in 'pwd' handler */
271 static int shell_pwd(struct job *dummy, struct jobSet *junk)
273 getcwd(cwd, sizeof(cwd));
274 fprintf(stdout, "%s\n", cwd);
278 /* built-in 'export VAR=value' handler */
279 static int shell_export(struct job *cmd, struct jobSet *junk)
283 if (!cmd->progs[0].argv[1] == 1) {
284 return (shell_env(cmd, junk));
286 res = putenv(cmd->progs[0].argv[1]);
288 fprintf(stdout, "export: %s\n", strerror(errno));
292 /* Built-in '.' handler (read-in and execute commands from file) */
293 static int shell_source(struct job *cmd, struct jobSet *junk)
298 if (!cmd->progs[0].argv[1] == 1)
301 input = fopen(cmd->progs[0].argv[1], "r");
303 fprintf(stdout, "Couldn't open file '%s'\n",
304 cmd->progs[0].argv[1]);
308 /* Now run the file */
309 status = busy_loop(input);
313 /* built-in 'unset VAR' handler */
314 static int shell_unset(struct job *cmd, struct jobSet *junk)
316 if (!cmd->progs[0].argv[1] == 1) {
317 fprintf(stdout, "unset: parameter required.\n");
320 unsetenv(cmd->progs[0].argv[1]);
324 /* free up all memory from a job */
325 static void freeJob(struct job *cmd)
329 for (i = 0; i < cmd->numProgs; i++) {
330 free(cmd->progs[i].argv);
331 if (cmd->progs[i].redirections)
332 free(cmd->progs[i].redirections);
333 if (cmd->progs[i].freeGlob)
334 globfree(&cmd->progs[i].globResult);
342 /* remove a job from the jobList */
343 static void removeJob(struct jobSet *jobList, struct job *job)
348 if (job == jobList->head) {
349 jobList->head = job->next;
351 prevJob = jobList->head;
352 while (prevJob->next != job)
353 prevJob = prevJob->next;
354 prevJob->next = job->next;
360 /* Checks to see if any background processes have exited -- if they
361 have, figure out why and see if a job has completed */
362 static void checkJobs(struct jobSet *jobList)
369 while ((childpid = waitpid(-1, &status, WNOHANG | WUNTRACED)) > 0) {
370 for (job = jobList->head; job; job = job->next) {
372 while (progNum < job->numProgs &&
373 job->progs[progNum].pid != childpid) progNum++;
374 if (progNum < job->numProgs)
378 if (WIFEXITED(status) || WIFSIGNALED(status)) {
381 job->progs[progNum].pid = 0;
383 if (!job->runningProgs) {
384 printf(JOB_STATUS_FORMAT, job->jobId, "Done", job->text);
385 removeJob(jobList, job);
390 job->progs[progNum].isStopped = 1;
392 if (job->stoppedProgs == job->numProgs) {
393 printf(JOB_STATUS_FORMAT, job->jobId, "Stopped",
399 if (childpid == -1 && errno != ECHILD)
403 static int getCommand(FILE * source, char *command)
405 if (source == stdin) {
406 #ifdef BB_FEATURE_SH_COMMAND_EDITING
409 len=fprintf(stdout, "%s %s", cwd, prompt);
411 promptStr=(char*)malloc(sizeof(char)*(len+1));
412 sprintf(promptStr, "%s %s", cwd, prompt);
413 cmdedit_read_input(promptStr, command);
417 fprintf(stdout, "%s %s", cwd, prompt);
422 if (!fgets(command, BUFSIZ - 2, source)) {
428 /* remove trailing newline */
429 command[strlen(command) - 1] = '\0';
434 static void globLastArgument(struct childProgram *prog, int *argcPtr,
438 int argcAlloced = *argcAllocedPtr;
444 if (argc > 1) { /* cmd->globResult is already initialized */
446 i = prog->globResult.gl_pathc;
453 rc = glob(prog->argv[argc - 1], flags, NULL, &prog->globResult);
454 if (rc == GLOB_NOSPACE) {
455 fprintf(stderr, "out of space during glob operation\n");
457 } else if (rc == GLOB_NOMATCH ||
458 (!rc && (prog->globResult.gl_pathc - i) == 1 &&
459 !strcmp(prog->argv[argc - 1],
460 prog->globResult.gl_pathv[i]))) {
461 /* we need to remove whatever \ quoting is still present */
462 src = dst = prog->argv[argc - 1];
470 argcAlloced += (prog->globResult.gl_pathc - i);
472 realloc(prog->argv, argcAlloced * sizeof(*prog->argv));
473 memcpy(prog->argv + (argc - 1), prog->globResult.gl_pathv + i,
474 sizeof(*(prog->argv)) * (prog->globResult.gl_pathc - i));
475 argc += (prog->globResult.gl_pathc - i - 1);
478 *argcAllocedPtr = argcAlloced;
482 /* Return cmd->numProgs as 0 if no command is present (e.g. an empty
483 line). If a valid command is found, commandPtr is set to point to
484 the beginning of the next command (if the original command had more
485 then one job associated with it) or NULL if no more commands are
487 static int parseCommand(char **commandPtr, struct job *job, int *isBg)
490 char *returnCommand = NULL;
491 char *src, *buf, *chptr;
498 struct childProgram *prog;
500 /* skip leading white space */
501 while (**commandPtr && isspace(**commandPtr))
504 /* this handles empty lines or leading '#' characters */
505 if (!**commandPtr || (**commandPtr == '#')) {
513 job->progs = malloc(sizeof(*job->progs));
515 /* We set the argv elements to point inside of this string. The
516 memory is freed by freeJob().
518 Getting clean memory relieves us of the task of NULL
519 terminating things and makes the rest of this look a bit
520 cleaner (though it is, admittedly, a tad less efficient) */
521 job->cmdBuf = command = calloc(1, strlen(*commandPtr) + 1);
525 prog->numRedirections = 0;
526 prog->redirections = NULL;
531 prog->argv = malloc(sizeof(*prog->argv) * argvAlloced);
532 prog->argv[0] = job->cmdBuf;
536 while (*src && !done) {
543 fprintf(stderr, "character expected after \\\n");
548 /* in shell, "\'" should yield \' */
551 } else if (*src == '*' || *src == '?' || *src == '[' ||
552 *src == ']') *buf++ = '\\';
554 } else if (isspace(*src)) {
555 if (*prog->argv[argc]) {
557 /* +1 here leaves room for the NULL which ends argv */
558 if ((argc + 1) == argvAlloced) {
560 prog->argv = realloc(prog->argv,
561 sizeof(*prog->argv) *
564 prog->argv[argc] = buf;
566 globLastArgument(prog, &argc, &argvAlloced);
575 case '#': /* comment */
579 case '>': /* redirections */
581 i = prog->numRedirections++;
582 prog->redirections = realloc(prog->redirections,
583 sizeof(*prog->redirections) *
586 prog->redirections[i].fd = -1;
587 if (buf != prog->argv[argc]) {
588 /* the stuff before this character may be the file number
590 prog->redirections[i].fd =
591 strtol(prog->argv[argc], &chptr, 10);
593 if (*chptr && *prog->argv[argc]) {
595 globLastArgument(prog, &argc, &argvAlloced);
599 if (prog->redirections[i].fd == -1) {
601 prog->redirections[i].fd = 1;
603 prog->redirections[i].fd = 0;
608 prog->redirections[i].type =
609 REDIRECT_APPEND, src++;
611 prog->redirections[i].type = REDIRECT_OVERWRITE;
613 prog->redirections[i].type = REDIRECT_INPUT;
616 /* This isn't POSIX sh compliant. Oh well. */
618 while (isspace(*chptr))
622 fprintf(stderr, "file name expected after %c\n", *src);
627 prog->redirections[i].filename = buf;
628 while (*chptr && !isspace(*chptr))
631 src = chptr - 1; /* we src++ later */
632 prog->argv[argc] = ++buf;
636 /* finish this command */
637 if (*prog->argv[argc])
640 fprintf(stderr, "empty command in pipe\n");
644 prog->argv[argc] = NULL;
646 /* and start the next */
648 job->progs = realloc(job->progs,
649 sizeof(*job->progs) * job->numProgs);
650 prog = job->progs + (job->numProgs - 1);
651 prog->numRedirections = 0;
652 prog->redirections = NULL;
657 prog->argv = malloc(sizeof(*prog->argv) * argvAlloced);
658 prog->argv[0] = ++buf;
661 while (*src && isspace(*src))
665 fprintf(stderr, "empty command in pipe\n");
668 src--; /* we'll ++ it at the end of the loop */
672 case '&': /* background */
674 case ';': /* multiple commands */
676 returnCommand = *commandPtr + (src - *commandPtr) + 1;
683 fprintf(stderr, "character expected after \\\n");
686 if (*src == '*' || *src == '[' || *src == ']'
687 || *src == '?') *buf++ = '\\';
696 if (*prog->argv[argc]) {
698 globLastArgument(prog, &argc, &argvAlloced);
704 prog->argv[argc] = NULL;
706 if (!returnCommand) {
707 job->text = malloc(strlen(*commandPtr) + 1);
708 strcpy(job->text, *commandPtr);
710 /* This leaves any trailing spaces, which is a bit sloppy */
711 count = returnCommand - *commandPtr;
712 job->text = malloc(count + 1);
713 strncpy(job->text, *commandPtr, count);
714 job->text[count] = '\0';
717 *commandPtr = returnCommand;
722 static int runCommand(struct job newJob, struct jobSet *jobList, int inBg)
727 int pipefds[2]; /* pipefd[0] is for reading */
728 struct builtInCommand *x;
730 /* handle built-ins here -- we don't fork() so we can't background
732 for (x = bltins; x->cmd; x++) {
733 if (!strcmp(newJob.progs[0].argv[0], x->cmd)) {
734 return (x->function(&newJob, jobList));
738 nextin = 0, nextout = 1;
739 for (i = 0; i < newJob.numProgs; i++) {
740 if ((i + 1) < newJob.numProgs) {
742 nextout = pipefds[1];
747 if (!(newJob.progs[i].pid = fork())) {
748 signal(SIGTTOU, SIG_DFL);
760 /* explicit redirections override pipes */
761 setupRedirections(newJob.progs + i);
763 execvp(newJob.progs[i].argv[0], newJob.progs[i].argv);
764 fatalError("sh: %s: %s\n", newJob.progs[i].argv[0],
768 /* put our child in the process group whose leader is the
769 first process in this pipe */
770 setpgid(newJob.progs[i].pid, newJob.progs[0].pid);
777 /* If there isn't another process, nextin is garbage
778 but it doesn't matter */
782 newJob.pgrp = newJob.progs[0].pid;
784 /* find the ID for the job to use */
786 for (job = jobList->head; job; job = job->next)
787 if (job->jobId >= newJob.jobId)
788 newJob.jobId = job->jobId + 1;
790 /* add the job to the list of running jobs */
791 if (!jobList->head) {
792 job = jobList->head = malloc(sizeof(*job));
794 for (job = jobList->head; job->next; job = job->next);
795 job->next = malloc(sizeof(*job));
801 job->runningProgs = job->numProgs;
802 job->stoppedProgs = 0;
805 /* we don't wait for background jobs to return -- append it
806 to the list of backgrounded jobs and leave it alone */
807 printf("[%d] %d\n", job->jobId,
808 newJob.progs[newJob.numProgs - 1].pid);
812 /* move the new process group into the foreground */
813 if (tcsetpgrp(0, newJob.pgrp))
820 static int setupRedirections(struct childProgram *prog)
825 struct redirectionSpecifier *redir = prog->redirections;
827 for (i = 0; i < prog->numRedirections; i++, redir++) {
828 switch (redir->type) {
832 case REDIRECT_OVERWRITE:
833 mode = O_RDWR | O_CREAT | O_TRUNC;
835 case REDIRECT_APPEND:
836 mode = O_RDWR | O_CREAT | O_APPEND;
840 openfd = open(redir->filename, mode, 0666);
842 /* this could get lost if stderr has been redirected, but
843 bash and ash both lose it as well (though zsh doesn't!) */
844 fprintf(stderr, "error opening %s: %s\n", redir->filename,
849 if (openfd != redir->fd) {
850 dup2(openfd, redir->fd);
859 static int busy_loop(FILE * input)
862 char *nextCommand = NULL;
863 struct jobSet jobList = { NULL, NULL };
869 command = (char *) calloc(BUFSIZ, sizeof(char));
871 /* don't pay any attention to this signal; it just confuses
872 things and isn't really meant for shells anyway */
873 signal(SIGTTOU, SIG_IGN);
877 /* no job is in the foreground */
879 /* see if any background processes have exited */
883 if (getCommand(input, command))
885 nextCommand = command;
888 if (!parseCommand(&nextCommand, &newJob, &inBg) &&
890 runCommand(newJob, &jobList, inBg);
893 /* a job is running in the foreground; wait for it */
895 while (!jobList.fg->progs[i].pid ||
896 jobList.fg->progs[i].isStopped) i++;
898 waitpid(jobList.fg->progs[i].pid, &status, WUNTRACED);
900 if (WIFEXITED(status) || WIFSIGNALED(status)) {
901 /* the child exited */
902 jobList.fg->runningProgs--;
903 jobList.fg->progs[i].pid = 0;
905 if (!jobList.fg->runningProgs) {
908 removeJob(&jobList, jobList.fg);
911 /* move the shell to the foreground */
912 if (tcsetpgrp(0, getpid()))
916 /* the child was stopped */
917 jobList.fg->stoppedProgs++;
918 jobList.fg->progs[i].isStopped = 1;
920 if (jobList.fg->stoppedProgs == jobList.fg->runningProgs) {
921 printf("\n" JOB_STATUS_FORMAT, jobList.fg->jobId,
922 "Stopped", jobList.fg->text);
928 /* move the shell to the foreground */
929 if (tcsetpgrp(0, getpid()))
940 int shell_main(int argc, char **argv)
947 /* initialize the cwd */
948 getcwd(cwd, sizeof(cwd));
950 #ifdef BB_FEATURE_SH_COMMAND_EDITING
951 signal(SIGWINCH, win_changed);
955 //if (argv[0] && argv[0][0] == '-') {
956 // shell_source("/etc/profile");
960 fprintf(stdout, "\n\nBusyBox v%s (%s) Built-in shell\n", BB_VER, BB_BT);
961 fprintf(stdout, "Enter 'help' for a list of built-in commands.\n\n");
964 usage("sh\n\nlash -- the BusyBox LAme SHell (command interpreter)\n");
966 input = fopen(argv[1], "r");
968 fatalError("sh: Couldn't open file '%s': %s\n", argv[1],
973 return (busy_loop(input));