From 7f14ff82bf6b1f23a70d5b3f48651373b16a3862 Mon Sep 17 00:00:00 2001 From: Fabio Scotto di Santolo Date: Fri, 6 Sep 2024 17:46:55 +0200 Subject: [PATCH] Write a shell with history capabilities --- process_chp3/osh_shell/.gitignore | 2 + process_chp3/osh_shell/Makefile | 18 ++++ process_chp3/osh_shell/history.c | 109 ++++++++++++++++++++++ process_chp3/osh_shell/history.h | 29 ++++++ process_chp3/osh_shell/osh.c | 147 ++++++++++++++++++++++++++++++ 5 files changed, 305 insertions(+) create mode 100644 process_chp3/osh_shell/.gitignore create mode 100644 process_chp3/osh_shell/Makefile create mode 100644 process_chp3/osh_shell/history.c create mode 100644 process_chp3/osh_shell/history.h create mode 100644 process_chp3/osh_shell/osh.c diff --git a/process_chp3/osh_shell/.gitignore b/process_chp3/osh_shell/.gitignore new file mode 100644 index 0000000..3eec607 --- /dev/null +++ b/process_chp3/osh_shell/.gitignore @@ -0,0 +1,2 @@ +osh_history +osh diff --git a/process_chp3/osh_shell/Makefile b/process_chp3/osh_shell/Makefile new file mode 100644 index 0000000..5b91c5f --- /dev/null +++ b/process_chp3/osh_shell/Makefile @@ -0,0 +1,18 @@ +CC=gcc +CFLAGS=-o osh -Wall +SRCS=history.c osh.c + +.DEFAULT_GOAL := build + +build: + $(CC) $(CFLAGS) -fsanitize=address -static-libasan $(SRCS) + +release: + $(CC) $(CFLAGS) $(SRCS) + +debug: + $(CC) $(CFLAGS) -fsanitize=address -static-libasan -ggdb $(SRCS) + gdb -q osh + +clean: + rm -f osh diff --git a/process_chp3/osh_shell/history.c b/process_chp3/osh_shell/history.c new file mode 100644 index 0000000..1e1b99d --- /dev/null +++ b/process_chp3/osh_shell/history.c @@ -0,0 +1,109 @@ +#include "history.h" +#include +#include +#include +#include + +#define HISTORY_FILENAME "osh_history" +#define W_MODE "w" +#define R_MODE "r" +#define A_MODE "a+" + +int allocate_history(void) { + FILE *file; + if (access(HISTORY_FILENAME, F_OK) == -1) { + if ((file = fopen(HISTORY_FILENAME, W_MODE)) == NULL) { + perror("Error"); + return -1; + } + fclose(file); + } + + if ((file = fopen(HISTORY_FILENAME, R_MODE)) == NULL) { + perror("Error"); + return -1; + } + + int count = -1; + char line[MAXLEN]; + while (!feof(file)) { + fgets(line, sizeof(line), file); + count++; + } + + fclose(file); + return count; +} + +history_entry_t *get_history(int history_count) { + FILE *file = fopen(HISTORY_FILENAME, R_MODE); + if (!file) { + perror("Error"); + exit(EXIT_FAILURE); + } + + int num = 0; + char line[MAXLEN]; + const int offset = history_count - HISTORY_SIZE; + history_entry_t *history = calloc(HISTORY_SIZE, sizeof(history_entry_t)); + while (fgets(line, sizeof(line), file)) { + if (num >= offset) { + history_entry_t entry; + entry.line_number = num + 1; + strcpy(entry.command, line); + history[num - offset] = entry; + } + num++; + } + fclose(file); + return history; +} + +int add_history(int count, char *cmd) { + FILE *file = fopen(HISTORY_FILENAME, A_MODE); + if (!file) { + perror("Error"); + exit(EXIT_FAILURE); + } + + fprintf(file, "%s\n", cmd); + fclose(file); + return count + 1; +} + +int last_command(char *last) { + FILE *file = fopen(HISTORY_FILENAME, A_MODE); + if (!file) { + perror("Error"); + exit(EXIT_FAILURE); + } + + char line[MAXLEN]; + while (!feof(file)) { + fgets(line, sizeof(line), file); + } + + fclose(file); + strcpy(last, line); + return 0; +} + +int nth_command(char *s, int n) { + FILE *file = fopen(HISTORY_FILENAME, R_MODE); + if (!file) { + perror("Error"); + exit(EXIT_FAILURE); + } + + int num = 0; + char line[MAXLEN]; + while (fgets(line, sizeof(line), file)) { + if ((num + 1) == n) { + strcpy(s, line); + return 0; + } + num++; + } + fclose(file); + return -1; +} diff --git a/process_chp3/osh_shell/history.h b/process_chp3/osh_shell/history.h new file mode 100644 index 0000000..d0133ae --- /dev/null +++ b/process_chp3/osh_shell/history.h @@ -0,0 +1,29 @@ +#ifndef __HISTORY_H__ +#define __HISTORY_H__ + +#define MAXLEN 80 +#define HISTORY_SIZE 10 + +typedef struct __HISTORY_ENTRY__ { + int line_number; + char command[MAXLEN]; +} history_entry_t; + +// Create new history file if not exists, +// otherwise read last command count and return it +int allocate_history(void); + +// Return last ten command in the history file +history_entry_t *get_history(int); + +// Write a record in the history file +int add_history(int, char *); + +// Read last command in the history file +int last_command(char *); + +// Search nth command in the history file, +// if not found nth command return -1. +int nth_command(char *, int); + +#endif // __HISTORY_H__ diff --git a/process_chp3/osh_shell/osh.c b/process_chp3/osh_shell/osh.c new file mode 100644 index 0000000..21dbe84 --- /dev/null +++ b/process_chp3/osh_shell/osh.c @@ -0,0 +1,147 @@ +#include "history.h" +#include +#include +#include +#include +#include +#include +#include + +struct command_t { + char **args; + bool bg_flag; +}; + +struct command_t parse_command(char *); +int digits(char *); + +int main(void) { + + // Create and/or read history file + int history_count = allocate_history(); + if (history_count < 0) { + perror("Error"); + exit(EXIT_FAILURE); + } + + int should_run = 1; + while (should_run) { + char raw[MAXLEN / 2 + 1]; + printf("osh> "); + fflush(stdout); + fgets(raw, MAXLEN / 2 + 1, stdin); + + // check if user press newline + if (strcmp(raw, "\n") == 0) { + continue; + } + + // FIXME: handle ^D shortcut + if (strcmp(raw, "\000") == 0) { + exit(EXIT_SUCCESS); + } + + char *c; + if (strcmp(raw, "!!\n") == 0) { + if (nth_command(raw, history_count) != -1) { + printf("%s\n", raw); + } else { + printf("History is empty\n"); + continue; + } + } else if ((c = strchr(raw, '!')) != NULL) { + const int n = digits(c); + if (nth_command(raw, n) != -1) { + printf("%s\n", raw); + } else { + printf("Command not found in position %d\n", n); + continue; + } + } + + // remove carriage return + raw[strlen(raw) - 1] = '\0'; + + // save command in the history file + history_count = add_history(history_count, raw); + + struct command_t cmd = parse_command(raw); + if (strcmp(cmd.args[0], "exit") == 0) { + should_run = 0; + } else if (strcmp(cmd.args[0], "history") == 0) { + // print on the stdout last 10 commands in reverse way + history_entry_t *history = get_history(history_count); + for (int i = HISTORY_SIZE - 1; i >= 0; i--) { + history_entry_t entry = history[i]; + if (entry.line_number != 0) { + printf("%5d\t%s", entry.line_number, entry.command); + } + } + + free(history); + history = NULL; + } else { + pid_t pid = fork(); + switch (pid) { + case -1: + perror("Fork\n"); + exit(EXIT_FAILURE); + case 0: + should_run = 0; + if (execvp(cmd.args[0], cmd.args)) { + perror("Error"); + } + break; + default: + if (!cmd.bg_flag) { + wait(NULL); + } + + break; + } + } + + free(cmd.args); + cmd.args = NULL; + } + + return EXIT_SUCCESS; +} + +struct command_t parse_command(char *raw) { + int i = 0; + bool bg_flag = false; + char **args = (char **)calloc(MAXLEN, sizeof(char *)); + char *tok = strtok(raw, " "); + while (tok != NULL) { + if (strcmp(tok, "&") == 0) { + bg_flag = true; + } else { + args[i] = tok; + } + tok = strtok(NULL, " "); + i++; + } + + struct command_t cmd = {.args = args, .bg_flag = bg_flag}; + return cmd; +} + +int digits(char *s) { + char *sub = malloc(strlen(s) * sizeof(s)); + char c; + int i = 0, j = 0; + while ((c = s[i]) != '\n') { + if (isdigit(c)) { + sub[j++] = c; + } + i++; + } + + int n = atoi(sub); + + free(sub); + sub = NULL; + + return n; +}