456 lines
12 KiB
C
456 lines
12 KiB
C
#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;
|
|
}
|