Removed submodules

This commit is contained in:
2026-05-31 14:34:00 -04:00
commit 46c36b11da
352 changed files with 14792 additions and 0 deletions
+24
View File
@@ -0,0 +1,24 @@
*.swp
*.swo
# Ignore all compiled files regardless of location
build/*.o
tests/public.o
tests/testsuite.o
# Ignore executables for this project
dukesh
testsuite
bin/*
# Ignore test outputs
tests/ckstyle
tests/itests.txt
tests/outputs
tests/style.txt
tests/valgrind
tests/utests.txt
# Ignore all vscode stuff and NFS files
**/.nfs*
**/.vscode
+61
View File
@@ -0,0 +1,61 @@
#
# Simple Makefile
# Mike Lam, James Madison University, August 2016
#
# This makefile builds a simple application that contains a main module
# (specified by the EXE variable) and a predefined list of additional modules
# (specified by the MODS variable). If there are any external library
# dependencies (e.g., the math library, "-lm"), list them in the LIBS variable.
# If there are any precompiled object files, list them in the OBJS variable.
#
# By default, this makefile will build the project with debugging symbols and
# without optimization. To change this, edit or remove the "-g" and "-O0"
# options in CFLAGS and LDFLAGS accordingly.
#
# By default, this makefile build the application using the GNU C compiler,
# adhering to the C99 standard with all warnings enabled.
# application-specific settings and run target
EXE=dukesh
MODS=main.o process.o shell.o builtins.o cmd.o hash.o
OBJS=
LIBS=
default: build $(EXE)
build:
mkdir build
test: build $(EXE)
make -C tests test
style: $(EXE)
make -C tests style
# compiler/linker settings
CC=gcc
CFLAGS=-g -O0 -Wall -Werror -std=c99 -pedantic -D_POSIX_C_SOURCE=200809L
LDFLAGS=-g -O0
# build targets
BUILD=$(addprefix build/, $(MODS))
$(EXE): build/main.o $(BUILD) $(OBJS)
$(CC) $(LDFLAGS) -o $(EXE) $^ $(LIBS)
make -C utils
build/%.o: src/%.c
$(CC) -c $(CFLAGS) -o $@ $<
clean:
rm -rf $(EXE) build
make -C utils clean
make -C tests clean
.PHONY: default clean
+15
View File
@@ -0,0 +1,15 @@
Answer the following questions to describe your code submission. Please keep
all lines to a maximum of 80 characters wide.
1 - From the main() function, how did you distinguish if the shell was using
a script or an interactive prompt?
If the -b flag was passed we read in whatever file was passed, printing
an error message if the input file was invalid. Otherwise, use stdin.
2 - How does the quit command exit the shell?
It calls exit(0)
+26
View File
@@ -0,0 +1,26 @@
Answer the following questions to describe your code submission. Please keep
all lines to a maximum of 80 characters wide.
1 - When implementing the built-ins, which ones essentially only required
making a call to an existing C function? What function(s)?
The pwd, cd, and quit are essentially just calls to c functions.
2 - Briefly describe how you sorted the files in the ls program. If you used
an existing C function to sort, explain the arguments you passed.
We created a custom, leading dot ignoring, case-insensitive comparison
function that can be used by scandir to sort the files before they are
returned to us. The two arguments we two dirents, the names of which
were compared, and an integer returned to tell scandir which should be
sorted first.
3 - If you used your lab 2 code to parse the command line, briefly describe
any changes or adaptations you made. If you didn't use lab 2, briefly
describe how you built the array of arguments to pass when executing the
program.
Most of the code from lab 2 was fairly plug in play. The only adaptation
necessary was turning the parse_buffer and functions process.c to use the
fsm from lab2 instead of just strings.
+27
View File
@@ -0,0 +1,27 @@
Answer the following questions to describe your code submission. Please keep
all lines to a maximum of 80 characters wide.
1 - Explain how you are keeping track of the return code for use in echo $?.
After each process is executed in the shell's main loop, the "?" key is
set to the return code of that process.
2 - Briefly describe how you are storing or deleting environment variales.
Environment variables are stored directly in the hashmap. Export sets
the value to the corresponding key in the hashmap. Unset removes the
key from the hasmap. Echo finds the value in the hashmap based on the key.
3 - Consider the following three command lines:
$ echo ${VAR}
$ echo {VAR}
$ echo ${VAR
Briefly explain how your code processes each. Does your code handle bad
input strings?
In the first case, the correct environment variable is printed.
In the second case the literal string {VAR} is printed.
In the third case nothig is printed.
In the first two cases the exit code of echo is 0 because they are both
valid inputs to echo, but the third exits with a code 1 because the
syntax is invalid.
+73
View File
@@ -0,0 +1,73 @@
Answer the following questions to describe your code submission. Please keep
all lines to a maximum of 80 characters wide.
1 - In pseudo-code, show your general algorithm for setting up two processes
that are connected by a pipe. You need to show all calls to relevant
functions (e.g., pipe(), fork(), etc.) but you do not need to show error
checking or precise syntax.
pipes[# of processes][2]
for process in processes:
pipe(pipes[process #])
for process in processes:
if not first process:
add_dup2(pipes[process #][0], stdin)
if not last process:
add_dup2(pipes[process #][1], stdout)
for pipe in pipes:
add_close(pipe[0])
add_close(pipe[1])
if process is util:
posix_spawn(process)
else:
posix_spawnp(process)
2 - The project does not ask you to implement pipes with built-ins, but show
a pseudo-code approach to modifying echo for this purpose. For simplicity,
ignore the issues of escape characters and environment variables, and
assume that echo only writes into the pipe. Specifically, what would be
the flow of relevant functions (pipe(), fork(), etc.) for the following
command line:
$ echo hello world | cut -f2
(HINT: Be careful that you do NOT close or redirect the shell's STDOUT,
which would prevent it from displaying any more prompts!)
pipefd[2]
pipe(fd)
echo(pipefd[1], "Message") // Modify echo to take in a file descriptor to write to
close(pipefd[1])
add_dup2(pipefd[0], stdin)
add_close(pipefd[0]);
posix_spawnp("cut", argv)
close(pipefd[0])
3 - The project does not ask you to implement file redirection, but show a
general algorithm in pseudo-code (like above) for the following command
line (note that 2>&1 says to send STDERR to the same file as STDOUT):
$ ls > data.txt 2>&1
fd = open("data.txt")
add_dup2(fd, stdout)
add_dup2(fd, stderr)
add_close(fd)
posix_spawnp("ls", argv)
close(fd)
+2
View File
@@ -0,0 +1,2 @@
hello,world,me
later,gator
1 hello,world,me
2 later,gator
+2
View File
@@ -0,0 +1,2 @@
hello world me
later gator
+1
View File
@@ -0,0 +1 @@
oops
+1
View File
@@ -0,0 +1 @@
First
View File
+2
View File
@@ -0,0 +1,2 @@
pwd
quit
+1
View File
@@ -0,0 +1 @@
yet another test file
+281
View File
@@ -0,0 +1,281 @@
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "hash.h"
// Given a message as input, print it to the screen followed by a
// newline ('\n'). If the message contains the two-byte escape sequence
// "\\n", print a newline '\n' instead. No other escape sequence is
// allowed. If the sequence contains a '$', it must be an environment
// variable or the return code variable ("$?"). Environment variable
// names must be wrapped in curly braces (e.g., ${PATH}).
//
// Returns 0 for success, 1 for errors (invalid escape sequence or no
// curly braces around environment variables).
int
echo (char **args)
{
// Loop through arguments skipping echo
for (int i = 1; args[i] != NULL; i++)
{
char *msg = args[i];
// Loop over the arg string
for (int j = 0; msg[j] != '\0'; j++)
{
// Check for escape key
if (msg[j] == '\\')
{
if (msg[j + 1] == 'n')
{
putchar ('\n');
j++;
}
else
{
putchar ('\n');
return 1;
}
}
else if (msg[j] == '$')
{
if (msg[j + 1] == '?')
{
printf ("%s", hash_find ("?"));
j++;
}
else if (msg[j + 1] == '{')
{
j += 2;
char varname[256];
int z = 0;
while (msg[j] != '\0' && msg[j] != '}')
{
if (z >= 255)
return 1;
varname[z++] = msg[j++];
}
if (msg[j] != '}')
{
putchar ('\n');
return 1;
}
varname[z] = '\0';
char *val = hash_find (varname);
if (val != NULL)
{
printf ("%s", val);
}
}
else
{
putchar ('\n');
return 1;
}
}
else
{
putchar (msg[j]);
}
}
if (args[i + 1] != NULL)
putchar (' ');
}
putchar ('\n');
return 0;
}
// Given a key-value pair string (e.g., "alpha=beta"), insert the mapping
// into the global hash table (hash_insert ("alpha", "beta")).
//
// Returns 0 on success, 1 for an invalid pair string (kvpair is NULL or
// there is no '=' in the string).
//
// NOTE: For some strange reason, clang-format (used for checking the style)
// expects export to be initially formatted this way...
int export (char **args)
{
if (args[1] == NULL)
{
return 1;
}
char buffer[256];
snprintf (buffer, sizeof (buffer), "%s", args[1]);
char *name = strtok (buffer, "=");
char *set = strtok (NULL, "=");
if (name == NULL || set == NULL)
{
return 1;
}
hash_insert (name, set);
return 0;
}
// Prints the current working directory (see getcwd()). Returns 0.
int
pwd (void)
{
char cwd[1024];
if (getcwd (cwd, sizeof (cwd)) != NULL)
{
printf ("%s\n", cwd);
}
else
{
return 1;
}
return 0;
}
// Removes a key-value pair from the global hash table.
// Returns 0 on success, 1 if the key does not exist.
int
unset (char **args)
{
if (args[1] == NULL)
{
return 1;
}
char *value = hash_find (args[1]);
if (value == NULL)
{
return 1;
}
hash_remove (args[1]);
return 0;
}
// Given a string of commands, find their location(s) in the $PATH global
// variable. If the string begins with "-a", print all locations, not just
// the first one.
//
// Returns 0 if at least one location is found, 1 if no commands were
// passed or no locations found.
int
which (char **args)
{
const char *builtins[]
= { "cd", "echo", "pwd", "which", "export", "unset", "quit", NULL };
if (args[1] == NULL)
{
return 1;
}
int print_all = 0;
int index = 1;
if (strcmp (args[index], "-a") == 0)
{
print_all = 1;
index = 2;
if (args[index] == NULL)
{
return 1;
}
}
int found_any = 0;
int if_builtin = 0;
for (int a = index; args[a] != NULL; a++)
{
char *cmd = args[a];
// Check if its a built-in
for (int i = 0; builtins[i] != NULL; i++)
{
if (strcmp (cmd, builtins[i]) == 0)
{
printf ("%s: dukesh built-in command\n", cmd);
found_any = 1;
if_builtin = 1;
}
}
if (if_builtin)
{
continue;
}
// Check if its an executable
if (strncmp (cmd, "./", 2) == 0)
{
printf ("%s\n", cmd);
found_any = 1;
continue;
}
// Search through the path
char *path_env = getenv ("PATH");
char *path_copy = strdup (path_env);
char *dir = strtok (path_copy, ":");
while (dir != NULL)
{
char fullpath[1024];
snprintf (fullpath, sizeof (fullpath), "%s/%s", dir, cmd);
if (access (fullpath, X_OK) == 0)
{
printf ("%s\n", fullpath);
found_any = 1;
if (!print_all)
break;
}
dir = strtok (NULL, ":");
}
free (path_copy);
}
if (found_any)
{
return 0;
}
else
{
return 1;
}
}
int
cd (char **args)
{
if (args[1] == NULL)
{
return 1;
}
if (chdir (args[1]) != 0)
{
return 1;
}
return 0;
}
// Immediately exits the shell
int
quit (void)
{
printf ("\n");
exit (0);
return 0;
}
+14
View File
@@ -0,0 +1,14 @@
#ifndef __cs361_builtins__
#define __cs361_builtins__
#include <stdbool.h>
int echo (char **);
int export (char **);
int pwd (void);
int unset (char **);
int which (char **);
int cd (char **);
int quit (void);
#endif
+202
View File
@@ -0,0 +1,202 @@
#include "cmd.h"
#include <assert.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// Integrate the FSM command-line parser from lab 2 here. Note that the FSM
// effects will be vastly different from that of lab 2. Instead of implementing
// the effects here, this file should focus on the model and the parsing. You
// should modify the model's effects table to call functions in process.c or
// builtins.c as appropriate. (Or you can have a function here to call those
// functions indirectly.)
static state_t const _transitions[NUM_STATES][NUM_EVENTS]
= { { Command, Term, NST },
{ Arguments, Make_Pipe, Term },
{ Arguments, Make_Pipe, Term },
{ Command, Term, Term },
{ NST, NST, NST } };
static action_t const _effects[NUM_STATES][NUM_EVENTS]
= { { start_command, error_pipe, NULL },
{ append, NULL, NULL },
{ append, NULL, NULL },
{ start_command, error_pipe, error_newline },
{ NULL, NULL, NULL } };
/* Helper function for providing a printable string name for an event */
const char *
event_name (event_t evt)
{
assert (evt <= NIL);
// Event names for printing out
const char *names[] = { "TOKEN", "PIPE", "NEWLINE", "NIL" };
return names[evt];
}
/* Helper function for providing a printable string name for an state */
const char *
state_name (state_t st)
{
assert (st <= NST);
// State names for printing out
const char *names[]
= { "Init", "Command", "Arguments", "Make_Pipe", "Term", "NST" };
return names[st];
}
/* Executed when starting to process a new command line. The fsm_t
should have been updated to include a pointer to the current token.
For instance, if the command line was "ls -l data NL", the fsm_t
has a field that points to "ls". */
void
start_command (fsm_t *cmdmodel)
{
// printf ("Starting new command: %s\n", cmdmodel->current_token);
// TODO: Copy the current token to store it in the FSM's command
// field. Next, create the FSM's args array (length MAX_ARGUMENTS)
// set the current token as args[0], and initialize nargs to be
// the number of arguments (1 at this point).
// Allocate args array (NULL-initialized) if not already
cmdmodel->args = calloc (MAX_ARGUMENTS, sizeof (char *));
cmdmodel->command = cmdmodel->current_token;
cmdmodel->args[0] = cmdmodel->current_token;
cmdmodel->nargs = 1;
}
/* Executed when processing a token after the command name. For instance,
if the command line was "ls -l data NL", this function will be called
when the current token is "-l" and again when it is "data". */
void
append (fsm_t *cmdmodel)
{
if (cmdmodel->nargs >= MAX_ARGUMENTS)
return;
// printf ("Appending %s to the argument list\n", cmdmodel->current_token);
assert (cmdmodel->args != NULL);
// TODO: Store the current token into the args array and increment nargs
cmdmodel->args[cmdmodel->nargs++] = cmdmodel->current_token;
}
void
error_pipe (fsm_t *cmdmodel)
{
printf ("ERROR: Received token %s while in state %s\n",
cmdmodel->current_token, state_name (cmdmodel->state));
}
void
error_newline (fsm_t *cmdmodel)
{
printf ("ERROR: Received token %s while in state %s\n",
cmdmodel->current_token, state_name (cmdmodel->state));
}
state_t
transition (struct fsm *fsm, event_t event, action_t *effect)
{
assert (fsm->state < NST);
assert (event < NIL);
*effect = _effects[fsm->state][event];
return _transitions[fsm->state][event];
}
/* Generic front-end for handling events. Should do nothing more
than calling the FSM's transition function, performing an effect
(if appropriate) and updating the state. Return false if the new
state is the terminal state. */
bool
handle_event (fsm_t *fsm, event_t event)
{
assert (fsm != NULL);
// Look up the current state/event combination in the
// transition table. Print the following line for debugging
// purposes just for this lab. This should be printed even
// if there is no transition.
action_t effect;
state_t new_state = fsm->transition (fsm, event, &effect);
// printf("[%s.%s -> %s]\n", state_name(fsm->state), event_name(event),
// state_name(new_state));
// If the state/event combination is valid, execute
// the transition and effect function (if there is one).
// If the next state is Term (terminated), return false.
// Otherwise return true.
if (new_state != NST)
{
if (effect != NULL)
{
effect (fsm);
}
fsm->state = new_state;
}
return fsm->state != Term;
}
/* Given a string, return the event type. Do not modify this function. */
event_t
lookup (char *token)
{
if (!strcmp (token, "|"))
return PIPE;
if (!strcmp (token, "NL"))
return NEWLINE;
return TOKEN;
}
char ***
split_commands (char **tokens)
{
size_t ncmds = 0;
char ***cmds = calloc (MAX_ARGUMENTS, sizeof (*cmds));
size_t start = 0;
size_t i;
for (i = 0; tokens[i]; i++)
{
if (strcmp (tokens[i], "|") == 0)
{
tokens[i] = NULL;
cmds[ncmds++] = &tokens[start];
start = i + 1;
}
}
cmds[ncmds++] = &tokens[start];
cmds[ncmds] = NULL;
return cmds;
}
// You should provide some function like this to serve as the interface to
// parsing the command line. This function is just a placeholder and you
// should define your own.
char ***
parse_buffer (char *buffer)
{
buffer[strcspn (buffer, "\n")]
= '\0'; // Replace newline with null terminator
char **argv = calloc (MAX_ARGUMENTS, sizeof (*argv));
size_t i = 0;
char *token = strtok (buffer, " ");
while (token != NULL)
{
argv[i++] = token;
token = strtok (NULL, " ");
}
return split_commands (argv);
}
+74
View File
@@ -0,0 +1,74 @@
#include <stddef.h>
#ifndef __cs361_cmd_h__
#define __cs361_cmd_h__
// Generic definitions for any type of statemodel
// States and events should just be integers
typedef int state_t;
typedef int event_t;
// Needed for circular typedef. This lets action_t use fsm_t in its parameter
// list, while the struct fsm can use action_t as a field.
typedef struct fsm fsm_t;
// All entry, exit, and effect instances use the action type
typedef void (*action_t) (fsm_t *);
// Each FSM instance contains a current state
struct fsm
{
state_t state; // current state
// pointer to the FSM's transition function
state_t (*transition) (struct fsm *, event_t, action_t *);
// Additional data fields specific to this FSM
char *command; // the name of the command to run
size_t nargs; // the number of command-line arguments
char **args; // the command-line arguments
char *current_token; // current token being processed
};
#define MAX_ARGUMENTS 32
// Events
typedef enum
{
TOKEN, // normal command-line token
PIPE, // vertical bar character
NEWLINE, // newline at the end of the command
NIL // invalid non-event
} cmdevt_t;
#define NUM_EVENTS NIL
// States
typedef enum
{
Init, // initial state
Command, // establishing the command name
Arguments, // building the argument list
Make_Pipe, // linking the commands together for a pipe
Term, // terminal state (execute program or error)
NST // invalid non-state
} cmdst_t;
#define NUM_STATES NST
state_t transition (struct fsm *fsm, event_t event, action_t *effect);
event_t lookup (char *); // convert an event string to its numeric value
// Translate event/state numbers to their string equivalent
const char *event_name (event_t);
const char *state_name (state_t);
void start_command (fsm_t *);
void append (fsm_t *);
void execute (fsm_t *);
void link_commands (fsm_t *);
void error_pipe (fsm_t *);
void error_newline (fsm_t *);
char ***parse_buffer (char *buffer);
#endif
+284
View File
@@ -0,0 +1,284 @@
#include <assert.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "hash.h"
static ssize_t find_index (char *);
static unsigned long hash (unsigned char *);
static bool insert_help (char *, char *, bool);
static void rehash (size_t);
#define MINSIZE 100
#define PRIME 97
typedef struct kvpair
{
char *key;
char *value;
bool alive;
} kvpair_t;
static kvpair_t *table = NULL;
static size_t capacity = 0;
static size_t entries = 0;
void
hash_destroy (void)
{
if (table == NULL)
return;
for (size_t i = 0; i < capacity; i++)
{
if (table[i].key != NULL)
{
free (table[i].key);
table[i].key = NULL;
}
if (table[i].value != NULL)
{
free (table[i].value);
table[i].value = NULL;
}
}
free (table);
}
/* Dumps the table contents to STDOUT (useful for debugging) */
void
hash_dump (void)
{
printf ("TABLE:\n");
for (size_t i = 0; i < capacity; i++)
if (table[i].key != NULL)
printf (" [%zd].%s = %s%s\n", i, table[i].key, table[i].value,
(!table[i].alive ? " [deleted]" : ""));
}
/* Initializes the hash table to a given size (minimum 100) */
void
hash_init (size_t size)
{
if (table != NULL)
free (table);
// Minimum of 100 entries to start
if (size < MINSIZE)
size = MINSIZE;
table = calloc (size, sizeof (kvpair_t));
capacity = size;
entries = 0;
}
/* Find the value for a given key. Returns NULL if there is no entry for
the given key. */
char *
hash_find (char *key)
{
if (table == NULL) // uninitialized table
return NULL;
ssize_t index = find_index (key);
assert (index >= 0);
if (table[index].key == NULL) // key not found
return NULL;
// If .key is not NULL, find_index found a key match
assert (!strcmp (table[index].key, key));
if (table[index].alive) // not marked for deletion
return table[index].value;
return NULL;
}
/* Inserts a new entry into the hash table. If there is already an entry
for the given key, replace the value (freeing the old one). */
bool
hash_insert (char *key, char *value)
{
if (table == NULL) // uninitialized table
return false;
return insert_help (key, value, true);
}
/* Gets a list of pointers to the keys in the hash table. */
char **
hash_keys (void)
{
if (table == NULL) // uninitialized table
return NULL;
char **keys = calloc (entries + 1, sizeof (char *));
size_t next = 0;
for (size_t i = 0; i < capacity; i++)
if (table[i].key != NULL && table[i].alive)
keys[next++] = table[i].key;
return keys;
}
/* Removes a key-value pair from the hash table. Marks the entry as
deleted. Can be overwritten later. */
bool
hash_remove (char *key)
{
if (table == NULL) // uninitialized table
return false;
ssize_t index = find_index (key);
assert (index >= 0);
if (table[index].key == NULL) // key not found
return true;
// If .key is not NULL, find_index found a key match
assert (!strcmp (table[index].key, key));
table[index].alive = false;
entries--;
if ((entries < capacity / 4) && (capacity / 2 >= MINSIZE))
rehash (capacity / 2);
return true;
}
/* **********************************************************************
* Helper functions only below this point *
* ********************************************************************** */
static bool
insert_help (char *key, char *value, bool dup)
{
ssize_t index = find_index (key);
assert (index >= 0); // failed to find an open slot; should never happen
if (table[index].key == NULL)
{
// New entry for this key. Check if rehashing is needed.
if (entries + 1 > capacity / 2)
{
rehash (capacity * 2);
index = find_index (key);
}
// Set the entries in the table (duplicating if requested)
if (dup)
{
table[index].key = strdup (key);
table[index].value = strdup (value);
}
else
{
table[index].key = key;
table[index].value = value;
}
entries++;
table[index].alive = true;
return true;
}
// If table[index].key is not NULL, it must match the existing key OR
// the existing entry has been deleted. Otherwise, the double hashing
// should have found a different location. If the key does not match
// the current key, replace it.
if (strcmp (table[index].key, key))
{
assert (!table[index].alive);
free (table[index].key);
if (dup)
table[index].key = strdup (key);
else
table[index].key = key;
}
// Free the old value and replace it
free (table[index].value);
if (dup)
table[index].value = strdup (value);
else
table[index].value = value;
table[index].alive = true;
return true;
}
static ssize_t
find_index (char *str)
{
if (table == NULL)
return -1;
unsigned long keyhash = hash ((unsigned char *)str);
// probe is 1 .. PRIME, guaranteed non-zero
unsigned long probe = PRIME - (keyhash % PRIME);
// Use double hashing to resolve collisions
size_t index = (size_t)(keyhash % capacity);
ssize_t first_open = -1;
for (size_t i = 0; i < capacity; i++)
{
size_t trial = (index + (size_t)(i * probe)) % capacity;
// If the key is NULL, we have not encountered the string. If there
// was an earlier open spot, use that one. Otherwise, use the spot
// with the empty key.
if (table[trial].key == NULL)
{
if (first_open < 0)
return (ssize_t)trial;
else
return first_open;
}
// Keep track of the first open index. This will be returned if
// the key has not been previously entered and deleted.
if (first_open < 0 && !table[trial].alive)
first_open = trial;
// If the key has been previously used, re-use this position
if (!strcmp (table[trial].key, str))
return (ssize_t)trial;
}
// Due to rehashing, there should always be at least 50% of the table
// entries free. So there should always be a free space.
abort ();
}
static unsigned long
hash (unsigned char *string)
{
if (string == NULL)
return 0;
unsigned long hash = 5381;
// Derived from djb2 by Dan Bernstein
// hash = hash * 33 + ch
for (unsigned char *ptr = string; *ptr != '\0'; ptr++)
hash = ((hash << 5) + hash) + *ptr;
return hash;
}
static void
rehash (size_t newcap)
{
size_t oldcap = capacity;
capacity = newcap;
kvpair_t *oldtable = table;
table = calloc (capacity, sizeof (kvpair_t));
entries = 0;
for (int i = 0; i < oldcap; i++)
{
if (oldtable[i].key != NULL && oldtable[i].alive)
insert_help (oldtable[i].key, oldtable[i].value, false);
}
}
+15
View File
@@ -0,0 +1,15 @@
#ifndef __cs361_hash__
#define __cs361_hash__
#include <stdbool.h>
void hash_dump (void); // for debugging if needed
void hash_destroy (void);
void hash_init (size_t);
char *hash_find (char *);
bool hash_insert (char *, char *);
char **hash_keys (void);
bool hash_remove (char *);
#endif
+65
View File
@@ -0,0 +1,65 @@
#include <assert.h>
#include <getopt.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include "hash.h"
#include "shell.h"
static bool get_args (int, char **, FILE **);
static void usage (void);
int
main (int argc, char *argv[])
{
FILE *input = stdin;
if (!get_args (argc, argv, &input))
{
usage ();
}
if (input == NULL)
{
printf ("Invalid input file\n");
return EXIT_FAILURE;
}
shell (input);
return EXIT_SUCCESS;
}
/* Parse the command-line arguments. Sets the client/server variables to
point to a file name (typically in the data/ directory). Can also set
the bot variable if a second file is used to interact with the
client/server. If -d was passed, turn on debugging mode to print
information about state transitions. */
static bool
get_args (int argc, char **argv, FILE **script)
{
int ch = 0;
while ((ch = getopt (argc, argv, "b:h")) != -1)
{
switch (ch)
{
case 'b':
*script = fopen (optarg, "r");
break;
default:
return false;
}
}
return true;
}
static void
usage (void)
{
printf ("dukesh, a simple command shell\n");
printf ("usage: dukesh [-b FILE]\n");
printf (" -b FILE use FILE as a shell script to execute\n");
printf ("If no script is passed, then the shell should be interactive,\n");
printf ("processing one command at a time from STDIN.\n");
}
+152
View File
@@ -0,0 +1,152 @@
#include <spawn.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <wait.h>
#include "builtins.h"
#include "cmd.h"
#include "hash.h"
// The contents of this file are up to you, but they should be related to
// running separate processes. It is recommended that you have functions
// for:
// - performing a $PATH lookup
// - determining if a command is a built-in or executable
// - running a single command in a second process
// - running a pair of commands that are connected with a pipe
// You should provide some function like this to serve as the interface to
// parsing the command line. This function is just a placeholder and you
// should define your own.
bool
is_util (char *str)
{
char *utils[] = { "./bin/cat", "./bin/chmod", "./bin/cut", "./bin/env",
"./bin/head", "./bin/ls", "./bin/repeat" };
for (size_t i = 0; i < 7; i++)
{
if (strcmp (str, utils[i]) == 0)
{
return true;
}
}
return false;
}
char **
hash_to_envp ()
{
char **keys = hash_keys ();
size_t key_count = 0;
while (keys[key_count] != NULL)
key_count++;
char **envp = calloc (key_count + 1, sizeof (char *));
for (size_t i = 0; i < key_count; i++)
{
char *key = keys[i]; // get the key
char *value
= hash_find (key); // entry should be found since this is a key
size_t len = strlen (key) + strlen (value) + 2;
envp[i] = calloc (1, len);
snprintf (envp[i], len, "%s=%s", key, value);
}
return envp;
}
int
run_process (char ***cmds)
{
if (strcmp (cmds[0][0], "quit") == 0)
return quit ();
if (strcmp (cmds[0][0], "echo") == 0)
return echo (cmds[0]);
if (strcmp (cmds[0][0], "cd") == 0)
return cd (cmds[0]);
if (strcmp (cmds[0][0], "pwd") == 0)
return pwd ();
if (strcmp (cmds[0][0], "which") == 0)
return which (cmds[0]);
if (strcmp (cmds[0][0], "export") == 0)
return export (cmds[0]);
if (strcmp (cmds[0][0], "unset") == 0)
return unset (cmds[0]);
int ncmds = 0;
while (cmds[ncmds])
ncmds++;
int pipes[ncmds - 1][2];
for (int i = 0; i < ncmds - 1; i++)
{
if (pipe (pipes[i]) < 0)
{
perror ("pipe");
return -1;
}
}
pid_t pids[ncmds];
for (int i = 0; i < ncmds; i++)
{
posix_spawn_file_actions_t actions;
posix_spawn_file_actions_init (&actions);
if (i > 0)
{
posix_spawn_file_actions_adddup2 (&actions, pipes[i - 1][0],
STDIN_FILENO);
}
if (i < ncmds - 1)
{
posix_spawn_file_actions_adddup2 (&actions, pipes[i][1],
STDOUT_FILENO);
}
for (int j = 0; j < ncmds - 1; j++)
{
posix_spawn_file_actions_addclose (&actions, pipes[j][0]);
posix_spawn_file_actions_addclose (&actions, pipes[j][1]);
}
if (is_util (cmds[i][0]))
{
posix_spawn (&pids[i], cmds[i][0], &actions, NULL, cmds[i],
hash_to_envp ());
}
else
{
posix_spawnp (&pids[i], cmds[i][0], &actions, NULL, cmds[i],
hash_to_envp ());
}
posix_spawn_file_actions_destroy (&actions);
}
// parent closes all pipe fds
for (int i = 0; i < ncmds - 1; i++)
{
close (pipes[i][0]);
close (pipes[i][1]);
}
// wait for all children
int status;
for (int i = 0; i < ncmds; i++)
{
waitpid (pids[i], &status, 0);
}
free (cmds);
return WEXITSTATUS (status);
}
+25
View File
@@ -0,0 +1,25 @@
#include "cmd.h"
#include <stdbool.h>
#ifndef __cs361_process__
#define __cs361_process__
// The contents of this file are up to you, but they should be related to
// running separate processes. It is recommended that you have functions
// for:
// - performing a $PATH lookup
// - determining if a command is a built-in or executable
// - running a single command in a second process
// - running a pair of commands that are connected with a pipe
// You should provide some function like this to serve as the interface to
// parsing the command line. This function is just a placeholder and you
// should define your own.
bool is_util (char *str);
char **hash_to_envp ();
int run_process (char ***cmd);
#endif
+43
View File
@@ -0,0 +1,43 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "builtins.h"
#include "cmd.h"
#include "hash.h"
#include "process.h"
// No command line can be more than 100 characters
#define MAXLENGTH 100
void
shell (FILE *input)
{
hash_init (100);
hash_insert ("?", "0");
hash_insert ("PATH", getenv ("PATH"));
char buffer[MAXLENGTH];
while (1)
{
// Print the cursor and get the next command entered
printf ("$ ");
memset (buffer, 0, sizeof (buffer));
if (fgets (buffer, MAXLENGTH, input) == NULL)
break;
if (input != stdin)
printf ("%s", buffer);
// Keep this here to avoid weird line interleavings
fflush (stdout);
char ***cmds = parse_buffer (buffer);
int rc = run_process (cmds);
char buffer[20];
sprintf (buffer, "%d", rc);
char *str = buffer;
hash_insert ("?", str);
}
printf ("\n");
hash_destroy ();
}
+6
View File
@@ -0,0 +1,6 @@
#ifndef __cs361_shell__
#define __cs361_shell__
void shell (FILE *);
#endif
+89
View File
@@ -0,0 +1,89 @@
#
# Simple Test Makefile
# Mike Lam, James Madison University, August 2016
#
# This version of the Makefile includes support for building a test suite. The
# recommended framework is Check (http://check.sourceforge.net/). To build and
# run the test suite, execute the "test" target. The test suite must be located
# in a module called "testsuite". The MODS, LIBS, and OBJS variables work as
# they do in the main Makefile.
#
# To change the default build target (which executes when you just type
# "make"), change the right-hand side of the definition of the "default"
# target.
#
# By default, this makefile will build the project with debugging symbols and
# without optimization. To change this, edit or remove the "-g" and "-O0"
# options in CFLAGS and LDFLAGS accordingly.
#
# By default, this makefile build the application using the GNU C compiler,
# adhering to the C99 standard with all warnings enabled.
# application-specific settings and run target
EXE=../dukesh
TEST=testsuite
MODS=public.o
OBJS=../build/process.o ../build/shell.o ../build/builtins.o ../build/cmd.o ../build/hash.o
LIBS=
UTESTOUT=utests.txt
ITESTOUT=itests.txt
SCHECKOUT=style.txt
default: $(TEST)
$(EXE):
make -C ../
test: utest itest style
@echo "========================================"
utest: $(EXE) $(TEST)
@echo "========================================"
@echo " UNIT TESTS"
@./$(TEST) 2>/dev/null >$(UTESTOUT)
@cat $(UTESTOUT) | sed -n -e '/Checks/,$$p' | sed -e 's/^private.*:[EF]://g'
itest: $(EXE)
@echo "========================================"
@echo " INTEGRATION TESTS"
@./integration.sh | tee $(ITESTOUT)
style: $(EXE)
@echo "========================================"
@echo " CODING STYLE CHECK"
@./style.sh 2>/dev/null >$(SCHECKOUT)
@cat $(SCHECKOUT)
# compiler/linker settings
CC=gcc
CFLAGS=-g -O0 -Wall --std=c99 -pedantic -D_POSIX_C_SOURCE=200809L
LDFLAGS=-g -O0
#CFLAGS+=-I/opt/local/include -Wno-gnu-zero-variadic-macro-arguments
CFLAGS+=-Wno-gnu-zero-variadic-macro-arguments
#LDFLAGS+=-L/opt/local/lib
LDFLAGS=
LIBS+=-lcheck -lm -lpthread
ifeq ($(shell uname -s),Linux)
LIBS+=-lrt -lsubunit
endif
# build targets
$(TEST): $(TEST).o $(MODS) $(OBJS)
$(CC) $(LDFLAGS) -o $(TEST) $^ $(LIBS)
%.o: %.c
$(CC) -c $(CFLAGS) $<
clean:
rm -rf $(TEST) $(TEST).o $(MODS) $(UTESTOUT) $(ITESTOUT) $(SCHECKOUT) outputs valgrind ckstyle
.PHONY: default clean test unittest inttest
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
+2
View File
@@ -0,0 +1,2 @@
hello,world,me
later,gator
1 hello,world,me
2 later,gator
+2
View File
@@ -0,0 +1,2 @@
hello world me
later gator
+1
View File
@@ -0,0 +1 @@
oops
+1
View File
@@ -0,0 +1 @@
First
View File
+2
View File
@@ -0,0 +1,2 @@
pwd
quit
+1
View File
@@ -0,0 +1 @@
yet another test file
+10
View File
@@ -0,0 +1,10 @@
$ ./bin/cat cut_data/data.csv
hello,world,me
later,gator
$ ./bin/cat cut_data/data.csv | ./bin/cut -d , -f 2
world
gator
$ ./bin/cat cut_data/data.csv | tail -n 1
later,gator
$ quit
+17
View File
@@ -0,0 +1,17 @@
$ export A=5
$ ./bin/env ./bin/repeat 1 A
A=5
$ ./bin/env B=6 C=7 ./bin/repeat 1 A 2 B 3 C
A=5
B=6
B=6
C=7
C=7
C=7
$ ./bin/env C=7 ./bin/repeat 1 A 2 B 3 C | head -n 4
A=5
B=
B=
C=7
$ quit
+17
View File
@@ -0,0 +1,17 @@
$ ./bin/ls -a data | ./bin/head -n 1
empty.txt
$ ./bin/ls -a data | ./bin/head -n 2000
empty.txt
FIRST.txt
.hidden.txt
pwd.txt
subdir
yat.txt
$ ./bin/head -n 1 cut_data/data.spaces | ./bin/cut -f 2
world
$ ./bin/head -n 1 cut_data/data.csv | ./bin/cut -d , -f 1
hello
$ ./bin/head -n 1 cut_data/data.csv | ./bin/cut -d , -f 3
me
$ quit
+6
View File
@@ -0,0 +1,6 @@
$ which ./bin/rm
./bin/rm
$ which ./bin/cat
./bin/cat
$ quit
+18
View File
@@ -0,0 +1,18 @@
$ export A=5
$ ./bin/repeat 2 A
A=5
A=5
$ ./bin/env B=6 ./bin/repeat 1 A
A=5
$ ./bin/env C=10 ./bin/repeat 2 A 3 B 4 C
A=5
A=5
B=
B=
B=
C=10
C=10
C=10
C=10
$ quit
+9
View File
@@ -0,0 +1,9 @@
$ which export
export: dukesh built-in command
$ echo N=${NUM}
N=
$ export NUM=5
$ echo N=${NUM}
N=5
$ quit
+9
View File
@@ -0,0 +1,9 @@
$ export SHELL=/bin/bash
$ export USER=me
$ ./bin/repeat 1 USER
USER=me
$ ./bin/repeat 2 SHELL
SHELL=/bin/bash
SHELL=/bin/bash
$ quit
+13
View File
@@ -0,0 +1,13 @@
$ ./bin/ls data
empty.txt
FIRST.txt
pwd.txt
subdir
yat.txt
$ echo $?
0
$ ./bin/ls asldfkjasldfkj
$ echo $?
1
$ quit
+14
View File
@@ -0,0 +1,14 @@
$ ./bin/env A=5 ./bin/repeat 2 A
A=5
A=5
$ ./bin/env A=5 B=6 C=7 ./bin/repeat 1 A 2 B 3 C
A=5
B=6
B=6
C=7
C=7
C=7
$ ./bin/env A=10 ./bin/repeat 1 B
B=
$ quit
+14
View File
@@ -0,0 +1,14 @@
$ ./bin/ls data
empty.txt
FIRST.txt
pwd.txt
subdir
yat.txt
$ ./bin/head Makefile
#
# Simple Test Makefile
# Mike Lam, James Madison University, August 2016
#
# This version of the Makefile includes support for building a test suite. The
$ quit
@@ -0,0 +1,4 @@
$ ./bin/ls -l
$ ./bin/head -c 5 Makefile
$ quit
+6
View File
@@ -0,0 +1,6 @@
$ ./bin/ls -l
./bin/ls: invalid option -- 'l'
$ ./bin/head -c 5 Makefile
./bin/head: invalid option -- 'c'
$ quit
+35
View File
@@ -0,0 +1,35 @@
$ /usr/bin/chmod 751 data/subdir
$ /usr/bin/chmod 640 data/empty.txt
$ /usr/bin/chmod 640 data/FIRST.txt
$ /usr/bin/chmod 400 data/.hidden.txt
$ /usr/bin/chmod 640 data/pwd.txt
$ /usr/bin/chmod 640 data/yat.txt
$ ./bin/ls -a data
empty.txt
FIRST.txt
.hidden.txt
pwd.txt
subdir
yat.txt
$ ./bin/ls -s data
0 empty.txt
6 FIRST.txt
9 pwd.txt
22 yat.txt
$ ./bin/ls -sa data
0 empty.txt
6 FIRST.txt
5 .hidden.txt
9 pwd.txt
22 yat.txt
$ ./bin/ls -ap data
-rw-r----- empty.txt
-rw-r----- FIRST.txt
-r-------- .hidden.txt
-rw-r----- pwd.txt
drwxr-x--x subdir
-rw-r----- yat.txt
$ ./bin/head -n 1 Makefile
#
$ quit
+7
View File
@@ -0,0 +1,7 @@
$ cd /usr/bin
$ pwd
/usr/bin
$ which ls
/usr/bin/ls
$ quit
+35
View File
@@ -0,0 +1,35 @@
$ ./bin/ls -a data
empty.txt
FIRST.txt
.hidden.txt
pwd.txt
subdir
yat.txt
$ ./bin/ls -s data
0 empty.txt
6 FIRST.txt
9 pwd.txt
22 yat.txt
$ ./bin/ls -sa data
0 empty.txt
6 FIRST.txt
5 .hidden.txt
9 pwd.txt
22 yat.txt
$ ./bin/chmod rw- rw- --- data/empty.txt
$ ./bin/chmod r-- --- --x data/FIRST.txt
$ ./bin/chmod rw- rw- rw- data/pwd.txt
$ ./bin/chmod r-x r-x r-x data/yat.txt
$ ./bin/chmod rwx --- --- data/.hidden.txt
$ ./bin/chmod r-x --x r-- data/subdir
$ ./bin/ls -ap data
-rw-rw---- empty.txt
-r-------x FIRST.txt
-rwx------ .hidden.txt
-rw-rw-rw- pwd.txt
dr-x--xr-- subdir
-r-xr-xr-x yat.txt
$ ./bin/head -n 1 Makefile
#
$ quit
+17
View File
@@ -0,0 +1,17 @@
$ ./bin/cut cut_data/data.spaces
hello
later
$ ./bin/cut -f 2 cut_data/data.spaces
world
gator
$ ./bin/cut cut_data/data.csv
hello,world,me
later,gator
$ ./bin/cut -d , cut_data/data.csv
hello
later
$ ./bin/cut -d , -f 2 cut_data/data.csv
world
gator
$ quit
+15
View File
@@ -0,0 +1,15 @@
$ ./bin/cut -f -1 cut_data/data.spaces
cut, splits each line based on a delimiter
usage: cut [FLAG] FILE
FLAG can be:
-d C split each line based on the character C (default ' ')
-f N print the Nth field (1 is first, default 1)
If no FILE specified, read from STDIN
$ ./bin/cut -f 3 cut_data/data.spaces
me
$ ./bin/cut -d , -f 5 cut_data/data.csv
$ quit
+7
View File
@@ -0,0 +1,7 @@
$ echo goodbye
goodbye
$ echo hello\nworld
hello
world
$ quit
+8
View File
@@ -0,0 +1,8 @@
$ echo this has extra spaces
this has extra spaces
$ echo this\nhas\nnewlines
this
has
newlines
$ quit
+18
View File
@@ -0,0 +1,18 @@
$ which cd
cd: dukesh built-in command
$ which echo
echo: dukesh built-in command
$ which pwd
pwd: dukesh built-in command
$ which which
which: dukesh built-in command
$ which ./bin/ls
./bin/ls
$ which ./bin/head
./bin/head
$ which ls
/usr/bin/ls
$ which head
/usr/bin/head
$ quit
+6
View File
@@ -0,0 +1,6 @@
$ echo hello
hello
$ echo goodbye
goodbye
$ quit
+2
View File
@@ -0,0 +1,2 @@
$ quit
+95
View File
@@ -0,0 +1,95 @@
#!/bin/bash
# Ensure the data directory has 750 permissions
chmod 750 data
# Must run with env -i so that students cannot "accidentally" use the
# standard shell environment variables
EXE="env -i PATH=/usr/bin:. ../dukesh"
SKIP_C=""
SKIP_B=""
SKIP_A=""
function run_test {
# parameters
TAG=$1
ARGS=$2
PTAG=$(printf '%-30s' "$TAG")
if [[ $(echo $TAG | cut -d'_' -f1) == $SKIP_C ||
$(echo $TAG | cut -d'_' -f1) == $SKIP_B ||
$(echo $TAG | cut -d'_' -f1) == $SKIP_A ]] ; then
echo "$PTAG SKIPPED (previous phases not complete)"
return
fi
# file paths
OUTPUT=outputs/$TAG.txt
DIFF=outputs/$TAG.diff
EXPECT=expected/$TAG.txt
VALGRND=valgrind/$TAG.txt
# run test and compare output to the expected version
$EXE $ARGS 2>/dev/null >"$OUTPUT"
diff -u "$OUTPUT" "$EXPECT" >"$DIFF"
passed=false
EFILES=$(find expected -type f -name "$TAG-*.txt")
if [ ! -s "$DIFF" ]; then
passed=true
elif [ "$EFILES" != "" ]; then
for EF in $EFILES ; do
DF=$(echo $EF | sed 's/^expected/outputs/' | sed 's/txt$/diff/')
diff -u "$OUTPUT" "$EF" >"$DF"
if [ ! -s "$DF" ]; then
passed=true
fi
done
fi
if [[ $passed = true ]] ; then
echo "$PTAG pass"
rm outputs/$TAG*diff
else
echo "$PTAG FAIL - Command line: $EXE $ARGS"
if [ "$EFILES" != "" ] ; then
echo "$(printf '%30s' ' ') *WARNING* This test has more than one possible output file."
echo "$(printf '%30s' ' ') *WARNING* Please inspect each manually for comparison."
else
echo "$(printf '%30s' ' ') See $DIFF for diff with expected output"
fi
echo ""
if [[ $(echo $PTAG | cut -d'_' -f1) == 'D' ]] ; then
SKIP_C="C"
SKIP_B="B"
SKIP_A="A"
elif [[ $(echo $PTAG | cut -d'_' -f1) == 'C' ]] ; then
SKIP_B="B"
SKIP_A="A"
elif [[ $(echo $PTAG | cut -d'_' -f1) == 'B' ]] ; then
SKIP_A="A"
fi
fi
# run valgrind
valgrind $EXE $ARGS &>$VALGRND
}
# initialize output folders
mkdir -p outputs
mkdir -p valgrind
rm -f outputs/* valgrind/*
# run individual tests
source itests.include
# check for memory leaks
LEAK=`cat valgrind/*.txt | grep 'definitely lost' | grep -v ' 0 bytes in 0 blocks'`
if [ -z "$LEAK" ]; then
echo "No memory leak found."
else
echo "Memory leak(s) found. See files listed below for details."
grep 'definitely lost' valgrind/*.txt | grep -v ' 0 bytes in 0 blocks' | sed -e 's/:.*$//g' | sed -e 's/^/ - /g'
fi
+40
View File
@@ -0,0 +1,40 @@
# list of integration tests
# format: run_test <TAG> <ARGS>
# <TAG> used as the root for all filenames (i.e., "expected/$TAG.txt")
# <ARGS> command-line arguments to test
run_test D_quit "-b scripts/quit.txt"
run_test D_echo "-b scripts/echo_min.txt"
run_test C_cd_pwd "-b scripts/dirs.txt"
run_test C_echo "-b scripts/echo.txt"
run_test C_echo_space "-b scripts/echo_space.txt"
run_test C_binaries "-b scripts/bins.txt"
# Your ls implementation must list in alphabetical order
run_test C_binaries_flags "-b scripts/bins_flags.txt"
# Your ls must print directories (. and ..) first, then
# the rest in alphabetical order (ignoring leading . and case)
run_test C_chmod "-b scripts/bins_chmod.txt"
# Your ls must work correctly before chmod will work
run_test C_binaries_bad "-b scripts/bins_bad.txt"
run_test C_which "-b scripts/which.txt"
run_test C_cut "-b scripts/cut.txt"
run_test C_cut_bad "-b scripts/cut_bad.txt"
run_test B_return_code "-b scripts/rc.txt"
# Your ls implementation must list in alphabetical order
run_test B_export_unset "-b scripts/export.txt"
# Previous test exports and uses echo, no binaries
run_test B_repeat "-b scripts/repeat.txt"
# Previous test exports and uses repeat binary
run_test B_setenv "-b scripts/setenv.txt"
# Previous test uses ./bin/env to set env vars
run_test B_env "-b scripts/env.txt"
# Previous combines export and ./bin/env with ./bin/repeat
run_test A_cat_tail "-b scripts/tail.txt"
run_test A_pipe "-b scripts/pipe.txt"
run_test A_env "-b scripts/env_pipe.txt"
+114
View File
@@ -0,0 +1,114 @@
#include <check.h>
#include "../src/hash.h"
START_TEST (C_hash_insert)
{
char *strings[] = { "foo", "bar", "zoo", "yadda", "help", "goo", "hi", "boo",
"nine", "ten" };
hash_init (100);
for (int i = 0; i < 10; i++)
ck_assert (hash_insert (strings[i], strings[(i + 1) % 10]));
for (int i = 0; i < 10; i++)
{
char *value = hash_find (strings[i]);
ck_assert_str_eq (value, strings[(i + 1) % 10]);
}
}
END_TEST
START_TEST (C_hash_find_missing)
{
char *strings[] = { "foo", "bar", "zoo", "yadda", "help", "goo", "hi", "boo",
"nine", "ten" };
hash_init (100);
for (int i = 0; i < 10; i++)
ck_assert (hash_insert (strings[i], strings[(i + 1) % 10]));
ck_assert (hash_find ("gobble") == NULL);
}
END_TEST
START_TEST (C_hash_remove)
{
char *strings[] = { "foo", "bar", "zoo", "yadda", "help", "goo", "hi", "boo",
"nine", "ten" };
hash_init (100);
for (int i = 0; i < 10; i++)
ck_assert (hash_insert (strings[i], strings[(i + 1) % 10]));
for (int i = 0; i < 5; i++)
ck_assert (hash_remove (strings[i]));
ck_assert_str_eq (hash_find ("goo"), "hi");
for (int i = 5; i < 10; i++)
ck_assert (hash_remove (strings[i]));
ck_assert (hash_find ("goo") == NULL);
}
END_TEST
START_TEST (C_hash_replace)
{
char *strings[] = { "foo", "bar", "zoo", "yadda", "help", "goo", "hi", "boo",
"nine", "ten" };
hash_init (100);
for (int i = 0; i < 10; i++)
ck_assert (hash_insert (strings[i], strings[(i + 1) % 10]));
ck_assert (hash_insert ("yadda", "again?!"));
ck_assert_str_eq (hash_find ("yadda"), "again?!");
ck_assert (hash_insert ("yadda", "really?!"));
ck_assert_str_eq (hash_find ("yadda"), "really?!");
}
END_TEST
START_TEST (C_hash_collisions)
{
char *strings[] = { "foo", "bar", "zoo", "yadda", "help", "goo", "hi", "boo",
"nine", "ten" };
hash_init (100);
for (int i = 0; i < 10; i++)
ck_assert (hash_insert (strings[i], strings[(i + 1) % 10]));
ck_assert (hash_insert ("bar", "a"));
ck_assert (hash_insert ("help", "b"));
ck_assert_str_eq (hash_find ("bar"), "a");
ck_assert_str_eq (hash_find ("help"), "b");
ck_assert (hash_remove ("bar"));
ck_assert (hash_remove ("help"));
ck_assert (hash_find ("bar") == NULL);
ck_assert (hash_find ("help") == NULL);
ck_assert (hash_insert ("help", "asldasdffkj"));
ck_assert (hash_insert ("ten", "asldfkj"));
ck_assert_str_eq (hash_find ("help"), "asldasdffkj");
ck_assert_str_eq (hash_find ("ten"), "asldfkj");
}
END_TEST
void
public_tests (Suite *s)
{
TCase *tc_public = tcase_create ("Public");
tcase_add_test (tc_public, C_hash_insert);
tcase_add_test (tc_public, C_hash_find_missing);
tcase_add_test (tc_public, C_hash_remove);
tcase_add_test (tc_public, C_hash_replace);
tcase_add_test (tc_public, C_hash_collisions);
suite_add_tcase (s, tc_public);
}
+3
View File
@@ -0,0 +1,3 @@
./bin/ls data
./bin/head Makefile
quit
+3
View File
@@ -0,0 +1,3 @@
./bin/ls -l
./bin/head -c 5 Makefile
quit
+12
View File
@@ -0,0 +1,12 @@
./bin/ls -a data
./bin/ls -s data
./bin/ls -sa data
./bin/chmod rw- rw- --- data/empty.txt
./bin/chmod r-- --- --x data/FIRST.txt
./bin/chmod rw- rw- rw- data/pwd.txt
./bin/chmod r-x r-x r-x data/yat.txt
./bin/chmod rwx --- --- data/.hidden.txt
./bin/chmod r-x --x r-- data/subdir
./bin/ls -ap data
./bin/head -n 1 Makefile
quit
+12
View File
@@ -0,0 +1,12 @@
/usr/bin/chmod 751 data/subdir
/usr/bin/chmod 640 data/empty.txt
/usr/bin/chmod 640 data/FIRST.txt
/usr/bin/chmod 400 data/.hidden.txt
/usr/bin/chmod 640 data/pwd.txt
/usr/bin/chmod 640 data/yat.txt
./bin/ls -a data
./bin/ls -s data
./bin/ls -sa data
./bin/ls -ap data
./bin/head -n 1 Makefile
quit
+6
View File
@@ -0,0 +1,6 @@
./bin/cut cut_data/data.spaces
./bin/cut -f 2 cut_data/data.spaces
./bin/cut cut_data/data.csv
./bin/cut -d , cut_data/data.csv
./bin/cut -d , -f 2 cut_data/data.csv
quit
+4
View File
@@ -0,0 +1,4 @@
./bin/cut -f -1 cut_data/data.spaces
./bin/cut -f 3 cut_data/data.spaces
./bin/cut -d , -f 5 cut_data/data.csv
quit
+4
View File
@@ -0,0 +1,4 @@
cd /usr/bin
pwd
which ls
quit
+3
View File
@@ -0,0 +1,3 @@
echo goodbye
echo hello\nworld
quit
+3
View File
@@ -0,0 +1,3 @@
echo hello
echo goodbye
quit
+3
View File
@@ -0,0 +1,3 @@
echo this has extra spaces
echo this\nhas\nnewlines
quit
+5
View File
@@ -0,0 +1,5 @@
export A=5
./bin/repeat 2 A
./bin/env B=6 ./bin/repeat 1 A
./bin/env C=10 ./bin/repeat 2 A 3 B 4 C
quit
+5
View File
@@ -0,0 +1,5 @@
export A=5
./bin/env ./bin/repeat 1 A
./bin/env B=6 C=7 ./bin/repeat 1 A 2 B 3 C
./bin/env C=7 ./bin/repeat 1 A 2 B 3 C | head -n 4
quit
+5
View File
@@ -0,0 +1,5 @@
which export
echo N=${NUM}
export NUM=5
echo N=${NUM}
quit
+6
View File
@@ -0,0 +1,6 @@
./bin/ls -a data | ./bin/head -n 1
./bin/ls -a data | ./bin/head -n 2000
./bin/head -n 1 cut_data/data.spaces | ./bin/cut -f 2
./bin/head -n 1 cut_data/data.csv | ./bin/cut -d , -f 1
./bin/head -n 1 cut_data/data.csv | ./bin/cut -d , -f 3
quit
+1
View File
@@ -0,0 +1 @@
quit
+5
View File
@@ -0,0 +1,5 @@
./bin/ls data
echo $?
./bin/ls asldfkjasldfkj
echo $?
quit
+5
View File
@@ -0,0 +1,5 @@
export SHELL=/bin/bash
export USER=me
./bin/repeat 1 USER
./bin/repeat 2 SHELL
quit
+4
View File
@@ -0,0 +1,4 @@
./bin/env A=5 ./bin/repeat 2 A
./bin/env A=5 B=6 C=7 ./bin/repeat 1 A 2 B 3 C
./bin/env A=10 ./bin/repeat 1 B
quit
+4
View File
@@ -0,0 +1,4 @@
./bin/cat cut_data/data.csv
./bin/cat cut_data/data.csv | ./bin/cut -d , -f 2
./bin/cat cut_data/data.csv | tail -n 1
quit
+8
View File
@@ -0,0 +1,8 @@
echo N=${NUM}
export NUM=5
echo N=${NUM}
unset NUM
echo N=${NUM}
export NUM=10
echo N=${NUM}
quit
+9
View File
@@ -0,0 +1,9 @@
which cd
which echo
which pwd
which which
which ./bin/ls
which ./bin/head
which ls
which head
quit
+3
View File
@@ -0,0 +1,3 @@
which ./bin/rm
which ./bin/cat
quit
+53
View File
@@ -0,0 +1,53 @@
#!/bin/bash
STYLE="gnu"
IGNORE=()
FAIL=0
function comp_file {
SRC=$1
SRC_NAME=$2
# file paths
FORMAT=ckstyle/${SRC_NAME}.$STYLE
DIFF=ckstyle/${SRC_NAME}.diff
# run clang-format and compare results
clang-format --style=$STYLE $source > $FORMAT
diff -u $SRC $FORMAT >$DIFF
PTAG=$(printf '%-30s' "$SRC_NAME")
if [ -s $DIFF ]; then
echo "$PTAG FAIL (see $DIFF for details)"
FAIL=1
else
echo "$PTAG pass"
rm $DIFF
fi
rm $FORMAT
}
mkdir -p ckstyle
rm -f ckstyle/*
for source in $(ls ../src/*.c ../src/*.h) ; do
SKIP=0
src=$(basename $source)
for ignore in ${IGNORE[*]} ; do
if [ "$src" = "$ignore" ] ; then
SKIP=1
fi
done
if [ $SKIP = 0 ] ; then
comp_file $source $src
fi
done
if [ $FAIL != 0 ] ; then
echo "Code that does not adhere to GNU standards will not be accepted."
echo "You must fix these files before submission."
else
echo "Code correctly adheres to required style."
fi
+32
View File
@@ -0,0 +1,32 @@
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <assert.h>
#include <time.h>
#include <check.h>
extern void public_tests (Suite *s);
Suite * test_suite (void)
{
Suite *s = suite_create ("Default");
public_tests (s);
return s;
}
void run_testsuite ()
{
Suite *s = test_suite ();
SRunner *sr = srunner_create (s);
srunner_run_all (sr, CK_NORMAL);
srunner_free (sr);
}
int main (void)
{
srand((unsigned)time(NULL));
run_testsuite ();
return EXIT_SUCCESS;
}
+41
View File
@@ -0,0 +1,41 @@
#
# Simple Makefile
# Mike Lam, James Madison University, August 2016
#
# This makefile builds a simple application that contains a main module
# (specified by the EXE variable) and a predefined list of additional modules
# (specified by the MODS variable). If there are any external library
# dependencies (e.g., the math library, "-lm"), list them in the LIBS variable.
# If there are any precompiled object files, list them in the OBJS variable.
#
# By default, this makefile will build the project with debugging symbols and
# without optimization. To change this, edit or remove the "-g" and "-O0"
# options in CFLAGS and LDFLAGS accordingly.
#
# By default, this makefile build the application using the GNU C compiler,
# adhering to the C99 standard with all warnings enabled.
EXES=ls chmod head cut repeat env cat
# compiler/linker settings
CC=gcc
CFLAGS=-g -O0 -Wall -Werror -std=c99 -pedantic -D_POSIX_C_SOURCE=200809L
LDFLAGS=-O0
# build targets
all: ../bin $(EXES)
.c:
$(CC) $(CFLAGS) $(LDFLAGS) -o $@ $<
mv $@ ../bin
../bin:
mkdir ../bin
clean:
rm -rf ../bin
.PHONY: all clean
+41
View File
@@ -0,0 +1,41 @@
#include <stdio.h>
#include <stdlib.h>
static void usage (void) __attribute__ ((unused));
int
main (int argc, char *argv[])
{
if (argc < 2)
{
int c;
while ((c = getchar()) != EOF)
{
putchar(c);
}
return EXIT_SUCCESS;
}
FILE *fp = fopen(argv[1], "r");
if (fp == NULL)
{
return EXIT_FAILURE;
}
int c;
while ((c = fgetc(fp)) != EOF)
{
putchar(c);
}
fclose(fp);
return EXIT_SUCCESS;
}
static void
usage (void)
{
printf ("cat, print the contents of a file\n");
printf ("usage: cat FILE\n");
}
+70
View File
@@ -0,0 +1,70 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
static void usage (void);
mode_t
calculate_permission (char *arg_str)
{
const char perms[4] = "rwx";
if (strlen (arg_str) != 3)
{
usage ();
exit (EXIT_FAILURE);
}
mode_t permission = 0;
for (size_t j = 0; j < 3; j++)
{
int perm = 1 << (2 - j);
if (arg_str[j] == perms[j])
{
permission |= perm;
}
else if (arg_str[j] != '-')
{
usage ();
exit (EXIT_FAILURE);
}
}
return permission;
}
int
main (int argc, char **argv)
{
if (argc != 5)
{
usage ();
return EXIT_FAILURE;
}
mode_t user = calculate_permission (argv[1]);
mode_t group = calculate_permission (argv[2]);
mode_t other = calculate_permission (argv[3]);
mode_t permissions = (user << 6) | (group << 3) | other;
if (chmod (argv[4], permissions) == -1)
{
perror ("Failed to chmod");
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
static void
usage (void)
{
printf ("chmod, changes permissions on a file\n");
printf ("usage: chmod USR GRP OTH FILE\n\n");
printf ("USR, GRP, and OTH must be of the rwx format,\n");
printf ("with - indicating a permission is not allwed.\n");
}
+75
View File
@@ -0,0 +1,75 @@
#define _GNU_SOURCE
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
// You may assume that lines are no longer than 1024 bytes
#define LINELEN 1024
static void usage (void);
int
main (int argc, char *argv[])
{
int opt;
char *delimitter = " ";
long field = 1;
while ((opt = getopt (argc, argv, "d:f:")) != -1)
{
switch (opt)
{
case 'd':
delimitter = optarg;
break;
case 'f':
field = strtol (optarg, NULL, 10);
break;
default:
printf ("./bin/cut: invalid option -- \'%c\'\n", optopt);
exit (EXIT_FAILURE);
}
}
if (field < 1)
{
usage ();
exit (EXIT_FAILURE);
}
FILE *file = optind < argc ? fopen (argv[optind], "r") : stdin;
if (file == NULL)
{
perror ("Failed to open input file");
exit (EXIT_FAILURE);
}
char buffer[LINELEN];
while (fgets (buffer, sizeof (buffer), file) != NULL)
{
buffer[strcspn (buffer, "\n")] = '\0';
char *token = strtok (buffer, delimitter);
long i = 1;
while (i++ < field) token = strtok (NULL, delimitter);
printf("%s\n", token != NULL ? token : "");
}
return EXIT_SUCCESS;
}
static void
usage (void)
{
printf ("cut, splits each line based on a delimiter\n");
printf ("usage: cut [FLAG] FILE\n");
printf ("FLAG can be:\n");
printf (
" -d C split each line based on the character C (default ' ')\n");
printf (" -f N print the Nth field (1 is first, default 1)\n");
printf ("If no FILE specified, read from STDIN\n");
}
+65
View File
@@ -0,0 +1,65 @@
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
static void usage (void);
bool
is_util (char *str)
{
char *utils[] = { "./bin/cat", "./bin/chmod", "./bin/cut", "./bin/env",
"./bin/head", "./bin/ls", "./bin/repeat" };
for (size_t i = 0; i < 7; i++)
{
if (strcmp (str, utils[i]) == 0)
{
return true;
}
}
return false;
}
int
main (int argc, char *argv[], char *envp[])
{
size_t envp_count = 0;
while (envp[envp_count] != NULL) envp_count++;
size_t env_args = 0;
char* util = NULL;
for (size_t i = 1; i < argc; i++)
{
if (is_util (argv[i])) {
util = argv[i];
break;
}
env_args++;
}
if (util == NULL) {
usage();
exit(EXIT_FAILURE);
}
char **combined_envp = calloc (envp_count + env_args + 1, sizeof (char *));
memcpy (combined_envp, envp, envp_count * sizeof (char *));
memcpy (combined_envp + envp_count, &argv[1], env_args * sizeof (char *));
combined_envp[envp_count + env_args] = NULL;
execve(util, &argv[env_args + 1], combined_envp);
return EXIT_SUCCESS;
}
static void
usage (void)
{
printf ("env, set environment variables and execute program\n");
printf ("usage: env [name=value ...] PROG ARGS\n");
}
+58
View File
@@ -0,0 +1,58 @@
#define _GNU_SOURCE
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
// You may assume that lines are no longer than 1024 bytes
#define LINELEN 1024
static void usage (void) __attribute__ ((unused));
int
main (int argc, char *argv[])
{
int opt;
int n = 5;
while ((opt = getopt (argc, argv, "n:")) != -1)
{
switch (opt)
{
case 'n':
n = strtol (optarg, NULL, 10);
break;
default:
printf ("./bin/head: invalid option -- \'%c\'\n", optopt);
exit (EXIT_FAILURE);
}
}
FILE *file = optind < argc ? fopen (argv[optind], "r") : stdin;
if (file == NULL)
{
perror ("Failed to open input file");
exit (EXIT_FAILURE);
}
char buffer[LINELEN];
int count = 0;
while (count < n && fgets (buffer, sizeof (buffer), file) != NULL)
{
printf ("%s", buffer);
count++;
}
return EXIT_SUCCESS;
}
static void
usage (void)
{
printf ("head, prints the first few lines of a file\n");
printf ("usage: head [FLAG] FILE\n");
printf ("FLAG can be:\n");
printf (" -n N show the first N lines (default 5)\n");
printf ("If no FILE specified, read from STDIN\n");
}
+148
View File
@@ -0,0 +1,148 @@
#define _GNU_SOURCE
#include <dirent.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <sys/stat.h>
#include <unistd.h>
#define MAX_PATH_LEN 4096
static void usage (void) __attribute__ ((unused));
// WARNING WARNING WARNING:
// When using opendir and readdir to read directory listings, the files
// are returned in the order they were created. There is randomness to that
// creation that can cause tests to fail. You MUST sort the file names as
// expected in the test case files. To avoid making your task harder than
// it needs to be, we strongly suggest you use scandir instead.
int
filename_cmp (const struct dirent **a, const struct dirent **b)
{
const char *name1 = (*a)->d_name;
const char *name2 = (*b)->d_name;
while (*name1 == '.')
name1++;
while (*name2 == '.')
name2++;
return strcasecmp (name1, name2);
}
void
print_permissions (mode_t mode)
{
char perms[11];
perms[0] = S_ISDIR (mode) ? 'd' : '-';
perms[1] = (mode & S_IRUSR) ? 'r' : '-';
perms[2] = (mode & S_IWUSR) ? 'w' : '-';
perms[3] = (mode & S_IXUSR) ? 'x' : '-';
perms[4] = (mode & S_IRGRP) ? 'r' : '-';
perms[5] = (mode & S_IWGRP) ? 'w' : '-';
perms[6] = (mode & S_IXGRP) ? 'x' : '-';
perms[7] = (mode & S_IROTH) ? 'r' : '-';
perms[8] = (mode & S_IWOTH) ? 'w' : '-';
perms[9] = (mode & S_IXOTH) ? 'x' : '-';
perms[10] = '\0';
printf ("%s", perms);
}
int
main (int argc, char *argv[])
{
int opt;
int a_flag = false, p_flag = false, s_flag = false;
while ((opt = getopt (argc, argv, "aps")) != -1)
{
switch (opt)
{
case 'a':
a_flag = true;
break;
case 'p':
p_flag = true;
break;
case 's':
s_flag = true;
break;
default:
printf ("./bin/ls: invalid option -- \'%c\'\n", optopt);
exit (EXIT_FAILURE);
}
}
char *dirname = optind < argc ? argv[optind] : ".";
struct stat dirstat;
if (stat(dirname, &dirstat) != 0) {
return EXIT_FAILURE;
}
struct dirent **namelist;
int n = scandir (dirname, &namelist, NULL, filename_cmp);
for (int i = 0; i < n; i++)
{
char path[MAX_PATH_LEN];
snprintf (path, sizeof (path), "%s/%s", dirname, namelist[i]->d_name);
struct stat st;
lstat (path, &st);
bool dot_dir = strcmp (namelist[i]->d_name, ".") == 0
|| strcmp (namelist[i]->d_name, "..") == 0;
bool hidden = namelist[i]->d_name[0] == '.';
if (dot_dir || (!a_flag && hidden) || (s_flag && S_ISDIR (st.st_mode)))
{
free (namelist[i]);
continue;
}
if (s_flag)
{
printf ("%ld ", st.st_size);
}
if (p_flag)
{
print_permissions (st.st_mode);
printf (" ");
}
printf ("%s\n", namelist[i]->d_name);
free (namelist[i]);
}
free (namelist);
return EXIT_SUCCESS;
}
static void
usage (void)
{
printf ("ls, list directory contents\n");
printf ("usage: ls [FLAG ...] [DIR]\n");
printf ("FLAG is one or more of:\n");
printf (" -a list all files (even hidden ones)\n");
printf (" -p list permission bitmask\n");
printf (" -s list file sizes\n");
printf ("If no DIR specified, list current directory contents.\n\n");
printf ("Files must be sorted alphabetically, case insensitive.\n");
printf ("Leading dots should be ignored when sorting.\n\n");
printf ("With the -s flag, do not show entries for subdirectories.\n");
printf ("Permission bitmasks are 10-character strings such as:\n");
printf (" -rwxr-x---\n\n");
printf (
"The first character is d for directories and - for regular files.\n\n");
printf ("Do not show the \".\" or \"..\" directory entries.\n");
}
+76
View File
@@ -0,0 +1,76 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
static void usage (void) __attribute__ ((unused));
int
main (int argc, char *argv[], char *envp[])
{
if (argc < 3)
{
usage();
return EXIT_FAILURE;
}
int i = 1;
while (i < argc)
{
char *endptr;
long count = strtol (argv[i], &endptr, 10);
if (*endptr != '\0' || count <= 0)
{
usage();
return EXIT_FAILURE;
}
i++;
if (i >= argc)
{
usage();
return EXIT_FAILURE;
}
char *var = argv[i];
char *val = NULL;
int x = 0;
while (envp[x] != NULL)
{
if (strstr(envp[x], var) == envp[x])
{
val = envp[x];
break;
}
x++;
}
if (val == NULL)
{
val = "";
for (long j = 0; j < count; j++)
{
printf("%s=%s\n", var, val);
}
val = "";
}
else
{
for (long j = 0; j < count; j++)
{
printf("%s\n", val);
}
}
i++;
}
return EXIT_SUCCESS;
}
static void
usage (void)
{
printf ("repeat, a tool for printing repeated environment variables\n");
printf ("usage: repeat N VAR ...\n");
printf ("each N must be a positive integer\n");
printf ("N VAR can be repeated, but each repetition must have both\n");
}