90s

Minimalist, customizable shell written in C99 with syntax highlighting
git clone https://codeberg.org/night0721/90s
Log | Files | Refs | README | LICENSE

commit e4e118046cec9e76e69131b42b4c02de770cbc79
parent 253ec734f00725e1597b727d69306da084c75052
Author: night0721 <[email protected]>
Date:   Mon,  1 Jul 2024 17:40:47 +0100

rebrand to 90s

Diffstat:
M.gitignore | 2+-
A90s.1 | 10++++++++++
MMakefile | 67+++++++++++++++++++++++++++++++++++--------------------------------
MREADME.md | 6+++---
Dcolor.c | 18------------------
Dcommands.c | 456-------------------------------------------------------------------------------
Dconstants.h | 16----------------
Dhistory.c | 134-------------------------------------------------------------------------------
Rrush.h -> include/90s.h | 0
Rcolor.h -> include/color.h | 0
Rcommands.h -> include/commands.h | 0
Ainclude/constants.h | 16++++++++++++++++
Rhistory.h -> include/history.h | 0
Rjob.h -> include/job.h | 0
Djob.c | 60------------------------------------------------------------
Drush.1 | 10----------
Drush.c | 513-------------------------------------------------------------------------------
Asrc/90s.c | 513+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/color.c | 18++++++++++++++++++
Asrc/commands.c | 456+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/history.c | 134+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/job.c | 60++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
22 files changed, 1246 insertions(+), 1243 deletions(-)

