277 lines
7.8 KiB
C
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;
|
|
} |