diff --git a/main.c b/main.c new file mode 100644 index 0000000..6c4e9c4 --- /dev/null +++ b/main.c @@ -0,0 +1,283 @@ +/***************************************************************************//** + + @file main.c + + @author Stephen Brennan + + @date Thursday, 8 January 2015 + + @brief LSH (Libstephen SHell) + +*******************************************************************************/ + +#include +#include +#include +#include +#include +#include + +/* + Function Declarations for builtin shell commands: + */ +int lsh_cd(char **args); +int lsh_help(char **args); +int lsh_exit(char **args); + +/* + List of builtin commands, followed by their corresponding functions. + */ +char *builtin_str[] = { + "cd", + "help", + "exit" +}; + +int (*builtin_func[]) (char **) = { + &lsh_cd, + &lsh_help, + &lsh_exit +}; + +int lsh_num_builtins() { + return sizeof(builtin_str) / sizeof(char *); +} + +/* + Builtin function implementations. +*/ + +/** + @brief Builtin command: change directory. + @param args List of args. args[0] is "cd". args[1] is the directory. + @return Always returns 1, to continue executing. + */ +int lsh_cd(char **args) +{ + if (args[1] == NULL) { + fprintf(stderr, "lsh: expected argument to \"cd\"\n"); + } else { + if (chdir(args[1]) != 0) { + perror("lsh"); + } + } + return 1; +} + +/** + @brief Builtin command: print help. + @param args List of args. Not examined. + @return Always returns 1, to continue executing. + */ +int lsh_help(char **args) +{ + int i; + printf("Stephen Brennan's LSH\n"); + printf("Type program names and arguments, and hit enter.\n"); + printf("The following are built in:\n"); + + for (i = 0; i < lsh_num_builtins(); i++) { + printf(" %s\n", builtin_str[i]); + } + + printf("Use the man command for information on other programs.\n"); + return 1; +} + +/** + @brief Builtin command: exit. + @param args List of args. Not examined. + @return Always returns 0, to terminate execution. + */ +int lsh_exit(char **args) +{ + return 0; +} + +/** + @brief Launch a program and wait for it to terminate. + @param args Null terminated list of arguments (including program). + @return Always returns 1, to continue execution. + */ +int lsh_launch(char **args) +{ + pid_t pid; + int status; + + pid = fork(); + if (pid == 0) { + // Child process + if (execvp(args[0], args) == -1) { + perror("lsh"); + } + exit(EXIT_FAILURE); + } else if (pid < 0) { + // Error forking + perror("lsh"); + } else { + // Parent process + do { + waitpid(pid, &status, WUNTRACED); + } while (!WIFEXITED(status) && !WIFSIGNALED(status)); + } + + return 1; +} + +/** + @brief Execute shell built-in or launch program. + @param args Null terminated list of arguments. + @return 1 if the shell should continue running, 0 if it should terminate + */ +int lsh_execute(char **args) +{ + int i; + + if (args[0] == NULL) { + // An empty command was entered. + return 1; + } + + for (i = 0; i < lsh_num_builtins(); i++) { + if (strcmp(args[0], builtin_str[i]) == 0) { + return (*builtin_func[i])(args); + } + } + + return lsh_launch(args); +} + +/** + @brief Read a line of input from stdin. + @return The line from stdin. + */ +char *lsh_read_line(void) +{ +#ifdef LSH_USE_STD_GETLINE + char *line = NULL; + ssize_t bufsize = 0; // have getline allocate a buffer for us + if (getline(&line, &bufsize, stdin) == -1) { + if (feof(stdin)) { + exit(EXIT_SUCCESS); // We received an EOF + } else { + perror("lsh: getline\n"); + exit(EXIT_FAILURE); + } + } + return line; +#else +#define LSH_RL_BUFSIZE 1024 + int bufsize = LSH_RL_BUFSIZE; + int position = 0; + char *buffer = malloc(sizeof(char) * bufsize); + int c; + + if (!buffer) { + fprintf(stderr, "lsh: allocation error\n"); + exit(EXIT_FAILURE); + } + + while (1) { + // Read a character + c = getchar(); + + if (c == EOF) { + exit(EXIT_SUCCESS); + } else if (c == '\n') { + buffer[position] = '\0'; + return buffer; + } else { + buffer[position] = c; + } + position++; + + // If we have exceeded the buffer, reallocate. + if (position >= bufsize) { + bufsize += LSH_RL_BUFSIZE; + buffer = realloc(buffer, bufsize); + if (!buffer) { + fprintf(stderr, "lsh: allocation error\n"); + exit(EXIT_FAILURE); + } + } + } +#endif +} + +#define LSH_TOK_BUFSIZE 64 +#define LSH_TOK_DELIM " \t\r\n\a" +/** + @brief Split a line into tokens (very naively). + @param line The line. + @return Null-terminated array of tokens. + */ +char **lsh_split_line(char *line) +{ + int bufsize = LSH_TOK_BUFSIZE, position = 0; + char **tokens = malloc(bufsize * sizeof(char*)); + char *token, **tokens_backup; + + if (!tokens) { + fprintf(stderr, "lsh: allocation error\n"); + exit(EXIT_FAILURE); + } + + token = strtok(line, LSH_TOK_DELIM); + while (token != NULL) { + tokens[position] = token; + position++; + + if (position >= bufsize) { + bufsize += LSH_TOK_BUFSIZE; + tokens_backup = tokens; + tokens = realloc(tokens, bufsize * sizeof(char*)); + if (!tokens) { + free(tokens_backup); + fprintf(stderr, "lsh: allocation error\n"); + exit(EXIT_FAILURE); + } + } + + token = strtok(NULL, LSH_TOK_DELIM); + } + tokens[position] = NULL; + return tokens; +} + +/** + @brief Loop getting input and executing it. + */ +void lsh_loop(void) +{ + char *line; + char **args; + int status; + + do { + printf("> "); + line = lsh_read_line(); + args = lsh_split_line(line); + status = lsh_execute(args); + + free(line); + free(args); + } while (status); +}s + +/** + @brief Main entry point. + @param argc Argument count. + @param argv Argument vector. + @return status code + */ +int main(int argc, char **argv) +{ + // Load config files, if any. + + // Run command loop. + lsh_loop(); + + // Perform any shutdown/cleanup. + + return EXIT_SUCCESS; +}