From 10d9c9dec9270a1a652b369e5e184d5df4a02fd0 Mon Sep 17 00:00:00 2001 From: night0721 Date: Wed, 31 Jan 2024 01:02:32 +0000 Subject: [PATCH] Initial commit --- Makefile | 4 + README.md | 36 ++++++ color.c | 22 ++++ color.h | 10 ++ constants.h | 9 ++ history.c | 91 +++++++++++++ history.h | 8 ++ rush | Bin 0 -> 22568 bytes rush.c | 362 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 9 files changed, 542 insertions(+) create mode 100644 Makefile create mode 100644 README.md create mode 100644 color.c create mode 100644 color.h create mode 100644 constants.h create mode 100644 history.c create mode 100644 history.h create mode 100755 rush create mode 100644 rush.c diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..bfed4df --- /dev/null +++ b/Makefile @@ -0,0 +1,4 @@ +rush: rush.c color.c constants.h history.c + gcc -o rush rush.c color.c history.c +all: + rush diff --git a/README.md b/README.md new file mode 100644 index 0000000..46fabdc --- /dev/null +++ b/README.md @@ -0,0 +1,36 @@ +# rush + +rush is a minimalistic shell for Unix systems written in C. + +# Dependencies +- gcc + +# Building +```sh +$ make +``` + +# Usage +```sh +$ ./rush +``` + +# Features +- Showing current time and directory with custom color +- syntax highlighting on valid commands using ANSI colors +- history navigation using up and down keys + +# Todo Features +- Pipe +- stdin, stdout, stderr redirect +- background jobs +- editing using left and right arrow keys +- history command +- export command to setenv +- tab completion + +# Credits +- [Tutorial - Write a shell in C](https://brennan.io/2015/01/16/write-a-shell-in-c/) +- [dash](https://github.com/danishprakash/dash) +- [Shell assignment](https://www.cs.cornell.edu/courses/cs414/2004su/homework/shell/shell.html) +- [khol](https://github.com/SanketDG/khol/) diff --git a/color.c b/color.c new file mode 100644 index 0000000..667b47a --- /dev/null +++ b/color.c @@ -0,0 +1,22 @@ +#include +#include +#include + +// color str in place + +void color_text(char str[], const char *color) { + int size = snprintf(NULL, 0, "\x1b[38;2;%sm%s\x1b[0m", color, str) + 1; // calculate size that is needed for colored string + if (size < 0) { + fprintf(stderr, "rush: snprintf failed\n"); + exit(EXIT_FAILURE); + } + char *buf = malloc(size); + if (buf == NULL) { + fprintf(stderr, "rush: Memory allocation failed\n"); + exit(EXIT_FAILURE); + } + + snprintf(buf, size, "\x1b[38;2;%sm%s\x1b[0m", color, str); // format string to buf + strcpy(str, buf); + free(buf); +} diff --git a/color.h b/color.h new file mode 100644 index 0000000..e39d738 --- /dev/null +++ b/color.h @@ -0,0 +1,10 @@ +#ifndef COLOR_H_ +#define COLOR_H_ + +const char *lavender = "174;174;255"; +const char *pink = "255;210;239"; +const char *blue = "137;180;250"; + +void color_text(char str[], const char *color); + +#endif diff --git a/constants.h b/constants.h new file mode 100644 index 0000000..f638a7c --- /dev/null +++ b/constants.h @@ -0,0 +1,9 @@ +#ifndef CONSTANTS_H_ +#define CONSTANTS_H_ + +#define HISTFILE ".rush_history" // history file name +#define TOK_BUFSIZE 64 // buffer size of each token +#define RL_BUFSIZE 1024 +#define TOK_DELIM " \t\r\n\a" // delimiter for token + +#endif diff --git a/history.c b/history.c new file mode 100644 index 0000000..d00ab12 --- /dev/null +++ b/history.c @@ -0,0 +1,91 @@ +#include +#include +#include +#include + +#include "constants.h" + +FILE *history_file; +char *histfile_path; +int cmd_count = 0; + +void check_history_file() { + char *env_home; + env_home = getenv("XDG_CONFIG_HOME"); + if (env_home == NULL) { + // fallback to $HOME if $XDG_CONFIG_HOME is null + env_home = getenv("HOME"); + } + int env_home_len = strlen(env_home); + int histfilename_len = strlen(HISTFILE); + int path_len = env_home_len + histfilename_len + 2; // 2 for slash and null byte + histfile_path = malloc(sizeof(char) * path_len); + // concatenate home and history file name to a path + strcat(histfile_path, env_home); + strcat(histfile_path, "/"); + strcat(histfile_path, HISTFILE); + histfile_path[path_len] = '\0'; + if (access(histfile_path, F_OK) != 0) { // check for file existence + history_file = fopen(histfile_path, "w"); // read and write, if doesn't exist, create + if (history_file == NULL) { + fprintf(stderr, "rush: Error opening history file\n"); + exit(EXIT_FAILURE); + } + fclose(history_file); + } +} + +void save_command_history(char *command) { + history_file = fopen(histfile_path, "a+"); + if (history_file == NULL) { + fprintf(stderr, "rush: Error opening history file\n"); + exit(EXIT_FAILURE); + } + int cmd_len = strlen(command); + command[cmd_len] = '\n'; // put new line feed to split commands + // ptr to first obj, size of each obj, number of obj, file ptr + fwrite(command, sizeof(char), cmd_len + 1, history_file); + fclose(history_file); +} + +char *read_command(int direction) { + history_file = fopen(histfile_path, "rb"); // read binary mode + if (history_file == NULL) { + fprintf(stderr, "rush: Error opening history file\n"); + exit(EXIT_FAILURE); + } + // normal bufsize is 1024, we serach for 1025 bytes for new line feed + int search_len = RL_BUFSIZE + 1; + char search[search_len]; + fseek(history_file, -search_len, SEEK_END); // go back 1025 characters from end of file + int count = fread(search, 1, search_len - 1, history_file); // try to read 1025 characters from file, returning count number of bytes + search[count] = '\0'; + char *last_nlf = strrchr(search, '\n'); // locate last occurence of \n in a searching string + if (last_nlf == NULL) { + // no history + return NULL; + } + if (direction == 1) { // up + cmd_count++; + } else { // down + if (cmd_count == 0) { + return NULL; + } else { + cmd_count--; + } + } + for (int i = 0; i < cmd_count; i++) { + search[last_nlf - search] = '\0'; // terminate string earlier to find second last \n, search points to first char and last_nlf is last \n, difference is the index of \n + last_nlf = strrchr(search, '\n'); // call strrchr 2 times to get second last new line feed in search string as every life is new line feed + if ((last_nlf - search) == (strchr(search, '\n') - search)) { + // check if the first \n is the last \n we searching for, if yes it is first command + cmd_count--; + search[last_nlf - search] = '\0'; // terminate string earlier to find second last \n, search points to first char and last_nlf is last \n, difference is the index of \n + char *first_cmd = malloc(sizeof(char) * (last_nlf - search) + 1); + strcpy(first_cmd, search); + return first_cmd; + } + } + fclose(history_file); + return last_nlf + 1; // return the string from the new line feed +} diff --git a/history.h b/history.h new file mode 100644 index 0000000..bf057ea --- /dev/null +++ b/history.h @@ -0,0 +1,8 @@ +#ifndef HISTORY_H_ +#define HISTORY_H_ + +void save_command_history(char *command); +void check_history_file(); +char *read_command(int direction); + +#endif diff --git a/rush b/rush new file mode 100755 index 0000000000000000000000000000000000000000..eb13a7901964288738b88b71a5a362dbf9726de9 GIT binary patch literal 22568 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 literal 0 HcmV?d00001 diff --git a/rush.c b/rush.c new file mode 100644 index 0000000..fe63f67 --- /dev/null +++ b/rush.c @@ -0,0 +1,362 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "color.h" +#include "constants.h" +#include "history.h" + +/* + * Function Declarations for builtin shell commands: + */ +int rush_cd(char **args); +int help(char **args); +int rush_exit(char **args); + + +/* + * List of builtin commands, followed by their corresponding functions. + */ +char *builtin_str[] = { + "cd", + "help", + "exit" +}; + +int (*builtin_func[]) (char **) = { + &rush_cd, + &help, + &rush_exit +}; + +int rush_num_builtins() { + return sizeof(builtin_str) / sizeof(char *); +} + +// change directory +int rush_cd(char **args) { + if (args[1] == NULL) { + char *home = getenv("HOME"); + if (chdir(home) != 0) { + perror("rush"); + } + } else { + if (chdir(args[1]) != 0) { + perror("rush"); + } + } + return 1; +} + +// show help menu +int help(char **args) { + printf("rush v0.01-alpha\n"); + printf("Built in commands:\n"); + + for (int i = 0; i < rush_num_builtins(); i++) { + printf(" %s\n", builtin_str[i]); + } + + printf("Use 'man' to read manual of programs\n"); + printf("Licensed under GPL v3\n"); + return 1; +} + +int rush_exit(char **args) { + return 0; // exit prompting loop, which also the shell +} + +/** + @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 rush_launch(char **args) { + pid_t pid, wpid; + int status; + + pid = fork(); + if (pid == 0) { + // Child process + if (execvp(args[0], args) == -1) { + if (errno == ENOENT) { + fprintf(stderr, "rush: command not found: %s\n", args[0]); + } + } + exit(EXIT_FAILURE); + } else if (pid < 0) { + perror("Cannot fork"); + } else { + // Parent process + do { + wpid = 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 rush_execute(char **args) { + if (args[0] == NULL) { + // An empty command was entered. + return 1; + } + + for (int i = 0; i < rush_num_builtins(); i++) { + if (strcmp(args[0], builtin_str[i]) == 0) { + return (*builtin_func[i])(args); + } + } + + return rush_launch(args); +} + +void change_terminal_attribute(int option) { + static struct termios oldt, newt; + tcgetattr(STDIN_FILENO, &oldt); + if (option) { + newt = oldt; + newt.c_lflag &= ~(ICANON | ECHO); // allows getchar without pressing enter key and echoing the character twice + tcsetattr(STDIN_FILENO, TCSANOW, &newt); // set settings to stdin + } else { + tcsetattr(STDIN_FILENO, TCSANOW, &oldt); // restore to old settings + } +} + +char **setup_path_variable() { + char *envpath = getenv("PATH"); + char *path_cpy = malloc(sizeof(char) * (strlen(envpath) + 1)); + char *path = malloc(sizeof(char) * (strlen(envpath) + 1)); + strcpy(path_cpy, envpath); + strcpy(path, envpath); + int path_count = 0; + while (*path_cpy != '\0') { + // count number of : to count number of elements + if (*path_cpy == ':') { + path_count++; + } + path_cpy++; + } + path_count += 2; // adding one to be correct and one for terminator + char **paths = malloc(sizeof(char *) * path_count); + char *token = strtok(path, ":"); + int counter = 0; + while (token != NULL) { + paths[counter] = token; // set element to the pointer of start of path + token = strtok(NULL, ":"); + counter++; + } + paths[counter] = NULL; + return paths; +} + +bool find_command(char **paths, char *command) { + if (strcmp(command, "") == 0) { + return false; + } + int counter = 0; + while (*paths != NULL) { + char current_path[PATH_MAX]; + sprintf(current_path, "%s/%s", *paths, command); + if (access(current_path, X_OK) == 0) { + // command is executable + return true; + } + paths++; + } + return false; +} + +char *rush_read_line(char **paths) { + int bufsize = RL_BUFSIZE; + int position = 0; + char *buffer = malloc(sizeof(char) * bufsize); + int c; + + if (!buffer) { + fprintf(stderr, "rush: allocation error\n"); + exit(EXIT_FAILURE); + } + + buffer[0] = '\0'; + while (1) { + c = getchar(); // read a character + int buf_len = strlen(buffer); + if (buf_len > 0) { + printf("\033[%dD", strlen(buffer)); // move cursor to the beginning + printf("\033[K"); // clear line to the right of cursor + } + // check each character user has input + // printf("%i\n", c); + switch (c) { + case EOF: + exit(EXIT_SUCCESS); + case 10: // enter/new line feed + buffer[buf_len] = '\0'; + // clear all characters after the command + for (int start = buf_len + 1; buffer[start] != '\0'; start++) { + buffer[start] = '\0'; + } + printf("%s\n", buffer); // print back the command in prompt + save_command_history(buffer); + return buffer; + case 127: // backspace + if (buf_len >= 1) { + buffer[buf_len - 1] = '\0'; // putting null character at last character to act as backspace + } + break; + case 27: // arrow keys comes at three characters, 27, 91, then 65-68 + if (getchar() == 91) { + int arrow_key = getchar(); + if (arrow_key == 65) { // up + // read history file and fill prompt with latest command + char *last_command = read_command(1); + if (last_command != NULL) { + strcpy(buffer, last_command); + buf_len = strlen(buffer); + } + break; + } else if (arrow_key == 66) { // down + char *last_command = read_command(0); + if (last_command != NULL) { + strcpy(buffer, last_command); + buf_len = strlen(buffer); + } + break; + } else if (arrow_key == 67) { // right + break; + } else if (arrow_key == 68) { // left + break; + } + } + default: + if (c > 31 && c < 127) { + buffer[buf_len] = c; + buffer[buf_len + 1] = '\0'; // make sure printf don't print random characters + } + } + char *cmd_part = strchr(buffer, ' '); + bool valid; + if (cmd_part != NULL) { + char *cmd = malloc(sizeof(char) * (cmd_part - buffer + 1)); + for (int i = 0; i < (cmd_part - buffer); i++) { + cmd[i] = buffer[i]; + } + cmd[cmd_part - buffer] = '\0'; + valid = find_command(paths, cmd); + } else { + valid = find_command(paths, buffer); + } + if (valid) { + printf("\x1b[38;2;000;255;000m%s\x1b[0m", buffer); // print green as valid command + } else { + printf("\x1b[38;2;255;000;000m%s\x1b[0m", buffer); // print red as sinvalid command + } + fflush(stdout); + + // If we have exceeded the buffer, reallocate. + if ((buf_len + 1) >= bufsize) { + bufsize += RL_BUFSIZE; + buffer = realloc(buffer, bufsize); + if (!buffer) { + fprintf(stderr, "rush: allocation error\n"); + exit(EXIT_FAILURE); + } + } + } +} + +// split line into arguments +char **rush_split_line(char *line) { + int bufsize = TOK_BUFSIZE, position = 0; + char **tokens = malloc(bufsize * sizeof(char*)); + char *token; + + if (!tokens) { + fprintf(stderr, "rush: Allocation error\n"); + exit(EXIT_FAILURE); + } + + token = strtok(line, TOK_DELIM); + while (token != NULL) { + tokens[position] = token; + position++; + + if (position >= bufsize) { + bufsize += TOK_BUFSIZE; + tokens = realloc(tokens, bufsize * sizeof(char*)); + if (!tokens) { + fprintf(stderr, "rush: Allocation error\n"); + exit(EXIT_FAILURE); + } + } + + token = strtok(NULL, TOK_DELIM); + } + tokens[position] = NULL; + return tokens; +} + +// continously prompt for command and execute it +void command_loop(char **paths) { + char *line; + char **args; + int status = 1; + + while (status) { + time_t t = time(NULL); + struct tm* current_time = localtime(&t); // get current time + char timestr[256]; + char cwdstr[PATH_MAX]; + if (strftime(timestr, sizeof(timestr), "[%H:%M:%S]", current_time) == 0) { // format time string + return; + } + if (getcwd(cwdstr, sizeof(cwdstr)) == NULL) { // get current working directory + return; + } + char time[256]; + strcpy(time, timestr); + color_text(time, lavender); // lavender colored time string + char *cwd = malloc(sizeof(char) * PATH_MAX); + sprintf(cwd, "[%s]", cwdstr); + color_text(cwd, pink); // pink colored current directory + char arrow[32] = "ยป"; + color_text(arrow, blue); + printf("%s %s %s ", time, cwd, arrow); + + + line = rush_read_line(paths); + args = rush_split_line(line); + status = rush_execute(args); + + free(line); + free(args); + free(cwd); + }; +} + + +int main(int argc, char **argv) { + // setup + check_history_file(); + char **paths = setup_path_variable(); + change_terminal_attribute(1); // turn off echoing and disabling getchar requires pressing enter key to return + + command_loop(paths); + + // cleanup + change_terminal_attribute(0); // change back to default settings + return EXIT_SUCCESS; +}