90s

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

commit 44a74c50f25e56a2fe13ef584b4473afcc427f45
parent 93979512d9e16356981356514115f2a2978f94ca
Author: night0721 <[email protected]>
Date:   Sun, 11 Feb 2024 01:01:28 +0000

background jobs & stdout,in,err redirect

Diffstat:
MMakefile | 2+-
MREADME.md | 8++++++--
Mcommands.c | 290+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------
Mcommands.h | 3++-
Mconstants.h | 7+++++++
Ajob.c | 62++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Ajob.h | 17+++++++++++++++++
Alog | 28++++++++++++++++++++++++++++
8 files changed, 376 insertions(+), 41 deletions(-)

diff --git a/Makefile b/Makefile @@ -6,7 +6,7 @@ MANPREFIX = ${PREFIX}/share/man CFLAGS = -std=gnu11 -O0 -Wall -DVERSION=\"${VERSION}\" -SRC = rush.c color.c constants.h history.c commands.c +SRC = rush.c color.c constants.h history.c commands.c job.c OBJ = ${SRC:.c=.o} .c.o: diff --git a/README.md b/README.md @@ -14,6 +14,7 @@ rush is a minimalistic shell for Unix systems written in C. # Building ```sh $ make +$ sudo make install ``` # Usage @@ -30,6 +31,9 @@ $ ./rush - Editing using left and right arrow keys - !! to repeat last command - Pipes +- autojump to directorys +- stdin, stdout, stderr redirect +- background jobs # Built in commands - cd @@ -38,10 +42,10 @@ $ ./rush - history - export - source +- j +- bg # Todo Features -- stdin, stdout, stderr redirect -- background jobs - tab completion # Credits diff --git a/commands.c b/commands.c @@ -9,6 +9,7 @@ #include "constants.h" #include "history.h" #include "rush.h" +#include "job.h" int execute(char **args); @@ -19,6 +20,8 @@ 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[] = { @@ -27,7 +30,9 @@ char *builtin_cmds[] = { "exit", "history", "export", - "source" + "source", + "j", + "bg", }; int (*builtin_func[]) (char **) = { @@ -36,26 +41,124 @@ int (*builtin_func[]) (char **) = { &quit, // cant name it exit as it is taken &history, &export, - &source + &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 = getenv("HOME"); - if (home == NULL) { - fprintf(stderr, "rush: HOME environment variable is missing\n"); - exit(EXIT_FAILURE); - } + 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"); } @@ -144,6 +247,24 @@ int source(char **args) { 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) { @@ -153,6 +274,58 @@ bool is_builtin(char *command) { 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. @@ -165,52 +338,94 @@ int execute(char **args) { return (*builtin_func[i])(args); } } - int num_arg = 0; - while (*args != NULL) { - num_arg++; // count number of args - args++; - } - - args -= num_arg; - - pid_t pid; + 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++; + } - int status; - if ((pid = fork()) == 0) { - // Child process - if (execvp(args[0], args) == -1) { - if (errno == ENOENT) { - fprintf(stderr, "rush: command not found: %s\n", args[0]); + 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); } - exit(EXIT_FAILURE); // exit the child - } else if (pid < 0) { - perror("fork failed"); - } else { - // Parent process - do { - waitpid(pid, &status, WUNTRACED); // wait child to be exited to return to prompt - } while (!WIFEXITED(status) && !WIFSIGNALED(status)); + 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 1; + return launch(args, STDOUT_FILENO, OPT_FGJ); } // execute_pipe with as many pipes as needed int execute_pipe(char ***args) { int pipefd[2]; - int status; 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) { + //printf("args [%i]: %s\n", num_cmds, args[num_cmds][num_args]); + num_args++; + } num_cmds++; } for (int i = 0; i < num_cmds - 1; i++) { + //printf("i: %d\n", i); pipe(pipefd); if ((pid = fork()) == 0) { // then this (child) @@ -219,8 +434,8 @@ int execute_pipe(char ***args) { dup2(pipefd[1], STDOUT_FILENO); // make output go to pipe (next output) } close(pipefd[0]); // close original input - status = execute(args[i]); - exit(status); + execute(args[i]); + exit(EXIT_SUCCESS); } else if (pid < 0) { perror("fork failed"); } @@ -230,16 +445,17 @@ int execute_pipe(char ***args) { } if ((pid = fork()) == 0) { + // printf("last command\n"); dup2(in, STDIN_FILENO); // get input from pipe - status = execute(args[num_cmds - 1]); - exit(status); + 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++) { - wait(&status); + waitpid(pid, NULL, 0); } return 1; } diff --git a/commands.h b/commands.h @@ -1,9 +1,10 @@ #ifndef COMMANDS_H_ #define COMMANDS_H_ +char *replace_absolute_home(char *str); int num_builtins(); bool is_builtin(char *command); -int execute(char **args); +int execute(char **args, int fd, int options); int execute_pipe(char ***args); #endif diff --git a/constants.h b/constants.h @@ -6,4 +6,11 @@ #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/job.c b/job.c @@ -0,0 +1,62 @@ +#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++) { + printf("current: %s\n", current->command); + current = current->next; + printf("next: %s\n", current->command); + } + return current; +} diff --git a/job.h b/job.h @@ -0,0 +1,17 @@ +#ifndef JOB_H_ +#define JOB_H_ + +#include <unistd.h> +#include <stdbool.h> + +typedef struct job { + pid_t pid; + char *command; + bool status; + struct job *next; +} job; + +int add_job(pid_t pid, char *command, bool status); +job *get_job(pid_t pid); + +#endif diff --git a/log b/log @@ -0,0 +1,28 @@ +drwxr-xr-x 3 n n 4.0K Feb 11 00:04 . +drwxr-xr-x 13 n n 4.0K Feb 6 21:29 .. +-rw-r--r-- 1 n n 522 Feb 10 22:42 color.c +-rw-r--r-- 1 n n 199 Feb 10 22:42 color.h +-rw-r--r-- 1 n n 6.0K Feb 10 22:44 color.o +-rw-r--r-- 1 n n 12K Feb 11 00:04 commands.c +-rw-r--r-- 1 n n 220 Feb 10 19:17 commands.h +-rw-r--r-- 1 n n 15K Feb 11 00:02 commands.o +-rw-r--r-- 1 n n 598 Feb 10 23:23 constants.h +drwxr-xr-x 8 n n 4.0K Feb 10 23:31 .git +-rw-r--r-- 1 n n 18 Feb 6 18:42 .gitignore +-rw-r--r-- 1 n n 4.0K Feb 10 16:10 history.c +-rw-r--r-- 1 n n 207 Feb 9 18:36 history.h +-rw-r--r-- 1 n n 13K Feb 10 22:19 history.o +-rw-r--r-- 1 n n 938 Feb 10 23:24 job.c +-rw-r--r-- 1 n n 133 Feb 10 19:06 job.h +-rw-r--r-- 1 n n 4.6K Feb 10 23:26 job.o +-rw-r--r-- 1 n n 35K Feb 6 18:24 LICENSE +-rw-r--r-- 1 n n 0 Feb 11 00:05 log +-rw-r--r-- 1 n n 878 Feb 10 23:27 Makefile +-rw-r--r-- 1 n n 1.3K Feb 10 19:11 README.md +-rwxr-xr-x 1 n n 43K Feb 11 00:02 rush +-rw-r--r-- 1 n n 23K Feb 8 22:25 rush-0.1.tar.gz +-rw-r--r-- 1 n n 373 Feb 6 18:56 rush.1 +-rw-r--r-- 1 n n 17K Feb 10 23:30 rush.c +-rw-r--r-- 1 n n 119 Feb 10 16:06 rush.h +-rw-r--r-- 1 n n 15K Feb 10 23:50 rush.o +[Job: 1] [Process ID: 2150241] [Command: firefox]