]> err.no Git - sash/blob - sash.c
Stop stripping during build. Also thanks to Helmut Grohne. Closes: #852771
[sash] / sash.c
1 /*
2  * Copyright (c) 2014 by David I. Bell
3  * Permission is granted to use, distribute, or modify this source,
4  * provided that this copyright notice remains intact.
5  *
6  * Stand-alone shell for system maintainance for Linux.
7  * This program should NOT be built using shared libraries.
8  */
9
10 #include <sys/types.h>
11 #include <sys/wait.h>
12 #include <signal.h>
13 #include <errno.h>
14
15 #include "sash.h"
16
17
18 static const char * const       version = "3.8";
19
20
21 /*
22  * The special maximum argument value which means that there is
23  * no limit to the number of arguments on the command line.
24  */
25 #define INFINITE_ARGS   0x7fffffff
26
27
28 /*
29  * One entry of the command table.
30  */
31 typedef struct
32 {
33         const char *    name;
34         int             (*func)(int argc, const char ** argv);
35         int             minArgs;
36         int             maxArgs;
37         const char *    description;
38         const char *    usage;
39 } CommandEntry;
40
41
42 /*
43  * The table of built-in commands.
44  * This is terminated wih an entry containing NULL values.
45  */
46 static const CommandEntry       commandEntryTable[] =
47 {
48         {
49                 "alias",        do_alias,       1,      INFINITE_ARGS,
50                 "Define a command alias",
51                 "[name [command]]"
52         },
53
54         {
55                 "aliasall",     do_aliasall,    1,      1,
56                 "Define aliases for all of the build-in commands",
57                 ""
58         },
59
60         {
61                 "-ar",          do_ar,          3,      INFINITE_ARGS,
62                 "Extract or list files from an AR file",
63                 "[txp]v arFileName fileName ..."
64         },
65
66         {
67                 "cd",           do_cd,          1,      2,
68                 "Change current directory",
69                 "[dirName]"
70         },
71
72 #if     HAVE_LINUX_ATTR
73         {
74                 "-chattr",      do_chattr,      3,      INFINITE_ARGS,
75                 "Change ext2 file attributes",
76                 "[+i] [-i] [+a] [-a] fileName ..."
77         },
78 #endif
79
80         {
81                 "-chgrp",       do_chgrp,       3,      INFINITE_ARGS,
82                 "Change the group id of some files",
83                 "gid fileName ..."
84         },
85
86         {
87                 "-chmod",       do_chmod,       3,      INFINITE_ARGS,
88                 "Change the protection of some files",
89                 "mode fileName ..."
90         },
91
92         {
93                 "-chown",       do_chown,       3,      INFINITE_ARGS,
94                 "Change the owner id of some files",
95                 "uid fileName ..."
96         },
97
98         {
99                 "-cmp",         do_cmp,         3,      3,
100                 "Compare two files for equality",
101                 "fileName1 fileName2"
102         },
103
104         {
105                 "-cp",          do_cp,          3,      INFINITE_ARGS,
106                 "Copy files",
107                 "srcName ... destName"
108         },
109
110 #ifdef  HAVE_LINUX_CHROOT
111         {
112                 "-chroot",      do_chroot,      2,      2,
113                 "change root file system",
114                 "new_root_dir"
115         },
116 #endif
117
118         {
119                 "-dd",          do_dd,          3,      INFINITE_ARGS,
120                 "Copy data between two files",
121                 "if=name of=name [bs=n] [count=n] [skip=n] [seek=n]"
122         },
123
124         {
125                 "-echo",        do_echo,        1,      INFINITE_ARGS,
126                 "Echo the arguments",
127                 "[args] ..."
128         },
129
130         {
131                 "-ed",          do_ed,          1,      2,
132                 "Edit a fileName using simple line mode commands",
133                 "[fileName]"
134         },
135
136         {
137                 "exec",         do_exec,        2,      INFINITE_ARGS,
138                 "Execute another program in place of this sash process",
139                 "fileName [args]"
140         },
141
142         {
143                 "exit",         do_exit,        1,      2,
144                 "Exit from sash",
145                 "[exit value]"
146         },
147
148         {
149                 "-file",        do_file,        1,      INFINITE_ARGS,
150                 "Describe information about files",
151                 "fileName ..."
152         },
153
154         {
155                 "-find",        do_find,        2,      INFINITE_ARGS,
156                 "Find files in a directory tree meeting some conditions",
157                 "dirName [-xdev] [-type chars] [-name pattern] [-size minSize]"
158         },
159
160         {
161                 "-grep",        do_grep,        3,      INFINITE_ARGS,
162                 "Look for lines containing a word in some files",
163                 "[-in] word fileName ..."
164         },
165
166 #if     HAVE_GZIP
167         {
168                 "-gunzip",      do_gunzip,      2,      INFINITE_ARGS,
169                 "Uncompress files which were saved in GZIP or compress format",
170                 "fileName ... [-o outputPath]"
171         },
172
173         {
174                 "-gzip",        do_gzip,        2,      INFINITE_ARGS,
175                 "Compress files into GZIP format",
176                 "fileName ... [-o outputPath]"
177         },
178 #endif
179
180         {
181                 "help",         do_help,        1,      2,
182                 "Print help about a command",
183                 "[word]"
184         },
185
186         {
187                 "-kill",        do_kill,        2,      INFINITE_ARGS,
188                 "Send a signal to the specified process",
189                 "[-sig] pid ..."
190         },
191
192 #ifdef  HAVE_LINUX_LOSETUP
193         {
194                 "-losetup",     do_losetup,     3,      3,
195                 "Associate a loopback device with a file",
196                 "[-d] device\n       -losetup device filename"
197         },
198 #endif
199
200         {
201                 "-ln",          do_ln,          3,      INFINITE_ARGS,
202                 "Link one fileName to another",
203                 "[-s] srcName ... destName"
204         },
205
206         {
207                 "-ls",          do_ls,          1,      INFINITE_ARGS,
208                 "List information about files or directories",
209                 "[-lidFC] fileName ..."
210         },
211
212 #if     HAVE_LINUX_ATTR
213         {
214                 "-lsattr",      do_lsattr,      2,      INFINITE_ARGS,
215                 "List ext2 file attributes",
216                 "fileName ..."
217         },
218 #endif
219
220         {
221                 "-mkdir",       do_mkdir,       2,      INFINITE_ARGS,
222                 "Create a directory",
223                 "dirName ..."
224         },
225
226         {
227                 "-mknod",       do_mknod,       5,      5,
228                 "Create a special type of file",
229                 "fileName type major minor"
230         },
231
232         {
233                 "-more",        do_more,        2,      INFINITE_ARGS,
234                 "Type file contents page by page",
235                 "fileName ..."
236         },
237
238         {
239                 "-mount",       do_mount,       3,      INFINITE_ARGS,
240                 "Mount or remount a filesystem on a directory",
241 #if     HAVE_LINUX_MOUNT
242                 "[-t type] [-r] [-s] [-e] [-m] devName dirName"
243 #elif   HAVE_BSD_MOUNT
244                 "[-t type] [-r] [-s] [-e] devName dirName"
245 #else
246                 "[-t type] devName dirName"
247 #endif
248         },
249
250         {
251                 "-mv",          do_mv,          3,      INFINITE_ARGS,
252                 "Move or rename files",
253                 "srcName ... destName"
254         },
255
256 #ifdef  HAVE_LINUX_PIVOT
257         {
258                 "-pivot_root",  do_pivot_root,  3,      3,
259                 "pivot the root file system",
260                 "new_dir old_dir"
261         },
262 #endif
263
264         {
265                 "-printenv",    do_printenv,    1,      2,
266                 "Print environment variables",
267                 "[name]"
268         },
269
270         {
271                 "prompt",       do_prompt,      2,      INFINITE_ARGS,
272                 "Set the prompt string for sash",
273                 "string"
274         },
275
276         {
277                 "-pwd",         do_pwd,         1,      1,
278                 "Print the current working directory",
279                 ""
280         },
281
282         {
283                 "quit",         do_exit,        1,      1,
284                 "Exit from sash",
285                 ""
286         },
287
288         {
289                 "-rm",          do_rm,          2,      INFINITE_ARGS,
290                 "Remove the specified files",
291                 "fileName ..."
292         },
293
294         {
295                 "-rmdir",       do_rmdir,       2,      INFINITE_ARGS,
296                 "Remove the specified empty directories",
297                 "dirName ..."
298         },
299
300         {
301                 "setenv",       do_setenv,      3,      3,
302                 "Set an environment variable value",
303                 "name value"
304         },
305
306         {
307                 "source",       do_source,      2,      2,
308                 "Read commands from the specified file",
309                 "fileName"
310         },
311
312         {
313                 "-sum",         do_sum,         2,      INFINITE_ARGS,
314                 "Calculate checksums of the specified files",
315                 "fileName ..."
316         },
317
318         {
319                 "-sync",        do_sync,        1,      1,
320                 "Sync the disks to force cached data to them",
321                 ""
322         },
323
324         {
325                 "-tar",         do_tar,         2,      INFINITE_ARGS,
326                 "Create, extract, or list files from a TAR file",
327                 "[cxtv]f tarFileName fileName ..."
328         },
329
330         {
331                 "-touch",       do_touch,       2,      INFINITE_ARGS,
332                 "Update times or create the specified files",
333                 "fileName ..."
334         },
335
336         {
337                 "umask",        do_umask,       1,      2,
338                 "Set the umask value for file protections",
339                 "[mask]"
340         },
341
342         {
343 #if     HAVE_BSD_MOUNT
344                 "-umount",      do_umount,      2,      3,
345                 "Unmount a filesystem",
346                 "[-f] fileName"
347 #else
348                 "-umount",      do_umount,      2,      2,
349                 "Unmount a filesystem",
350                 "fileName"
351 #endif
352         },
353
354         {
355                 "unalias",      do_unalias,     2,      2,
356                 "Remove a command alias",
357                 "name"
358         },
359
360         {
361                 "-where",       do_where,       2,      2,
362                 "Type the location of a program",
363                 "program"
364         },
365
366         {
367                 NULL,           0,              0,      0,
368                 NULL,
369                 NULL
370         }
371 };
372
373
374 /*
375  * The definition of an command alias.
376  */
377 typedef struct
378 {
379         char *  name;
380         char *  value;
381 } Alias;
382
383
384 /*
385  * Local data.
386  */
387 static  Alias * aliasTable;
388 static  int     aliasCount;
389
390 static  FILE *  sourcefiles[MAX_SOURCE];
391 static  int     sourceCount;
392
393 static  BOOL    intCrlf = TRUE;
394 static  char *  prompt;
395
396
397 /*
398  * Local procedures.
399  */
400 static  void    catchInt(int);
401 static  void    catchQuit(int);
402 static  int     readFile(const char * name);
403 static  int     command(const char * cmd);
404 static  BOOL    tryBuiltIn(const char * cmd);
405 static  int     runCmd(const char * cmd);
406 static  void    childProcess(const char * cmd);
407 static  void    showPrompt(void);
408 static  void    usage(void);
409 static  Alias * findAlias(const char * name);
410 static  void    expandVariable(char * name);
411
412
413 /*
414  * Global interrupt flag.
415  */
416 BOOL    intFlag;
417
418
419 int
420 main(int argc, const char ** argv)
421 {
422         const char *    cp;
423         const char *    singleCommand;
424         const char *    commandFile;
425         BOOL            quietFlag;
426         BOOL            aliasFlag;
427         BOOL            interactiveFlag;
428         char            buf[PATH_LEN];
429
430         singleCommand = NULL;
431         commandFile = NULL;
432         quietFlag = FALSE;
433         aliasFlag = FALSE;
434         interactiveFlag = FALSE;
435
436         /*
437          * Look for options.
438          */
439         argv++;
440         argc--;
441
442         while ((argc > 0) && (**argv == '-'))
443         {
444                 cp = *argv++ + 1;
445                 argc--;
446
447                 while (*cp) switch (*cp++)
448                 {
449                         case '-':
450                                 /*
451                                  * Ignore.  This is so that we can be
452                                  * run from login.
453                                  */
454                                 break;
455
456                         case 'c':
457                                 /*
458                                  * Execute specified command.
459                                  */
460                                 if ((argc != 1) || singleCommand || interactiveFlag)
461                                         usage();
462
463                                 singleCommand = *argv++;
464                                 argc--;
465
466                                 break;
467
468                         case 'f':
469                                 /*
470                                  * Execute commands from file.
471                                  * This is used for sash script files.
472                                  * The quiet flag is also set.
473                                  */
474                                 if ((argc != 1) || commandFile)
475                                         usage();
476
477                                 quietFlag = TRUE;
478                                 commandFile = *argv++;
479                                 argc--;
480
481                                 break;
482
483                         case 'i':
484                                 /*
485                                  * Be an interactive shell
486                                  * ..is a no-op, but some contexts require this
487                                  * ..interactiveFlag is to avoid -ic as a legacy
488                                  */
489                                  if (singleCommand)
490                                         usage();
491                                  
492                                  interactiveFlag = TRUE;
493                                  break;
494                                  
495                         case 'p':
496                                 /*
497                                  * Set the prompt string.
498                                  */
499                                 if ((argc <= 0) || (**argv == '-'))
500                                         usage();
501
502                                 if (prompt)
503                                         free(prompt);
504
505                                 prompt = strdup(*argv++);
506                                 argc--;
507
508                                 break;
509
510                         case 'q':
511                                 quietFlag = TRUE;
512                                 break;
513
514                         case 'a':
515                                 aliasFlag = TRUE;
516                                 break;
517
518                         case 'h':
519                         case '?':
520                                 usage();
521                                 break;
522
523                         default:
524                                 fprintf(stderr, "Unknown option -%c\n", cp[-1]);
525
526                                 return 1;
527                 }
528         }
529
530         /*
531          * No more arguments are allowed.
532          */
533         if (argc > 0)
534                 usage();
535
536         /*
537          * Default our path if it is not set.
538          */
539         if (getenv("PATH") == NULL)
540                 putenv("PATH=/bin:/usr/bin:/sbin:/usr/sbin:/etc");
541
542         /*
543          * If the alias flag is set then define all aliases.
544          */
545         if (aliasFlag)
546                 do_aliasall(0, NULL);
547
548         /*
549          * If we are to execute a single command, then do so and exit.
550          */
551         if (singleCommand)
552         {
553                 return command(singleCommand);
554         }
555
556         /*
557          * Print a hello message unless we are told to be silent.
558          */
559         if (!quietFlag && isatty(STDIN))
560         {
561                 printf("Stand-alone shell (version %s)\n", version);
562
563                 if (aliasFlag)
564                         printf("Built-in commands are aliased to standard commands\n");
565         }
566
567         signal(SIGINT, catchInt);
568         signal(SIGQUIT, catchQuit);
569
570         /*
571          * Execute the user's alias file if present.
572          */
573         cp = getenv("HOME");
574
575         if (cp)
576         {
577                 strcpy(buf, cp);
578                 strcat(buf, "/");
579                 strcat(buf, ".aliasrc");
580
581                 if ((access(buf, 0) == 0) || (errno != ENOENT))
582                         readFile(buf);
583         }
584
585         /*
586          * Read commands from stdin or from a command file.
587          */
588         return readFile(commandFile);
589
590 }
591
592
593 /*
594  * Read commands from the specified file.
595  * A null name pointer indicates to read from stdin.
596  */
597 static int
598 readFile(const char * name)
599 {
600         FILE *  fp;
601         int     cc;
602         BOOL    ttyFlag;
603         char    buf[CMD_LEN];
604         int     r = 0;
605
606         if (sourceCount >= MAX_SOURCE)
607         {
608                 fprintf(stderr, "Too many source files\n");
609
610                 return 1;
611         }
612
613         fp = stdin;
614
615         if (name)
616         {
617                 fp = fopen(name, "r");
618
619                 if (fp == NULL)
620                 {
621                         perror(name);
622
623                         return 1;
624                 }
625         }
626
627         sourcefiles[sourceCount++] = fp;
628
629         ttyFlag = isatty(fileno(fp));
630
631         while (TRUE)
632         {
633                 if (ttyFlag)
634                         showPrompt();
635
636                 if (intFlag && !ttyFlag && (fp != stdin))
637                 {
638                         fclose(fp);
639                         sourceCount--;
640
641                         return 1;
642                 }
643         
644                 if (fgets(buf, CMD_LEN - 1, fp) == NULL)
645                 {
646                         if (ferror(fp) && (errno == EINTR))
647                         {
648                                 clearerr(fp);
649
650                                 continue;
651                         }
652
653                         break;
654                 }
655
656                 cc = strlen(buf);
657
658                 if (buf[cc - 1] == '\n')
659                         cc--;
660
661                 while ((cc > 0) && isBlank(buf[cc - 1]))
662                         cc--;
663
664                 buf[cc] = '\0';
665
666                 r = command(buf);
667         }
668
669         if (ferror(fp))
670         {
671                 perror("Reading command line");
672
673                 if (fp == stdin)
674                         exit(1);
675         }
676
677         clearerr(fp);
678
679         if (fp != stdin)
680                 fclose(fp);
681
682         sourceCount--;
683
684         return r;
685 }
686
687
688 /*
689  * Parse and execute one null-terminated command line string.
690  * This breaks the command line up into words, checks to see if the
691  * command is an alias, and expands wildcards.
692  */
693 static int
694 command(const char * cmd)
695 {
696         const char *    endCmd;
697         const Alias *   alias;
698         char            newCommand[CMD_LEN];
699         char            cmdName[CMD_LEN];
700
701         /*
702          * Rest the interrupt flag and free any memory chunks that
703          * were allocated by the previous command.
704          */
705         intFlag = FALSE;
706
707         freeChunks();
708
709         /*
710          * Skip leading blanks.
711          */
712         while (isBlank(*cmd))
713                 cmd++;
714
715         /*
716          * If the command is empty or is a comment then ignore it.
717          */
718         if ((*cmd == '\0') || (*cmd == '#'))
719                 return 0;
720
721         /*
722          * Look for the end of the command name and then copy the
723          * command name to a buffer so we can null terminate it.
724          */
725         endCmd = cmd;
726
727         while (*endCmd && !isBlank(*endCmd))
728                 endCmd++;
729
730         memcpy(cmdName, cmd, endCmd - cmd);
731
732         cmdName[endCmd - cmd] = '\0';
733
734         /*
735          * Search for the command name in the alias table.
736          * If it is found, then replace the command name with
737          * the alias value, and append the current command
738          * line arguments to that.
739          */
740         alias = findAlias(cmdName);
741
742         if (alias)
743         {
744                 strcpy(newCommand, alias->value);
745                 strcat(newCommand, endCmd);
746
747                 cmd = newCommand;
748         }
749
750         /*
751          * Expand simple environment variables
752          */
753         while (strstr(cmd, "$(")) expandVariable((char *)cmd);
754
755         /*
756          * Now look for the command in the builtin table, and execute
757          * the command if found.
758          */
759         if (tryBuiltIn(cmd))
760                 return 0; /* This is a blatant lie */
761
762         /*
763          * The command is not a built-in, so run the program along
764          * the PATH list.
765          */
766         return runCmd(cmd);
767 }
768
769
770 /*
771  * Try to execute a built-in command.
772  * Returns TRUE if the command is a built in, whether or not the
773  * command succeeds.  Returns FALSE if this is not a built-in command.
774  */
775 static BOOL
776 tryBuiltIn(const char * cmd)
777 {
778         const char *            endCmd;
779         const CommandEntry *    entry;
780         int                     argc;
781         const char **           argv;
782         char                    cmdName[CMD_LEN];
783
784         /*
785          * Look for the end of the command name and then copy the
786          * command name to a buffer so we can null terminate it.
787          */
788         endCmd = cmd;
789
790         while (*endCmd && !isBlank(*endCmd))
791                 endCmd++;
792
793         memcpy(cmdName, cmd, endCmd - cmd);
794
795         cmdName[endCmd - cmd] = '\0';
796
797         /*
798          * Search the command table looking for the command name.
799          */
800         for (entry = commandEntryTable; entry->name != NULL; entry++)
801         {
802                 if (strcmp(entry->name, cmdName) == 0)
803                         break;
804         }
805
806         /*
807          * If the command is not a built-in, return indicating that.
808          */
809         if (entry->name == NULL)
810                 return FALSE;
811
812         /*
813          * The command is a built-in.
814          * Break the command up into arguments and expand wildcards.
815          */
816         if (!makeArgs(cmd, &argc, &argv))
817                 return TRUE;
818
819         /*
820          * Give a usage string if the number of arguments is too large
821          * or too small.
822          */
823         if ((argc < entry->minArgs) || (argc > entry->maxArgs))
824         {
825                 fprintf(stderr, "usage: %s %s\n", entry->name, entry->usage);
826
827                 return TRUE;
828         }
829
830         /*
831          * Call the built-in function with the argument list.
832          */
833         entry->func(argc, argv);
834
835         return TRUE;
836 }
837
838
839 /*
840  * Execute the specified command either by forking and executing
841  * the program ourself, or else by using the shell.  Returns the
842  * exit status, or -1 if the program cannot be executed at all.
843  */
844 static int
845 runCmd(const char * cmd)
846 {
847         const char *    cp;
848         BOOL            magic;
849         pid_t           pid;
850         int             status;
851
852         /*
853          * Check the command for any magic shell characters
854          * except for quoting.
855          */
856         magic = FALSE;
857
858         for (cp = cmd; *cp; cp++)
859         {
860                 if ((*cp >= 'a') && (*cp <= 'z'))
861                         continue;
862
863                 if ((*cp >= 'A') && (*cp <= 'Z'))
864                         continue;
865
866                 if (isDecimal(*cp))
867                         continue;
868
869                 if (isBlank(*cp))
870                         continue;
871
872                 if ((*cp == '.') || (*cp == '/') || (*cp == '-') ||
873                         (*cp == '+') || (*cp == '=') || (*cp == '_') ||
874                         (*cp == ':') || (*cp == ',') || (*cp == '\'') ||
875                         (*cp == '"'))
876                 {
877                         continue;
878                 }
879
880                 magic = TRUE;
881         }
882
883         /*
884          * If there were any magic characters used then run the
885          * command using the shell.
886          */
887         if (magic)
888                 return trySystem(cmd);
889
890         /*
891          * No magic characters were in the command, so we can do the fork
892          * and exec ourself.
893          */
894         pid = fork();
895
896         if (pid < 0)
897         {
898                 perror("fork failed");
899
900                 return -1;
901         }
902
903         /*
904          * If we are the child process, then go execute the program.
905          */
906         if (pid == 0)
907                 childProcess(cmd);
908
909         /*
910          * We are the parent process.
911          * Wait for the child to complete.
912          */
913         status = 0;
914         intCrlf = FALSE;
915
916         while (((pid = waitpid(pid, &status, 0)) < 0) && (errno == EINTR))
917                 ;
918
919         intCrlf = TRUE;
920
921         if (pid < 0)
922         {
923                 fprintf(stderr, "Error from waitpid: %s", strerror(errno));
924
925                 return -1;
926         }
927
928         if (WIFSIGNALED(status))
929         {
930                 fprintf(stderr, "pid %ld: killed by signal %d\n",
931                         (long) pid, WTERMSIG(status));
932
933                 return -1;
934         }
935
936         return WEXITSTATUS(status);
937 }
938
939
940 /*
941  * Here as the child process to try to execute the command.
942  * This is only called if there are no meta-characters in the command.
943  * This procedure never returns.
944  */
945 static void
946 childProcess(const char * cmd)
947 {
948         const char **   argv;
949         int             argc;
950
951         /*
952          * Close any extra file descriptors we have opened.
953          */     
954         while (--sourceCount >= 0)
955         {
956                 if (sourcefiles[sourceCount] != stdin)
957                         fclose(sourcefiles[sourceCount]);
958         }
959
960         /*
961          * Break the command line up into individual arguments.
962          * If this fails, then run the shell to execute the command.
963          */
964         if (!makeArgs(cmd, &argc, &argv))
965         {
966                 int status = trySystem(cmd);
967
968                 if (status == -1)
969                         exit(99);
970
971                 exit(status);
972         }
973
974         /*
975          * Try to execute the program directly.
976          */
977         execvp(argv[0], (char **) argv);
978
979         /*
980          * The exec failed, so try to run the command using the shell
981          * in case it is a shell script.
982          */
983         if (errno == ENOEXEC)
984         {
985                 int status = trySystem(cmd);
986
987                 if (status == -1)
988                         exit(99);
989
990                 exit(status);
991         }
992
993         /*
994          * There was something else wrong, complain and exit.
995          */
996         perror(argv[0]);
997         exit(1);
998 }
999
1000
1001 int
1002 do_help(int argc, const char ** argv)
1003 {
1004         const CommandEntry *    entry;
1005         const char *            str;
1006
1007         str = NULL;
1008
1009         if (argc == 2)
1010                 str = argv[1];
1011
1012         /*
1013          * Check for an exact match, in which case describe the program.
1014          */
1015         if (str)
1016         {
1017                 for (entry = commandEntryTable; entry->name; entry++)
1018                 {
1019                         if (strcmp(str, entry->name) == 0)
1020                         {
1021                                 printf("%s\n", entry->description);
1022
1023                                 printf("usage: %s %s\n", entry->name,
1024                                         entry->usage);
1025
1026                                 return 0;
1027                         }
1028                 }
1029         }
1030
1031         /*
1032          * Print short information about commands which contain the
1033          * specified word.
1034          */
1035         for (entry = commandEntryTable; entry->name; entry++)
1036         {
1037                 if ((str == NULL) || (strstr(entry->name, str) != NULL) ||
1038                         (strstr(entry->usage, str) != NULL))
1039                 {
1040                         printf("%-10s %s\n", entry->name, entry->usage);
1041                 }
1042         }
1043
1044         return 0;
1045 }
1046
1047
1048 int
1049 do_alias(int argc, const char ** argv)
1050 {
1051         const char *    name;
1052         char *          value;
1053         Alias *         alias;
1054         int             count;
1055         char            buf[CMD_LEN];
1056
1057         if (argc < 2)
1058         {
1059                 count = aliasCount;
1060
1061                 for (alias = aliasTable; count-- > 0; alias++)
1062                         printf("%s\t%s\n", alias->name, alias->value);
1063
1064                 return 0;
1065         }
1066
1067         name = argv[1];
1068
1069         if (argc == 2)
1070         {
1071                 alias = findAlias(name);
1072
1073                 if (alias)
1074                         printf("%s\n", alias->value);
1075                 else
1076                 {
1077                         fprintf(stderr, "Alias \"%s\" is not defined\n", name);
1078
1079                         return 1;
1080                 }
1081
1082                 return 0;       
1083         }
1084
1085         if (strcmp(name, "alias") == 0)
1086         {
1087                 fprintf(stderr, "Cannot alias \"alias\"\n");
1088
1089                 return 1;
1090         }
1091
1092         if (!makeString(argc - 2, argv + 2, buf, CMD_LEN))
1093                 return 1;
1094
1095         value = malloc(strlen(buf) + 1);
1096
1097         if (value == NULL)
1098         {
1099                 fprintf(stderr, "No memory for alias value\n");
1100
1101                 return 1;
1102         }
1103
1104         strcpy(value, buf);
1105
1106         alias = findAlias(name);
1107
1108         if (alias)
1109         {
1110                 free(alias->value);
1111                 alias->value = value;
1112
1113                 return 0;
1114         }
1115
1116         if ((aliasCount % ALIAS_ALLOC) == 0)
1117         {
1118                 count = aliasCount + ALIAS_ALLOC;
1119
1120                 if (aliasTable)
1121                 {
1122                         alias = (Alias *) realloc(aliasTable,
1123                                 sizeof(Alias) * count);
1124                 }
1125                 else
1126                         alias = (Alias *) malloc(sizeof(Alias) * count);
1127
1128                 if (alias == NULL)
1129                 {
1130                         free(value);
1131                         fprintf(stderr, "No memory for alias table\n");
1132
1133                         return 1;
1134                 }
1135
1136                 aliasTable = alias;
1137         }
1138
1139         alias = &aliasTable[aliasCount];
1140
1141         alias->name = malloc(strlen(name) + 1);
1142
1143         if (alias->name == NULL)
1144         {
1145                 free(value);
1146                 fprintf(stderr, "No memory for alias name\n");
1147
1148                 return 1;
1149         }
1150
1151         strcpy(alias->name, name);
1152         alias->value = value;
1153         aliasCount++;
1154
1155         return 0;
1156 }
1157
1158
1159 /*
1160  * Build aliases for all of the built-in commands which start with a dash,
1161  * using the names without the dash.
1162  */
1163 int
1164 do_aliasall(int argc, const char **argv)
1165 {
1166         const CommandEntry *    entry;
1167         const char *            name;
1168         const char *            newArgv[4];
1169
1170         for (entry = commandEntryTable; entry->name; entry++)
1171         {
1172                 name = entry->name;
1173
1174                 if (*name != '-')
1175                         continue;
1176
1177                 newArgv[0] = "alias";
1178                 newArgv[1] = name + 1;
1179                 newArgv[2] = name;
1180                 newArgv[3] = NULL;
1181
1182                 do_alias(3, newArgv);
1183         }
1184
1185         return 0;
1186 }
1187
1188
1189 /*
1190  * Look up an alias name, and return a pointer to it.
1191  * Returns NULL if the name does not exist.
1192  */
1193 static Alias *
1194 findAlias(const char * name)
1195 {
1196         Alias * alias;
1197         int     count;
1198
1199         count = aliasCount;
1200
1201         for (alias = aliasTable; count-- > 0; alias++)
1202         {
1203                 if (strcmp(name, alias->name) == 0)
1204                         return alias;
1205         }
1206
1207         return NULL;
1208 }
1209
1210
1211 int
1212 do_source(int argc, const char ** argv)
1213 {
1214         return readFile(argv[1]);
1215 }
1216
1217
1218 int
1219 do_exec(int argc, const char ** argv)
1220 {
1221         const char *    name;
1222
1223         name = argv[1];
1224
1225         while (--sourceCount >= 0)
1226         {
1227                 if (sourcefiles[sourceCount] != stdin)
1228                         fclose(sourcefiles[sourceCount]);
1229         }
1230
1231         argv[argc] = NULL;
1232
1233         execvp(name, (char **) argv + 1);
1234         perror(name);
1235
1236         return 1;
1237 }
1238
1239
1240 int
1241 do_prompt(int argc, const char ** argv)
1242 {
1243         char *  cp;
1244         char    buf[CMD_LEN];
1245
1246         if (!makeString(argc - 1, argv + 1, buf, CMD_LEN))
1247                 return 1;
1248
1249         cp = malloc(strlen(buf) + 2);
1250
1251         if (cp == NULL)
1252         {
1253                 fprintf(stderr, "No memory for prompt\n");
1254
1255                 return 1;
1256         }
1257
1258         strcpy(cp, buf);
1259         strcat(cp, " ");
1260
1261         if (prompt)
1262                 free(prompt);
1263
1264         prompt = cp;
1265
1266         return 0;
1267 }
1268
1269
1270 int
1271 do_unalias(int argc, const char ** argv)
1272 {
1273         Alias * alias;
1274
1275         while (--argc > 0)
1276         {
1277                 alias = findAlias(*++argv);
1278
1279                 if (alias == NULL)
1280                         continue;
1281
1282                 free(alias->name);
1283                 free(alias->value);
1284                 aliasCount--;
1285                 alias->name = aliasTable[aliasCount].name;
1286                 alias->value = aliasTable[aliasCount].value;    
1287         }
1288
1289         return 0;
1290 }
1291
1292
1293 /*
1294  * Display the prompt string.
1295  */
1296 static void
1297 showPrompt(void)
1298 {
1299         const char *    cp;
1300
1301         cp = "> ";
1302
1303         if (prompt)
1304                 cp = prompt;
1305
1306         tryWrite(STDOUT, cp, strlen(cp));
1307 }       
1308
1309
1310 static void
1311 catchInt(int val)
1312 {
1313         signal(SIGINT, catchInt);
1314
1315         intFlag = TRUE;
1316
1317         if (intCrlf)
1318                 tryWrite(STDOUT, "\n", 1);
1319 }
1320
1321
1322 static void
1323 catchQuit(int val)
1324 {
1325         signal(SIGQUIT, catchQuit);
1326
1327         intFlag = TRUE;
1328
1329         if (intCrlf)
1330                 tryWrite(STDOUT, "\n", 1);
1331 }
1332
1333
1334 /*
1335  * Print the usage information and quit.
1336  */
1337 static void
1338 usage(void)
1339 {
1340         fprintf(stderr, "Stand-alone shell (version %s)\n", version);
1341         fprintf(stderr, "\n");
1342         fprintf(stderr, "Usage: sash [-a] [-q] [-f fileName] [-c command] [-p prompt] [-i]\n");
1343
1344         exit(1);
1345 }
1346
1347
1348 /*
1349  * Expand one environment variable: Syntax $(VAR)
1350  */
1351 static void
1352 expandVariable(char * cmd)
1353 {
1354         char    tmp[CMD_LEN];
1355         char    *cp;
1356         char    *ep;
1357
1358         strcpy(tmp, cmd);
1359         cp = strstr(tmp, "$(");
1360         if (cp) {
1361                 *cp++ = '\0';
1362                 strcpy(cmd, tmp);
1363                 ep = ++cp;
1364                 while (*ep && (*ep != ')')) ep++;
1365                 if (*ep == ')') *ep++ = '\0';
1366                 cp = getenv(cp);
1367                 if (cp) strcat(cmd, cp);
1368                 strcat(cmd, ep);
1369         }
1370         return;
1371 }
1372
1373 /* END CODE */