commit 44a74c50f25e56a2fe13ef584b4473afcc427f45
parent 93979512d9e16356981356514115f2a2978f94ca
Author: night0721 <[email protected]>
Date: Sun, 11 Feb 2024 01:01:28 +0000
background jobs & stdout,in,err redirect
Diffstat:
M | Makefile | | | 2 | +- |
M | README.md | | | 8 | ++++++-- |
M | commands.c | | | 290 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------- |
M | commands.h | | | 3 | ++- |
M | constants.h | | | 7 | +++++++ |
A | job.c | | | 62 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | job.h | | | 17 | +++++++++++++++++ |
A | log | | | 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]