First commit

This commit is contained in:
Sami Samhuri 2022-01-16 00:00:02 -08:00
commit 7f95910a9c
12 changed files with 869 additions and 0 deletions

13
Makefile Normal file
View file

@ -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

BIN
a1.pdf Normal file

Binary file not shown.

143
builtins.c Normal file
View file

@ -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 <errno.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#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 <command>'");
queue_message (" runs <command> 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 <job number>'");
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);
}

17
builtins.h Normal file
View file

@ -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 );

98
exec.c Normal file
View file

@ -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 <errno.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/types.h> /* waitpid */
#include <sys/wait.h> /* waitpid */
#include <unistd.h>
#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;
}

14
exec.h Normal file
View file

@ -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 );

143
jobs.c Normal file
View file

@ -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 <stdio.h>
#include <string.h>
#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;
}

29
jobs.h Normal file
View file

@ -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 <stdlib.h>
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 );

281
main.c Normal file
View file

@ -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 <assert.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <readline/readline.h>
#include <readline/history.h>
#include <unistd.h>
#include <wordexp.h>
#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;
}

12
main.h Normal file
View file

@ -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 );

80
utils.c Normal file
View file

@ -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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#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);
}

39
utils.h Normal file
View file

@ -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 <stdlib.h>
#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 );