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.
6 * Stand-alone shell for system maintainance for Linux.
7 * This program should NOT be built using shared libraries.
10 #include <sys/types.h>
18 static const char * const version = "3.8";
22 * The special maximum argument value which means that there is
23 * no limit to the number of arguments on the command line.
25 #define INFINITE_ARGS 0x7fffffff
29 * One entry of the command table.
34 int (*func)(int argc, const char ** argv);
37 const char * description;
43 * The table of built-in commands.
44 * This is terminated wih an entry containing NULL values.
46 static const CommandEntry commandEntryTable[] =
49 "alias", do_alias, 1, INFINITE_ARGS,
50 "Define a command alias",
55 "aliasall", do_aliasall, 1, 1,
56 "Define aliases for all of the build-in commands",
61 "-ar", do_ar, 3, INFINITE_ARGS,
62 "Extract or list files from an AR file",
63 "[txp]v arFileName fileName ..."
68 "Change current directory",
74 "-chattr", do_chattr, 3, INFINITE_ARGS,
75 "Change ext2 file attributes",
76 "[+i] [-i] [+a] [-a] fileName ..."
81 "-chgrp", do_chgrp, 3, INFINITE_ARGS,
82 "Change the group id of some files",
87 "-chmod", do_chmod, 3, INFINITE_ARGS,
88 "Change the protection of some files",
93 "-chown", do_chown, 3, INFINITE_ARGS,
94 "Change the owner id of some files",
100 "Compare two files for equality",
101 "fileName1 fileName2"
105 "-cp", do_cp, 3, INFINITE_ARGS,
107 "srcName ... destName"
110 #ifdef HAVE_LINUX_CHROOT
112 "-chroot", do_chroot, 2, 2,
113 "change root file system",
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]"
125 "-echo", do_echo, 1, INFINITE_ARGS,
126 "Echo the arguments",
132 "Edit a fileName using simple line mode commands",
137 "exec", do_exec, 2, INFINITE_ARGS,
138 "Execute another program in place of this sash process",
143 "exit", do_exit, 1, 2,
149 "-file", do_file, 1, INFINITE_ARGS,
150 "Describe information about files",
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]"
161 "-grep", do_grep, 3, INFINITE_ARGS,
162 "Look for lines containing a word in some files",
163 "[-in] word fileName ..."
168 "-gunzip", do_gunzip, 2, INFINITE_ARGS,
169 "Uncompress files which were saved in GZIP or compress format",
170 "fileName ... [-o outputPath]"
174 "-gzip", do_gzip, 2, INFINITE_ARGS,
175 "Compress files into GZIP format",
176 "fileName ... [-o outputPath]"
181 "help", do_help, 1, 2,
182 "Print help about a command",
187 "-kill", do_kill, 2, INFINITE_ARGS,
188 "Send a signal to the specified process",
192 #ifdef HAVE_LINUX_LOSETUP
194 "-losetup", do_losetup, 3, 3,
195 "Associate a loopback device with a file",
196 "[-d] device\n -losetup device filename"
201 "-ln", do_ln, 3, INFINITE_ARGS,
202 "Link one fileName to another",
203 "[-s] srcName ... destName"
207 "-ls", do_ls, 1, INFINITE_ARGS,
208 "List information about files or directories",
209 "[-lidFC] fileName ..."
214 "-lsattr", do_lsattr, 2, INFINITE_ARGS,
215 "List ext2 file attributes",
221 "-mkdir", do_mkdir, 2, INFINITE_ARGS,
222 "Create a directory",
227 "-mknod", do_mknod, 5, 5,
228 "Create a special type of file",
229 "fileName type major minor"
233 "-more", do_more, 2, INFINITE_ARGS,
234 "Type file contents page by page",
239 "-mount", do_mount, 3, INFINITE_ARGS,
240 "Mount or remount a filesystem on a directory",
242 "[-t type] [-r] [-s] [-e] [-m] devName dirName"
244 "[-t type] [-r] [-s] [-e] devName dirName"
246 "[-t type] devName dirName"
251 "-mv", do_mv, 3, INFINITE_ARGS,
252 "Move or rename files",
253 "srcName ... destName"
256 #ifdef HAVE_LINUX_PIVOT
258 "-pivot_root", do_pivot_root, 3, 3,
259 "pivot the root file system",
265 "-printenv", do_printenv, 1, 2,
266 "Print environment variables",
271 "prompt", do_prompt, 2, INFINITE_ARGS,
272 "Set the prompt string for sash",
277 "-pwd", do_pwd, 1, 1,
278 "Print the current working directory",
283 "quit", do_exit, 1, 1,
289 "-rm", do_rm, 2, INFINITE_ARGS,
290 "Remove the specified files",
295 "-rmdir", do_rmdir, 2, INFINITE_ARGS,
296 "Remove the specified empty directories",
301 "setenv", do_setenv, 3, 3,
302 "Set an environment variable value",
307 "source", do_source, 2, 2,
308 "Read commands from the specified file",
313 "-sum", do_sum, 2, INFINITE_ARGS,
314 "Calculate checksums of the specified files",
319 "-sync", do_sync, 1, 1,
320 "Sync the disks to force cached data to them",
325 "-tar", do_tar, 2, INFINITE_ARGS,
326 "Create, extract, or list files from a TAR file",
327 "[cxtv]f tarFileName fileName ..."
331 "-touch", do_touch, 2, INFINITE_ARGS,
332 "Update times or create the specified files",
337 "umask", do_umask, 1, 2,
338 "Set the umask value for file protections",
344 "-umount", do_umount, 2, 3,
345 "Unmount a filesystem",
348 "-umount", do_umount, 2, 2,
349 "Unmount a filesystem",
355 "unalias", do_unalias, 2, 2,
356 "Remove a command alias",
361 "-where", do_where, 2, 2,
362 "Type the location of a program",
375 * The definition of an command alias.
387 static Alias * aliasTable;
388 static int aliasCount;
390 static FILE * sourcefiles[MAX_SOURCE];
391 static int sourceCount;
393 static BOOL intCrlf = TRUE;
394 static char * prompt;
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);
414 * Global interrupt flag.
420 main(int argc, const char ** argv)
423 const char * singleCommand;
424 const char * commandFile;
427 BOOL interactiveFlag;
430 singleCommand = NULL;
434 interactiveFlag = FALSE;
442 while ((argc > 0) && (**argv == '-'))
447 while (*cp) switch (*cp++)
451 * Ignore. This is so that we can be
458 * Execute specified command.
460 if ((argc != 1) || singleCommand || interactiveFlag)
463 singleCommand = *argv++;
470 * Execute commands from file.
471 * This is used for sash script files.
472 * The quiet flag is also set.
474 if ((argc != 1) || commandFile)
478 commandFile = *argv++;
485 * Be an interactive shell
486 * ..is a no-op, but some contexts require this
487 * ..interactiveFlag is to avoid -ic as a legacy
492 interactiveFlag = TRUE;
497 * Set the prompt string.
499 if ((argc <= 0) || (**argv == '-'))
505 prompt = strdup(*argv++);
524 fprintf(stderr, "Unknown option -%c\n", cp[-1]);
531 * No more arguments are allowed.
537 * Default our path if it is not set.
539 if (getenv("PATH") == NULL)
540 putenv("PATH=/bin:/usr/bin:/sbin:/usr/sbin:/etc");
543 * If the alias flag is set then define all aliases.
546 do_aliasall(0, NULL);
549 * If we are to execute a single command, then do so and exit.
553 return command(singleCommand);
557 * Print a hello message unless we are told to be silent.
559 if (!quietFlag && isatty(STDIN))
561 printf("Stand-alone shell (version %s)\n", version);
564 printf("Built-in commands are aliased to standard commands\n");
567 signal(SIGINT, catchInt);
568 signal(SIGQUIT, catchQuit);
571 * Execute the user's alias file if present.
579 strcat(buf, ".aliasrc");
581 if ((access(buf, 0) == 0) || (errno != ENOENT))
586 * Read commands from stdin or from a command file.
588 return readFile(commandFile);
594 * Read commands from the specified file.
595 * A null name pointer indicates to read from stdin.
598 readFile(const char * name)
606 if (sourceCount >= MAX_SOURCE)
608 fprintf(stderr, "Too many source files\n");
617 fp = fopen(name, "r");
627 sourcefiles[sourceCount++] = fp;
629 ttyFlag = isatty(fileno(fp));
636 if (intFlag && !ttyFlag && (fp != stdin))
644 if (fgets(buf, CMD_LEN - 1, fp) == NULL)
646 if (ferror(fp) && (errno == EINTR))
658 if (buf[cc - 1] == '\n')
661 while ((cc > 0) && isBlank(buf[cc - 1]))
671 perror("Reading command line");
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.
694 command(const char * cmd)
698 char newCommand[CMD_LEN];
699 char cmdName[CMD_LEN];
702 * Rest the interrupt flag and free any memory chunks that
703 * were allocated by the previous command.
710 * Skip leading blanks.
712 while (isBlank(*cmd))
716 * If the command is empty or is a comment then ignore it.
718 if ((*cmd == '\0') || (*cmd == '#'))
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.
727 while (*endCmd && !isBlank(*endCmd))
730 memcpy(cmdName, cmd, endCmd - cmd);
732 cmdName[endCmd - cmd] = '\0';
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.
740 alias = findAlias(cmdName);
744 strcpy(newCommand, alias->value);
745 strcat(newCommand, endCmd);
751 * Expand simple environment variables
753 while (strstr(cmd, "$(")) expandVariable((char *)cmd);
756 * Now look for the command in the builtin table, and execute
757 * the command if found.
760 return 0; /* This is a blatant lie */
763 * The command is not a built-in, so run the program along
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.
776 tryBuiltIn(const char * cmd)
779 const CommandEntry * entry;
782 char cmdName[CMD_LEN];
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.
790 while (*endCmd && !isBlank(*endCmd))
793 memcpy(cmdName, cmd, endCmd - cmd);
795 cmdName[endCmd - cmd] = '\0';
798 * Search the command table looking for the command name.
800 for (entry = commandEntryTable; entry->name != NULL; entry++)
802 if (strcmp(entry->name, cmdName) == 0)
807 * If the command is not a built-in, return indicating that.
809 if (entry->name == NULL)
813 * The command is a built-in.
814 * Break the command up into arguments and expand wildcards.
816 if (!makeArgs(cmd, &argc, &argv))
820 * Give a usage string if the number of arguments is too large
823 if ((argc < entry->minArgs) || (argc > entry->maxArgs))
825 fprintf(stderr, "usage: %s %s\n", entry->name, entry->usage);
831 * Call the built-in function with the argument list.
833 entry->func(argc, argv);
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.
845 runCmd(const char * cmd)
853 * Check the command for any magic shell characters
854 * except for quoting.
858 for (cp = cmd; *cp; cp++)
860 if ((*cp >= 'a') && (*cp <= 'z'))
863 if ((*cp >= 'A') && (*cp <= 'Z'))
872 if ((*cp == '.') || (*cp == '/') || (*cp == '-') ||
873 (*cp == '+') || (*cp == '=') || (*cp == '_') ||
874 (*cp == ':') || (*cp == ',') || (*cp == '\'') ||
884 * If there were any magic characters used then run the
885 * command using the shell.
888 return trySystem(cmd);
891 * No magic characters were in the command, so we can do the fork
898 perror("fork failed");
904 * If we are the child process, then go execute the program.
910 * We are the parent process.
911 * Wait for the child to complete.
916 while (((pid = waitpid(pid, &status, 0)) < 0) && (errno == EINTR))
923 fprintf(stderr, "Error from waitpid: %s", strerror(errno));
928 if (WIFSIGNALED(status))
930 fprintf(stderr, "pid %ld: killed by signal %d\n",
931 (long) pid, WTERMSIG(status));
936 return WEXITSTATUS(status);
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.
946 childProcess(const char * cmd)
952 * Close any extra file descriptors we have opened.
954 while (--sourceCount >= 0)
956 if (sourcefiles[sourceCount] != stdin)
957 fclose(sourcefiles[sourceCount]);
961 * Break the command line up into individual arguments.
962 * If this fails, then run the shell to execute the command.
964 if (!makeArgs(cmd, &argc, &argv))
966 int status = trySystem(cmd);
975 * Try to execute the program directly.
977 execvp(argv[0], (char **) argv);
980 * The exec failed, so try to run the command using the shell
981 * in case it is a shell script.
983 if (errno == ENOEXEC)
985 int status = trySystem(cmd);
994 * There was something else wrong, complain and exit.
1002 do_help(int argc, const char ** argv)
1004 const CommandEntry * entry;
1013 * Check for an exact match, in which case describe the program.
1017 for (entry = commandEntryTable; entry->name; entry++)
1019 if (strcmp(str, entry->name) == 0)
1021 printf("%s\n", entry->description);
1023 printf("usage: %s %s\n", entry->name,
1032 * Print short information about commands which contain the
1035 for (entry = commandEntryTable; entry->name; entry++)
1037 if ((str == NULL) || (strstr(entry->name, str) != NULL) ||
1038 (strstr(entry->usage, str) != NULL))
1040 printf("%-10s %s\n", entry->name, entry->usage);
1049 do_alias(int argc, const char ** argv)
1061 for (alias = aliasTable; count-- > 0; alias++)
1062 printf("%s\t%s\n", alias->name, alias->value);
1071 alias = findAlias(name);
1074 printf("%s\n", alias->value);
1077 fprintf(stderr, "Alias \"%s\" is not defined\n", name);
1085 if (strcmp(name, "alias") == 0)
1087 fprintf(stderr, "Cannot alias \"alias\"\n");
1092 if (!makeString(argc - 2, argv + 2, buf, CMD_LEN))
1095 value = malloc(strlen(buf) + 1);
1099 fprintf(stderr, "No memory for alias value\n");
1106 alias = findAlias(name);
1111 alias->value = value;
1116 if ((aliasCount % ALIAS_ALLOC) == 0)
1118 count = aliasCount + ALIAS_ALLOC;
1122 alias = (Alias *) realloc(aliasTable,
1123 sizeof(Alias) * count);
1126 alias = (Alias *) malloc(sizeof(Alias) * count);
1131 fprintf(stderr, "No memory for alias table\n");
1139 alias = &aliasTable[aliasCount];
1141 alias->name = malloc(strlen(name) + 1);
1143 if (alias->name == NULL)
1146 fprintf(stderr, "No memory for alias name\n");
1151 strcpy(alias->name, name);
1152 alias->value = value;
1160 * Build aliases for all of the built-in commands which start with a dash,
1161 * using the names without the dash.
1164 do_aliasall(int argc, const char **argv)
1166 const CommandEntry * entry;
1168 const char * newArgv[4];
1170 for (entry = commandEntryTable; entry->name; entry++)
1177 newArgv[0] = "alias";
1178 newArgv[1] = name + 1;
1182 do_alias(3, newArgv);
1190 * Look up an alias name, and return a pointer to it.
1191 * Returns NULL if the name does not exist.
1194 findAlias(const char * name)
1201 for (alias = aliasTable; count-- > 0; alias++)
1203 if (strcmp(name, alias->name) == 0)
1212 do_source(int argc, const char ** argv)
1214 return readFile(argv[1]);
1219 do_exec(int argc, const char ** argv)
1225 while (--sourceCount >= 0)
1227 if (sourcefiles[sourceCount] != stdin)
1228 fclose(sourcefiles[sourceCount]);
1233 execvp(name, (char **) argv + 1);
1241 do_prompt(int argc, const char ** argv)
1246 if (!makeString(argc - 1, argv + 1, buf, CMD_LEN))
1249 cp = malloc(strlen(buf) + 2);
1253 fprintf(stderr, "No memory for prompt\n");
1271 do_unalias(int argc, const char ** argv)
1277 alias = findAlias(*++argv);
1285 alias->name = aliasTable[aliasCount].name;
1286 alias->value = aliasTable[aliasCount].value;
1294 * Display the prompt string.
1306 tryWrite(STDOUT, cp, strlen(cp));
1313 signal(SIGINT, catchInt);
1318 tryWrite(STDOUT, "\n", 1);
1325 signal(SIGQUIT, catchQuit);
1330 tryWrite(STDOUT, "\n", 1);
1335 * Print the usage information and quit.
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");
1349 * Expand one environment variable: Syntax $(VAR)
1352 expandVariable(char * cmd)
1359 cp = strstr(tmp, "$(");
1364 while (*ep && (*ep != ')')) ep++;
1365 if (*ep == ')') *ep++ = '\0';
1367 if (cp) strcat(cmd, cp);