blob: 05dab925442a96660c6c60e24f3e155bfeb6f7be [file] [log] [blame]
Erik Andersen3522eb12000-03-12 23:49:18 +00001/* vi: set sw=4 ts=4: */
2/*
Erik Andersen6acaa402000-03-26 14:03:20 +00003 * lash -- the BusyBox Lame-Ass SHell
Erik Andersen3522eb12000-03-12 23:49:18 +00004 *
5 * Copyright (C) 2000 by Lineo, inc.
6 * Written by Erik Andersen <andersen@lineo.com>, <andersee@debian.org>
7 *
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."
11 *
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.
16 *
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.
21 *
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
25 *
26 */
27
28#include "internal.h"
29#include <stdio.h>
30#include <stdlib.h>
31#include <ctype.h>
32#include <errno.h>
33#include <fcntl.h>
34#include <glob.h>
35#include <signal.h>
36#include <string.h>
37#include <sys/ioctl.h>
38#include <sys/wait.h>
39#include <unistd.h>
Erik Andersenf0657d32000-04-12 17:49:52 +000040#ifdef BB_FEATURE_SH_COMMAND_EDITING
41#include "cmdedit.h"
42#endif
Erik Andersen3522eb12000-03-12 23:49:18 +000043
44
Erik Andersen3522eb12000-03-12 23:49:18 +000045#define JOB_STATUS_FORMAT "[%d] %-22s %.40s\n"
46
Erik Andersend75af992000-03-16 08:09:09 +000047
48enum redirectionType { REDIRECT_INPUT, REDIRECT_OVERWRITE,
Erik Andersen161220c2000-03-16 08:12:48 +000049 REDIRECT_APPEND
50};
Erik Andersen3522eb12000-03-12 23:49:18 +000051
52struct jobSet {
Erik Andersen161220c2000-03-16 08:12:48 +000053 struct job *head; /* head of list of running jobs */
54 struct job *fg; /* current foreground job */
Erik Andersen3522eb12000-03-12 23:49:18 +000055};
56
57struct redirectionSpecifier {
Erik Andersen161220c2000-03-16 08:12:48 +000058 enum redirectionType type; /* type of redirection */
59 int fd; /* file descriptor being redirected */
60 char *filename; /* file to redirect fd to */
Erik Andersen3522eb12000-03-12 23:49:18 +000061};
62
63struct childProgram {
Erik Andersen161220c2000-03-16 08:12:48 +000064 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? */
Erik Andersen3522eb12000-03-12 23:49:18 +000071};
72
73struct job {
Erik Andersen161220c2000-03-16 08:12:48 +000074 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 */
Erik Andersen3522eb12000-03-12 23:49:18 +000083};
84
85struct builtInCommand {
Erik Andersen161220c2000-03-16 08:12:48 +000086 char *cmd; /* name */
87 char *descr; /* description */
88 char *usage; /* usage */
89 int (*function) (struct job *, struct jobSet * jobList); /* function ptr */
Erik Andersen3522eb12000-03-12 23:49:18 +000090};
91
92/* Some function prototypes */
Erik Andersend75af992000-03-16 08:09:09 +000093static int shell_cd(struct job *cmd, struct jobSet *junk);
94static int shell_env(struct job *dummy, struct jobSet *junk);
95static int shell_exit(struct job *cmd, struct jobSet *junk);
96static int shell_fg_bg(struct job *cmd, struct jobSet *jobList);
97static int shell_help(struct job *cmd, struct jobSet *junk);
98static int shell_jobs(struct job *dummy, struct jobSet *jobList);
99static int shell_pwd(struct job *dummy, struct jobSet *junk);
Erik Andersen6273f652000-03-17 01:12:41 +0000100static int shell_export(struct job *cmd, struct jobSet *junk);
Erik Andersend75af992000-03-16 08:09:09 +0000101static int shell_source(struct job *cmd, struct jobSet *jobList);
102static int shell_unset(struct job *cmd, struct jobSet *junk);
Erik Andersen3522eb12000-03-12 23:49:18 +0000103
Erik Andersend75af992000-03-16 08:09:09 +0000104static void checkJobs(struct jobSet *jobList);
105static int getCommand(FILE * source, char *command);
106static int parseCommand(char **commandPtr, struct job *job, int *isBg);
107static int setupRedirections(struct childProgram *prog);
108static int runCommand(struct job newJob, struct jobSet *jobList, int inBg);
Erik Andersen3522eb12000-03-12 23:49:18 +0000109static int busy_loop(FILE * input);
110
Erik Andersend75af992000-03-16 08:09:09 +0000111
Erik Andersen3522eb12000-03-12 23:49:18 +0000112/* Table of built-in functions */
113static struct builtInCommand bltins[] = {
Erik Andersen161220c2000-03-16 08:12:48 +0000114 {"bg", "Resume a job in the background", "bg [%%job]", shell_fg_bg},
115 {"cd", "Change working directory", "cd [dir]", shell_cd},
Erik Andersen161220c2000-03-16 08:12:48 +0000116 {"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},
Erik Andersen6273f652000-03-17 01:12:41 +0000121 {"export", "Set environment variable", "export [VAR=value]", shell_export},
Erik Andersen161220c2000-03-16 08:12:48 +0000122 {"unset", "Unset environment variable", "unset VAR", shell_unset},
Erik Andersenc7c634b2000-03-19 05:28:55 +0000123 {".", "Source-in and run commands in a file", ". filename", shell_source},
Erik Andersen161220c2000-03-16 08:12:48 +0000124 {"help", "List shell built-in commands", "help", shell_help},
125 {NULL, NULL, NULL, NULL}
Erik Andersen3522eb12000-03-12 23:49:18 +0000126};
127
128static const char shell_usage[] =
Erik Andersen161220c2000-03-16 08:12:48 +0000129
Erik Andersen7ab9c7e2000-05-12 19:41:47 +0000130 "sh [FILE]...\n"
131#ifndef BB_FEATURE_TRIVIAL_HELP
132 "\nlash: The BusyBox command interpreter (shell).\n\n"
133#endif
134 ;
Erik Andersen3522eb12000-03-12 23:49:18 +0000135
136static char cwd[1024];
137static char *prompt = "# ";
138
Erik Andersenf0657d32000-04-12 17:49:52 +0000139#ifdef BB_FEATURE_SH_COMMAND_EDITING
140void win_changed(int sig)
141{
142 struct winsize win = { 0, 0 };
143 ioctl(0, TIOCGWINSZ, &win);
144 if (win.ws_col > 0) {
145 cmdedit_setwidth( win.ws_col - 1);
146 }
147}
148#endif
Erik Andersen3522eb12000-03-12 23:49:18 +0000149
Erik Andersen3522eb12000-03-12 23:49:18 +0000150
Erik Andersend75af992000-03-16 08:09:09 +0000151/* built-in 'cd <path>' handler */
152static int shell_cd(struct job *cmd, struct jobSet *junk)
153{
Erik Andersen161220c2000-03-16 08:12:48 +0000154 char *newdir;
Erik Andersend75af992000-03-16 08:09:09 +0000155
Erik Andersen161220c2000-03-16 08:12:48 +0000156 if (!cmd->progs[0].argv[1] == 1)
157 newdir = getenv("HOME");
158 else
159 newdir = cmd->progs[0].argv[1];
160 if (chdir(newdir)) {
161 printf("cd: %s: %s\n", newdir, strerror(errno));
162 return FALSE;
163 }
164 getcwd(cwd, sizeof(cwd));
165
166 return TRUE;
Erik Andersen3522eb12000-03-12 23:49:18 +0000167}
168
169/* built-in 'env' handler */
Erik Andersend75af992000-03-16 08:09:09 +0000170static int shell_env(struct job *dummy, struct jobSet *junk)
Erik Andersen3522eb12000-03-12 23:49:18 +0000171{
Erik Andersen161220c2000-03-16 08:12:48 +0000172 char **e;
Erik Andersen3522eb12000-03-12 23:49:18 +0000173
Erik Andersen161220c2000-03-16 08:12:48 +0000174 for (e = environ; *e; e++) {
175 fprintf(stdout, "%s\n", *e);
176 }
177 return (0);
Erik Andersen3522eb12000-03-12 23:49:18 +0000178}
179
180/* built-in 'exit' handler */
Erik Andersend75af992000-03-16 08:09:09 +0000181static int shell_exit(struct job *cmd, struct jobSet *junk)
Erik Andersen3522eb12000-03-12 23:49:18 +0000182{
Erik Andersen161220c2000-03-16 08:12:48 +0000183 if (!cmd->progs[0].argv[1] == 1)
184 exit TRUE;
185
186 else
187 exit(atoi(cmd->progs[0].argv[1]));
Erik Andersen3522eb12000-03-12 23:49:18 +0000188}
189
190/* built-in 'fg' and 'bg' handler */
Erik Andersend75af992000-03-16 08:09:09 +0000191static int shell_fg_bg(struct job *cmd, struct jobSet *jobList)
Erik Andersen3522eb12000-03-12 23:49:18 +0000192{
Erik Andersen161220c2000-03-16 08:12:48 +0000193 int i, jobNum;
Erik Andersen6273f652000-03-17 01:12:41 +0000194 struct job *job=NULL;
Erik Andersen3522eb12000-03-12 23:49:18 +0000195
Erik Andersen161220c2000-03-16 08:12:48 +0000196 if (!jobList->head) {
197 if (!cmd->progs[0].argv[1] || cmd->progs[0].argv[2]) {
198 fprintf(stderr, "%s: exactly one argument is expected\n",
199 cmd->progs[0].argv[0]);
200 return FALSE;
201 }
202 if (sscanf(cmd->progs[0].argv[1], "%%%d", &jobNum) != 1) {
203 fprintf(stderr, "%s: bad argument '%s'\n",
204 cmd->progs[0].argv[0], cmd->progs[0].argv[1]);
205 return FALSE;
206 for (job = jobList->head; job; job = job->next) {
207 if (job->jobId == jobNum) {
208 break;
209 }
210 }
211 }
212 } else {
213 job = jobList->head;
Erik Andersend75af992000-03-16 08:09:09 +0000214 }
Erik Andersen161220c2000-03-16 08:12:48 +0000215
216 if (!job) {
217 fprintf(stderr, "%s: unknown job %d\n",
218 cmd->progs[0].argv[0], jobNum);
219 return FALSE;
Erik Andersend75af992000-03-16 08:09:09 +0000220 }
Erik Andersen3522eb12000-03-12 23:49:18 +0000221
Erik Andersen161220c2000-03-16 08:12:48 +0000222 if (*cmd->progs[0].argv[0] == 'f') {
223 /* Make this job the foreground job */
224 if (tcsetpgrp(0, job->pgrp))
225 perror("tcsetpgrp");
226 jobList->fg = job;
227 }
Erik Andersen3522eb12000-03-12 23:49:18 +0000228
Erik Andersen161220c2000-03-16 08:12:48 +0000229 /* Restart the processes in the job */
230 for (i = 0; i < job->numProgs; i++)
231 job->progs[i].isStopped = 0;
Erik Andersen3522eb12000-03-12 23:49:18 +0000232
Erik Andersen161220c2000-03-16 08:12:48 +0000233 kill(-job->pgrp, SIGCONT);
Erik Andersen3522eb12000-03-12 23:49:18 +0000234
Erik Andersen161220c2000-03-16 08:12:48 +0000235 job->stoppedProgs = 0;
Erik Andersen3522eb12000-03-12 23:49:18 +0000236
Erik Andersen161220c2000-03-16 08:12:48 +0000237 return TRUE;
Erik Andersen3522eb12000-03-12 23:49:18 +0000238}
239
240/* built-in 'help' handler */
Erik Andersend75af992000-03-16 08:09:09 +0000241static int shell_help(struct job *cmd, struct jobSet *junk)
Erik Andersen3522eb12000-03-12 23:49:18 +0000242{
Erik Andersen161220c2000-03-16 08:12:48 +0000243 struct builtInCommand *x;
Erik Andersen3522eb12000-03-12 23:49:18 +0000244
Erik Andersen161220c2000-03-16 08:12:48 +0000245 fprintf(stdout, "\nBuilt-in commands:\n");
246 fprintf(stdout, "-------------------\n");
247 for (x = bltins; x->cmd; x++) {
248 fprintf(stdout, "%s\t%s\n", x->cmd, x->descr);
249 }
250 fprintf(stdout, "\n\n");
251 return TRUE;
Erik Andersen3522eb12000-03-12 23:49:18 +0000252}
253
254/* built-in 'jobs' handler */
Erik Andersend75af992000-03-16 08:09:09 +0000255static int shell_jobs(struct job *dummy, struct jobSet *jobList)
Erik Andersen3522eb12000-03-12 23:49:18 +0000256{
Erik Andersen161220c2000-03-16 08:12:48 +0000257 struct job *job;
258 char *statusString;
Erik Andersen3522eb12000-03-12 23:49:18 +0000259
Erik Andersen161220c2000-03-16 08:12:48 +0000260 for (job = jobList->head; job; job = job->next) {
261 if (job->runningProgs == job->stoppedProgs)
262 statusString = "Stopped";
263 else
264 statusString = "Running";
265
266 printf(JOB_STATUS_FORMAT, job->jobId, statusString, job->text);
267 }
268 return TRUE;
Erik Andersen3522eb12000-03-12 23:49:18 +0000269}
270
271
272/* built-in 'pwd' handler */
Erik Andersend75af992000-03-16 08:09:09 +0000273static int shell_pwd(struct job *dummy, struct jobSet *junk)
Erik Andersen3522eb12000-03-12 23:49:18 +0000274{
Erik Andersen161220c2000-03-16 08:12:48 +0000275 getcwd(cwd, sizeof(cwd));
276 fprintf(stdout, "%s\n", cwd);
277 return TRUE;
Erik Andersen3522eb12000-03-12 23:49:18 +0000278}
279
Erik Andersen6273f652000-03-17 01:12:41 +0000280/* built-in 'export VAR=value' handler */
281static int shell_export(struct job *cmd, struct jobSet *junk)
Erik Andersen3522eb12000-03-12 23:49:18 +0000282{
Erik Andersen161220c2000-03-16 08:12:48 +0000283 int res;
Erik Andersen3522eb12000-03-12 23:49:18 +0000284
Erik Andersen161220c2000-03-16 08:12:48 +0000285 if (!cmd->progs[0].argv[1] == 1) {
286 return (shell_env(cmd, junk));
287 }
288 res = putenv(cmd->progs[0].argv[1]);
289 if (res)
Erik Andersen6273f652000-03-17 01:12:41 +0000290 fprintf(stdout, "export: %s\n", strerror(errno));
Erik Andersen161220c2000-03-16 08:12:48 +0000291 return (res);
Erik Andersen3522eb12000-03-12 23:49:18 +0000292}
293
294/* Built-in '.' handler (read-in and execute commands from file) */
Erik Andersend75af992000-03-16 08:09:09 +0000295static int shell_source(struct job *cmd, struct jobSet *junk)
Erik Andersen3522eb12000-03-12 23:49:18 +0000296{
Erik Andersen161220c2000-03-16 08:12:48 +0000297 FILE *input;
298 int status;
Erik Andersen3522eb12000-03-12 23:49:18 +0000299
Erik Andersen161220c2000-03-16 08:12:48 +0000300 if (!cmd->progs[0].argv[1] == 1)
301 return FALSE;
Erik Andersen3522eb12000-03-12 23:49:18 +0000302
Erik Andersen161220c2000-03-16 08:12:48 +0000303 input = fopen(cmd->progs[0].argv[1], "r");
304 if (!input) {
305 fprintf(stdout, "Couldn't open file '%s'\n",
306 cmd->progs[0].argv[1]);
307 return FALSE;
308 }
Erik Andersend75af992000-03-16 08:09:09 +0000309
Erik Andersen161220c2000-03-16 08:12:48 +0000310 /* Now run the file */
311 status = busy_loop(input);
312 return (status);
Erik Andersen3522eb12000-03-12 23:49:18 +0000313}
314
315/* built-in 'unset VAR' handler */
Erik Andersend75af992000-03-16 08:09:09 +0000316static int shell_unset(struct job *cmd, struct jobSet *junk)
Erik Andersen3522eb12000-03-12 23:49:18 +0000317{
Erik Andersen161220c2000-03-16 08:12:48 +0000318 if (!cmd->progs[0].argv[1] == 1) {
319 fprintf(stdout, "unset: parameter required.\n");
320 return FALSE;
321 }
322 unsetenv(cmd->progs[0].argv[1]);
323 return TRUE;
Erik Andersen3522eb12000-03-12 23:49:18 +0000324}
325
326/* free up all memory from a job */
Erik Andersend75af992000-03-16 08:09:09 +0000327static void freeJob(struct job *cmd)
Erik Andersen3522eb12000-03-12 23:49:18 +0000328{
Erik Andersen161220c2000-03-16 08:12:48 +0000329 int i;
Erik Andersen3522eb12000-03-12 23:49:18 +0000330
Erik Andersen161220c2000-03-16 08:12:48 +0000331 for (i = 0; i < cmd->numProgs; i++) {
332 free(cmd->progs[i].argv);
333 if (cmd->progs[i].redirections)
334 free(cmd->progs[i].redirections);
335 if (cmd->progs[i].freeGlob)
336 globfree(&cmd->progs[i].globResult);
337 }
338 free(cmd->progs);
339 if (cmd->text)
340 free(cmd->text);
341 free(cmd->cmdBuf);
Erik Andersen3522eb12000-03-12 23:49:18 +0000342}
343
344/* remove a job from the jobList */
Erik Andersend75af992000-03-16 08:09:09 +0000345static void removeJob(struct jobSet *jobList, struct job *job)
Erik Andersen3522eb12000-03-12 23:49:18 +0000346{
Erik Andersen161220c2000-03-16 08:12:48 +0000347 struct job *prevJob;
Erik Andersen3522eb12000-03-12 23:49:18 +0000348
Erik Andersen161220c2000-03-16 08:12:48 +0000349 freeJob(job);
350 if (job == jobList->head) {
351 jobList->head = job->next;
352 } else {
353 prevJob = jobList->head;
354 while (prevJob->next != job)
355 prevJob = prevJob->next;
356 prevJob->next = job->next;
357 }
Erik Andersen3522eb12000-03-12 23:49:18 +0000358
Erik Andersen161220c2000-03-16 08:12:48 +0000359 free(job);
Erik Andersen3522eb12000-03-12 23:49:18 +0000360}
361
362/* Checks to see if any background processes have exited -- if they
363 have, figure out why and see if a job has completed */
Erik Andersend75af992000-03-16 08:09:09 +0000364static void checkJobs(struct jobSet *jobList)
Erik Andersen3522eb12000-03-12 23:49:18 +0000365{
Erik Andersen161220c2000-03-16 08:12:48 +0000366 struct job *job;
367 pid_t childpid;
368 int status;
369 int progNum = 0;
Erik Andersend75af992000-03-16 08:09:09 +0000370
Erik Andersen161220c2000-03-16 08:12:48 +0000371 while ((childpid = waitpid(-1, &status, WNOHANG | WUNTRACED)) > 0) {
372 for (job = jobList->head; job; job = job->next) {
373 progNum = 0;
374 while (progNum < job->numProgs &&
375 job->progs[progNum].pid != childpid) progNum++;
376 if (progNum < job->numProgs)
377 break;
378 }
379
380 if (WIFEXITED(status) || WIFSIGNALED(status)) {
381 /* child exited */
382 job->runningProgs--;
383 job->progs[progNum].pid = 0;
384
385 if (!job->runningProgs) {
386 printf(JOB_STATUS_FORMAT, job->jobId, "Done", job->text);
387 removeJob(jobList, job);
388 }
389 } else {
390 /* child stopped */
391 job->stoppedProgs++;
392 job->progs[progNum].isStopped = 1;
393
394 if (job->stoppedProgs == job->numProgs) {
395 printf(JOB_STATUS_FORMAT, job->jobId, "Stopped",
396 job->text);
397 }
398 }
Erik Andersend75af992000-03-16 08:09:09 +0000399 }
Erik Andersen3522eb12000-03-12 23:49:18 +0000400
Erik Andersen161220c2000-03-16 08:12:48 +0000401 if (childpid == -1 && errno != ECHILD)
402 perror("waitpid");
Erik Andersen3522eb12000-03-12 23:49:18 +0000403}
404
Erik Andersend75af992000-03-16 08:09:09 +0000405static int getCommand(FILE * source, char *command)
Erik Andersen3522eb12000-03-12 23:49:18 +0000406{
Erik Andersen161220c2000-03-16 08:12:48 +0000407 if (source == stdin) {
Erik Andersend75af992000-03-16 08:09:09 +0000408#ifdef BB_FEATURE_SH_COMMAND_EDITING
Erik Andersenc7c634b2000-03-19 05:28:55 +0000409 int len;
410 char *promptStr;
Erik Andersend4bc1fc2000-04-05 05:19:03 +0000411 len=fprintf(stdout, "%s %s", cwd, prompt);
Erik Andersenc7c634b2000-03-19 05:28:55 +0000412 fflush(stdout);
413 promptStr=(char*)malloc(sizeof(char)*(len+1));
Erik Andersend4bc1fc2000-04-05 05:19:03 +0000414 sprintf(promptStr, "%s %s", cwd, prompt);
Erik Andersenf0657d32000-04-12 17:49:52 +0000415 cmdedit_read_input(promptStr, command);
Erik Andersenc7c634b2000-03-19 05:28:55 +0000416 free( promptStr);
Erik Andersen161220c2000-03-16 08:12:48 +0000417 return 0;
Erik Andersenc7c634b2000-03-19 05:28:55 +0000418#else
419 fprintf(stdout, "%s %s", cwd, prompt);
420 fflush(stdout);
Erik Andersend75af992000-03-16 08:09:09 +0000421#endif
Erik Andersen161220c2000-03-16 08:12:48 +0000422 }
Erik Andersen3522eb12000-03-12 23:49:18 +0000423
Erik Andersen161220c2000-03-16 08:12:48 +0000424 if (!fgets(command, BUFSIZ - 2, source)) {
425 if (source == stdin)
426 printf("\n");
427 return 1;
428 }
Erik Andersen3522eb12000-03-12 23:49:18 +0000429
Erik Andersen161220c2000-03-16 08:12:48 +0000430 /* remove trailing newline */
431 command[strlen(command) - 1] = '\0';
Erik Andersen3522eb12000-03-12 23:49:18 +0000432
Erik Andersen161220c2000-03-16 08:12:48 +0000433 return 0;
Erik Andersen3522eb12000-03-12 23:49:18 +0000434}
435
Erik Andersend75af992000-03-16 08:09:09 +0000436static void globLastArgument(struct childProgram *prog, int *argcPtr,
Erik Andersen161220c2000-03-16 08:12:48 +0000437 int *argcAllocedPtr)
Erik Andersen3522eb12000-03-12 23:49:18 +0000438{
Erik Andersen161220c2000-03-16 08:12:48 +0000439 int argc = *argcPtr;
440 int argcAlloced = *argcAllocedPtr;
441 int rc;
442 int flags;
443 int i;
444 char *src, *dst;
Erik Andersen3522eb12000-03-12 23:49:18 +0000445
Erik Andersen161220c2000-03-16 08:12:48 +0000446 if (argc > 1) { /* cmd->globResult is already initialized */
447 flags = GLOB_APPEND;
448 i = prog->globResult.gl_pathc;
449 } else {
450 prog->freeGlob = 1;
451 flags = 0;
452 i = 0;
Erik Andersend75af992000-03-16 08:09:09 +0000453 }
Erik Andersen3522eb12000-03-12 23:49:18 +0000454
Erik Andersen161220c2000-03-16 08:12:48 +0000455 rc = glob(prog->argv[argc - 1], flags, NULL, &prog->globResult);
456 if (rc == GLOB_NOSPACE) {
457 fprintf(stderr, "out of space during glob operation\n");
458 return;
459 } else if (rc == GLOB_NOMATCH ||
460 (!rc && (prog->globResult.gl_pathc - i) == 1 &&
461 !strcmp(prog->argv[argc - 1],
462 prog->globResult.gl_pathv[i]))) {
463 /* we need to remove whatever \ quoting is still present */
464 src = dst = prog->argv[argc - 1];
465 while (*src) {
466 if (*src != '\\')
467 *dst++ = *src;
468 src++;
469 }
470 *dst = '\0';
471 } else if (!rc) {
472 argcAlloced += (prog->globResult.gl_pathc - i);
473 prog->argv =
474 realloc(prog->argv, argcAlloced * sizeof(*prog->argv));
475 memcpy(prog->argv + (argc - 1), prog->globResult.gl_pathv + i,
476 sizeof(*(prog->argv)) * (prog->globResult.gl_pathc - i));
477 argc += (prog->globResult.gl_pathc - i - 1);
478 }
479
480 *argcAllocedPtr = argcAlloced;
481 *argcPtr = argc;
Erik Andersen3522eb12000-03-12 23:49:18 +0000482}
483
484/* Return cmd->numProgs as 0 if no command is present (e.g. an empty
485 line). If a valid command is found, commandPtr is set to point to
486 the beginning of the next command (if the original command had more
487 then one job associated with it) or NULL if no more commands are
488 present. */
Erik Andersend75af992000-03-16 08:09:09 +0000489static int parseCommand(char **commandPtr, struct job *job, int *isBg)
Erik Andersen3522eb12000-03-12 23:49:18 +0000490{
Erik Andersen161220c2000-03-16 08:12:48 +0000491 char *command;
492 char *returnCommand = NULL;
493 char *src, *buf, *chptr;
494 int argc = 0;
495 int done = 0;
496 int argvAlloced;
497 int i;
498 char quote = '\0';
499 int count;
500 struct childProgram *prog;
Erik Andersen3522eb12000-03-12 23:49:18 +0000501
Erik Andersen161220c2000-03-16 08:12:48 +0000502 /* skip leading white space */
503 while (**commandPtr && isspace(**commandPtr))
504 (*commandPtr)++;
Erik Andersen3522eb12000-03-12 23:49:18 +0000505
Erik Andersen161220c2000-03-16 08:12:48 +0000506 /* this handles empty lines or leading '#' characters */
507 if (!**commandPtr || (**commandPtr == '#')) {
508 job->numProgs = 0;
509 *commandPtr = NULL;
510 return 0;
511 }
Erik Andersen3522eb12000-03-12 23:49:18 +0000512
Erik Andersen161220c2000-03-16 08:12:48 +0000513 *isBg = 0;
514 job->numProgs = 1;
515 job->progs = malloc(sizeof(*job->progs));
Erik Andersen3522eb12000-03-12 23:49:18 +0000516
Erik Andersen161220c2000-03-16 08:12:48 +0000517 /* We set the argv elements to point inside of this string. The
518 memory is freed by freeJob().
Erik Andersen3522eb12000-03-12 23:49:18 +0000519
Erik Andersen161220c2000-03-16 08:12:48 +0000520 Getting clean memory relieves us of the task of NULL
521 terminating things and makes the rest of this look a bit
522 cleaner (though it is, admittedly, a tad less efficient) */
523 job->cmdBuf = command = calloc(1, strlen(*commandPtr) + 1);
524 job->text = NULL;
Erik Andersen3522eb12000-03-12 23:49:18 +0000525
Erik Andersen161220c2000-03-16 08:12:48 +0000526 prog = job->progs;
527 prog->numRedirections = 0;
528 prog->redirections = NULL;
529 prog->freeGlob = 0;
530 prog->isStopped = 0;
Erik Andersen3522eb12000-03-12 23:49:18 +0000531
Erik Andersen161220c2000-03-16 08:12:48 +0000532 argvAlloced = 5;
533 prog->argv = malloc(sizeof(*prog->argv) * argvAlloced);
534 prog->argv[0] = job->cmdBuf;
Erik Andersen3522eb12000-03-12 23:49:18 +0000535
Erik Andersen161220c2000-03-16 08:12:48 +0000536 buf = command;
537 src = *commandPtr;
538 while (*src && !done) {
539 if (quote == *src) {
540 quote = '\0';
541 } else if (quote) {
542 if (*src == '\\') {
543 src++;
544 if (!*src) {
545 fprintf(stderr, "character expected after \\\n");
546 freeJob(job);
547 return 1;
548 }
549
550 /* in shell, "\'" should yield \' */
551 if (*src != quote)
552 *buf++ = '\\';
553 } else if (*src == '*' || *src == '?' || *src == '[' ||
554 *src == ']') *buf++ = '\\';
555 *buf++ = *src;
556 } else if (isspace(*src)) {
557 if (*prog->argv[argc]) {
558 buf++, argc++;
559 /* +1 here leaves room for the NULL which ends argv */
560 if ((argc + 1) == argvAlloced) {
561 argvAlloced += 5;
562 prog->argv = realloc(prog->argv,
563 sizeof(*prog->argv) *
564 argvAlloced);
565 }
566 prog->argv[argc] = buf;
567
568 globLastArgument(prog, &argc, &argvAlloced);
569 }
570 } else
571 switch (*src) {
572 case '"':
573 case '\'':
574 quote = *src;
575 break;
576
577 case '#': /* comment */
578 done = 1;
579 break;
580
581 case '>': /* redirections */
582 case '<':
583 i = prog->numRedirections++;
584 prog->redirections = realloc(prog->redirections,
585 sizeof(*prog->redirections) *
586 (i + 1));
587
588 prog->redirections[i].fd = -1;
589 if (buf != prog->argv[argc]) {
590 /* the stuff before this character may be the file number
591 being redirected */
592 prog->redirections[i].fd =
593 strtol(prog->argv[argc], &chptr, 10);
594
595 if (*chptr && *prog->argv[argc]) {
596 buf++, argc++;
597 globLastArgument(prog, &argc, &argvAlloced);
598 }
599 }
600
601 if (prog->redirections[i].fd == -1) {
602 if (*src == '>')
603 prog->redirections[i].fd = 1;
604 else
605 prog->redirections[i].fd = 0;
606 }
607
608 if (*src++ == '>') {
609 if (*src == '>')
610 prog->redirections[i].type =
611 REDIRECT_APPEND, src++;
612 else
613 prog->redirections[i].type = REDIRECT_OVERWRITE;
614 } else {
615 prog->redirections[i].type = REDIRECT_INPUT;
616 }
617
618 /* This isn't POSIX sh compliant. Oh well. */
619 chptr = src;
620 while (isspace(*chptr))
621 chptr++;
622
623 if (!*chptr) {
624 fprintf(stderr, "file name expected after %c\n", *src);
625 freeJob(job);
626 return 1;
627 }
628
629 prog->redirections[i].filename = buf;
630 while (*chptr && !isspace(*chptr))
631 *buf++ = *chptr++;
632
633 src = chptr - 1; /* we src++ later */
634 prog->argv[argc] = ++buf;
635 break;
636
637 case '|': /* pipe */
638 /* finish this command */
639 if (*prog->argv[argc])
640 argc++;
641 if (!argc) {
642 fprintf(stderr, "empty command in pipe\n");
643 freeJob(job);
644 return 1;
645 }
646 prog->argv[argc] = NULL;
647
648 /* and start the next */
649 job->numProgs++;
650 job->progs = realloc(job->progs,
651 sizeof(*job->progs) * job->numProgs);
652 prog = job->progs + (job->numProgs - 1);
653 prog->numRedirections = 0;
654 prog->redirections = NULL;
655 prog->freeGlob = 0;
656 argc = 0;
657
658 argvAlloced = 5;
659 prog->argv = malloc(sizeof(*prog->argv) * argvAlloced);
660 prog->argv[0] = ++buf;
661
662 src++;
663 while (*src && isspace(*src))
664 src++;
665
666 if (!*src) {
667 fprintf(stderr, "empty command in pipe\n");
668 return 1;
669 }
670 src--; /* we'll ++ it at the end of the loop */
671
672 break;
673
674 case '&': /* background */
675 *isBg = 1;
676 case ';': /* multiple commands */
677 done = 1;
678 returnCommand = *commandPtr + (src - *commandPtr) + 1;
679 break;
680
681 case '\\':
682 src++;
683 if (!*src) {
684 freeJob(job);
685 fprintf(stderr, "character expected after \\\n");
686 return 1;
687 }
688 if (*src == '*' || *src == '[' || *src == ']'
689 || *src == '?') *buf++ = '\\';
690 /* fallthrough */
691 default:
692 *buf++ = *src;
693 }
694
Erik Andersend75af992000-03-16 08:09:09 +0000695 src++;
Erik Andersen161220c2000-03-16 08:12:48 +0000696 }
Erik Andersen3522eb12000-03-12 23:49:18 +0000697
Erik Andersen161220c2000-03-16 08:12:48 +0000698 if (*prog->argv[argc]) {
699 argc++;
Erik Andersend75af992000-03-16 08:09:09 +0000700 globLastArgument(prog, &argc, &argvAlloced);
Erik Andersen161220c2000-03-16 08:12:48 +0000701 }
702 if (!argc) {
703 freeJob(job);
704 return 0;
705 }
706 prog->argv[argc] = NULL;
Erik Andersen3522eb12000-03-12 23:49:18 +0000707
Erik Andersen161220c2000-03-16 08:12:48 +0000708 if (!returnCommand) {
709 job->text = malloc(strlen(*commandPtr) + 1);
710 strcpy(job->text, *commandPtr);
711 } else {
712 /* This leaves any trailing spaces, which is a bit sloppy */
Erik Andersen161220c2000-03-16 08:12:48 +0000713 count = returnCommand - *commandPtr;
714 job->text = malloc(count + 1);
715 strncpy(job->text, *commandPtr, count);
716 job->text[count] = '\0';
717 }
Erik Andersen3522eb12000-03-12 23:49:18 +0000718
Erik Andersen161220c2000-03-16 08:12:48 +0000719 *commandPtr = returnCommand;
Erik Andersen3522eb12000-03-12 23:49:18 +0000720
Erik Andersend75af992000-03-16 08:09:09 +0000721 return 0;
Erik Andersen3522eb12000-03-12 23:49:18 +0000722}
723
Erik Andersenbcd61772000-05-13 06:33:19 +0000724
Erik Andersend75af992000-03-16 08:09:09 +0000725static int runCommand(struct job newJob, struct jobSet *jobList, int inBg)
Erik Andersen3522eb12000-03-12 23:49:18 +0000726{
Erik Andersen161220c2000-03-16 08:12:48 +0000727 struct job *job;
728 int i;
729 int nextin, nextout;
730 int pipefds[2]; /* pipefd[0] is for reading */
731 struct builtInCommand *x;
Erik Andersenbcd61772000-05-13 06:33:19 +0000732#ifdef BB_FEATURE_STANDALONE_SHELL
733 const struct BB_applet *a = applets;
734#endif
Erik Andersen3522eb12000-03-12 23:49:18 +0000735
Erik Andersen3522eb12000-03-12 23:49:18 +0000736
Erik Andersen161220c2000-03-16 08:12:48 +0000737 nextin = 0, nextout = 1;
738 for (i = 0; i < newJob.numProgs; i++) {
739 if ((i + 1) < newJob.numProgs) {
740 pipe(pipefds);
741 nextout = pipefds[1];
742 } else {
743 nextout = 1;
744 }
745
746 if (!(newJob.progs[i].pid = fork())) {
747 signal(SIGTTOU, SIG_DFL);
748
749 if (nextin != 0) {
750 dup2(nextin, 0);
751 close(nextin);
752 }
753
754 if (nextout != 1) {
755 dup2(nextout, 1);
756 close(nextout);
757 }
758
759 /* explicit redirections override pipes */
760 setupRedirections(newJob.progs + i);
761
Erik Andersenbcd61772000-05-13 06:33:19 +0000762 /* Match any built-ins here */
763 for (x = bltins; x->cmd; x++) {
764 if (!strcmp(newJob.progs[i].argv[0], x->cmd)) {
765 exit (x->function(&newJob, jobList));
766 }
767 }
768#ifdef BB_FEATURE_STANDALONE_SHELL
769 /* Handle busybox internals here */
770 while (a->name != 0) {
771 if (strcmp(newJob.progs[i].argv[0], a->name) == 0) {
772 int argc;
773 char** argv=newJob.progs[i].argv;
774 for(argc=0;*argv!=NULL, argv++, argc++);
775 exit((*(a->main)) (argc, newJob.progs[i].argv));
776 }
777 a++;
778 }
779#endif
780
Erik Andersen161220c2000-03-16 08:12:48 +0000781 execvp(newJob.progs[i].argv[0], newJob.progs[i].argv);
782 fatalError("sh: %s: %s\n", newJob.progs[i].argv[0],
783 strerror(errno));
784 }
785
786 /* put our child in the process group whose leader is the
787 first process in this pipe */
788 setpgid(newJob.progs[i].pid, newJob.progs[0].pid);
789
790 if (nextin != 0)
791 close(nextin);
792 if (nextout != 1)
793 close(nextout);
794
795 /* If there isn't another process, nextin is garbage
796 but it doesn't matter */
797 nextin = pipefds[0];
798 }
799
800 newJob.pgrp = newJob.progs[0].pid;
801
802 /* find the ID for the job to use */
803 newJob.jobId = 1;
804 for (job = jobList->head; job; job = job->next)
805 if (job->jobId >= newJob.jobId)
806 newJob.jobId = job->jobId + 1;
807
808 /* add the job to the list of running jobs */
809 if (!jobList->head) {
810 job = jobList->head = malloc(sizeof(*job));
Erik Andersend75af992000-03-16 08:09:09 +0000811 } else {
Erik Andersen161220c2000-03-16 08:12:48 +0000812 for (job = jobList->head; job->next; job = job->next);
813 job->next = malloc(sizeof(*job));
814 job = job->next;
Erik Andersend75af992000-03-16 08:09:09 +0000815 }
Erik Andersen3522eb12000-03-12 23:49:18 +0000816
Erik Andersen161220c2000-03-16 08:12:48 +0000817 *job = newJob;
818 job->next = NULL;
819 job->runningProgs = job->numProgs;
820 job->stoppedProgs = 0;
Erik Andersen3522eb12000-03-12 23:49:18 +0000821
Erik Andersen161220c2000-03-16 08:12:48 +0000822 if (inBg) {
823 /* we don't wait for background jobs to return -- append it
824 to the list of backgrounded jobs and leave it alone */
Erik Andersen161220c2000-03-16 08:12:48 +0000825 printf("[%d] %d\n", job->jobId,
826 newJob.progs[newJob.numProgs - 1].pid);
827 } else {
828 jobList->fg = job;
Erik Andersen3522eb12000-03-12 23:49:18 +0000829
Erik Andersen161220c2000-03-16 08:12:48 +0000830 /* move the new process group into the foreground */
Erik Andersen161220c2000-03-16 08:12:48 +0000831 if (tcsetpgrp(0, newJob.pgrp))
832 perror("tcsetpgrp");
Erik Andersend75af992000-03-16 08:09:09 +0000833 }
Erik Andersen3522eb12000-03-12 23:49:18 +0000834
Erik Andersen161220c2000-03-16 08:12:48 +0000835 return 0;
Erik Andersen3522eb12000-03-12 23:49:18 +0000836}
837
Erik Andersend75af992000-03-16 08:09:09 +0000838static int setupRedirections(struct childProgram *prog)
Erik Andersen3522eb12000-03-12 23:49:18 +0000839{
Erik Andersen161220c2000-03-16 08:12:48 +0000840 int i;
841 int openfd;
842 int mode = O_RDONLY;
843 struct redirectionSpecifier *redir = prog->redirections;
Erik Andersen3522eb12000-03-12 23:49:18 +0000844
Erik Andersen161220c2000-03-16 08:12:48 +0000845 for (i = 0; i < prog->numRedirections; i++, redir++) {
846 switch (redir->type) {
847 case REDIRECT_INPUT:
848 mode = O_RDONLY;
849 break;
850 case REDIRECT_OVERWRITE:
851 mode = O_RDWR | O_CREAT | O_TRUNC;
852 break;
853 case REDIRECT_APPEND:
854 mode = O_RDWR | O_CREAT | O_APPEND;
855 break;
856 }
857
858 openfd = open(redir->filename, mode, 0666);
859 if (openfd < 0) {
860 /* this could get lost if stderr has been redirected, but
861 bash and ash both lose it as well (though zsh doesn't!) */
862 fprintf(stderr, "error opening %s: %s\n", redir->filename,
863 strerror(errno));
864 return 1;
865 }
866
867 if (openfd != redir->fd) {
868 dup2(openfd, redir->fd);
869 close(openfd);
870 }
Erik Andersend75af992000-03-16 08:09:09 +0000871 }
Erik Andersen3522eb12000-03-12 23:49:18 +0000872
Erik Andersen161220c2000-03-16 08:12:48 +0000873 return 0;
Erik Andersen3522eb12000-03-12 23:49:18 +0000874}
875
876
Erik Andersend75af992000-03-16 08:09:09 +0000877static int busy_loop(FILE * input)
Erik Andersen3522eb12000-03-12 23:49:18 +0000878{
Erik Andersen161220c2000-03-16 08:12:48 +0000879 char *command;
880 char *nextCommand = NULL;
881 struct jobSet jobList = { NULL, NULL };
882 struct job newJob;
883 int i;
884 int status;
885 int inBg;
Erik Andersen3522eb12000-03-12 23:49:18 +0000886
Erik Andersen161220c2000-03-16 08:12:48 +0000887 command = (char *) calloc(BUFSIZ, sizeof(char));
Erik Andersend75af992000-03-16 08:09:09 +0000888
Erik Andersen161220c2000-03-16 08:12:48 +0000889 /* don't pay any attention to this signal; it just confuses
890 things and isn't really meant for shells anyway */
891 signal(SIGTTOU, SIG_IGN);
Erik Andersend75af992000-03-16 08:09:09 +0000892
Erik Andersen161220c2000-03-16 08:12:48 +0000893 while (1) {
Erik Andersend75af992000-03-16 08:09:09 +0000894 if (!jobList.fg) {
895 /* no job is in the foreground */
Erik Andersen3522eb12000-03-12 23:49:18 +0000896
Erik Andersend75af992000-03-16 08:09:09 +0000897 /* see if any background processes have exited */
898 checkJobs(&jobList);
Erik Andersen3522eb12000-03-12 23:49:18 +0000899
Erik Andersend75af992000-03-16 08:09:09 +0000900 if (!nextCommand) {
Erik Andersen161220c2000-03-16 08:12:48 +0000901 if (getCommand(input, command))
902 break;
903 nextCommand = command;
Erik Andersend75af992000-03-16 08:09:09 +0000904 }
Erik Andersen3522eb12000-03-12 23:49:18 +0000905
Erik Andersend75af992000-03-16 08:09:09 +0000906 if (!parseCommand(&nextCommand, &newJob, &inBg) &&
Erik Andersen161220c2000-03-16 08:12:48 +0000907 newJob.numProgs) {
908 runCommand(newJob, &jobList, inBg);
Erik Andersend75af992000-03-16 08:09:09 +0000909 }
910 } else {
911 /* a job is running in the foreground; wait for it */
912 i = 0;
913 while (!jobList.fg->progs[i].pid ||
Erik Andersen161220c2000-03-16 08:12:48 +0000914 jobList.fg->progs[i].isStopped) i++;
Erik Andersen3522eb12000-03-12 23:49:18 +0000915
Erik Andersend75af992000-03-16 08:09:09 +0000916 waitpid(jobList.fg->progs[i].pid, &status, WUNTRACED);
Erik Andersen3522eb12000-03-12 23:49:18 +0000917
Erik Andersend75af992000-03-16 08:09:09 +0000918 if (WIFEXITED(status) || WIFSIGNALED(status)) {
Erik Andersen161220c2000-03-16 08:12:48 +0000919 /* the child exited */
920 jobList.fg->runningProgs--;
921 jobList.fg->progs[i].pid = 0;
Erik Andersen3522eb12000-03-12 23:49:18 +0000922
Erik Andersen161220c2000-03-16 08:12:48 +0000923 if (!jobList.fg->runningProgs) {
924 /* child exited */
Erik Andersen3522eb12000-03-12 23:49:18 +0000925
Erik Andersen161220c2000-03-16 08:12:48 +0000926 removeJob(&jobList, jobList.fg);
927 jobList.fg = NULL;
Erik Andersen3522eb12000-03-12 23:49:18 +0000928
Erik Andersen161220c2000-03-16 08:12:48 +0000929 /* move the shell to the foreground */
930 if (tcsetpgrp(0, getpid()))
931 perror("tcsetpgrp");
932 }
Erik Andersend75af992000-03-16 08:09:09 +0000933 } else {
Erik Andersen161220c2000-03-16 08:12:48 +0000934 /* the child was stopped */
935 jobList.fg->stoppedProgs++;
936 jobList.fg->progs[i].isStopped = 1;
Erik Andersen3522eb12000-03-12 23:49:18 +0000937
Erik Andersen161220c2000-03-16 08:12:48 +0000938 if (jobList.fg->stoppedProgs == jobList.fg->runningProgs) {
939 printf("\n" JOB_STATUS_FORMAT, jobList.fg->jobId,
940 "Stopped", jobList.fg->text);
941 jobList.fg = NULL;
942 }
Erik Andersend75af992000-03-16 08:09:09 +0000943 }
944
945 if (!jobList.fg) {
Erik Andersen161220c2000-03-16 08:12:48 +0000946 /* move the shell to the foreground */
947 if (tcsetpgrp(0, getpid()))
948 perror("tcsetpgrp");
Erik Andersend75af992000-03-16 08:09:09 +0000949 }
950 }
951 }
Erik Andersen161220c2000-03-16 08:12:48 +0000952 free(command);
Erik Andersen3522eb12000-03-12 23:49:18 +0000953
Erik Andersen161220c2000-03-16 08:12:48 +0000954 return 0;
Erik Andersen3522eb12000-03-12 23:49:18 +0000955}
956
957
Erik Andersend75af992000-03-16 08:09:09 +0000958int shell_main(int argc, char **argv)
Erik Andersen3522eb12000-03-12 23:49:18 +0000959{
Erik Andersen161220c2000-03-16 08:12:48 +0000960 FILE *input = stdin;
Erik Andersen3522eb12000-03-12 23:49:18 +0000961
Erik Andersen161220c2000-03-16 08:12:48 +0000962 if (argc > 2) {
963 usage(shell_usage);
964 }
965 /* initialize the cwd */
966 getcwd(cwd, sizeof(cwd));
Erik Andersen3522eb12000-03-12 23:49:18 +0000967
Erik Andersenf0657d32000-04-12 17:49:52 +0000968#ifdef BB_FEATURE_SH_COMMAND_EDITING
Erik Andersen1d1d9502000-04-21 01:26:49 +0000969 cmdedit_init();
Erik Andersenf0657d32000-04-12 17:49:52 +0000970 signal(SIGWINCH, win_changed);
971 win_changed(0);
972#endif
Erik Andersen3522eb12000-03-12 23:49:18 +0000973
Erik Andersen161220c2000-03-16 08:12:48 +0000974 //if (argv[0] && argv[0][0] == '-') {
975 // shell_source("/etc/profile");
976 //}
Erik Andersen3522eb12000-03-12 23:49:18 +0000977
Erik Andersen161220c2000-03-16 08:12:48 +0000978 if (argc < 2) {
Erik Andersenf0657d32000-04-12 17:49:52 +0000979 fprintf(stdout, "\n\nBusyBox v%s (%s) Built-in shell\n", BB_VER, BB_BT);
980 fprintf(stdout, "Enter 'help' for a list of built-in commands.\n\n");
Erik Andersen161220c2000-03-16 08:12:48 +0000981 } else {
Erik Andersene5b6c7d2000-04-17 16:16:10 +0000982 if (*argv[1]=='-') {
983 usage("sh\n\nlash -- the BusyBox LAme SHell (command interpreter)\n");
984 }
Erik Andersen161220c2000-03-16 08:12:48 +0000985 input = fopen(argv[1], "r");
Erik Andersenf0657d32000-04-12 17:49:52 +0000986 if (!input) {
Erik Andersene5b6c7d2000-04-17 16:16:10 +0000987 fatalError("sh: Couldn't open file '%s': %s\n", argv[1],
Erik Andersen161220c2000-03-16 08:12:48 +0000988 strerror(errno));
Erik Andersenf0657d32000-04-12 17:49:52 +0000989 }
Erik Andersen161220c2000-03-16 08:12:48 +0000990 }
Erik Andersend75af992000-03-16 08:09:09 +0000991
Erik Andersen161220c2000-03-16 08:12:48 +0000992 return (busy_loop(input));
Erik Andersen3522eb12000-03-12 23:49:18 +0000993}