#include #include #include #include #include #include #include #include #define MAX_SIZE 1024 #define HISTORY_SIZE 5 // RAW TERMINAL INPUT / LOGGING HISTORY IMPLEMENTATION ----------- // Global variable for termios struct termios orig_termios; // Termios functions void EnableRawMode(struct termios* orig_termios) { struct termios raw = *orig_termios; raw.c_lflag &= ~(ECHO | ICANON); tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw); } void DisableRawMode(struct termios* orig_termios) { tcsetattr(STDIN_FILENO, TCSAFLUSH, orig_termios); } void DisableRawModeWrapper() { DisableRawMode(&orig_termios); } int LogCommandHistory(char* buf, char** command_log, int* n) { int index = *n % HISTORY_SIZE; int prev_index = (*n == 0) ? HISTORY_SIZE - 1 : (*n - 1) % HISTORY_SIZE; if (command_log[prev_index] != NULL && strcmp(buf, command_log[prev_index]) == 0) { return 0; } if (command_log[index] != NULL) { free(command_log[index]); } command_log[index] = strdup(buf); (*n)++; return 0; } void FreeCommandHistory(char** command_log) { for (int i = 0; i < HISTORY_SIZE; i++) { if (command_log[i] != NULL) { free(command_log[i]); } } } void ClearBufferContent(int length) { // Function to clear buffer for (int i = 0; i < length; i++) { printf("\b \b"); } } // Get raw input from terminal + arrow key history navigation void GetRawInput(char* buf, char** command_log, int n) { int index = n % HISTORY_SIZE; int history_counter = 0; int pos = 0; char c; while (1) { c = getchar(); if (c == '\n') { // Enter key buf[pos] = '\0'; printf("\n"); return; } else if (c == 127 || c == '\b') { // Backspace if (pos > 0) { pos--; buf[pos] = '\0'; printf("\b \b"); } } else if (c == '\033') { // Inputting arrow keys getchar(); // Skip '[' character char arrow = getchar(); if (arrow == 'A') { // Up arrow int prev_index = (index == 0) ? HISTORY_SIZE - 1 : (index - 1) % HISTORY_SIZE; if (command_log[prev_index] != NULL) { ClearBufferContent(pos); // Clear current buffer index = (index == 0) ? HISTORY_SIZE - 1 : index - 1; history_counter++; strcpy(buf, command_log[index]); printf("%s", buf); pos = strlen(buf); } } else if (arrow == 'B') { // Down arrow if (history_counter > 0) { index = (index + 1) % HISTORY_SIZE; history_counter--; ClearBufferContent(pos); if (history_counter == 0) { buf[0] = '\0'; // Clear buffer pos = 0; } else { strcpy(buf, command_log[index]); printf("%s", buf); pos = strlen(buf); } } } } else { // Regular character if (pos < MAX_SIZE - 1) { buf[pos++] = c; buf[pos] = '\0'; printf("%c", c); } } } } // MAIN MICROSHELL FUNCTIONS ----------- char* GetCurrentUserAndDir() { // return current user + working directory char cwd[MAX_SIZE]; if (getcwd(cwd, sizeof(cwd)) == NULL) { perror("getcwd() error"); return NULL; } char* user = getenv("USER"); if (user == NULL) { perror("getenv() error"); return NULL; } size_t result_size = strlen(user) + strlen(cwd) + 7; // Allocate memory (include extra space for format characters) char* result = (char*)malloc(result_size); snprintf(result, result_size, "[%s@%s] $ ", user, cwd); // Format the prompt string return result; } int ChangeDirectory(char** args) { // cd args++; // Moving pointer to the next element (cd requires local or global path, the first argument is command name, so it's skipped) char* path = args[0]; if (path == NULL || strcmp(path, "~") == 0) { path = getenv("HOME"); } if (chdir(path) != 0) { perror("cd"); return -1; } return 0; } void PrintHelp() { // help printf("-------------------------------------------------\n"); printf("C Microshell implementation by Maciej Życzyński\n"); printf("\"exit\" - exit the program\n"); printf("\"help\" - display this menu\n"); printf("Other bash commands work accordingly in child processes\n"); printf("-------------------------------------------------\n"); } int HandleBuiltInCommands(char* command, char** args) { // Manual implementation of commands unsupported by execvp() if (strcmp(command, "cd") == 0) { ChangeDirectory(args); return 1; } else if (strcmp(command, "help") == 0) { PrintHelp(); return 1; } else if (strcmp(command, "exit") == 0) { return 2; } return 0; } int HandleCommand(char* command, char** args) { // Execute commands supported by execvp() if (execvp(command, args) == -1) { printf("Error executing command\n"); return -1; } return 0; } int ParseCommand(char* buf, char** command, char** args) { // Parse input string into command and array of arguments char buf_cp[MAX_SIZE]; strcpy(buf_cp, buf); // Leave the original buffer (important for command history implementation) char* token = strtok(buf_cp, " "); int tok_count = 0; if (token == NULL) { // Retrieve command and arguments return -1; } *command = token; while (token != NULL) { args[tok_count] = token; tok_count++; token = strtok(NULL, " "); } args[tok_count] = NULL; // Manually add NULL at the end (execvp() requires the argument array to be null-terminated) return 0; } int main() { char buf[MAX_SIZE]; char* command; char* args[MAX_SIZE]; int n = 0; char* command_log[HISTORY_SIZE] = {NULL}; // Save original terminal settings and restore them on exit if (tcgetattr(STDIN_FILENO, &orig_termios) == -1) { perror("tcgetattr() error"); return 1; } atexit(DisableRawModeWrapper); // call DisableRawModeWrapper when program exits while (1) { char* prompt = GetCurrentUserAndDir(); // Display prompt if (prompt != NULL) { printf("%s", prompt); free(prompt); } EnableRawMode(&orig_termios); GetRawInput(buf, command_log, n); DisableRawMode(&orig_termios); if (ParseCommand(buf, &command, args) < 0) { // Parse buffer input printf("Invalid command\n"); continue; } LogCommandHistory(buf, command_log, &n); // Add buffer to logging history int handler_value = HandleBuiltInCommands(command, args); if (handler_value == 2) { // Exit command break; } else if (handler_value == 0) { // Not a built-in command // Forking logic pid_t pid = fork(); if (pid < 0) { perror("Error while forking\n"); } else if (pid == 0) { // Child process if (HandleCommand(command, args) == -1) { exit(EXIT_FAILURE); } exit(EXIT_SUCCESS); } else { // Parent process int status; if (waitpid(pid, &status, 0) == -1) { perror("Error with parent process\n"); } } } } FreeCommandHistory(command_log); return 0; }