Removed submodules
This commit is contained in:
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
hello,world,me
|
||||
later,gator
|
||||
|
@@ -0,0 +1,2 @@
|
||||
hello world me
|
||||
later gator
|
||||
Executable
+1
@@ -0,0 +1 @@
|
||||
oops
|
||||
@@ -0,0 +1 @@
|
||||
First
|
||||
@@ -0,0 +1,2 @@
|
||||
pwd
|
||||
quit
|
||||
Executable
+1
@@ -0,0 +1 @@
|
||||
yet another test 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;
|
||||
}
|
||||
@@ -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
@@ -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);
|
||||
}
|
||||
@@ -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
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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");
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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
|
||||
@@ -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 ();
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
#ifndef __cs361_shell__
|
||||
#define __cs361_shell__
|
||||
|
||||
void shell (FILE *);
|
||||
|
||||
#endif
|
||||
@@ -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
|
||||
|
||||
Executable
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
@@ -0,0 +1,2 @@
|
||||
hello,world,me
|
||||
later,gator
|
||||
|
@@ -0,0 +1,2 @@
|
||||
hello world me
|
||||
later gator
|
||||
Executable
+1
@@ -0,0 +1 @@
|
||||
oops
|
||||
@@ -0,0 +1 @@
|
||||
First
|
||||
@@ -0,0 +1,2 @@
|
||||
pwd
|
||||
quit
|
||||
Executable
+1
@@ -0,0 +1 @@
|
||||
yet another test 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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
$ which ./bin/rm
|
||||
./bin/rm
|
||||
$ which ./bin/cat
|
||||
./bin/cat
|
||||
$ quit
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
$ which export
|
||||
export: dukesh built-in command
|
||||
$ echo N=${NUM}
|
||||
N=
|
||||
$ export NUM=5
|
||||
$ echo N=${NUM}
|
||||
N=5
|
||||
$ quit
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
$ ./bin/ls data
|
||||
empty.txt
|
||||
FIRST.txt
|
||||
pwd.txt
|
||||
subdir
|
||||
yat.txt
|
||||
$ echo $?
|
||||
0
|
||||
$ ./bin/ls asldfkjasldfkj
|
||||
$ echo $?
|
||||
1
|
||||
$ quit
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
$ ./bin/ls -l
|
||||
./bin/ls: invalid option -- 'l'
|
||||
$ ./bin/head -c 5 Makefile
|
||||
./bin/head: invalid option -- 'c'
|
||||
$ quit
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
$ cd /usr/bin
|
||||
$ pwd
|
||||
/usr/bin
|
||||
$ which ls
|
||||
/usr/bin/ls
|
||||
$ quit
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
$ echo goodbye
|
||||
goodbye
|
||||
$ echo hello\nworld
|
||||
hello
|
||||
world
|
||||
$ quit
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
$ echo this has extra spaces
|
||||
this has extra spaces
|
||||
$ echo this\nhas\nnewlines
|
||||
this
|
||||
has
|
||||
newlines
|
||||
$ quit
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
$ echo hello
|
||||
hello
|
||||
$ echo goodbye
|
||||
goodbye
|
||||
$ quit
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
$ quit
|
||||
|
||||
Executable
+95
@@ -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
|
||||
|
||||
@@ -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"
|
||||
@@ -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);
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
./bin/ls data
|
||||
./bin/head Makefile
|
||||
quit
|
||||
@@ -0,0 +1,3 @@
|
||||
./bin/ls -l
|
||||
./bin/head -c 5 Makefile
|
||||
quit
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -0,0 +1,4 @@
|
||||
cd /usr/bin
|
||||
pwd
|
||||
which ls
|
||||
quit
|
||||
@@ -0,0 +1,3 @@
|
||||
echo goodbye
|
||||
echo hello\nworld
|
||||
quit
|
||||
@@ -0,0 +1,3 @@
|
||||
echo hello
|
||||
echo goodbye
|
||||
quit
|
||||
@@ -0,0 +1,3 @@
|
||||
echo this has extra spaces
|
||||
echo this\nhas\nnewlines
|
||||
quit
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -0,0 +1,5 @@
|
||||
which export
|
||||
echo N=${NUM}
|
||||
export NUM=5
|
||||
echo N=${NUM}
|
||||
quit
|
||||
@@ -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
|
||||
@@ -0,0 +1 @@
|
||||
quit
|
||||
@@ -0,0 +1,5 @@
|
||||
./bin/ls data
|
||||
echo $?
|
||||
./bin/ls asldfkjasldfkj
|
||||
echo $?
|
||||
quit
|
||||
@@ -0,0 +1,5 @@
|
||||
export SHELL=/bin/bash
|
||||
export USER=me
|
||||
./bin/repeat 1 USER
|
||||
./bin/repeat 2 SHELL
|
||||
quit
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -0,0 +1,9 @@
|
||||
which cd
|
||||
which echo
|
||||
which pwd
|
||||
which which
|
||||
which ./bin/ls
|
||||
which ./bin/head
|
||||
which ls
|
||||
which head
|
||||
quit
|
||||
@@ -0,0 +1,3 @@
|
||||
which ./bin/rm
|
||||
which ./bin/cat
|
||||
quit
|
||||
Executable
+53
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user