From e9aa5cc4c0c391ba2e15804107da17a8e3401d23 Mon Sep 17 00:00:00 2001 From: Stefan 'psYchotic' Zwanenburg Date: Sat, 30 Jan 2010 13:44:36 +0100 Subject: [PATCH] New '.' builtin, and 'exit' builtin changed: - there is now a '.' builtin, which executes the commands in the file its argument points to in the current shell. - the 'exit' builtin now accepts a single argument, which will be the exit status of the current shell. --- builtins.h | 1 + ss.c | 104 +++++++++++++++++++++++++++++++++++++++++++++++++++---------- 2 files changed, 89 insertions(+), 16 deletions(-) diff --git a/builtins.h b/builtins.h index a885fea..0782676 100644 --- a/builtins.h +++ b/builtins.h @@ -10,6 +10,7 @@ int cd_cmd(char **argv); int exit_cmd(char **argv); int help_cmd(char **argv); int kill_cmd(char **argv); +int source_cmd(char **argv); int status_cmd(char **argv); int true_cmd(char **argv); #endif diff --git a/ss.c b/ss.c index 6fc96ad..f84dc25 100644 --- a/ss.c +++ b/ss.c @@ -38,9 +38,20 @@ #include #include +/** + * Returns true if the argument is an empty SH_BUILTIN struct. + */ #define IS_LAST_BUILTIN(builtin) ((builtin).name == NULL && \ (builtin).command == NULL && \ (builtin).help == NULL) +/** + * Returns true if last_status indicates that the shell is done reading input. + */ +#define IS_DONE ((last_status >> 8) & 1) +/** + * Returns true if last_status indicates that the shell should exit. + */ +#define IS_EXIT ((last_status >> 9) & 1) static const struct SH_BUILTIN builtins[] = { {"cd", cd_cmd, "Usage: cd \nChanges the current working directory to DIR.\n"}, @@ -49,6 +60,7 @@ static const struct SH_BUILTIN builtins[] = { {"kill", kill_cmd, "Usage: kill ...\nSends the SIGTERM signal to all the PIDs.\n"}, {"status", status_cmd, "Usage: status\nPrints the exit status of the last command.\n"}, {":", true_cmd, "Usage: :\nDoes absolutely nothing.\n"}, + {".", source_cmd, "Usage: . \nReads a file and executes whatever is inside it.\n"}, {NULL, NULL, NULL} }; @@ -138,6 +150,10 @@ char **split_input(char *input) { return split; } +/** + * Prints help about this shell or one of its builtins. + * @return always zero + */ int help_cmd(char **argv) { int i; if (argv[0] == NULL) { @@ -163,14 +179,25 @@ int help_cmd(char **argv) { } /** - * Exits the shell. - * @return non-zero on success (no arguments were passed), zero otherwise. + * Exits the shell, unless an incorrect argument or too many arguments specified. + * @return zero if no argument was specified, + * the argument & 0xFF (see man 3 exit for details) if specified, + * one if there were more than one arguments or if the one argument could not + * be parsed, in which two cases no actual exiting occurs. */ int exit_cmd(char **argv) { - if (argv[0] != NULL) { - fprintf(stderr, "The 'exit' builtin does not take any arguments.\n"); - return 0; + int exit_status; + if (argv[0] == NULL) { + return (3 << 8); + } else if (argv[0] != NULL && argv[1] == NULL) { + if (!sscanf(argv[0], "%d", &exit_status)) { + fprintf(stderr, "Could not parse the argument ('%s') as an integer.\n", argv[0]); + return 1; + } else { + return (3 << 8) | (exit_status & 0xFF); + } } else { + fprintf(stderr, "The 'exit' builtin takes at most one argument. See 'help exit' for details.\n"); return 1; } } @@ -205,13 +232,25 @@ int kill_cmd(char **argv) { return 0; } +/** + * Does nothing, and always succeeds, even if it has arguments. + * Note that the arguments *may* be executed if the shell implements some + * sort of expansion. Use '#' to create comments. + * @return always zero + */ int true_cmd(char **argv) { return 0; } +/** + * Prints the exit status of the last command. + * @return nonzero if an argument was specified (in which case it doesn't + * actually print the last exit status), zero otherwise. + */ int status_cmd(char **argv) { if (argv[0] != NULL) { fprintf(stderr, "The 'status' builtin doesn't take any arguments."); + return 1; } else { printf("%d\n", last_status); } @@ -257,19 +296,16 @@ int cd_cmd(char **argv) { * - the process forks and launches the application * specified in the input string * @param input a string representing a single line of input - * @return zero to indicate the main program should continue, - * non-zero otherwise. */ -int handle_input(char *input) { - int return_value = 0; +void handle_input(char *input) { pid_t pid; char **argv; int wait_status; int i; if (input == NULL) { printf("exit\n"); - return_value = 1; - } else if (input[0] == '\0') { + last_status = (1 << 8); + } else if (input[0] == '\0' || input[0] == '#') { /* do nothing */ free(input); } else { @@ -280,7 +316,7 @@ int handle_input(char *input) { free(input); for (i = 0; !IS_LAST_BUILTIN(builtins[i]); i++) { if (!strcmp(argv[0], builtins[i].name)) { - last_status = return_value = builtins[i].command(&argv[1]); + last_status = builtins[i].command(&argv[1]); break; } } @@ -290,7 +326,7 @@ int handle_input(char *input) { if (execvp(argv[0], argv)) { fprintf(stderr, "Could not execute '%s': %s\n", argv[0], strerror(errno)); } - return_value = 1; + last_status = (1 << 8); } else { while (!waitpid(pid, &wait_status, 0) && WIFEXITED(wait_status)); last_status = WEXITSTATUS(wait_status); @@ -301,8 +337,39 @@ int handle_input(char *input) { } free(argv); } +} - return return_value; +/** + * Executes the contents of the argument file in the current shell. + * Note that the 'exit' builtin within these files does not currently + * actually exit the current shell, it merely stops the parsing of the file. + * @return + */ +int source_cmd(char **argv) { + FILE *input; + char *line; + if (argv[0] == NULL || argv[1] != NULL) { + fprintf(stderr, "The '.' builtin takes exactly one argument. See 'help .' for details.\n"); + } else { + input = fopen(argv[0], "r"); + if (input == NULL) { + fprintf(stderr, "Could not open '%s' for sourcing: %s\n", argv[0], strerror(errno)); + } else { + rl_instream = input; + do { + line = readline(NULL); + handle_input(line); + } while (!IS_DONE); + fclose(input); + rl_instream = stdin; + } + } + + if (IS_EXIT) { + return last_status; + } else { + return last_status & 0xFF; + } } /** @@ -370,6 +437,10 @@ void print_usage(char *progname) { printf(" -h Display this help\n"); } +/** + * Handle SIGINT. + * Discards the current readline input and redisplays the prompt. + */ void handle_interrupt(int signal) { printf("\n"); rl_on_new_line(); @@ -422,7 +493,8 @@ int main(int argc, char **argv) { do { input = get_input(prompt); - } while (!handle_input(input)); + handle_input(input); + } while (!IS_DONE); - return 0; + return last_status & 0xFF; } -- 2.11.4.GIT