UW-Platteville_ClassProjects/shell_syscalls_practice.c

439 lines
7.9 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/***************************************************************************//**
@file main.c
@author Stephen Brennan; Revised by Evan Niederwerfer
@date 03/09/25
@brief Custom bash shell written in C
*******************************************************************************/
#include <sys/wait.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
int size = 64;
/*
Function Declarations for builtin shell commands:
*/
int lsh_cd(char **args);
int lsh_help(char **args);
int lsh_exit(char **args);
int lsh_about(char **args);
char *builtin_str[] = {
"cd",
"help",
"exit",
"about"
};
int (*builtin_func[]) (char **) = {
&lsh_cd,
&lsh_help,
&lsh_exit,
&lsh_about
};
int lsh_num_builtins() {
return sizeof(builtin_str) / sizeof(char *);
}
/*
Builtin function implementations.
*/
/**
@brief Bultin command: change directory.
@param args List of args. args[0] is "cd". args[1] is the directory.
@return Always returns 1, to continue executing.
*/
int lsh_cd(char **args)
{
if (args[1] == NULL) {
fprintf(stderr, "lsh: expected argument to \"cd\"\n");
} else {
if (chdir(args[1]) != 0) {
perror("lsh");
}
}
return 1;
}
/**
@brief Builtin command: print help.
@param args List of args. Not examined.
@return Always returns 1, to continue executing.
*/
int lsh_help(char **args)
{
int i;
printf("Stephen Brennan's (revised by Evan Niederwerfer) LSH\n");
printf("Type program names and arguments, and hit enter.\n");
printf("The following are built in:\n");
for (i = 0; i < lsh_num_builtins(); i++) {
printf(" %s\n", builtin_str[i]);
}
printf("Use the man command for information on other programs.\n");
return 1;
}
/**
@brief Builtin command: exit.
@param args List of args. Not examined.
@return Always returns 0, to terminate execution.
*/
int lsh_exit(char **args)
{
return 0;
}
int lsh_about(char **args) {
printf("Bash shell written in C - Stephen Brennan's shell revised by Evan Niederwerfer \n\n");
FILE *cmd = popen("bash --version", "r");
if (cmd == NULL) {
perror("popen");
return 1;
}
char buffer[128];
while (fgets(buffer, sizeof(buffer), cmd) != NULL) {
printf("%s", buffer);
}
fclose(cmd);
return 1;
}
/**
@brief Launch a program and wait for it to terminate.
@param args Null terminated list of arguments (including program).
@return Always returns 1, to continue execution.
*/
int lsh_launch(char **args)
{
pid_t pid;
int status;
pid = fork();
if (pid == 0) {
// Child process
if (execvp(args[0], args) == -1) {
perror("lsh");
}
exit(EXIT_FAILURE);
} else if (pid < 0) {
// Error forking
perror("lsh");
} else {
// Parent process
do {
waitpid(pid, &status, WUNTRACED);
} while (!WIFEXITED(status) && !WIFSIGNALED(status));
}
return 1;
}
int myPipe(char **args, int j)
{
pid_t pid, pid2;
int pipefd[2];
if (pipe(pipefd) == -1) {
perror("pipe");
return -1;
}
pid = fork();
if (pid == 0) { // Child
close(pipefd[0]);
dup2(pipefd[1], STDOUT_FILENO); // redirect stdout to the grandchild
close(pipefd[1]);
args[j] = NULL;
execvp(args[0], args);
}
pid2 = fork();
if (pid2 == 0) { // Grandchild
close(pipefd[1]);
dup2(pipefd[0], STDIN_FILENO); // take in input from the child pipe
close(pipefd[0]);
execvp(args[j+1], &args[j+1]);
}
//parent
close(pipefd[0]);
close(pipefd[1]);
waitpid(pid, NULL, 0);
waitpid(pid2, NULL, 0);
return 1;
}
int myAmphersand(char **args, int j)
{
pid_t pid;
pid = fork();
if (pid == -1) {
perror("Fork Failed");
return(-1);
}
if (pid == 0) {
args[j] = NULL;
lsh_launch(args);
}
else {
args = &args[j+1];
lsh_launch(args);
}
waitpid(pid, NULL, 0);
return 1;
}
int mySemicolon(char **args, int j)
{
args[j] = NULL;
lsh_launch(args);
args = &args[j+1];
lsh_launch(args);
return 1;
}
/**
@brief Execute shell built-in or launch program.
@param args Null terminated list of arguments.
@return 1 if the shell should continue running, 0 if it should terminate
*/
int lsh_execute(char **args)
{
int i;
if (args[0] == NULL) {
// An empty command was entered.
return 1;
}
for(int j = 0; args[j] != NULL; j++)
{
// compare "|", "&", ";" with args[j]
// and go to coorresponding functions.
for (int j = 0; args[j] != NULL; j++) {
if (strcmp(args[j], "|") == 0) {
return myPipe(args, j); // j is the index where the pipe symbol is found
}
}
for (int j = 0; args[j] != NULL; j++) {
if (strcmp(args[j], ";") == 0) {
return mySemicolon(args, j);
}
}
for (int j = 0; args[j] != NULL; j++) {
if (strcmp(args[j], "&") == 0) {
return myAmphersand(args, j);
}
}
}
for (i = 0; i < lsh_num_builtins(); i++) {
if (strcmp(args[0], builtin_str[i]) == 0) {
return (*builtin_func[i])(args);
}
}
return lsh_launch(args);
}
#define LSH_RL_BUFSIZE 1024
/**
@brief Read a line of input from stdin.
@return The line from stdin.
*/
char *lsh_read_line(void)
{
int bufsize = LSH_RL_BUFSIZE;
int position = 0;
char *buffer = malloc(sizeof(char) * bufsize);
int c;
if (!buffer) {
fprintf(stderr, "lsh: allocation error\n");
exit(EXIT_FAILURE);
}
while (1) {
// Read a character
c = getchar();
if (c == EOF) {
exit(EXIT_SUCCESS);
} else if (c == '\n') {
buffer[position] = '\0';
return buffer;
} else {
buffer[position] = c;
}
position++;
// If we have exceeded the buffer, reallocate.
if (position >= bufsize) {
bufsize += LSH_RL_BUFSIZE;
buffer = realloc(buffer, bufsize);
if (!buffer) {
fprintf(stderr, "lsh: allocation error\n");
exit(EXIT_FAILURE);
}
}
}
}
char* process_line(char *oline)
{
int length = strlen(oline);
if (length > 100) {
oline[100] = '\0';
}
for (int i = 0; oline[i] != '\0'; i++) {
if (oline[i] < 0 || oline[i] > 127) { // ASCII Range
printf("Error: Invalid character detected. Only ASCII characters (AZ, az, 09, dash, dot, forward slash) are allowed.\n");
exit(EXIT_FAILURE);
}
}
return oline;
}
#define LSH_TOK_BUFSIZE 64
#define LSH_TOK_DELIM " \t\r\n\a"
/**
@brief Split a line into tokens (very naively).
@param line The line.
@return Null-terminated array of tokens.
*/
char **lsh_split_line(char *line)
{
int bufsize = LSH_TOK_BUFSIZE, position = 0;
char **tokens = malloc(bufsize * sizeof(char*));
char *token, **tokens_backup;
if (!tokens) {
fprintf(stderr, "lsh: allocation error\n");
exit(EXIT_FAILURE);
}
token = strtok(line, LSH_TOK_DELIM);
while (token != NULL) {
tokens[position] = token;
position++;
if (position >= bufsize) {
bufsize += LSH_TOK_BUFSIZE;
tokens_backup = tokens;
tokens = realloc(tokens, bufsize * sizeof(char*));
if (!tokens) {
free(tokens_backup);
fprintf(stderr, "lsh: allocation error\n");
exit(EXIT_FAILURE);
}
}
token = strtok(NULL, LSH_TOK_DELIM);
}
tokens[position] = NULL;
return tokens;
}
/**
@brief Loop getting input and executing it.
*/
void lsh_loop(void)
{
char *line, *oline;
char **args;
int status;
do {
printf("CS3230-Evan>> ");
oline = lsh_read_line();
line = process_line(oline); // process the input
args = lsh_split_line(line);
status = lsh_execute(args);
free(line);
free(args);
} while (status);
}
/**
@brief Main entry point.
@param argc Argument count.
@param argv Argument vector.
@return status code
*/
int main(int argc, char **argv)
{
// Load config files, if any.
// Run command loop.
lsh_loop();
// Perform any shutdown/cleanup.
return EXIT_SUCCESS;
}