microshell_project/main.c
2025-01-04 18:24:13 +01:00

277 lines
7.8 KiB
C

#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <termios.h>
#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;
}