commit 7f95910a9c6b0a2fce460b679de6b1ecb3dde324 Author: Sami Samhuri Date: Sun Jan 16 00:00:02 2022 -0800 First commit diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..1716472 --- /dev/null +++ b/Makefile @@ -0,0 +1,13 @@ +CFLAGS = -Wall -g +CPPFLAGS = -I/usr/local/opt/readline/include +LDFLAGS = -L/usr/local/opt/readline/lib + +OBJS = utils.o builtins.o exec.o jobs.o main.o + +all: a1 + +a1: $(OBJS) + $(CC) $(CFLAGS) -o a1 $(OBJS) $(LDFLAGS) -lreadline -lhistory -ltermcap + +clean: + rm -rf $(OBJS) a1 diff --git a/a1.pdf b/a1.pdf new file mode 100644 index 0000000..745ee7d Binary files /dev/null and b/a1.pdf differ diff --git a/builtins.c b/builtins.c new file mode 100644 index 0000000..d8399e7 --- /dev/null +++ b/builtins.c @@ -0,0 +1,143 @@ +/* + * A simple shell with some basic features. + * + * Sami Samhuri 0327342 + * January 31, 2006 + * CSC 360, Assignment 1 + * + * builtins.c + * $Id: builtins.c 184 2006-01-29 08:53:30Z sjs $ + */ + +/*#define _GNU_SOURCE*/ + +#include +#include +#include +#include +#include +#include +#include + +#include "builtins.h" +#include "main.h" +#include "exec.h" +#include "jobs.h" +#include "utils.h" + +int builtin_bg ( int argc, char **argv ) +{ + if ( argc < 2 ) + { + queue_message ("bg: usage 'bg '"); + queue_message (" runs in the background"); + return -1; + } + + pid_t pid = exec_command (&argv[1], 1); /* &argv[1] skips 'bg' */ + if (pid > 0) + { + job j = add_job (pid, &argv[1]); + + char *message = (char *)myxmalloc (MSGLEN); + snprintf (message, MSGLEN, + "Running job " YELLOW "%i" WHITE " (pid %i) in background" CLEAR, + j->id, pid); + queue_message (message); + free (message); + } + return pid; +} + +int builtin_bgkill ( int argc, char **argv ) +{ + if ( argc != 2 ) + { + queue_message ("bgkill: usage 'bgkill '"); + queue_message (" type 'bglist' to see running jobs"); + return -1; + } + + int job_id = atoi (argv[1]); + job j = job_with_id (job_id); + if (!j) + { + queue_message (YELLOW "Invalid job number"); + queue_message ("(type 'bglist' to see running jobs)"); + return -1; + } + + kill (j->pid, SIGTERM); + /*delete_job (j);*/ +/* queue_message ("Job killed");*/ + return 1; +} + +int builtin_bglist ( void ) +{ + int num_jobs; + if ( !(num_jobs = get_num_jobs()) ) + return 0; + + job j; + char *message = (char *)myxmalloc (MSGLEN); + for (j = get_job_list(); j; j = j->next) + { + snprintf (message, MSGLEN, + YELLOW "%i" WHITE ": (pid %i)" YELLOW " %s" CLEAR, + j->id, j->pid, j->cmdline); + queue_message (message); + } + snprintf (message, MSGLEN, + GREEN "Total: %i background job(s) running" CLEAR, num_jobs); + queue_message (message); + free (message); + return num_jobs; +} + +int builtin_cd ( int argc, char **argv ) +{ + static char *lastdir = NULL; + char *dir, *pwd = getcwd(NULL, 0); + + if (!lastdir) /* initialize */ + lastdir = pwd; + + if ( argc < 2 ) /* cd w/ no args acts like cd $HOME */ + dir = getenv("HOME"); + else + { + if ( !strncmp(argv[1], "-", 1) ) + dir = lastdir; /* cd - changes to previous dir */ + else + dir = argv[1]; + } + + if ( chdir (dir) < 0 ) + { /* error */ + size_t len = strlen (dir); + char *message = (char *)myxmalloc (len + MSGLEN); + (void) snprintf (message, len + MSGLEN, + RED "cd: %s: %s" CLEAR, strerror (errno), dir); + queue_message (message); + free (message); + return -1; + } + + /* save the last dir for cd -, if it's different */ + if ( strcmp(pwd, dir) ) + lastdir = pwd; + return 1; +} + +void builtin_clear ( void ) +{ + printf ("\033[2J"); +} + +void builtin_pwd ( void ) +{ + char *pwd = getcwd(NULL, 0); + queue_message (pwd); + xfree (pwd); +} diff --git a/builtins.h b/builtins.h new file mode 100644 index 0000000..919095e --- /dev/null +++ b/builtins.h @@ -0,0 +1,17 @@ +/* + * A simple shell with some basic features. + * + * Sami Samhuri 0327342 + * January 31, 2006 + * CSC 360, Assignment 1 + * + * builtins.h + * $Id: builtins.h 184 2006-01-29 08:53:30Z sjs $ + */ + +int builtin_bg ( int argc, char **argv ); +int builtin_bgkill ( int argc, char **argv ); +int builtin_bglist ( void ); +int builtin_cd ( int argc, char **argv ); +void builtin_clear ( void ); +void builtin_pwd ( void ); diff --git a/exec.c b/exec.c new file mode 100644 index 0000000..e7498a1 --- /dev/null +++ b/exec.c @@ -0,0 +1,98 @@ +/* + * A simple shell with some basic features. + * + * Sami Samhuri 0327342 + * January 31, 2006 + * CSC 360, Assignment 1 + * + * exec.c -- fork and execute a program + * $Id: exec.c 184 2006-01-29 08:53:30Z sjs $ + */ + +/*#define _GNU_SOURCE*/ + +#include +#include +#include +#include +#include /* waitpid */ +#include /* waitpid */ +#include + +#include "exec.h" +#include "utils.h" +#include "main.h" + +char *is_executable ( char *file ) +{ + if ( strchr (file, '/') ) + { /* process absolute and relative paths directly */ + if ( access (file, X_OK) == 0 ) + return strdup (file); + else + return NULL; + } + + char *path = strdup ( getenv ("PATH") ); + int file_len = strlen (file); + char *dir = strtok (path, ":"); + char *filename = NULL; + while ( dir ) + { + filename = (char *)myxmalloc (strlen (dir) + file_len + 2); + sprintf (filename, "%s/%s", dir, file); + if ( access (filename, X_OK) == 0 ) + break; + free (filename); + filename = NULL; + dir = strtok (NULL, ":"); + } + xfree (path); + return filename; +} + +pid_t exec_command ( char **argv, int background ) +{ + int status; + pid_t pid; + char *filename; + + if ( !(filename = is_executable (argv[0])) ) + { /* error, not executable */ + char *msg = (char *)myxmalloc (MSGLEN); + sprintf (msg, RED "%s: %s" CLEAR, argv[0], strerror (errno)); + queue_message (msg); + free (msg); + return -1; + } + + if ( (pid = fork()) > 0 ) + { /* parent */ + if ( background ) + waitpid (pid, &status, WNOHANG); + else + waitpid (pid, &status, 0); + } + else if ( pid == 0 ) + { /* child */ + /* kludge, briefly wait for the job to be created so that if the + * command exits quickly, the job is found and removed. otherwise there + * are zombie jobs erroneously lying around. note, this isn't guaranteed + * to work, it just seems to be enough time in tests here. + */ + usleep (100); + execv (filename, argv); + + /* if we get here there was an error, display it */ + printf (RED "\nCannot execute '%s' (%s)\n" CLEAR, argv[0], + strerror(errno)); + free (filename); + _exit (EXIT_FAILURE); + } + else + { /* error, pid < 0 */ + queue_message (RED "Unable to fork(), uh oh..." CLEAR); + } + free (filename); + return pid; +} diff --git a/exec.h b/exec.h new file mode 100644 index 0000000..d7f7b16 --- /dev/null +++ b/exec.h @@ -0,0 +1,14 @@ +/* + * A simple shell with some basic features. + * + * Sami Samhuri 0327342 + * January 31, 2006 + * CSC 360, Assignment 1 + * + * exec.h + * $Id: exec.h 183 2006-01-27 11:24:52Z sjs $ + */ + +/* execute the command argv[0], with arg list argv[], background can be + * non-zero to run the task in the background under our limited job control */ +pid_t exec_command ( char **argv, int background ); diff --git a/jobs.c b/jobs.c new file mode 100644 index 0000000..9a8c3aa --- /dev/null +++ b/jobs.c @@ -0,0 +1,143 @@ +/* + * A simple shell with some basic features. + * + * Sami Samhuri 0327342 + * January 31, 2006 + * CSC 360, Assignment 1 + * + * jobs.c + * $Id: jobs.c 184 2006-01-29 08:53:30Z sjs $ + */ + +/*#define _GNU_SOURCE*/ + +#include +#include + +#include "jobs.h" +#include "main.h" +#include "utils.h" + +#define MIN(a, b) ((a) < (b)) ? (a) : (b) + +static job job_list_head = NULL; +static int num_jobs = 0; +static int next_id = 1; + +static int get_next_id ( void ) +{ + while ( job_with_id (next_id) ) + next_id++; + return next_id++; +} + +job add_job ( pid_t pid, char **argv ) +{ + job i, j = (job)myxmalloc (sizeof (struct job)); + j->id = get_next_id(); + j->pid = pid; + j->cmdline = array_cat (argv); + if (DEBUG) + printf("DEBUG: cmdline='%s'\n", j->cmdline); + j->next = NULL; + j->prev = NULL; + + for (i = job_list_head; i && i->next; i = i->next) + { /* insert jobs in job_id order */ + if ( i->id > j->id ) + { /* insert BEFORE i */ + if (DEBUG) + printf("DEBUG: i=%i, i->next=%i, i->prev=%p\n", i->id, i->next->id, i->prev); + j->next = i; + j->prev = i->prev; + if (i->prev) + i->prev->next = j; + i->prev = j; + + if ( job_list_head == i ) + job_list_head = j; + break; + } + } + + if ( i == NULL ) /* empty list */ + { + if (DEBUG) + printf("DEBUG: i=%p, job_list_head=%p\n", i, job_list_head); + job_list_head = j; + } + else if ( !i->next ) /* at the end, i->next == NULL */ + { /* at this point, append the new job to the end of the list */ + if (DEBUG) + printf("DEBUG: i=%i\n", i->id); + i->next = j; + j->prev = i; + } + if (DEBUG) + printf("DEBUG: job added: (%i,%i)\n", j->id, j->pid); + num_jobs++; + return j; +} + +void delete_job ( job j ) +{ + next_id = MIN(next_id, j->id); /* prefer the lower id */ + + if ( j == job_list_head ) + { + if (j->next) + job_list_head = j->next; + else + job_list_head = NULL; + } + if (j->prev) + j->prev->next = j->next; + if (j->next) + j->next->prev = j->prev; + + if (DEBUG) + printf("DEBUG: str=%p\n", j->cmdline); + xfree (j->cmdline); + xfree (j); + num_jobs--; +} + +void free_job_list ( void ) +{ + while ( job_list_head ) + delete_job ( job_list_head ); +} + +job job_with_id ( int job_id ) +{ + job j; + for (j = job_list_head; j; j = j->next) + { +/* printf("DEBUG: id=%i, j=%p:%i:%i\n", job_id, j, j->id, j->pid); */ + if (j->id == job_id) + return j; + } + return NULL; +} + +job job_with_pid ( pid_t pid ) +{ + job j; + for (j = job_list_head; j; j = j->next) + { + printf("DEBUG: pid=%i, j=%p:%i:%i\n", pid, j, j->id, j->pid); + if (j->pid == pid) + return j; + } + return NULL; +} + +job get_job_list ( void ) +{ + return job_list_head; +} + +int get_num_jobs ( void ) +{ + return num_jobs; +} diff --git a/jobs.h b/jobs.h new file mode 100644 index 0000000..7960039 --- /dev/null +++ b/jobs.h @@ -0,0 +1,29 @@ +/* + * A simple shell with some basic features. + * + * Sami Samhuri 0327342 + * January 31, 2006 + * CSC 360, Assignment 1 + * + * jobs.h + * $Id: jobs.h 183 2006-01-27 11:24:52Z sjs $ + */ + +#include + +struct job { + int id; + pid_t pid; + char *cmdline; + struct job *next; + struct job *prev; +}; +typedef struct job *job; + +job add_job ( pid_t pid, char **argv ); +void delete_job ( job j ); +void free_job_list ( void ); +int get_num_jobs ( void ); +job get_job_list ( void ); +job job_with_id ( int job_id ); +job job_with_pid ( pid_t pid ); diff --git a/main.c b/main.c new file mode 100644 index 0000000..abb3161 --- /dev/null +++ b/main.c @@ -0,0 +1,281 @@ +/* + * A simple shell with some basic features. + * + * Sami Samhuri 0327342 + * January 31, 2006 + * CSC 360, Assignment 1 + * + * main.c + * $Id: main.c 186 2006-01-29 09:06:06Z sjs $ + */ + +/*#define _GNU_SOURCE*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "utils.h" +#include "jobs.h" +#include "exec.h" +#include "builtins.h" + +#define MAX(a, b) (a) > (b) ? (a) : (b) +#define PROMPT BLUE "%s" WHITE "%% " CLEAR /* looks like this: /path/to/somewhere% */ +#define INVALID_CHARS "&|;<>" /* for wordexp */ + +struct message { + char *data; + struct message *next; +}; +typedef struct message *message; + +static message msg_queue_head = NULL; /* message queue */ + +void queue_message ( char *msg ) +{ /* queue messages so they don't mix with a running program's output + * instead they're only displayed while waiting on input w/ readline + * message m: freed in print_messages() + */ + message i, m = (message)myxmalloc (sizeof (struct message)); + m->data = strdup (msg); + m->next = NULL; + for (i = msg_queue_head; i && i->next; i = i->next) + ; + + if (!i) /* if i is NULL, then i == msg_queue_head == NULL */ + msg_queue_head = m; + else /* queue m */ + i->next = m; +} + +void free_message_queue ( void ) +{ + message m, n = msg_queue_head; + while ( (m = n) ) + { + n = m->next; + xfree (m->data); + free (m); + } + msg_queue_head = NULL; +} + +int print_messages ( void ) +{ + if (!msg_queue_head) + return 0; + + /* there must be an easier way to interrupt readline... */ + char *old = rl_line_buffer; + rl_line_buffer = strdup (""); + rl_save_prompt(); + rl_clear_message(); + + message m; + for (m = msg_queue_head; m; m = m->next) + printf (WHITE "%s\n" CLEAR, m->data); + free_message_queue (); + + xfree (rl_line_buffer); + rl_line_buffer = old; + rl_restore_prompt(); + rl_forced_update_display(); + return 1; +} + +char *strsignal ( int status ) +{ /* like strerror for waitpid */ + char *str = (char *)myxmalloc (MSGLEN); + if ( WIFEXITED(status) ) + { + if ( WEXITSTATUS(status) ) /* non-zero exit status */ + sprintf (str, RED "exit %i", WEXITSTATUS(status)); + else /* clean exit */ + sprintf (str, GREEN "done"); + } + if ( WIFSIGNALED(status) ) + { + switch ( WTERMSIG(status) ) + { + case SIGTERM: + sprintf (str, RED "terminated"); + break; + + case SIGKILL: + sprintf (str, RED "killed"); + break; + + case SIGPIPE: + sprintf (str, RED "broken pipe"); + break; + + case SIGSEGV: + sprintf (str, RED "segmentation fault"); + break; + + case SIGABRT: + sprintf (str, RED "aborted"); + break; + + default: + sprintf (str, RED "signal %i", WTERMSIG(status)); + break; + } + } + return str; +} + +void child_state_changed ( int signum ) +{ /* handler for SIGCHLD when a child's state changes */ + int status; + pid_t pid; + + /* linux resets the sighandler after each call */ + signal (SIGCHLD, child_state_changed); + + pid = waitpid (-1, &status, WNOHANG); + job j = job_with_pid (pid); + if (j) + { + char *strstatus = strsignal (status); + /* alert the user of the termination, and delete the job */ + size_t len = strlen (j->cmdline); + char *msg = (char *)myxmalloc (len + MSGLEN); + snprintf (msg, len + MSGLEN, + YELLOW "%i" WHITE ": (pid %i) %s" YELLOW ": %s" CLEAR, + j->id, j->pid, strstatus, j->cmdline); + queue_message (msg); + xfree (msg); + xfree (strstatus); + delete_job (j); + } +} + +char *get_prompt ( void ) +{ /* display the pwd in the prompt */ + char *pwd, *prompt; + + pwd = getcwd(NULL, 0); + size_t len = strlen (pwd) + strlen (PROMPT); + prompt = (char *)myxmalloc (len); + snprintf (prompt, len, PROMPT, pwd); + xfree (pwd); + return prompt; +} + +int cmd_matches ( char *trigger, char *cmd ) +{ + return !strcmp (trigger, cmd); +} + +int handle_wordexp_result ( int result, char *cmd ) +{ + switch (result) + { + case WRDE_BADCHAR: + ; /* gcc chokes if the int decl is first, lame */ + int invalid_char = strcspn (cmd, INVALID_CHARS); + char *msg = (char *)myxmalloc (strlen (cmd) + MSGLEN); + int i; + for (i = 0; i < invalid_char; i++) + *(msg + i) = ' '; + sprintf (msg + invalid_char, + RED "^ invalid character in column %i", invalid_char + 1); + queue_message (cmd); + queue_message (msg); + xfree (msg); + result = 0; + break; + case WRDE_BADVAL: + queue_message ("undefined variable"); + result = 0; + break; + case WRDE_CMDSUB: + queue_message ("no command substitution allowed"); + result = 0; + break; + case WRDE_NOSPACE: + queue_message ("not enough memory"); + result = 0; + break; + case WRDE_SYNTAX: + queue_message ("syntax error"); + result = 0; + break; + default: + /* success */ + result = 1; + } + return result; +} + +int main ( void ) +{ + signal (SIGCHLD, child_state_changed); /* catch SIGCHLD */ + + /* while waiting for input, display messasges */ + rl_event_hook = print_messages; + + /* register clean-up function */ + atexit (free_job_list); + atexit (free_message_queue); + + for (;;) + { + char *prompt = get_prompt(); + char *cmd = readline (prompt); + xfree (prompt); + + if (!cmd) /* exit if we get an EOF, which returns NULL */ + break; + + wordexp_t words; + int result = wordexp (cmd, &words, WRDE_SHOWERR); + if ( handle_wordexp_result (result, cmd) && words.we_wordc > 0 ) + { + if (DEBUG) + { + int i; + printf("DEBUG: args = { "); + for (i = 0; i < words.we_wordc; i++) + printf ("'%s', ", words.we_wordv[i]); + printf ("}\n"); + } + /* try the built-in commands */ + if ( cmd_matches ("bg", words.we_wordv[0]) ) + builtin_bg ( words.we_wordc, words.we_wordv ); + else if ( cmd_matches ("bgkill", words.we_wordv[0]) ) + builtin_bgkill ( words.we_wordc, words.we_wordv ); + else if ( cmd_matches ("bglist", words.we_wordv[0]) ) + builtin_bglist (); + else if ( cmd_matches ("cd", words.we_wordv[0]) ) + builtin_cd ( words.we_wordc, words.we_wordv ); + else if ( cmd_matches ("clear", words.we_wordv[0]) ) + builtin_clear (); + else if ( cmd_matches ("pwd", words.we_wordv[0]) ) + builtin_pwd (); + else if ( cmd_matches ("exit", words.we_wordv[0]) ) + { /* quick clean-up, then break */ + wordfree (&words); + free (cmd); + break; + } + else /* default to trying to execute the cmd line */ + exec_command (words.we_wordv, 0); + + add_history (cmd); /* add to the readline history */ + wordfree (&words); + } /* if handle_wordexp_result (result) */ + free (cmd); + } /* for (;;) */ + return 0; +} diff --git a/main.h b/main.h new file mode 100644 index 0000000..85799dd --- /dev/null +++ b/main.h @@ -0,0 +1,12 @@ +/* + * A simple shell with some basic features. + * + * Sami Samhuri 0327342 + * January 31, 2006 + * CSC 360, Assignment 1 + * + * main.h + * $Id: main.h 183 2006-01-27 11:24:52Z sjs $ + */ + +void queue_message ( char *message ); diff --git a/utils.c b/utils.c new file mode 100644 index 0000000..e34562a --- /dev/null +++ b/utils.c @@ -0,0 +1,80 @@ +/* + * A simple shell with some basic features. + * + * Sami Samhuri 0327342 + * January 31, 2006 + * CSC 360, Assignment 1 + * + * utils.c + * $Id: utils.c 184 2006-01-29 08:53:30Z sjs $ + */ + +#include +#include +#include +#include "utils.h" + +char *array_cat ( char **array ) +{ + char *p = NULL, *str = NULL; + int i, pos = 0; + for (i = 0; array[i]; i++) + { + if (DEBUG) + printf("DEBUG: array[%i]=%p:'%s'\n", i, array[i], array[i]); + int len = strlen (array[i]); + str = (char *)myxrealloc (str, pos + len + 1); + p = str + pos; + memcpy (p, array[i], len); + p += len; + *p++ = ' '; + pos += len + 1; + } + *--p = '\0'; + if (DEBUG) + printf("DEBUG: str=%p\n", str); + return str; +} + +void free_array ( void **array ) +{ + int i = 0; + + if (!array) + return; + + while ( array[i] ) + free (array[i++]); + free (array); +} + +void *myxmalloc ( size_t size ) +{ + void *ptr = malloc (size); + if (ptr) + return ptr; + + printf(RED "Out of memory, bailing!\n" CLEAR); + exit (EXIT_FAILURE); +} + +void *myxrealloc( void *ptr, size_t size ) +{ + void *new_ptr = realloc (ptr, size); + if (new_ptr) + return new_ptr; + + printf(RED "Out of memory, bailing!\n" CLEAR); + exit (EXIT_FAILURE); +} + +void **array_realloc ( void **array, size_t size ) +{ + void **ptr = realloc (array, size * sizeof (void *)); + if (ptr) + return ptr; + + free_array (array); + printf (RED "Out of memory, bailing!\n" CLEAR); + exit (EXIT_FAILURE); +} diff --git a/utils.h b/utils.h new file mode 100644 index 0000000..0462b78 --- /dev/null +++ b/utils.h @@ -0,0 +1,39 @@ +/* + * A simple shell with some basic features. + * + * Sami Samhuri 0327342 + * January 31, 2006 + * CSC 360, Assignment 1 + * + * utils.h + * $Id: utils.h 184 2006-01-29 08:53:30Z sjs $ + */ + +#include + +#define DEBUG 1 +#define MSGLEN 255 /* soft limit on message lengths */ + +/* these colours should be safe on dark and light backgrounds */ +#define BLUE "\033[1;34m" +#define GREEN "\033[1;32m" +#define YELLOW "\033[1;33m" +#define RED "\033[1;31m" +#define WHITE "\033[1;37m" +#define CLEAR "\033[0;m" + +/* free an array/list's elements (then the array itself) */ +void free_array ( void **array ); + +/* concatenate an array of strings, adding space between words */ +char *array_cat ( char **array ); + +/* safe malloc & realloc, they exit on failure */ +void *myxmalloc ( size_t size ); +void *myxrealloc ( void *ptr, size_t size ); + +#define xfree(ptr) if (ptr) free (ptr); + +/* this takes n_elems of the original array, in case of failure it will + * free_array (n_elems, array) before exiting */ +void **array_realloc ( void **array, size_t size );