diff --git a/.gitignore b/.gitignore @@ -1,3 +1,3 @@ -rush +90s *.o *.tar.gz diff --git a/90s.1 b/90s.1 @@ -0,0 +1,10 @@ +.TH 90s 1 90s\-VERSION +.SH NAME +90s \- the 90s shell +.SH SYNOPSIS +.B 90s +.SH DESCRIPTION +90s is a shell that is heavily customized, minimalistic, simple but with several features. That includes simple syntax highlighting for showing validity of commands with history search and support of environment varaibles. +.SH AUTHOR +Made by Night Kaly +.B <[email protected]> diff --git a/Makefile b/Makefile @@ -1,42 +1,45 @@ -CC=gcc +.POSIX: +.SUFFIXES: +CC = cc VERSION = 1.0 -PREFIX = /usr/local -MANPREFIX = ${PREFIX}/share/man +TARGET = 90s +MANPAGE = $(TARGET).1 +PREFIX ?= /usr/local +BINDIR = $(PREFIX)/bin +MANDIR = $(PREFIX)/share/man/man1 -CFLAGS = -std=gnu11 -O0 -Wall -DVERSION=\"${VERSION}\" +# Flags +CFLAGS = -O3 -march=native -mtune=native -pipe -s -flto -std=c99 -pedantic -Wall -D_DEFAULT_SOURCE -D_XOPEN_SOURCE=600 -DVERSION=$(VERSION) -SRC = rush.c color.c constants.h history.c commands.c job.c -OBJ = ${SRC:.c=.o} +SRC = src/*.c +INCLUDE = include -.c.o: - ${CC} -c ${CFLAGS} $< - -rush: ${OBJ} - ${CC} -o $@ ${OBJ} - strip rush - -clean: - rm -rf rush +$(TARGET): $(SRC) + $(CC) $(SRC) -o $@ $(CFLAGS) -I$(INCLUDE) dist: - mkdir -p rush-${VERSION} - cp -R LICENSE README.md rush.1 rush rush-${VERSION} - tar -cf rush-${VERSION}.tar rush-${VERSION} - gzip rush-${VERSION}.tar - rm -rf rush-${VERSION} - -install: all - mkdir -p ${DESTDIR}${PREFIX}/bin - cp -f rush ${DESTDIR}${PREFIX}/bin - chmod 755 ${DESTDIR}${PREFIX}/bin/rush - mkdir -p ${DESTDIR}${MANPREFIX}/man1 - sed "s/VERSION/${VERSION}/g" < rush.1 > ${DESTDIR}${MANPREFIX}/man1/rush.1 - chmod 644 ${DESTDIR}${MANPREFIX}/man1/rush.1 + mkdir -p $(TARGET)-$(VERSION) + cp -R README.md $(MANPAGE) $(TARGET) $(TARGET)-$(VERSION) + tar -cf $(TARGET)-$(VERSION).tar $(TARGET)-$(VERSION) + gzip $(TARGET)-$(VERSION).tar + rm -rf $(TARGET)-$(VERSION) + +install: $(TARGET) + mkdir -p $(DESTDIR)$(BINDIR) + mkdir -p $(DESTDIR)$(MANDIR) + cp -p $(TARGET) $(DESTDIR)$(BINDIR)/$(TARGET) + chmod 755 $(DESTDIR)$(BINDIR)/$(TARGET) + cp -p $(MANPAGE) $(DESTDIR)$(MANDIR)/$(MANPAGE) + chmod 644 $(DESTDIR)$(MANDIR)/$(MANPAGE) uninstall: - rm -f ${DESTDIR}${PREFIX}/bin/rush\ - ${DESTDIR}${MANPREFIX}/man1/rush.1 -all: rush + $(RM) $(DESTDIR)$(BINDIR)/$(TARGET) + $(RM) $(DESTDIR)$(MANDIR)/$(MANPAGE) + +clean: + $(RM) $(TARGET) + +all: $(TARGET) -.PHONY: all clean dist install uninstall rush +.PHONY: all dist install uninstall clean diff --git a/README.md b/README.md @@ -1,4 +1,4 @@ -# rush +# 90s Minimalist, customizable shell with syntax highlighting. * Disclaimer: This project is for me to learn to write Unix syscalls, code might be inefficient, feel free to point out the mistakes and open a issue for that! @@ -37,7 +37,7 @@ Minimalist, customizable shell with syntax highlighting. # Usage ```sh -$ ./rush +$ ./90s # > to redirect stdout # < to redirect stdin @@ -66,7 +66,7 @@ $ make Contributions are welcomed, feel free to open a pull request. # License -This project is licensed under the GNU Public License v3.0. See [LICENSE](https://github.com/night0721/rush/blob/master/LICENSE) for more information. +This project is licensed under the GNU Public License v3.0. See [LICENSE](https://github.com/night0721/90s/blob/master/LICENSE) for more information. # Credits - [Tutorial - Write a shell in C](https://brennan.io/2015/01/16/write-a-shell-in-c/) diff --git a/color.c b/color.c @@ -1,18 +0,0 @@ -#include <stdio.h> -#include <string.h> -#include <stdlib.h> - -#include "rush.h" - -// color str in place -char *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 = memalloc(size); - - snprintf(buf, size, "\x1b[38;2;%sm%s\x1b[0m", color, str); // format string to buf - return buf; -} diff --git a/commands.c b/commands.c @@ -1,456 +0,0 @@ -#include <stdio.h> -#include <stdlib.h> -#include <string.h> -#include <unistd.h> -#include <stdbool.h> -#include <errno.h> -#include <sys/wait.h> - -#include "constants.h" -#include "history.h" -#include "rush.h" -#include "job.h" - -int execute(char **args); - -// Function declarations for builtin commands -int cd(char **args); -int help(char **args); -int quit(char **args); -int history(char **args); -int export(char **args); -int source(char **args); -int j(char **args); -int bg(char **args); - -// List of builtin commands' names -char *builtin_cmds[] = { - "cd", - "help", - "exit", - "history", - "export", - "source", - "j", - "bg", -}; - -int (*builtin_func[]) (char **) = { - &cd, - &help, - &quit, // cant name it exit as it is taken - &history, - &export, - &source, - &j, - &bg, -}; - -char *shortcut_dirs[] = { - "rush", - "bin", - "localbin", -}; - -char *shortcut_expand_dirs[] = { - "~/.nky/Coding/C/rush", - "~/.local/bin", - "/usr/local/bin", -}; - -char *gethome() { - char *home = getenv("HOME"); - if (home == NULL) { - fprintf(stderr, "Error: HOME environment variable not set.\n"); - exit(EXIT_FAILURE); - } - return home; -} - -char *replace_home_dir(char *str) { - char *home_path = gethome(); - - int path_len = strlen(str); - int home_len = strlen(home_path); - - // Allocate memory for the new path - char* new_path = memalloc(sizeof(char) * (path_len + home_len + 1)); - - int i = 0, j = 0; - while (str[i] != '\0') { - if (str[i] == '~') { - // Copy HOME environment variable value - for (int k = 0; k < home_len; k++) { - new_path[j++] = home_path[k]; - } - i++; - } else { - new_path[j++] = str[i++]; - } - } - - new_path[j] = '\0'; - return new_path; -} - -char *replace_absolute_home(char *str) { - char *home_path = gethome(); - - int path_len = strlen(str); - int home_len = strlen(home_path); - - // Allocate memory for the new path - char* new_path = memalloc(sizeof(char) * (path_len - home_len + 2)); - - int i = 0, j = 0; - while (str[i] != '\0') { - if (strncmp(&str[i], home_path, home_len) == 0) { - // Copy HOME environment variable value - new_path[j++] = '~'; - i += home_len; - } else { - new_path[j++] = str[i++]; - } - } - - new_path[j] = '\0'; - return new_path; -} - -// number of built in commands -int num_builtins() { - return sizeof(builtin_cmds) / sizeof(char *); -} - -// autojump -int j(char **args) { - if (args[1] == NULL) { - fprintf(stderr, "rush: not enough arguments\n"); - return -1; - } - for (int i = 0; i < sizeof(shortcut_dirs) / sizeof(char *); i++) { - int len = strlen(shortcut_dirs[i]); - if (strncmp(args[1], shortcut_dirs[i], len) == 0) { - char **merged_cd = memalloc(sizeof(char *) * 3); - merged_cd[0] = "cd"; - merged_cd[1] = shortcut_expand_dirs[i]; - merged_cd[2] = NULL; - cd(merged_cd); - printf("jumped to %s\n", shortcut_expand_dirs[i]); - return 1; - } - } - return 1; -} - -// change directory -int cd(char **args) { - int i = 0; - if (args[1] == NULL) { - char *home = gethome(); - if (chdir(home) != 0) { - perror("rush"); - } - } else { - while (args[1][i] != '\0') { - if (args[1][i] == '~') { - args[1] = replace_home_dir(args[1]); - break; - } - i++; - } - if (chdir(args[1]) != 0) { - perror("rush"); - } - } - return 1; -} - -// show help menu -int help(char **args) { - printf("rush %s\n", VERSION); - printf("Built in commands:\n"); - - for (int i = 0; i < num_builtins(); i++) { - printf(" %s\n", builtin_cmds[i]); - } - - printf("Use 'man' to read manual of programs\n"); - printf("Licensed under GPL v3\n"); - return 1; -} - -int quit(char **args) { - return 0; // exit prompting loop, which also the shell -} - -int history(char **args) { - char **history = get_all_history(true); - - for (int i = 0; history[i] != NULL; ++i) { - printf("%s\n", history[i]); - free(history[i]); - } - - free(history); - return 1; -} - -int export(char **args) { - args++; // skip the command - while (*args != NULL) { - char *variable = strtok(*args, "=\n"); - char *value = strtok(NULL, "=\n"); - if (variable != NULL && value != NULL) { - if (setenv(variable, value, 1) != 0) { - fprintf(stderr, "rush: Error setting environment variable\n"); - return 0; - } - } else { - fprintf(stderr, "rush: Syntax error when setting environment variable\nUse \"export VARIABLE=VALUE\"\n"); - return 0; - } - args++; - } - return 1; -} - -int source(char **args) { - if (args[1] == NULL) { - fprintf(stderr, "rush: not enough arguments\n"); - return -1; - } - - FILE *file = fopen(args[1], "r"); - - if (file == NULL) { - fprintf(stderr, "rush: no such file or directory '%s'\n", args[1]); - return -1; - } - - char line[RL_BUFSIZE]; - int status; - while (fgets(line, sizeof(line), file) != NULL) { - // Remove newline character if present - size_t len = strlen(line); - if (len > 0 && line[len - 1] == '\n') { - line[len - 1] = '\0'; - } - - char **args = argsplit(line); - status = execute(args); - } - - fclose(file); - return status; // Indicate success -} - -int bg(char **args) { - if (args[1] == NULL) { - fprintf(stderr, "rush: not enough arguments\n"); - return -1; - } - int job_index = atoi(args[1]); - if (job_index == 0) { - fprintf(stderr, "rush: invalid job index\n"); - return -1; - } - job *search = get_job(job_index - 1); - if (search == NULL) { - fprintf(stderr, "rush: no such job\n"); - return -1; - } - printf("Job %i: %s\n", job_index, search->command); - return 1; -} -bool is_builtin(char *command) { - for (int i = 0; i < num_builtins(); i++) { - if (strcmp(command, builtin_cmds[i]) == 0) { - return true; - } - } - return false; -} - -int launch(char **args, int fd, int options) { - int is_bgj = (options & OPT_BGJ) ? 1 : 0; - int redirect_stdout = (options & OPT_STDOUT) ? 1 : 0; - int redirect_stdin = (options & OPT_STDIN) ? 1 : 0; - int redirect_stderr = (options & OPT_STDERR) ? 1 : 0; - - pid_t pid; - - int status; - if ((pid = fork()) == 0) { - // Child process - if (fd > 2) { - // not stdin, stdout, or stderr - if (redirect_stdout) { - if (dup2(fd, STDOUT_FILENO) == -1) { - perror("rush"); - } - } - if (redirect_stdin) { - if (dup2(fd, STDIN_FILENO) == -1) { - perror("rush"); - } - } - if (redirect_stderr) { - if (dup2(fd, STDERR_FILENO) == -1) { - perror("rush"); - } - } - close(fd); // close fd as it is duplicated already - } - if (execvp(args[0], args) == -1) { - if (errno == ENOENT) { - fprintf(stderr, "rush: command not found: %s\n", args[0]); - } - } - exit(EXIT_FAILURE); // exit the child - } else if (pid < 0) { - perror("fork failed"); - } else { - // Parent process - if (is_bgj) { - int job_index = add_job(pid, args[0], true); - printf("[Job: %i] [Process ID: %i] [Command: %s]\n", job_index + 1, pid, args[0]); - return 1; - } else { - do { - waitpid(pid, &status, WUNTRACED); // wait child to be exited to return to prompt - } while (!WIFEXITED(status) && !WIFSIGNALED(status)); - } - } - return 1; -} -// execute built in commands or launch commands and wait it to terminate, return 1 to keep shell running -int execute(char **args) { - if (args[0] == NULL) { // An empty command was entered. - return 1; - } - - // prioritize builtin commands - for (int i = 0; i < num_builtins(); i++) { - if (strcmp(args[0], builtin_cmds[i]) == 0) { - return (*builtin_func[i])(args); - } - } - int num_arg = 0; - - while (args[num_arg] != NULL) { - if (strncmp(args[num_arg], "&", 1) == 0) { - args[num_arg] = NULL; - if (args[num_arg + 1] != NULL) { - // have commands after & - execute(&args[num_arg + 1]); - } - launch(args, STDOUT_FILENO, OPT_BGJ); - return 1; - } - if (strncmp(args[num_arg], ">", 1) == 0) { - int fd = fileno(fopen(args[num_arg + 1], "w+")); - if (fd == -1) { - perror("rush"); - return 1; - } - args[num_arg] = NULL; - int asdf = 0; - while (args[asdf] != NULL) { - fprintf(stderr, "args[%i]: %s\n", asdf, args[asdf]); - asdf++; - } - - return launch(args, fd, OPT_FGJ | OPT_STDOUT); - } - if (strncmp(args[num_arg], "<", 1) == 0) { - int fd = fileno(fopen(args[num_arg + 1], "r")); - if (fd == -1) { - perror("rush"); - return 1; - } - args[num_arg] = NULL; - return launch(args, fd, OPT_FGJ | OPT_STDIN); - } - if (strncmp(args[num_arg], "2>", 2) == 0) { - int fd = fileno(fopen(args[num_arg + 1], "w+")); - if (fd == -1) { - perror("rush"); - return 1; - } - args[num_arg] = NULL; - return launch(args, fd, OPT_FGJ | OPT_STDERR); - } - if (strncmp(args[num_arg], ">&", 2) == 0) { - int fd = fileno(fopen(args[num_arg + 1], "w+")); - if (fd == -1) { - perror("rush"); - return 1; - } - args[num_arg] = NULL; - return launch(args, fd, OPT_FGJ | OPT_STDOUT | OPT_STDERR); - } - if (strncmp(args[num_arg], ">>", 2) == 0) { - int fd = fileno(fopen(args[num_arg + 1], "a+")); - if (fd == -1) { - perror("rush"); - return 1; - } - args[num_arg] = NULL; - return launch(args, fd, OPT_FGJ | OPT_STDOUT); - } - - num_arg++; // count number of args - } - - return launch(args, STDOUT_FILENO, OPT_FGJ); -} - -// execute_pipe with as many pipes as needed -int execute_pipe(char ***args) { - int pipefd[2]; - pid_t pid; - int in = 0; - - int num_cmds = 0; - while (args[num_cmds] != NULL) { - int num_args = 0; - while (args[num_cmds][num_args] != NULL) { - num_args++; - } - num_cmds++; - } - - for (int i = 0; i < num_cmds - 1; i++) { - pipe(pipefd); - if ((pid = fork()) == 0) { - // then this (child) - dup2(in, STDIN_FILENO); // get input from previous command - if (i < num_cmds - 1) { - dup2(pipefd[1], STDOUT_FILENO); // make output go to pipe (next output) - } - close(pipefd[0]); // close original input - execute(args[i]); - exit(EXIT_SUCCESS); - } else if (pid < 0) { - perror("fork failed"); - } - // this will be executed first - close(pipefd[1]); // close output - in = pipefd[0]; // save the input for the next command - } - - if ((pid = fork()) == 0) { - dup2(in, STDIN_FILENO); // get input from pipe - execute(args[num_cmds - 1]); - exit(EXIT_SUCCESS); - } else if (pid < 0) { - perror("fork failed"); - } - - close(in); - for (int i = 0; i < num_cmds; i++) { - waitpid(pid, NULL, 0); - } - return 1; -} diff --git a/constants.h b/constants.h @@ -1,16 +0,0 @@ -#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 // size of each command -#define TOK_DELIM " \t\r\n\a" // delimiter for token -#define MAX_HISTORY 8192 // maximum lines of reading history - -#define MAX_JOBS 64 // maximum number of jobs -#define OPT_STDIN 0x01 // option for stdin -#define OPT_STDOUT 0x02 // option for stdout -#define OPT_STDERR 0x04 // option for stderr -#define OPT_FGJ 0x08 // option for foreground job -#define OPT_BGJ 0x10 // option for background job -#endif diff --git a/history.c b/history.c @@ -1,134 +0,0 @@ -#include <stdio.h> -#include <stdlib.h> -#include <stdbool.h> -#include <string.h> -#include <unistd.h> -#include <linux/limits.h> - -#include "history.h" -#include "rush.h" -#include "constants.h" - -FILE *history_file; -char *histfile_path; -int cmd_count = 0; - -FILE *open_history_file(char *mode) { - history_file = fopen(histfile_path, mode); - if (history_file == NULL) { - fprintf(stderr, "rush: Error opening history file\n"); - exit(EXIT_FAILURE); - } - return history_file; -} -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"); - } - if (env_home == NULL) { - fprintf(stderr, "rush: HOME AND XDG_CONFIG_HOME environment variable is missing\n"); - exit(EXIT_FAILURE); - } - 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 = memalloc(sizeof(char) * path_len); - histfile_path[0] = '\0'; // initialise string - // 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 - 1] = '\0'; - if (access(histfile_path, F_OK) != 0) { // check for file existence - history_file = open_history_file("w"); // read and write, if doesn't exist, create - fclose(history_file); - } -} - -void save_command_history(char *args) { - history_file = open_history_file("a+"); - char cmd[RL_BUFSIZE]; - cmd[0] = '\0'; - strcat(cmd, args); - int cmd_len = strlen(cmd); - cmd[cmd_len] = '\n'; // put new line feed to split commands - // ptr to first obj, size of each obj, number of obj, file ptr - fwrite(cmd, sizeof(char), cmd_len + 1, history_file); - fclose(history_file); -} - -char *read_command(int direction) { - if (direction == 1) { // up - cmd_count++; - } else { // down - if (cmd_count == 0) { - return NULL; - } else { - cmd_count--; - } - } - char **history = get_all_history(false); - int num_history = 0; - while (*history != NULL) { - num_history++; - history++; - } - if (cmd_count > num_history) { - cmd_count = num_history; - return NULL; - } - history -= num_history; - return history[num_history - cmd_count]; -} - -int is_duplicate(char **history, int line_count, char *line) { - for (int i = 0; i < line_count; ++i) { - if (strcmp(history[i], line) == 0) { - return 1; - } - } - return 0; -} - -char **get_all_history(bool check) { - history_file = open_history_file("r"); - char **history = memalloc(MAX_HISTORY * sizeof(char*)); - char buffer[RL_BUFSIZE]; - int line_count = 0; - - while (fgets(buffer, sizeof(buffer), history_file) != NULL) { - buffer[strcspn(buffer, "\n")] = '\0'; - if (check) { - if (!is_duplicate(history, line_count, buffer)) { - history[line_count] = strdup(buffer); - if (history[line_count] == NULL) { - fprintf(stderr, "Error allocating memory\n"); - exit(EXIT_FAILURE); - } - line_count++; - if (line_count >= MAX_HISTORY) { - fprintf(stderr, "Maximum number of lines reached.\n"); - exit(EXIT_FAILURE); - } - } - } else { - history[line_count] = strdup(buffer); - if (history[line_count] == NULL) { - fprintf(stderr, "Error allocating memory\n"); - exit(EXIT_FAILURE); - } - line_count++; - if (line_count >= MAX_HISTORY) { - fprintf(stderr, "Maximum number of lines reached.\n"); - exit(EXIT_FAILURE); - } - } - } - - fclose(history_file); - history[line_count] = NULL; - return history; -} diff --git a/rush.h b/include/90s.h diff --git a/color.h b/include/color.h diff --git a/commands.h b/include/commands.h diff --git a/include/constants.h b/include/constants.h @@ -0,0 +1,16 @@ +#ifndef CONSTANTS_H_ +#define CONSTANTS_H_ + +#define HISTFILE ".90s_history" // history file name +#define TOK_BUFSIZE 64 // buffer size of each token +#define RL_BUFSIZE 1024 // size of each command +#define TOK_DELIM " \t\r\n\a" // delimiter for token +#define MAX_HISTORY 8192 // maximum lines of reading history + +#define MAX_JOBS 64 // maximum number of jobs +#define OPT_STDIN 0x01 // option for stdin +#define OPT_STDOUT 0x02 // option for stdout +#define OPT_STDERR 0x04 // option for stderr +#define OPT_FGJ 0x08 // option for foreground job +#define OPT_BGJ 0x10 // option for background job +#endif diff --git a/history.h b/include/history.h diff --git a/job.h b/include/job.h diff --git a/job.c b/job.c @@ -1,60 +0,0 @@ -#include <unistd.h> -#include <stdbool.h> -#include <string.h> - -#include "rush.h" - -typedef struct job { - pid_t pid; - char *command; - bool status; - struct job *next; -} job; - -job *jobs = NULL; - -int num_jobs() { - job *current = jobs; - int count = 0; - while (current != NULL) { - count++; - current = current->next; - } - return count; -} - -int add_job(pid_t pid, char *command, bool status) { - job *current = jobs; - job *new_job = memalloc(sizeof(job)); - new_job->pid = pid; - char *buf = memalloc(strlen(command) + 1); - strcpy(buf, command); - new_job->command = buf; - new_job->status = status; - new_job->next = NULL; - if (current == NULL) { - jobs = new_job; - return 0; - } - int index = 1; - while (current->next != NULL) { - current = current->next; - index++; - } - current->next = new_job; - return index; -} - -job *get_job(int index) { - job *current = jobs; - if (index == 0) { - return current; - } - if (index > num_jobs()) { - return NULL; - } - for (int i = 0; i < index; i++) { - current = current->next; - } - return current; -} diff --git a/rush.1 b/rush.1 @@ -1,10 +0,0 @@ -.TH rush 1 rush\-VERSION -.SH NAME -rush \- the rush shell -.SH SYNOPSIS -.B rush -.SH DESCRIPTION -rush is a shell that is heavily customized, minimalistic, simple but with several features. That includes simple syntax highlighting for showing validity of commands with history search and support of environment varaibles. -.SH AUTHOR -Made by Night Kaly -.B <[email protected]> diff --git a/rush.c b/rush.c @@ -1,513 +0,0 @@ -#include <termios.h> -#include <unistd.h> -#include <stdlib.h> -#include <stdio.h> -#include <string.h> -#include <linux/limits.h> -#include <time.h> -#include <stdbool.h> -#include <signal.h> -#include <ctype.h> - -#include "color.h" -#include "constants.h" -#include "history.h" -#include "commands.h" - -void *memalloc(size_t size) { - void *ptr = malloc(size); - if (!ptr) { - fputs("rush: Error allocating memory\n", stderr); - exit(EXIT_FAILURE); - } - return ptr; -} - -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"); - if (envpath == NULL) { - fprintf(stderr, "rush: PATH environment variable is missing\n"); - exit(EXIT_FAILURE); - } - char *path_cpy = memalloc(sizeof(char) * (strlen(envpath) + 1)); - char *path = memalloc(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 = memalloc(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 (strncmp(command, "", 1) == 0) { - return false; - } - while (*paths != NULL) { - char current_path[PATH_MAX]; - current_path[0] = '\0'; - sprintf(current_path, "%s/%s", *paths, command); - if (access(current_path, X_OK) == 0) { - // command is executable - return true; - } else { - if (is_builtin(command)) { - return true; - } - } - paths++; - } - return false; -} - -void shiftleft(int chars) { - printf("\033[%dD", chars); -} - -void shiftright(int chars) { - printf("\033[%dC", chars); -} - -void clearline() { - printf("\033[K"); // clear line to the right of cursor -} - -void highlight(char *buffer, char **paths) { - char *cmd_part = strchr(buffer, ' '); - char *command_without_arg = NULL; - int cmd_len = 0; - bool valid; - - if (cmd_part != NULL) { - cmd_len = cmd_part - buffer; - char *cmd = memalloc(sizeof(char) * (cmd_len + 1)); - command_without_arg = memalloc(sizeof(char) * (cmd_len + 1)); - for (int i = 0; i < (cmd_part - buffer); i++) { - cmd[i] = buffer[i]; - } - strcpy(command_without_arg, cmd); - cmd[cmd_len] = '\0'; - command_without_arg[cmd_len] = '\0'; - valid = find_command(paths, cmd); - free(cmd); - } else { - valid = find_command(paths, buffer); - } - - if (valid) { - if (command_without_arg != NULL) { - buffer += cmd_len; - printf("\x1b[38;2;137;180;250m%s\x1b[0m\x1b[38;2;255;255;255m%s\x1b[0m", command_without_arg, buffer); // print green as valid command, but only color the command, not the arguments - buffer -= cmd_len; - } else { - printf("\x1b[38;2;137;180;250m%s\x1b[0m", buffer); // print green as valid command - } - } else { - if (command_without_arg != NULL) { - buffer += cmd_len; - printf("\x1b[38;2;243;139;168m%s\x1b[0m\x1b[38;2;255;255;255m%s\x1b[0m", command_without_arg, buffer); // print green as valid command, but only color the command, not the arguments - buffer -= cmd_len; - } else { - printf("\x1b[38;2;243;139;168m%s\x1b[0m", buffer); // print red as invalid command - } - } - free(command_without_arg); -} - -char *readline(char **paths) { - int bufsize = RL_BUFSIZE; - int position = 0; - char *buffer = memalloc(sizeof(char) * bufsize); - - bool moved = false; - bool backspaced = false; - bool navigated = false; - bool insertatmiddle = false; - bool replaced = false; - - buffer[0] = '\0'; - while (1) { - int c = getchar(); // read a character - int buf_len = strlen(buffer); - - // check each character user has input - switch (c) { - case EOF: - exit(EXIT_SUCCESS); - case 10: { - // enter/new line feed - if (buf_len == 0) { - return NULL; - } - // check if command includes !! - if (strstr(buffer, "!!") != NULL) { - char *last_command = read_command(1); - if (last_command != NULL) { - // replace !! with the last command - char *replace = strstr(buffer, "!!"); - int replace_len = strlen(replace); - int last_command_len = strlen(last_command); - int buffer_len = strlen(buffer); - if (last_command_len > replace_len) { - buffer = realloc(buffer, buffer_len + last_command_len - replace_len + 1); - if (!buffer) { - fprintf(stderr, "rush: Error allocating memory\n"); - exit(EXIT_FAILURE); - } - } - memmove(replace + last_command_len, replace + replace_len, buffer_len - (replace - buffer) - replace_len + 1); - memcpy(replace, last_command, last_command_len); - position += last_command_len - replace_len; - shiftright(last_command_len - replace_len); - replaced = true; - break; - } - } else { - buffer[buf_len] = '\0'; - // clear all characters after the command - for (int start = buf_len + 1; buffer[start] != '\0'; start++) { - buffer[start] = '\0'; - } - printf("\n"); // give space for response - position = 0; - return buffer; - } - } - case 127: // backspace - if (buf_len >= 1) { - position--; - for (int i = position; i < buf_len; i++) { - // shift the buffer - buffer[i] = buffer[i + 1]; - } - backspaced = true; - moved = false; - } - 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); - } - navigated = true; - moved = false; - break; - } else if (arrow_key == 66) { // down - char *last_command = read_command(0); - if (last_command != NULL) { - strcpy(buffer, last_command); - } - navigated = true; - moved = false; - break; - } else if (arrow_key == 67) { // right - if (position < buf_len) { - shiftright(1); - position++; - } - moved = true; - break; - } else if (arrow_key == 68) { // left - if (position >= 1) { - shiftleft(1); - position--; - } - moved = true; - break; - } - } - default: - if (c > 31 && c < 127) { - if (position == buf_len) { - // Append character to the end of the buffer - buffer[buf_len] = c; - buffer[buf_len + 1] = '\0'; - moved = false; - navigated = false; - } else { - // Insert character at the current position - memmove(&buffer[position + 1], &buffer[position], buf_len - position + 1); - buffer[position] = c; - shiftright(1); - insertatmiddle = true; - } - position++; - } - } - - buf_len = strlen(buffer); - - if (replaced) { - shiftleft(buf_len); - clearline(); - } - - if (navigated && buf_len >= 1) { - if (position > 0) { - shiftleft(position); // move cursor to the beginning - clearline(); - } - position = buf_len; - } - - if (moved) { - moved = false; - continue; - } - - if (!navigated && !replaced) { - if (position != buf_len) { - // not at normal place - if (backspaced) { - shiftleft(position + 1); - } else { - shiftleft(position); // move cursor to the beginning - } - } else if (buf_len > 1) { - if (backspaced) { - shiftleft(buf_len + 1); // move cursor to the beginning - } else { - shiftleft(buf_len - 1); // move cursor to the beginning - } - } else if (buf_len == 1) { - if (backspaced) { - shiftleft(2); - } - } else if (buf_len == 0) { - if (backspaced) { - shiftleft(1); - } - } - clearline(); - } else { - navigated = false; - } - - highlight(buffer, paths); - - if (backspaced) { - if (buf_len != position) { - shiftleft(buf_len - position); - } - backspaced = false; - } - if (insertatmiddle) { - shiftleft(buf_len - position); // move cursor back to where it was - insertatmiddle = false; - } - if (replaced) { - replaced = false; - } - - // If we have exceeded the buffer, reallocate. - if ((buf_len + 1) >= bufsize) { - bufsize += RL_BUFSIZE; - buffer = realloc(buffer, bufsize); - if (!buffer) { - fprintf(stderr, "rush: Error allocating memory\n"); - exit(EXIT_FAILURE); - } - } - } -} - -// split line into arguments -char **argsplit(char *line) { - int bufsize = TOK_BUFSIZE, position = 0; - char **tokens = memalloc(bufsize * sizeof(char*)); - char *token; - - 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: Error allocating memory\n"); - exit(EXIT_FAILURE); - } - } - - token = strtok(NULL, TOK_DELIM); - } - tokens[position] = NULL; - return tokens; -} - -char **modifyargs(char **args) { - int num_arg = 0; - - // check if command is ls, diff, or grep, if so, add --color=auto to the arguments - // this is to make ls, diff, and grep have color without user typing it - // this is to make the shell more user friendly - while (args[num_arg] != NULL) { - num_arg++; - } - for (int i = 0; i < num_arg; i++) { - // makes ls and diff and grep have color without user typing it - if (strncmp(args[i], "ls", 2) == 0 || strncmp(args[i], "diff", 4) == 0 || strncmp(args[i], "grep", 4) == 0) { - for (int j = num_arg; j > i; j--) { - args[j + 1] = args[j]; - } - args[i + 1] = "--color=auto"; - num_arg++; - } - } - - return args; -} - -char *trimws(char *str) { - char *end; - while (isspace((unsigned char) *str)) - str++; - if(*str == 0) - return str; - end = str + strlen(str) - 1; - while (end > str && isspace((unsigned char) *end)) - end--; - *(end+1) = 0; - return str; -} - -char ***pipe_argsplit(char *line) { - char ***cmdv = memalloc(sizeof(char **) * 128); // 127 commands, 1 for NULL - char **cmds = memalloc(sizeof(char *) * 128); // 127 arguments, 1 for NULL - int num_arg = 0; - char *pipe = strtok(line, "|"); - while (pipe != NULL) { - pipe = trimws(pipe); - cmds[num_arg] = strdup(pipe); - pipe = strtok(NULL, "|"); - num_arg++; - } - cmds[num_arg] = NULL; - - for (int i = 0; i < num_arg; i++) { - char **splitted = argsplit(cmds[i]); - cmdv[i] = modifyargs(splitted); - - } - cmdv[num_arg] = NULL; - free(cmds); - return cmdv; -} - -// 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); - char *modtime = memalloc(sizeof(char) * 256); - modtime = color_text(time, lavender); // lavender colored time string - char *cwd = memalloc(sizeof(char) * (PATH_MAX + 2)); - sprintf(cwd, "[%s]", cwdstr); - cwd = replace_absolute_home(cwd); - cwd = color_text(cwd, pink); // pink colored current directory - char *arrow = memalloc(sizeof(char) * 32); - strcpy(arrow, "ยป"); - arrow = color_text(arrow, blue); - printf("%s %s %s ", modtime, cwd, arrow); - - cmd_count = 0; // upward arrow key resets command count - line = readline(paths); - if (line == NULL) { - printf("\n"); - continue; - } - save_command_history(line); - bool has_pipe = false; - for (int i = 0; line[i] != '\0'; i++) { - if (line[i] == '|') { - has_pipe = true; - break; - } - } - if (has_pipe) { - char ***pipe_args = pipe_argsplit(line); - status = execute_pipe(pipe_args); - while (*pipe_args != NULL) { - free(*pipe_args); - pipe_args++; - } - } else { - args = argsplit(line); - args = modifyargs(args); - status = execute(args, STDOUT_FILENO, OPT_FGJ); - free(args); - } - free(line); - free(modtime); - free(cwd); - free(arrow); - }; -} - -void quit_sig(int sig) { - exit(EXIT_SUCCESS); -} - -int main(int argc, char **argv) { - // setup - signal(SIGINT, quit_sig); - signal(SIGTERM, quit_sig); - signal(SIGQUIT, quit_sig); - 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 - free(paths); - change_terminal_attribute(0); // change back to default settings - return EXIT_SUCCESS; -} diff --git a/src/90s.c b/src/90s.c @@ -0,0 +1,513 @@ +#include <termios.h> +#include <unistd.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <linux/limits.h> +#include <time.h> +#include <stdbool.h> +#include <signal.h> +#include <ctype.h> + +#include "color.h" +#include "constants.h" +#include "history.h" +#include "commands.h" + +void *memalloc(size_t size) { + void *ptr = malloc(size); + if (!ptr) { + fputs("90s: Error allocating memory\n", stderr); + exit(EXIT_FAILURE); + } + return ptr; +} + +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"); + if (envpath == NULL) { + fprintf(stderr, "90s: PATH environment variable is missing\n"); + exit(EXIT_FAILURE); + } + char *path_cpy = memalloc(sizeof(char) * (strlen(envpath) + 1)); + char *path = memalloc(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 = memalloc(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 (strncmp(command, "", 1) == 0) { + return false; + } + while (*paths != NULL) { + char current_path[PATH_MAX]; + current_path[0] = '\0'; + sprintf(current_path, "%s/%s", *paths, command); + if (access(current_path, X_OK) == 0) { + // command is executable + return true; + } else { + if (is_builtin(command)) { + return true; + } + } + paths++; + } + return false; +} + +void shiftleft(int chars) { + printf("\033[%dD", chars); +} + +void shiftright(int chars) { + printf("\033[%dC", chars); +} + +void clearline() { + printf("\033[K"); // clear line to the right of cursor +} + +void highlight(char *buffer, char **paths) { + char *cmd_part = strchr(buffer, ' '); + char *command_without_arg = NULL; + int cmd_len = 0; + bool valid; + + if (cmd_part != NULL) { + cmd_len = cmd_part - buffer; + char *cmd = memalloc(sizeof(char) * (cmd_len + 1)); + command_without_arg = memalloc(sizeof(char) * (cmd_len + 1)); + for (int i = 0; i < (cmd_part - buffer); i++) { + cmd[i] = buffer[i]; + } + strcpy(command_without_arg, cmd); + cmd[cmd_len] = '\0'; + command_without_arg[cmd_len] = '\0'; + valid = find_command(paths, cmd); + free(cmd); + } else { + valid = find_command(paths, buffer); + } + + if (valid) { + if (command_without_arg != NULL) { + buffer += cmd_len; + printf("\x1b[38;2;137;180;250m%s\x1b[0m\x1b[38;2;255;255;255m%s\x1b[0m", command_without_arg, buffer); // print green as valid command, but only color the command, not the arguments + buffer -= cmd_len; + } else { + printf("\x1b[38;2;137;180;250m%s\x1b[0m", buffer); // print green as valid command + } + } else { + if (command_without_arg != NULL) { + buffer += cmd_len; + printf("\x1b[38;2;243;139;168m%s\x1b[0m\x1b[38;2;255;255;255m%s\x1b[0m", command_without_arg, buffer); // print green as valid command, but only color the command, not the arguments + buffer -= cmd_len; + } else { + printf("\x1b[38;2;243;139;168m%s\x1b[0m", buffer); // print red as invalid command + } + } + free(command_without_arg); +} + +char *readline(char **paths) { + int bufsize = RL_BUFSIZE; + int position = 0; + char *buffer = memalloc(sizeof(char) * bufsize); + + bool moved = false; + bool backspaced = false; + bool navigated = false; + bool insertatmiddle = false; + bool replaced = false; + + buffer[0] = '\0'; + while (1) { + int c = getchar(); // read a character + int buf_len = strlen(buffer); + + // check each character user has input + switch (c) { + case EOF: + exit(EXIT_SUCCESS); + case 10: { + // enter/new line feed + if (buf_len == 0) { + return NULL; + } + // check if command includes !! + if (strstr(buffer, "!!") != NULL) { + char *last_command = read_command(1); + if (last_command != NULL) { + // replace !! with the last command + char *replace = strstr(buffer, "!!"); + int replace_len = strlen(replace); + int last_command_len = strlen(last_command); + int buffer_len = strlen(buffer); + if (last_command_len > replace_len) { + buffer = realloc(buffer, buffer_len + last_command_len - replace_len + 1); + if (!buffer) { + fprintf(stderr, "90s: Error allocating memory\n"); + exit(EXIT_FAILURE); + } + } + memmove(replace + last_command_len, replace + replace_len, buffer_len - (replace - buffer) - replace_len + 1); + memcpy(replace, last_command, last_command_len); + position += last_command_len - replace_len; + shiftright(last_command_len - replace_len); + replaced = true; + break; + } + } else { + buffer[buf_len] = '\0'; + // clear all characters after the command + for (int start = buf_len + 1; buffer[start] != '\0'; start++) { + buffer[start] = '\0'; + } + printf("\n"); // give space for response + position = 0; + return buffer; + } + } + case 127: // backspace + if (buf_len >= 1) { + position--; + for (int i = position; i < buf_len; i++) { + // shift the buffer + buffer[i] = buffer[i + 1]; + } + backspaced = true; + moved = false; + } + 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); + } + navigated = true; + moved = false; + break; + } else if (arrow_key == 66) { // down + char *last_command = read_command(0); + if (last_command != NULL) { + strcpy(buffer, last_command); + } + navigated = true; + moved = false; + break; + } else if (arrow_key == 67) { // right + if (position < buf_len) { + shiftright(1); + position++; + } + moved = true; + break; + } else if (arrow_key == 68) { // left + if (position >= 1) { + shiftleft(1); + position--; + } + moved = true; + break; + } + } + default: + if (c > 31 && c < 127) { + if (position == buf_len) { + // Append character to the end of the buffer + buffer[buf_len] = c; + buffer[buf_len + 1] = '\0'; + moved = false; + navigated = false; + } else { + // Insert character at the current position + memmove(&buffer[position + 1], &buffer[position], buf_len - position + 1); + buffer[position] = c; + shiftright(1); + insertatmiddle = true; + } + position++; + } + } + + buf_len = strlen(buffer); + + if (replaced) { + shiftleft(buf_len); + clearline(); + } + + if (navigated && buf_len >= 1) { + if (position > 0) { + shiftleft(position); // move cursor to the beginning + clearline(); + } + position = buf_len; + } + + if (moved) { + moved = false; + continue; + } + + if (!navigated && !replaced) { + if (position != buf_len) { + // not at normal place + if (backspaced) { + shiftleft(position + 1); + } else { + shiftleft(position); // move cursor to the beginning + } + } else if (buf_len > 1) { + if (backspaced) { + shiftleft(buf_len + 1); // move cursor to the beginning + } else { + shiftleft(buf_len - 1); // move cursor to the beginning + } + } else if (buf_len == 1) { + if (backspaced) { + shiftleft(2); + } + } else if (buf_len == 0) { + if (backspaced) { + shiftleft(1); + } + } + clearline(); + } else { + navigated = false; + } + + highlight(buffer, paths); + + if (backspaced) { + if (buf_len != position) { + shiftleft(buf_len - position); + } + backspaced = false; + } + if (insertatmiddle) { + shiftleft(buf_len - position); // move cursor back to where it was + insertatmiddle = false; + } + if (replaced) { + replaced = false; + } + + // If we have exceeded the buffer, reallocate. + if ((buf_len + 1) >= bufsize) { + bufsize += RL_BUFSIZE; + buffer = realloc(buffer, bufsize); + if (!buffer) { + fprintf(stderr, "90s: Error allocating memory\n"); + exit(EXIT_FAILURE); + } + } + } +} + +// split line into arguments +char **argsplit(char *line) { + int bufsize = TOK_BUFSIZE, position = 0; + char **tokens = memalloc(bufsize * sizeof(char*)); + char *token; + + 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, "90s: Error allocating memory\n"); + exit(EXIT_FAILURE); + } + } + + token = strtok(NULL, TOK_DELIM); + } + tokens[position] = NULL; + return tokens; +} + +char **modifyargs(char **args) { + int num_arg = 0; + + // check if command is ls, diff, or grep, if so, add --color=auto to the arguments + // this is to make ls, diff, and grep have color without user typing it + // this is to make the shell more user friendly + while (args[num_arg] != NULL) { + num_arg++; + } + for (int i = 0; i < num_arg; i++) { + // makes ls and diff and grep have color without user typing it + if (strncmp(args[i], "ls", 2) == 0 || strncmp(args[i], "diff", 4) == 0 || strncmp(args[i], "grep", 4) == 0) { + for (int j = num_arg; j > i; j--) { + args[j + 1] = args[j]; + } + args[i + 1] = "--color=auto"; + num_arg++; + } + } + + return args; +} + +char *trimws(char *str) { + char *end; + while (isspace((unsigned char) *str)) + str++; + if(*str == 0) + return str; + end = str + strlen(str) - 1; + while (end > str && isspace((unsigned char) *end)) + end--; + *(end+1) = 0; + return str; +} + +char ***pipe_argsplit(char *line) { + char ***cmdv = memalloc(sizeof(char **) * 128); // 127 commands, 1 for NULL + char **cmds = memalloc(sizeof(char *) * 128); // 127 arguments, 1 for NULL + int num_arg = 0; + char *pipe = strtok(line, "|"); + while (pipe != NULL) { + pipe = trimws(pipe); + cmds[num_arg] = strdup(pipe); + pipe = strtok(NULL, "|"); + num_arg++; + } + cmds[num_arg] = NULL; + + for (int i = 0; i < num_arg; i++) { + char **splitted = argsplit(cmds[i]); + cmdv[i] = modifyargs(splitted); + + } + cmdv[num_arg] = NULL; + free(cmds); + return cmdv; +} + +// 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); + char *modtime = memalloc(sizeof(char) * 256); + modtime = color_text(time, lavender); // lavender colored time string + char *cwd = memalloc(sizeof(char) * (PATH_MAX + 2)); + sprintf(cwd, "[%s]", cwdstr); + cwd = replace_absolute_home(cwd); + cwd = color_text(cwd, pink); // pink colored current directory + char *arrow = memalloc(sizeof(char) * 32); + strcpy(arrow, "ยป"); + arrow = color_text(arrow, blue); + printf("%s %s %s ", modtime, cwd, arrow); + + cmd_count = 0; // upward arrow key resets command count + line = readline(paths); + if (line == NULL) { + printf("\n"); + continue; + } + save_command_history(line); + bool has_pipe = false; + for (int i = 0; line[i] != '\0'; i++) { + if (line[i] == '|') { + has_pipe = true; + break; + } + } + if (has_pipe) { + char ***pipe_args = pipe_argsplit(line); + status = execute_pipe(pipe_args); + while (*pipe_args != NULL) { + free(*pipe_args); + pipe_args++; + } + } else { + args = argsplit(line); + args = modifyargs(args); + status = execute(args, STDOUT_FILENO, OPT_FGJ); + free(args); + } + free(line); + free(modtime); + free(cwd); + free(arrow); + }; +} + +void quit_sig(int sig) { + exit(EXIT_SUCCESS); +} + +int main(int argc, char **argv) { + // setup + signal(SIGINT, quit_sig); + signal(SIGTERM, quit_sig); + signal(SIGQUIT, quit_sig); + 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 + free(paths); + change_terminal_attribute(0); // change back to default settings + return EXIT_SUCCESS; +} diff --git a/src/color.c b/src/color.c @@ -0,0 +1,18 @@ +#include <stdio.h> +#include <string.h> +#include <stdlib.h> + +#include "90s.h" + +// color str in place +char *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, "90s: snprintf failed\n"); + exit(EXIT_FAILURE); + } + char *buf = memalloc(size); + + snprintf(buf, size, "\x1b[38;2;%sm%s\x1b[0m", color, str); // format string to buf + return buf; +} diff --git a/src/commands.c b/src/commands.c @@ -0,0 +1,456 @@ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <stdbool.h> +#include <errno.h> +#include <sys/wait.h> + +#include "constants.h" +#include "history.h" +#include "90s.h" +#include "job.h" + +int execute(char **args); + +// Function declarations for builtin commands +int cd(char **args); +int help(char **args); +int quit(char **args); +int history(char **args); +int export(char **args); +int source(char **args); +int j(char **args); +int bg(char **args); + +// List of builtin commands' names +char *builtin_cmds[] = { + "cd", + "help", + "exit", + "history", + "export", + "source", + "j", + "bg", +}; + +int (*builtin_func[]) (char **) = { + &cd, + &help, + &quit, // cant name it exit as it is taken + &history, + &export, + &source, + &j, + &bg, +}; + +char *shortcut_dirs[] = { + "90s", + "bin", + "localbin", +}; + +char *shortcut_expand_dirs[] = { + "~/.nky/Coding/C/90s", + "~/.local/bin", + "/usr/local/bin", +}; + +char *gethome() { + char *home = getenv("HOME"); + if (home == NULL) { + fprintf(stderr, "Error: HOME environment variable not set.\n"); + exit(EXIT_FAILURE); + } + return home; +} + +char *replace_home_dir(char *str) { + char *home_path = gethome(); + + int path_len = strlen(str); + int home_len = strlen(home_path); + + // Allocate memory for the new path + char* new_path = memalloc(sizeof(char) * (path_len + home_len + 1)); + + int i = 0, j = 0; + while (str[i] != '\0') { + if (str[i] == '~') { + // Copy HOME environment variable value + for (int k = 0; k < home_len; k++) { + new_path[j++] = home_path[k]; + } + i++; + } else { + new_path[j++] = str[i++]; + } + } + + new_path[j] = '\0'; + return new_path; +} + +char *replace_absolute_home(char *str) { + char *home_path = gethome(); + + int path_len = strlen(str); + int home_len = strlen(home_path); + + // Allocate memory for the new path + char* new_path = memalloc(sizeof(char) * (path_len - home_len + 2)); + + int i = 0, j = 0; + while (str[i] != '\0') { + if (strncmp(&str[i], home_path, home_len) == 0) { + // Copy HOME environment variable value + new_path[j++] = '~'; + i += home_len; + } else { + new_path[j++] = str[i++]; + } + } + + new_path[j] = '\0'; + return new_path; +} + +// number of built in commands +int num_builtins() { + return sizeof(builtin_cmds) / sizeof(char *); +} + +// autojump +int j(char **args) { + if (args[1] == NULL) { + fprintf(stderr, "90s: not enough arguments\n"); + return -1; + } + for (int i = 0; i < sizeof(shortcut_dirs) / sizeof(char *); i++) { + int len = strlen(shortcut_dirs[i]); + if (strncmp(args[1], shortcut_dirs[i], len) == 0) { + char **merged_cd = memalloc(sizeof(char *) * 3); + merged_cd[0] = "cd"; + merged_cd[1] = shortcut_expand_dirs[i]; + merged_cd[2] = NULL; + cd(merged_cd); + printf("jumped to %s\n", shortcut_expand_dirs[i]); + return 1; + } + } + return 1; +} + +// change directory +int cd(char **args) { + int i = 0; + if (args[1] == NULL) { + char *home = gethome(); + if (chdir(home) != 0) { + perror("90s"); + } + } else { + while (args[1][i] != '\0') { + if (args[1][i] == '~') { + args[1] = replace_home_dir(args[1]); + break; + } + i++; + } + if (chdir(args[1]) != 0) { + perror("90s"); + } + } + return 1; +} + +// show help menu +int help(char **args) { + printf("90s %s\n", VERSION); + printf("Built in commands:\n"); + + for (int i = 0; i < num_builtins(); i++) { + printf(" %s\n", builtin_cmds[i]); + } + + printf("Use 'man' to read manual of programs\n"); + printf("Licensed under GPL v3\n"); + return 1; +} + +int quit(char **args) { + return 0; // exit prompting loop, which also the shell +} + +int history(char **args) { + char **history = get_all_history(true); + + for (int i = 0; history[i] != NULL; ++i) { + printf("%s\n", history[i]); + free(history[i]); + } + + free(history); + return 1; +} + +int export(char **args) { + args++; // skip the command + while (*args != NULL) { + char *variable = strtok(*args, "=\n"); + char *value = strtok(NULL, "=\n"); + if (variable != NULL && value != NULL) { + if (setenv(variable, value, 1) != 0) { + fprintf(stderr, "90s: Error setting environment variable\n"); + return 0; + } + } else { + fprintf(stderr, "90s: Syntax error when setting environment variable\nUse \"export VARIABLE=VALUE\"\n"); + return 0; + } + args++; + } + return 1; +} + +int source(char **args) { + if (args[1] == NULL) { + fprintf(stderr, "90s: not enough arguments\n"); + return -1; + } + + FILE *file = fopen(args[1], "r"); + + if (file == NULL) { + fprintf(stderr, "90s: no such file or directory '%s'\n", args[1]); + return -1; + } + + char line[RL_BUFSIZE]; + int status; + while (fgets(line, sizeof(line), file) != NULL) { + // Remove newline character if present + size_t len = strlen(line); + if (len > 0 && line[len - 1] == '\n') { + line[len - 1] = '\0'; + } + + char **args = argsplit(line); + status = execute(args); + } + + fclose(file); + return status; // Indicate success +} + +int bg(char **args) { + if (args[1] == NULL) { + fprintf(stderr, "90s: not enough arguments\n"); + return -1; + } + int job_index = atoi(args[1]); + if (job_index == 0) { + fprintf(stderr, "90s: invalid job index\n"); + return -1; + } + job *search = get_job(job_index - 1); + if (search == NULL) { + fprintf(stderr, "90s: no such job\n"); + return -1; + } + printf("Job %i: %s\n", job_index, search->command); + return 1; +} +bool is_builtin(char *command) { + for (int i = 0; i < num_builtins(); i++) { + if (strcmp(command, builtin_cmds[i]) == 0) { + return true; + } + } + return false; +} + +int launch(char **args, int fd, int options) { + int is_bgj = (options & OPT_BGJ) ? 1 : 0; + int redirect_stdout = (options & OPT_STDOUT) ? 1 : 0; + int redirect_stdin = (options & OPT_STDIN) ? 1 : 0; + int redirect_stderr = (options & OPT_STDERR) ? 1 : 0; + + pid_t pid; + + int status; + if ((pid = fork()) == 0) { + // Child process + if (fd > 2) { + // not stdin, stdout, or stderr + if (redirect_stdout) { + if (dup2(fd, STDOUT_FILENO) == -1) { + perror("90s"); + } + } + if (redirect_stdin) { + if (dup2(fd, STDIN_FILENO) == -1) { + perror("90s"); + } + } + if (redirect_stderr) { + if (dup2(fd, STDERR_FILENO) == -1) { + perror("90s"); + } + } + close(fd); // close fd as it is duplicated already + } + if (execvp(args[0], args) == -1) { + if (errno == ENOENT) { + fprintf(stderr, "90s: command not found: %s\n", args[0]); + } + } + exit(EXIT_FAILURE); // exit the child + } else if (pid < 0) { + perror("fork failed"); + } else { + // Parent process + if (is_bgj) { + int job_index = add_job(pid, args[0], true); + printf("[Job: %i] [Process ID: %i] [Command: %s]\n", job_index + 1, pid, args[0]); + return 1; + } else { + do { + waitpid(pid, &status, WUNTRACED); // wait child to be exited to return to prompt + } while (!WIFEXITED(status) && !WIFSIGNALED(status)); + } + } + return 1; +} +// execute built in commands or launch commands and wait it to terminate, return 1 to keep shell running +int execute(char **args) { + if (args[0] == NULL) { // An empty command was entered. + return 1; + } + + // prioritize builtin commands + for (int i = 0; i < num_builtins(); i++) { + if (strcmp(args[0], builtin_cmds[i]) == 0) { + return (*builtin_func[i])(args); + } + } + int num_arg = 0; + + while (args[num_arg] != NULL) { + if (strncmp(args[num_arg], "&", 1) == 0) { + args[num_arg] = NULL; + if (args[num_arg + 1] != NULL) { + // have commands after & + execute(&args[num_arg + 1]); + } + launch(args, STDOUT_FILENO, OPT_BGJ); + return 1; + } + if (strncmp(args[num_arg], ">", 1) == 0) { + int fd = fileno(fopen(args[num_arg + 1], "w+")); + if (fd == -1) { + perror("90s"); + return 1; + } + args[num_arg] = NULL; + int asdf = 0; + while (args[asdf] != NULL) { + fprintf(stderr, "args[%i]: %s\n", asdf, args[asdf]); + asdf++; + } + + return launch(args, fd, OPT_FGJ | OPT_STDOUT); + } + if (strncmp(args[num_arg], "<", 1) == 0) { + int fd = fileno(fopen(args[num_arg + 1], "r")); + if (fd == -1) { + perror("90s"); + return 1; + } + args[num_arg] = NULL; + return launch(args, fd, OPT_FGJ | OPT_STDIN); + } + if (strncmp(args[num_arg], "2>", 2) == 0) { + int fd = fileno(fopen(args[num_arg + 1], "w+")); + if (fd == -1) { + perror("90s"); + return 1; + } + args[num_arg] = NULL; + return launch(args, fd, OPT_FGJ | OPT_STDERR); + } + if (strncmp(args[num_arg], ">&", 2) == 0) { + int fd = fileno(fopen(args[num_arg + 1], "w+")); + if (fd == -1) { + perror("90s"); + return 1; + } + args[num_arg] = NULL; + return launch(args, fd, OPT_FGJ | OPT_STDOUT | OPT_STDERR); + } + if (strncmp(args[num_arg], ">>", 2) == 0) { + int fd = fileno(fopen(args[num_arg + 1], "a+")); + if (fd == -1) { + perror("90s"); + return 1; + } + args[num_arg] = NULL; + return launch(args, fd, OPT_FGJ | OPT_STDOUT); + } + + num_arg++; // count number of args + } + + return launch(args, STDOUT_FILENO, OPT_FGJ); +} + +// execute_pipe with as many pipes as needed +int execute_pipe(char ***args) { + int pipefd[2]; + pid_t pid; + int in = 0; + + int num_cmds = 0; + while (args[num_cmds] != NULL) { + int num_args = 0; + while (args[num_cmds][num_args] != NULL) { + num_args++; + } + num_cmds++; + } + + for (int i = 0; i < num_cmds - 1; i++) { + pipe(pipefd); + if ((pid = fork()) == 0) { + // then this (child) + dup2(in, STDIN_FILENO); // get input from previous command + if (i < num_cmds - 1) { + dup2(pipefd[1], STDOUT_FILENO); // make output go to pipe (next output) + } + close(pipefd[0]); // close original input + execute(args[i]); + exit(EXIT_SUCCESS); + } else if (pid < 0) { + perror("fork failed"); + } + // this will be executed first + close(pipefd[1]); // close output + in = pipefd[0]; // save the input for the next command + } + + if ((pid = fork()) == 0) { + dup2(in, STDIN_FILENO); // get input from pipe + execute(args[num_cmds - 1]); + exit(EXIT_SUCCESS); + } else if (pid < 0) { + perror("fork failed"); + } + + close(in); + for (int i = 0; i < num_cmds; i++) { + waitpid(pid, NULL, 0); + } + return 1; +} diff --git a/src/history.c b/src/history.c @@ -0,0 +1,134 @@ +#include <stdio.h> +#include <stdlib.h> +#include <stdbool.h> +#include <string.h> +#include <unistd.h> +#include <linux/limits.h> + +#include "history.h" +#include "90s.h" +#include "constants.h" + +FILE *history_file; +char *histfile_path; +int cmd_count = 0; + +FILE *open_history_file(char *mode) { + history_file = fopen(histfile_path, mode); + if (history_file == NULL) { + fprintf(stderr, "90s: Error opening history file\n"); + exit(EXIT_FAILURE); + } + return history_file; +} +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"); + } + if (env_home == NULL) { + fprintf(stderr, "90s: HOME AND XDG_CONFIG_HOME environment variable is missing\n"); + exit(EXIT_FAILURE); + } + 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 = memalloc(sizeof(char) * path_len); + histfile_path[0] = '\0'; // initialise string + // 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 - 1] = '\0'; + if (access(histfile_path, F_OK) != 0) { // check for file existence + history_file = open_history_file("w"); // read and write, if doesn't exist, create + fclose(history_file); + } +} + +void save_command_history(char *args) { + history_file = open_history_file("a+"); + char cmd[RL_BUFSIZE]; + cmd[0] = '\0'; + strcat(cmd, args); + int cmd_len = strlen(cmd); + cmd[cmd_len] = '\n'; // put new line feed to split commands + // ptr to first obj, size of each obj, number of obj, file ptr + fwrite(cmd, sizeof(char), cmd_len + 1, history_file); + fclose(history_file); +} + +char *read_command(int direction) { + if (direction == 1) { // up + cmd_count++; + } else { // down + if (cmd_count == 0) { + return NULL; + } else { + cmd_count--; + } + } + char **history = get_all_history(false); + int num_history = 0; + while (*history != NULL) { + num_history++; + history++; + } + if (cmd_count > num_history) { + cmd_count = num_history; + return NULL; + } + history -= num_history; + return history[num_history - cmd_count]; +} + +int is_duplicate(char **history, int line_count, char *line) { + for (int i = 0; i < line_count; ++i) { + if (strcmp(history[i], line) == 0) { + return 1; + } + } + return 0; +} + +char **get_all_history(bool check) { + history_file = open_history_file("r"); + char **history = memalloc(MAX_HISTORY * sizeof(char*)); + char buffer[RL_BUFSIZE]; + int line_count = 0; + + while (fgets(buffer, sizeof(buffer), history_file) != NULL) { + buffer[strcspn(buffer, "\n")] = '\0'; + if (check) { + if (!is_duplicate(history, line_count, buffer)) { + history[line_count] = strdup(buffer); + if (history[line_count] == NULL) { + fprintf(stderr, "Error allocating memory\n"); + exit(EXIT_FAILURE); + } + line_count++; + if (line_count >= MAX_HISTORY) { + fprintf(stderr, "Maximum number of lines reached.\n"); + exit(EXIT_FAILURE); + } + } + } else { + history[line_count] = strdup(buffer); + if (history[line_count] == NULL) { + fprintf(stderr, "Error allocating memory\n"); + exit(EXIT_FAILURE); + } + line_count++; + if (line_count >= MAX_HISTORY) { + fprintf(stderr, "Maximum number of lines reached.\n"); + exit(EXIT_FAILURE); + } + } + } + + fclose(history_file); + history[line_count] = NULL; + return history; +} diff --git a/src/job.c b/src/job.c @@ -0,0 +1,60 @@ +#include <unistd.h> +#include <stdbool.h> +#include <string.h> + +#include "90s.h" + +typedef struct job { + pid_t pid; + char *command; + bool status; + struct job *next; +} job; + +job *jobs = NULL; + +int num_jobs() { + job *current = jobs; + int count = 0; + while (current != NULL) { + count++; + current = current->next; + } + return count; +} + +int add_job(pid_t pid, char *command, bool status) { + job *current = jobs; + job *new_job = memalloc(sizeof(job)); + new_job->pid = pid; + char *buf = memalloc(strlen(command) + 1); + strcpy(buf, command); + new_job->command = buf; + new_job->status = status; + new_job->next = NULL; + if (current == NULL) { + jobs = new_job; + return 0; + } + int index = 1; + while (current->next != NULL) { + current = current->next; + index++; + } + current->next = new_job; + return index; +} + +job *get_job(int index) { + job *current = jobs; + if (index == 0) { + return current; + } + if (index > num_jobs()) { + return NULL; + } + for (int i = 0; i < index; i++) { + current = current->next; + } + return current; +}