commit e4e118046cec9e76e69131b42b4c02de770cbc79
parent 253ec734f00725e1597b727d69306da084c75052
Author: night0721 <[email protected]>
Date: Mon, 1 Jul 2024 17:40:47 +0100
rebrand to 90s
Diffstat:
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;
+}