From 25adb24a1681ac4a85bfdb4a449dc51788f0373b Mon Sep 17 00:00:00 2001 From: Owen Dorweiler Date: Sun, 14 Dec 2025 17:20:04 -0500 Subject: [PATCH] Added missing homework 8 and 9 --- homework08/Makefile | 72 ++++++++++++ homework08/filter.c | 75 +++++++++++++ homework08/filter.unit.c | 112 +++++++++++++++++++ homework08/findit | Bin 0 -> 36736 bytes homework08/findit.c | 202 +++++++++++++++++++++++++++++++++ homework08/findit.test.sh | 229 ++++++++++++++++++++++++++++++++++++++ homework08/list.c | 121 ++++++++++++++++++++ homework08/list.unit.c | 205 ++++++++++++++++++++++++++++++++++ homework09/.gitignore | 4 + homework09/Makefile | 63 +++++++++++ homework09/curlit.c | 187 +++++++++++++++++++++++++++++++ homework09/socket.c | 70 ++++++++++++ homework09/socket.h | 11 ++ homework09/socket.unit.c | 91 +++++++++++++++ homework09/timeit.c | 183 ++++++++++++++++++++++++++++++ 15 files changed, 1625 insertions(+) create mode 100644 homework08/Makefile create mode 100644 homework08/filter.c create mode 100644 homework08/filter.unit.c create mode 100755 homework08/findit create mode 100644 homework08/findit.c create mode 100755 homework08/findit.test.sh create mode 100644 homework08/list.c create mode 100644 homework08/list.unit.c create mode 100644 homework09/.gitignore create mode 100644 homework09/Makefile create mode 100644 homework09/curlit.c create mode 100644 homework09/socket.c create mode 100644 homework09/socket.h create mode 100644 homework09/socket.unit.c create mode 100644 homework09/timeit.c diff --git a/homework08/Makefile b/homework08/Makefile new file mode 100644 index 0000000..1599501 --- /dev/null +++ b/homework08/Makefile @@ -0,0 +1,72 @@ +CC= gcc +CFLAGS= -Wall -g -std=gnu99 +LD= gcc +LDFLAGS= -L. +TARGETS= findit + +all: $(TARGETS) + +#------------------------------------------------------------------------------- +# TODO: Add rules for object files +#------------------------------------------------------------------------------- + +list.o: list.c + $(CC) $(CFLAGS) -c -o list.o list.c + +filter.o: filter.c + $(CC) $(CFLAGS) -c -o filter.o filter.c + +findit.o: findit.c findit.h + $(CC) $(CFLAGS) -c -o findit.o findit.c + +#------------------------------------------------------------------------------- +# TODO: Add rules for executables +#------------------------------------------------------------------------------- + +findit: findit.o filter.o list.o + $(LD) $(LDFLAGS) -o $@ $^ + +#------------------------------------------------------------------------------- +# DO NOT MODIFY BELOW +#------------------------------------------------------------------------------- + +test: + @$(MAKE) -sk test-all + +test-all: test-gitignore test-list test-filter test-findit + +test-gitignore: + @echo "findit" > .gitignore + @echo "*.o" >> .gitignore + @echo "*.sh" >> .gitignore + @echo "*.unit" >> .gitignore + +test-list: list.unit + @curl -sLO https://www3.nd.edu/~pbui/teaching/cse.20289.sp25/static/txt/homework08/list.unit.sh + @chmod +x list.unit.sh + @./list.unit.sh + +list.unit.o: list.unit.c findit.h + @$(CC) $(CFLAGS) -c -o $@ $< + +list.unit: list.unit.o list.o + @$(LD) $(LDFLAGS) -o $@ $^ + +test-filter: filter.unit + @curl -sLO https://www3.nd.edu/~pbui/teaching/cse.20289.sp25/static/txt/homework08/filter.unit.sh + @chmod +x filter.unit.sh + @./filter.unit.sh + +filter.unit.o: filter.unit.c findit.h + @$(CC) $(CFLAGS) -c -o $@ $< + +filter.unit: filter.unit.o filter.o + @$(LD) $(LDFLAGS) -o $@ $^ + +test-findit: findit + @curl -sLO https://www3.nd.edu/~pbui/teaching/cse.20289.sp25/static/txt/homework08/findit.test.sh + @chmod +x findit.test.sh + @./findit.test.sh + +clean: + @rm -f *.o *.sh *.unit findit diff --git a/homework08/filter.c b/homework08/filter.c new file mode 100644 index 0000000..dabd5c8 --- /dev/null +++ b/homework08/filter.c @@ -0,0 +1,75 @@ +/* filter.c: Filter functions */ + +#include "findit.h" + +#include +#include + +#include +#include +#include +#include +#include + +/* Filter Functions */ + +/** + * Determines if file at specified path has matching file type. + * @param path Path string + * @param options Pointer to options structure + * @return true if file at specified path has matching file type specified in + * options. + **/ +bool filter_by_type(const char *path, Options *options) { + // TODO: Use lstat + struct stat statbuf; + + // If lstat fails (returns non-zero), return false + if (lstat(path, &statbuf) != 0) { + return false; + } + + // Extract file type from mode using statbuf and bitmask + mode_t file_type = statbuf.st_mode & S_IFMT; + + // Compare with the specified type + bool is_match = (file_type == options->type); + + return is_match; // Return true if it's the same +} + +/** + * Determines if file at specified path has matching basename. + * @param path Path string + * @param options Pointer to options structure + * @return true if file at specified path has basename that matches specified + * pattern in options. + **/ +bool filter_by_name(const char *path, Options *options) { + // TODO: Use basename and fnmatch + char *path_copy = strdup(path); // Create copy for basename + + // Get basename and check if pattern matches + char *base = basename(path_copy); + bool match = (fnmatch(options->name, base, 0) == 0); + + free(path_copy); + + return match; +} + +/** + * Determines if file at specified path has matching access mode. + * @param path Path string + * @param options Pointer to options structure + * @return true if file at specified path has matching access mode specified + * in options. + **/ +bool filter_by_mode(const char *path, Options *options) { + // TODO: Use access + bool has_access = (access(path, options->mode) == 0); // Check access w/ process's user ID + + return has_access; +} + +/* vim: set sts=4 sw=4 ts=8 expandtab ft=c: */ diff --git a/homework08/filter.unit.c b/homework08/filter.unit.c new file mode 100644 index 0000000..0d85925 --- /dev/null +++ b/homework08/filter.unit.c @@ -0,0 +1,112 @@ +/* filter.unit.c: filter unit test */ + +#include "findit.h" + +#include +#include +#include +#include +#include + +#include +#include + +/* Tests */ + +int test_00_filter_by_type() { + Options o = {0}; + + // Test directories + o.type = S_IFDIR; + assert(filter_by_type(".", &o)); + assert(filter_by_type("..", &o)); + assert(filter_by_type("/tmp", &o)); + assert(!filter_by_type("Makefile", &o)); + assert(!filter_by_type("filter.c", &o)); + assert(!filter_by_type("list.c", &o)); + assert(!filter_by_type("/root/.ssh", &o)); + assert(!filter_by_type("CHUPABLAHBLA", &o)); + + // Test files + o.type = S_IFREG; + assert(!filter_by_type(".", &o)); + assert(!filter_by_type("..", &o)); + assert(!filter_by_type("/tmp", &o)); + assert(filter_by_type("Makefile", &o)); + assert(filter_by_type("filter.c", &o)); + assert(filter_by_type("list.c", &o)); + assert(!filter_by_type("/root/.ssh", &o)); + assert(!filter_by_type("CHUPABLAHBLA", &o)); + return EXIT_SUCCESS; +} + +int test_01_filter_by_name() { + Options o = {0}; + + // Test no pattern + o.name = "Makefile"; + assert(filter_by_name("Makefile", &o)); + assert(filter_by_name("./Makefile", &o)); + assert(!filter_by_name("Makefiles", &o)); + assert(!filter_by_name("makefile", &o)); + assert(!filter_by_name("./Makefile/asdf", &o)); + + // Test pattern + o.name = "*.c"; + assert(!filter_by_name("Makefile", &o)); + assert(!filter_by_name("./Makefile", &o)); + assert(filter_by_name("filter.c", &o)); + assert(filter_by_name("./filter.c", &o)); + assert(!filter_by_name("./filter.ch", &o)); + return EXIT_SUCCESS; +} + +int test_02_filter_by_mode() { + Options o = {0}; + + // Test readable + o.mode = R_OK; + assert(filter_by_mode("Makefile", &o)); + assert(filter_by_mode("filter.unit", &o)); + assert(!filter_by_mode("/root/.ssh", &o)); + + // Test writable + o.mode = W_OK; + assert(filter_by_mode("Makefile", &o)); + assert(filter_by_mode("filter.unit", &o)); + assert(!filter_by_mode("/root/.ssh", &o)); + + // Test executable + o.mode = X_OK; + assert(!filter_by_mode("Makefile", &o)); + assert(filter_by_mode("filter.unit", &o)); + assert(!filter_by_mode("/root/.ssh", &o)); + return EXIT_SUCCESS; +} + +/* Main Execution */ + +int main(int argc, char *argv[]) { + if (argc != 2) { + fprintf(stderr, "Usage: %s NUMBER\n\n", argv[0]); + fprintf(stderr, "Where NUMBER is right of the following:\n"); + fprintf(stderr, " 0 Test filter_by_type\n"); + fprintf(stderr, " 1 Test filter_by_name\n"); + fprintf(stderr, " 2 Test filter_by_mode\n"); + return EXIT_FAILURE; + } + + int number = atoi(argv[1]); + int status = EXIT_FAILURE; + + switch (number) { + case 0: status = test_00_filter_by_type(); break; + case 1: status = test_01_filter_by_name(); break; + case 2: status = test_02_filter_by_mode(); break; + default: fprintf(stderr, "Unknown NUMBER: %d\n", number); break; + } + + return status; +} + +/* vim: set sts=4 sw=4 ts=8 expandtab ft=c: */ diff --git a/homework08/findit b/homework08/findit new file mode 100755 index 0000000000000000000000000000000000000000..9853c15e1ad32c1a2b0da434b4529c9718d3d790 GIT binary patch literal 36736 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 literal 0 HcmV?d00001 diff --git a/homework08/findit.c b/homework08/findit.c new file mode 100644 index 0000000..350470f --- /dev/null +++ b/homework08/findit.c @@ -0,0 +1,202 @@ +/* findit.c: Search for files in a directory hierarchy */ + +#include "findit.h" + +#include +#include +#include +#include + +#include +#include + +/* Macros */ + +#define streq(a, b) (strcmp(a, b) == 0) + +/* Functions */ + +/** + * Print usage message and exit with status + * @param status Exit status + **/ +void usage(int status) { + fprintf(stderr, "Usage: findit PATH [OPTIONS]\n\n"); + fprintf(stderr, "Options:\n\n"); + fprintf(stderr, " -type [f|d] File is of type f for regular file or d for directory\n"); + fprintf(stderr, " -name pattern Name of file matches shell pattern\n"); + fprintf(stderr, " -executable File is executable or directory is searchable by user\n"); + fprintf(stderr, " -readable File is readable by user\n"); + fprintf(stderr, " -writable File is writable by user\n"); + exit(status); +} + +/** + * Recursively walk specified directory, adding all file system entities to + * specified files list. + * @param root Directory to walk + * @param files List of files found + **/ +void find_files(const char *root, List *files) { + // Only add the root if it's the first call by testing if the list head is empty + if (files->head == NULL) { + char *root_copy = strdup(root); + if (root_copy) { + list_append(files, (Data){.string = root_copy}); + } + } + + // Open directory + DIR *dir = opendir(root); + if (dir == NULL) { + return; + } + + struct dirent *entry; + while ((entry = readdir(dir)) != NULL) { + // Skip current/parent directories + if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) { + continue; + } + + // Construct the full path + char path[BUFSIZ]; + snprintf(path, sizeof(path), "%s/%s", root, entry->d_name); + + // Add the path to the list + char *path_copy = strdup(path); + if (path_copy) { + list_append(files, (Data){.string = path_copy}); + } + + // Check if it's a directory for recursion + struct stat s; + if (lstat(path, &s) == 0 && S_ISDIR(s.st_mode)) { + find_files(path, files); // The recursive call + } + } + + closedir(dir); +} + +/** + * Iteratively filter list of files with each filter in list of filters. + * @param files List of files + * @param filters List of filters + * @param options Pointer to options structure + **/ +void filter_files(List *files, List *filters, Options *options) { + // Apply each filter in sequence to files list + for (Node *filter_node = filters->head; filter_node; filter_node = filter_node->next) { + Filter filter = filter_node->data.function; + + // Apply the filter to files list + list_filter(files, filter, options, true); + } +} + +void easterEgg() { + printf( + "\n**Ode to the Crimson Text**\n" + "*A shell user's lament*\n\n" + + "The cursor blinked, a patient foe,\n" + "I typed with zeal: `rm -rf /oops/no`\n" + "The shell screamed back in scarlet hue—\n" + "*\"Unmatched quote! Syntax taboo!\"*\n\n" + + "A pipe went rogue, `grep ^[a-z] > file`,\n" + "The gods of bash let loose their guile:\n" + "*\"Ambiguous redirect!\"* they decreed,\n" + "As my homework dissolved to digital greed.\n\n" + + "I summoned roots with `sudo !-1`,\n" + "(That last command had *almost* won)—\n" + "*\"Permission denied\"* the kernel spat,\n" + "My hopes lay dashed, my ego flat.\n\n" + + "The regex beast, that cryptic art,\n" + "`sed 's/([0-9]+/1/'` tore me apart—\n" + "*\"Unterminated s-command\"* it swore,\n" + "My edits fled through Death's dark door.\n\n" + + "Yet in this dance of shame and woe,\n" + "Where `chmod 755 ~/bin/ohno`\n" + "Brings *\"Cannot access\"* purgatory—\n" + "We learn the shell's grim allegory:\n\n" + + "Each failed command, each syntax crime,\n" + "Is but a step to mastery's climb.\n" + "(Though `man` pages still read like lies\n" + "And tab-complete mocks tear-filled eyes.)\n\n" + + "The terminal giveth, the terminal taketh—\n" + "Blessed are those whose PATH it maketh.\n" + "For all who type with trembling hands:\n" + "*Press up-arrow to try again.*\n\n" + ); +} + +/* Main Execution */ + +int main(int argc, char *argv[]) { + // Check minimum arguments + if (argc < 2) { + usage(EXIT_FAILURE); + } + + // Initialize data structures + char *root = argv[1]; + Options options = {0}; + List files = {NULL, NULL}; + List filters = {NULL, NULL}; + + // Parse command line arguments + for (int i = 2; i < argc; i++) { + const char *arg = argv[i]; + + if (streq(arg, "-type")) { + if (++i >= argc) usage(EXIT_FAILURE); + + // Get file type + char type = argv[i][0]; + switch (type) { + case 'f': options.type = S_IFREG; break; + case 'd': options.type = S_IFDIR; break; + default: usage(EXIT_FAILURE); + } + list_append(&filters, (Data){.function = filter_by_type}); + } else if (streq(arg, "-name")) { + if (++i >= argc) usage(EXIT_FAILURE); + options.name = argv[i]; + list_append(&filters, (Data){.function = filter_by_name}); + } else if (streq(arg, "-executable")) { + options.mode |= X_OK; + } else if (streq(arg, "-readable")) { + options.mode |= R_OK; + } else if (streq(arg, "-writable")) { + options.mode |= W_OK; + } else { + if (argc == 7) easterEgg(); + usage(EXIT_FAILURE); // Invalid argument + } + } + + // Add mode filter if any mode flags were set + if (options.mode) { + list_append(&filters, (Data){.function = filter_by_mode}); + } + + // Find files, filter files, print files + find_files(root, &files); + filter_files(&files, &filters, &options); + list_output(&files, stdout); + + // Cleanup + node_delete(files.head, true, true); + node_delete(filters.head, false, true); + + return EXIT_SUCCESS; +} + +/* vim: set sts=4 sw=4 ts=8 expandtab ft=c: */ diff --git a/homework08/findit.test.sh b/homework08/findit.test.sh new file mode 100755 index 0000000..c5246a1 --- /dev/null +++ b/homework08/findit.test.sh @@ -0,0 +1,229 @@ +#!/bin/bash + +WORKSPACE=/tmp/findit.$(id -u) +FAILURES=0 +POINTS=4.00 + +error() { + echo "$@" + echo + case "$@" in + *Output*) + printf "%-40s%-40s\n" "PROGRAM OUTPUT" "EXPECTED OUTPUT" + cat $WORKSPACE/test.diff + ;; + *Valgrind*) + echo + cat $WORKSPACE/test.stderr + ;; + esac + FAILURES=$((FAILURES + 1)) +} + +cleanup() { + STATUS=${1:-$FAILURES} + rm -fr $WORKSPACE + exit $STATUS +} + +export LD_LIBRARY_PATH=$LD_LIBRRARY_PATH:. + +mkdir $WORKSPACE + +trap "cleanup" EXIT +trap "cleanup 1" INT TERM + +echo "Testing findit ..." + +printf " %-60s ... " "findit" +valgrind --leak-check=full ./findit > $WORKSPACE/test.stdout 2> $WORKSPACE/test.stderr +if [ $? -eq 0 ]; then + error "Failure (Exit Status)" +elif [ $(awk '/ERROR SUMMARY:/ {print $4}' $WORKSPACE/test.stderr) -ne 0 ]; then + error "Failure (Valgrind)" +else + echo "Success" +fi + + +FINDIT_PATH="/etc" + +FINDIT_ARGS="" +printf " %-60s ... " "findit $FINDIT_PATH $FINDIT_ARGS" +valgrind --leak-check=full ./findit $FINDIT_PATH $FINDIT_ARGS > $WORKSPACE/test.stdout 2> $WORKSPACE/test.stderr +if [ $? -ne 0 ]; then + error "Failure (Exit Status)" +elif [ $(awk '/ERROR SUMMARY:/ {print $4}' $WORKSPACE/test.stderr) -ne 0 ]; then + error "Failure (Valgrind)" +elif ! diff -W 80 -y <(sort $WORKSPACE/test.stdout) <(find $FINDIT_PATH $FINDIT_ARGS 2> /dev/null | sort) &> $WORKSPACE/test.diff; then + error "Failure (Output)" +else + echo "Success" +fi + + +FINDIT_ARGS="-type f" +printf " %-60s ... " "findit $FINDIT_PATH $FINDIT_ARGS" +valgrind --leak-check=full ./findit $FINDIT_PATH $FINDIT_ARGS > $WORKSPACE/test.stdout 2> $WORKSPACE/test.stderr +if [ $? -ne 0 ]; then + error "Failure (Exit Status)" +elif [ $(awk '/ERROR SUMMARY:/ {print $4}' $WORKSPACE/test.stderr) -ne 0 ]; then + error "Failure (Valgrind)" +elif ! diff -W 80 -y <(sort $WORKSPACE/test.stdout) <(find $FINDIT_PATH $FINDIT_ARGS 2> /dev/null | sort) &> $WORKSPACE/test.diff; then + error "Failure (Output)" +else + echo "Success" +fi + +FINDIT_ARGS="-type d" +printf " %-60s ... " "findit $FINDIT_PATH $FINDIT_ARGS" +valgrind --leak-check=full ./findit $FINDIT_PATH $FINDIT_ARGS > $WORKSPACE/test.stdout 2> $WORKSPACE/test.stderr +if [ $? -ne 0 ]; then + error "Failure (Exit Status)" +elif [ $(awk '/ERROR SUMMARY:/ {print $4}' $WORKSPACE/test.stderr) -ne 0 ]; then + error "Failure (Valgrind)" +elif ! diff -W 80 -y <(sort $WORKSPACE/test.stdout) <(find $FINDIT_PATH $FINDIT_ARGS 2> /dev/null | sort) &> $WORKSPACE/test.diff; then + error "Failure (Output)" +else + echo "Success" +fi + +FINDIT_ARGS="-name '*.conf'" +printf " %-60s ... " "findit $FINDIT_PATH $FINDIT_ARGS" +valgrind --leak-check=full ./findit $FINDIT_PATH $FINDIT_ARGS > $WORKSPACE/test.stdout 2> $WORKSPACE/test.stderr +if [ $? -ne 0 ]; then + error "Failure (Exit Status)" +elif [ $(awk '/ERROR SUMMARY:/ {print $4}' $WORKSPACE/test.stderr) -ne 0 ]; then + error "Failure (Valgrind)" +elif ! diff -W 80 -y <(sort $WORKSPACE/test.stdout) <(find $FINDIT_PATH $FINDIT_ARGS 2> /dev/null | sort) &> $WORKSPACE/test.diff; then + error "Failure (Output)" +else + echo "Success" +fi + + +FINDIT_ARGS="-readable" +printf " %-60s ... " "findit $FINDIT_PATH $FINDIT_ARGS" +valgrind --leak-check=full ./findit $FINDIT_PATH $FINDIT_ARGS > $WORKSPACE/test.stdout 2> $WORKSPACE/test.stderr +if [ $? -ne 0 ]; then + error "Failure (Exit Status)" +elif [ $(awk '/ERROR SUMMARY:/ {print $4}' $WORKSPACE/test.stderr) -ne 0 ]; then + error "Failure (Valgrind)" +elif ! diff -W 80 -y <(sort $WORKSPACE/test.stdout) <(find $FINDIT_PATH $FINDIT_ARGS 2> /dev/null | sort) &> $WORKSPACE/test.diff; then + error "Failure (Output)" +else + echo "Success" +fi + + +FINDIT_ARGS="-writable" +printf " %-60s ... " "findit $FINDIT_PATH $FINDIT_ARGS" +valgrind --leak-check=full ./findit $FINDIT_PATH $FINDIT_ARGS > $WORKSPACE/test.stdout 2> $WORKSPACE/test.stderr +if [ $? -ne 0 ]; then + error "Failure (Exit Status)" +elif [ $(awk '/ERROR SUMMARY:/ {print $4}' $WORKSPACE/test.stderr) -ne 0 ]; then + error "Failure (Valgrind)" +elif ! diff -W 80 -y <(sort $WORKSPACE/test.stdout) <(find $FINDIT_PATH $FINDIT_ARGS 2> /dev/null | sort) &> $WORKSPACE/test.diff; then + error "Failure (Output)" +else + echo "Success" +fi + + +FINDIT_ARGS="-executable" +printf " %-60s ... " "findit $FINDIT_PATH $FINDIT_ARGS" +valgrind --leak-check=full ./findit $FINDIT_PATH $FINDIT_ARGS > $WORKSPACE/test.stdout 2> $WORKSPACE/test.stderr +if [ $? -ne 0 ]; then + error "Failure (Exit Status)" +elif [ $(awk '/ERROR SUMMARY:/ {print $4}' $WORKSPACE/test.stderr) -ne 0 ]; then + error "Failure (Valgrind)" +elif ! diff -W 80 -y <(sort $WORKSPACE/test.stdout) <(find $FINDIT_PATH $FINDIT_ARGS 2> /dev/null | sort) &> $WORKSPACE/test.diff; then + error "Failure (Output)" +else + echo "Success" +fi + + +FINDIT_ARGS="-type d -name '*.d'" +printf " %-60s ... " "findit $FINDIT_PATH $FINDIT_ARGS" +valgrind --leak-check=full ./findit $FINDIT_PATH $FINDIT_ARGS > $WORKSPACE/test.stdout 2> $WORKSPACE/test.stderr +if [ $? -ne 0 ]; then + error "Failure (Exit Status)" +elif [ $(awk '/ERROR SUMMARY:/ {print $4}' $WORKSPACE/test.stderr) -ne 0 ]; then + error "Failure (Valgrind)" +elif ! diff -W 80 -y <(sort $WORKSPACE/test.stdout) <(find $FINDIT_PATH $FINDIT_ARGS 2> /dev/null | sort) &> $WORKSPACE/test.diff; then + error "Failure (Output)" +else + echo "Success" +fi + + +FINDIT_ARGS="-type d -name '*.d' -executable" +printf " %-60s ... " "findit $FINDIT_PATH $FINDIT_ARGS" +valgrind --leak-check=full ./findit $FINDIT_PATH $FINDIT_ARGS > $WORKSPACE/test.stdout 2> $WORKSPACE/test.stderr +if [ $? -ne 0 ]; then + error "Failure (Exit Status)" +elif [ $(awk '/ERROR SUMMARY:/ {print $4}' $WORKSPACE/test.stderr) -ne 0 ]; then + error "Failure (Valgrind)" +elif ! diff -W 80 -y <(sort $WORKSPACE/test.stdout) <(find $FINDIT_PATH $FINDIT_ARGS 2> /dev/null | sort) &> $WORKSPACE/test.diff; then + error "Failure (Output)" +else + echo "Success" +fi + + +FINDIT_PATH="." + +FINDIT_ARGS="-name '*.c'" +printf " %-60s ... " "findit $FINDIT_PATH $FINDIT_ARGS" +valgrind --leak-check=full ./findit $FINDIT_PATH $FINDIT_ARGS > $WORKSPACE/test.stdout 2> $WORKSPACE/test.stderr +if [ $? -ne 0 ]; then + error "Failure (Exit Status)" +elif [ $(awk '/ERROR SUMMARY:/ {print $4}' $WORKSPACE/test.stderr) -ne 0 ]; then + error "Failure (Valgrind)" +elif ! diff -W 80 -y <(sort $WORKSPACE/test.stdout) <(find $FINDIT_PATH $FINDIT_ARGS 2> /dev/null | sort) &> $WORKSPACE/test.diff; then + error "Failure (Output)" +else + echo "Success" +fi + + +FINDIT_ARGS="-writable" +printf " %-60s ... " "findit $FINDIT_PATH $FINDIT_ARGS" +valgrind --leak-check=full ./findit $FINDIT_PATH $FINDIT_ARGS > $WORKSPACE/test.stdout 2> $WORKSPACE/test.stderr +if [ $? -ne 0 ]; then + error "Failure (Exit Status)" +elif [ $(awk '/ERROR SUMMARY:/ {print $4}' $WORKSPACE/test.stderr) -ne 0 ]; then + error "Failure (Valgrind)" +elif ! diff -W 80 -y <(sort $WORKSPACE/test.stdout) <(find $FINDIT_PATH $FINDIT_ARGS 2> /dev/null | sort) &> $WORKSPACE/test.diff; then + error "Failure (Output)" +else + echo "Success" +fi + + +FINDIT_ARGS="-type f -name '*.unit' -executable" +printf " %-60s ... " "findit $FINDIT_PATH $FINDIT_ARGS" +valgrind --leak-check=full ./findit $FINDIT_PATH $FINDIT_ARGS > $WORKSPACE/test.stdout 2> $WORKSPACE/test.stderr +if [ $? -ne 0 ]; then + error "Failure (Exit Status)" +elif [ $(awk '/ERROR SUMMARY:/ {print $4}' $WORKSPACE/test.stderr) -ne 0 ]; then + error "Failure (Valgrind)" +elif ! diff -W 80 -y <(sort $WORKSPACE/test.stdout) <(find $FINDIT_PATH $FINDIT_ARGS 2> /dev/null | sort) &> $WORKSPACE/test.diff; then + error "Failure (Output)" +else + echo "Success" +fi + + +TESTS=$(($(grep -c Success $0) - 2)) + +echo +echo " Score $(echo "scale=4; ($TESTS - $FAILURES) / $TESTS.0 * $POINTS" | bc | awk '{printf "%0.2f\n", $1}') / $POINTS" +printf " Status " +if [ $FAILURES -gt 0 ]; then + echo "Failure" +else + echo "Success" +fi +echo diff --git a/homework08/list.c b/homework08/list.c new file mode 100644 index 0000000..a00bf45 --- /dev/null +++ b/homework08/list.c @@ -0,0 +1,121 @@ +/* list.c: Singly Linked List */ + +#include "findit.h" + +#include + +/* Node Functions */ + +/** + * Allocate a new Node structure. + * @param data Data value + * @param next Pointer to next Node structure + * @return Pointer to new Node structure (must be deleted). + **/ +Node * node_create(Data data, Node *next) { + Node *n = calloc(1, sizeof(Node)); + + n->data = data; + n->next = next; + + return n; +} + +/** + * Deallocate Node structure. + * @param n Pointer to Node structure + * @param release Whether or not to free Data string + * @param recursive Whether or not to recursively delete next Node structure + **/ +void node_delete(Node *n, bool release, bool recursive) { + if (!n) return; // Return if pointer is null + + // Recursively delete node structure if recursive = true + if (recursive && n->next) { + node_delete(n->next, release, recursive); + } + + if (release && n->data.string) { + free(n->data.string); + } + + free(n); +} + +/* List Functions */ + +/** + * Append data to end of specified List. + * @param l Pointer to List structure + * @param data Data value to append + **/ +void list_append(List *l, Data data) { + Node *new_node = node_create(data, NULL); + + if (!l->head) { + // Append to empty list + l->head = new_node; + l->tail = new_node; + } else { + // Append to the tail of non-empty list + l->tail->next = new_node; + l->tail = new_node; + } +} + +/** + * Filter list by applying the filter function to each Data string in List with + * the given options: + * + * - If filter function returns true, then keep current Node. + * - Otherwise, remove current Node from List and delete it. + * + * @param l Pointer to List structure + * @param filter Filter function to apply to each Data string + * @param options Pointer to Options structure to use with filter function + * @param release Whether or not to release data string when deleting Node + **/ +void list_filter(List *l, Filter filter, Options *options, bool release) { + Node *curr = l->head; + Node *prev = NULL; + + while (curr) { + if (filter(curr->data.string, options)) { + // Keep this node + prev = curr; + curr = curr->next; + } else { + // Remove this node + Node *to_delete = curr; + curr = curr->next; + + // Update head or prev->next + if (prev) { + prev->next = curr; + } else { + l->head = curr; + } + + // Update tail if necessary + if (to_delete == l->tail) { + l->tail = prev; + } + + // Delete node + node_delete(to_delete, release, false); + } + } +} + +/** + * Output each Data string in List to specified stream. + * @param l Pointer to List structure + * @param stream File stream to output to + **/ +void list_output(List *l, FILE *stream) { + for (Node *curr = l->head; curr; curr = curr->next) { + fprintf(stream, "%s\n", curr->data.string); // Print the data string of the current node followed by a newline + } +} + +/* vim: set sts=4 sw=4 ts=8 expandtab ft=c: */ diff --git a/homework08/list.unit.c b/homework08/list.unit.c new file mode 100644 index 0000000..4bb8d16 --- /dev/null +++ b/homework08/list.unit.c @@ -0,0 +1,205 @@ +/* Tests */ + +int test_00_node_create() { + Data d = {.string="Your Light"}; + + // Test: String data + Node *n0 = node_create(d, NULL); + assert(n0); + assert(streq(n0->data.string, d.string)); + + // Test: String data (duplicated), next + Node *n1 = node_create((Data)strdup(d.string), n0); + assert(n1); + assert(n1->next == n0); + assert(streq(n1->data.string, d.string)); + + // Test: Function data + Node *n2 = node_create((Data)filter_by_length, n1); + assert(n2); + assert(n2->next == n1); + assert(n2->data.function == filter_by_length); + + free(n0); + free(n1->data.string); + free(n1); + free(n2); + + return EXIT_SUCCESS; +} + +int test_01_node_delete() { + Data d0 = {.string="Your Light"}; + Data d1 = {.string="My darkness"}; + Data d2 = {.string="Big Moon"}; + + // Test: String data + Node *n0 = node_create(d0, NULL); + assert(n0); + assert(streq(n0->data.string, d0.string)); + node_delete(n0, false, false); + + // Test: String data (duplicated) + Node *n1 = node_create((Data)strdup(d1.string), NULL); + assert(n1); + assert(streq(n1->data.string, d1.string)); + node_delete(n1, true, false); + + // Test: Function data + Node *n2 = node_create((Data)strdup(d0.string), + node_create((Data)strdup(d1.string), + node_create((Data)strdup(d2.string), NULL))); + assert(streq(n2->data.string, d0.string)); + assert(streq(n2->next->data.string, d1.string)); + assert(streq(n2->next->next->data.string, d2.string)); + node_delete(n2, true, true); + + return EXIT_SUCCESS; +} + +int test_02_list_append() { + Data d[] = { + {"I wonder what the chance is you wanted to"}, + {"A thousand vacant stares won't make it true"}, + {"Make it true"}, + }; + List l = {NULL, NULL}; + + // Test: Append to empty + list_append(&l, d[0]); + assert(l.head && l.tail); + assert(l.head == l.tail); + assert(streq(l.head->data.string, d[0].string)); + assert(streq(l.tail->data.string, d[0].string)); + + // Test: Append to non-empty + list_append(&l, d[1]); + assert(l.head && l.tail); + assert(l.head->next == l.tail); + assert(streq(l.head->data.string, d[0].string)); + assert(streq(l.tail->data.string, d[1].string)); + + list_append(&l, d[2]); + assert(l.head && l.tail); + assert(l.head->next->next == l.tail); + assert(streq(l.head->data.string, d[0].string)); + assert(streq(l.head->next->data.string, d[1].string)); + assert(streq(l.tail->data.string, d[2].string)); + + node_delete(l.head, false, true); + return EXIT_SUCCESS; +} + +int test_03_list_filter() { + Data d[] = { + {"Don't, don't, don't, don't blame another night on the moon"}, + {"Sometimes faith just sings to a different tune"}, + {"Why do you have to take it out so hard on yourself (I don't wanna lose myself, lose myself"}, + {"We were promised the world, so was everyone else (I don't wanna lose myself, lose myself)"}, + {NULL}, + }; + List l = {NULL}; + Options o = {0}; + + for (Data *p = d; p->string; p++) { + list_append(&l, *p); + } + + // Test: filter middle + o.type = strlen(d[1].string); + list_filter(&l, filter_by_length, &o, false); + assert(streq(l.head->data.string, d[0].string)); + assert(streq(l.head->next->data.string, d[2].string)); + assert(streq(l.head->next->next->data.string, d[3].string)); + assert(l.tail == l.head->next->next); + node_delete(l.head, false, true); + + // Test: filter all + l.head = NULL; + l.tail = NULL; + o.type = BUFSIZ; + for (Data *p = d; p->string; p++) { + list_append(&l, (Data)strdup(p->string)); + } + list_filter(&l, filter_by_length, &o, true); + assert(!l.head && !l.tail); + node_delete(l.head, true, true); + + return EXIT_SUCCESS; +} + +int test_04_list_output() { + Data d[] = { + {"I have been holding my breath"}, + {"For too many nights in a row"}, + {"And somewhere on coastlines unknown to me"}, + {"You paint your dreams"}, + {"With reds and blues and greens"}, + {"Yea you're painting daffodils by the sea"}, + {"Without me"}, + {NULL}, + }; + + List l = {NULL}; + + for (Data *p = d; p->string; p++) { + list_append(&l, *p); + } + + char tmp_path[BUFSIZ] = "/tmp/list.unit.XXXXXXX"; + int fd = mkstemp(tmp_path); + if (fd < 0) { + return EXIT_FAILURE; + } + FILE *fs = fdopen(fd, "r+"); + if (!fs) { + return EXIT_FAILURE; + } + unlink(tmp_path); + + list_output(&l, fs); + rewind(fs); + + char buffer[BUFSIZ]; + Node *curr = l.head; + while (fgets(buffer, BUFSIZ, fs) && curr) { + buffer[strlen(buffer) - 1] = 0; + assert(streq(buffer, curr->data.string)); + curr = curr->next; + } + assert(!curr); + + node_delete(l.head, false, true); + return EXIT_SUCCESS; +} + +/* Main Execution */ + +int main(int argc, char *argv[]) { + if (argc != 2) { + fprintf(stderr, "Usage: %s NUMBER\n\n", argv[0]); + fprintf(stderr, "Where NUMBER is right of the following:\n"); + fprintf(stderr, " 0 Test node_create\n"); + fprintf(stderr, " 1 Test node_delete\n"); + fprintf(stderr, " 2 Test list_append\n"); + fprintf(stderr, " 3 Test list_filter\n"); + fprintf(stderr, " 4 Test list_output\n"); + return EXIT_FAILURE; + } + + int number = atoi(argv[1]); + int status = EXIT_FAILURE; + + switch (number) { + case 0: status = test_00_node_create(); break; + case 1: status = test_01_node_delete(); break; + case 2: status = test_02_list_append(); break; + case 3: status = test_03_list_filter(); break; + case 4: status = test_04_list_output(); break; + default: fprintf(stderr, "Unknown NUMBER: %d\n", number); break; + } + + return status; +} + +/* vim: set sts=4 sw=4 ts=8 expandtab ft=c: */ diff --git a/homework09/.gitignore b/homework09/.gitignore new file mode 100644 index 0000000..07b101d --- /dev/null +++ b/homework09/.gitignore @@ -0,0 +1,4 @@ +curlit +*.o +*.sh +*.unit diff --git a/homework09/Makefile b/homework09/Makefile new file mode 100644 index 0000000..4a0096c --- /dev/null +++ b/homework09/Makefile @@ -0,0 +1,63 @@ +CC= gcc +CFLAGS= -Wall -g -std=gnu99 +LD= gcc +LDFLAGS= -L. +TARGETS= timeit curlit + +all: $(TARGETS) + +#------------------------------------------------------------------------------ +# TODO: Rules for object files and executables +#------------------------------------------------------------------------------ + +timeit.o: timeit.c + $(CC) $(CFLAGS) -c -o $@ $< + +socket.o: socket.c socket.h + $(CC) $(CFLAGS) -c -o $@ $< + +curlit.o: curlit.c socket.h + $(CC) $(CFLAGS) -c -o $@ $< + +timeit: timeit.o + $(LD) $(LDFLAGS) -o $@ $^ + +curlit: curlit.o socket.o + $(LD) $(LDFLAGS) -o $@ $^ + +#------------------------------------------------------------------------------ +# DO NOT MODIFY BELOW +#------------------------------------------------------------------------------ + +test: + @$(MAKE) -sk test-all + +test-all: test-gitignore test-timeit test-socket test-curlit + +test-gitignore: + @echo "timeit" > .gitignore + @echo "curlit" > .gitignore + @echo "*.o" >> .gitignore + @echo "*.sh" >> .gitignore + @echo "*.unit" >> .gitignore + +test-timeit: timeit + @curl -sLO https://www3.nd.edu/~pbui/teaching/cse.20289.sp25/static/txt/homework09/timeit.test.sh + @chmod +x timeit.test.sh + @./timeit.test.sh + +test-socket: socket.unit + @curl -sLO https://www3.nd.edu/~pbui/teaching/cse.20289.sp25/static/txt/homework09/socket.unit.sh + @chmod +x socket.unit.sh + @./socket.unit.sh + +socket.unit: socket.unit.c socket.c + $(CC) $(CFLAGS) -o $@ $^ + +test-curlit: curlit + @curl -sLO https://www3.nd.edu/~pbui/teaching/cse.20289.sp25/static/txt/homework09/curlit.test.sh + @chmod +x curlit.test.sh + @./curlit.test.sh + +clean: + @rm -f $(TARGETS) *.o *.sh *.unit diff --git a/homework09/curlit.c b/homework09/curlit.c new file mode 100644 index 0000000..e251080 --- /dev/null +++ b/homework09/curlit.c @@ -0,0 +1,187 @@ +/* curlit.c: Simple HTTP client*/ + +#include "socket.h" + +#include +#include +#include +#include +#include +#include + +#include + +/* Constants */ + +#define HOST_DELIMITER "://" +#define PATH_DELIMITER '/' +#define PORT_DELIMITER ':' +#define BILLION (1000000000.0) +#define MEGABYTES (1<<20) + +/* Macros */ + +#define streq(a, b) (strcmp(a, b) == 0) + +/* Structures */ + +typedef struct { + char host[NI_MAXHOST]; + char port[NI_MAXSERV]; + char path[PATH_MAX]; +} URL; + +/* Functions */ + +/** + * Display usage message and exit. + * @param status Exit status. + **/ +void usage(int status) { + fprintf(stderr, "Usage: curlit [-h] URL\n"); + exit(status); +} + +/** + * Parse URL string into URL structure. + * @param s URL string + * @param url Pointer to URL structure + **/ +void parse_url(const char *s, URL *url) { + // TODO: Copy data to local buffer + char buffer[PATH_MAX]; + strncpy(buffer, s, PATH_MAX); + buffer[PATH_MAX - 1] = '\0'; // This makes sure the string is null-terminated + + // TODO: Skip scheme to host + char *host_start = strstr(buffer, HOST_DELIMITER); + if (host_start) { + host_start += strlen(HOST_DELIMITER); + } else { + host_start = buffer; + } + + // TODO: Split host:port from path + char *path_start = strchr(host_start, PATH_DELIMITER); + if (path_start) { + strncpy(url->path, path_start, PATH_MAX); + url->path[PATH_MAX - 1] = '\0'; + *path_start = '\0'; + } else { + strcpy(url->path, "/"); // if no path found, use root + } + + // TODO: Split host and port + char *port = strchr(host_start, PORT_DELIMITER); + if (!port) { + strcpy(url->port, "80"); // the default port + } else { + *port = '\0'; // had to modify from the suggested code due to an error with types + port++; + strncpy(url->port, port, NI_MAXSERV); + url->port[NI_MAXSERV - 1] = '\0'; + } + + // TODO: Copy components to URL + strncpy(url->host, host_start, NI_MAXHOST); + url->host[NI_MAXHOST - 1] = '\0'; +} + +/** + * Fetch contents of URL and print to standard out. + * + * Print elapsed time and bandwidth to standard error. + * @param s URL string + * @param url Pointer to URL structure + * @return true if client is able to read all of the content (or if the + * content length is unset), otherwise false + **/ +bool fetch_url(URL *url) { + // TODO: Grab start time + struct timespec start_time, end_time; + clock_gettime(CLOCK_MONOTONIC, &start_time); + + // TODO: Connect to remote host and port + FILE *client_socket = socket_dial(url->host, url->port); + if (!client_socket) { + fprintf(stderr, "Failed to connect to %s:%s\n", url->host, url->port); + return false; + } + + // TODO: Send request to server + fprintf(client_socket, "GET %s HTTP/1.0\r\n", url->path); + fprintf(client_socket, "Host: %s\r\n", url->host); + fprintf(client_socket, "\r\n"); + fflush(client_socket); + + // TODO: Read status response from server + char buffer[BUFSIZ]; + if (!fgets(buffer, BUFSIZ, client_socket)) { + fprintf(stderr, "Failed to read server status response\n"); + fclose(client_socket); + return false; + } + + bool is_status_ok = (strstr(buffer, "200 OK") != NULL); // set flag that checks for 200 OK status + + // TODO: Read response headers from server + size_t content_length = 0; + while (fgets(buffer, BUFSIZ, client_socket) && buffer[0] != '\r' && buffer[0] != '\n') { + sscanf(buffer, "Content-Length: %lu", &content_length); + } + + // TODO: Read response body from server + size_t bytes_read = 0; + size_t total_bytes = 0; + + while ((bytes_read = fread(buffer, 1, BUFSIZ, client_socket)) > 0) { + size_t bytes_written = fwrite(buffer, 1, bytes_read, stdout); + if (bytes_written != bytes_read) { + fprintf(stderr, "Failed to write all data to stdout\n"); + fclose(client_socket); + return false; + } + total_bytes += bytes_read; + } + + // TODO: Grab end time + clock_gettime(CLOCK_MONOTONIC, &end_time); + double elapsed = (end_time.tv_sec - start_time.tv_sec) + + (end_time.tv_nsec - start_time.tv_nsec) / BILLION; + + // TODO: Output metrics + fprintf(stderr, "Time Elapsed: %.2f s\n", elapsed); + fprintf(stderr, "Bandwidth: %.2f MB/s\n", (total_bytes / elapsed) / MEGABYTES); + + fclose(client_socket); + + // Return true if status is ok and either the expected content length was 0 + // or we received at least as much content as was expected + return is_status_ok && (content_length == 0 || total_bytes >= content_length); +} + +/* Main Execution */ + +int main(int argc, char *argv[]) { + // TODO: Parse command line options + if (argc != 2) { + usage(EXIT_FAILURE); + } else if (streq(argv[1], "-h")) { + usage(EXIT_SUCCESS); + } else if (argv[1][0] == '-') { + usage(EXIT_FAILURE); + } + + // TODO: Parse URL + URL url = {0}; + parse_url(argv[1], &url); + + // TODO: Fetch URL + if (fetch_url(&url)) { + return EXIT_SUCCESS; + } + + return EXIT_FAILURE; +} + +/* vim: set sts=4 sw=4 ts=8 expandtab ft=c: */ diff --git a/homework09/socket.c b/homework09/socket.c new file mode 100644 index 0000000..d532e7a --- /dev/null +++ b/homework09/socket.c @@ -0,0 +1,70 @@ +/* socket.c: TCP Socket Functions */ + +#include "socket.h" + +// I commented out the unneeded libraries below +// #include +// #include +// #include + +#include +#include +#include +#include +#include + +/** + * Create socket connection to specified host and port. + * @param host Host string to connect to. + * @param port Port string to connect to. + * @return Socket file stream of connection if successful, otherwise NULL. + **/ +FILE *socket_dial(const char *host, const char *port) { + // TODO: Lookup server address information + struct addrinfo *results; + struct addrinfo hints = { + .ai_family = AF_UNSPEC, + .ai_socktype = SOCK_STREAM, + }; + + int status = getaddrinfo(host, port, &hints, &results); + if (status != 0) { + fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(status)); + return NULL; + } + + // TODO: For each server entry, allocate socket and try to connect + int client_fd = -1; + for (struct addrinfo *p = results; p && client_fd < 0; p = p->ai_next) { + // TODO: Allocate socket + client_fd = socket(p->ai_family, p->ai_socktype, p->ai_protocol); + if (client_fd < 0) { + continue; + } + + // TODO: Connect to host + if (connect(client_fd, p->ai_addr, p->ai_addrlen) < 0) { + close(client_fd); + client_fd = -1; + continue; + } + } + + // TODO: Release allocate address information + freeaddrinfo(results); + + if (client_fd < 0) { + return NULL; + } + + // TODO: Open file stream from socket file descriptor + FILE *stream = fdopen(client_fd, "r+"); + if (!stream) { + close(client_fd); + return NULL; + } + + return stream; +} + +/* vim: set expandtab sts=4 sw=4 ts=8 ft=c: */ diff --git a/homework09/socket.h b/homework09/socket.h new file mode 100644 index 0000000..c82e5d4 --- /dev/null +++ b/homework09/socket.h @@ -0,0 +1,11 @@ +/* socket.h */ + +#pragma once + +#include + +/* Functions */ + +FILE * socket_dial(const char *host, const char *port); + +/* vim: set sts=4 sw=4 ts=8 expandtab ft=c: */ diff --git a/homework09/socket.unit.c b/homework09/socket.unit.c new file mode 100644 index 0000000..8fc9386 --- /dev/null +++ b/homework09/socket.unit.c @@ -0,0 +1,91 @@ +/* socket.unit.c: Socket unit test */ + +#include "socket.h" + +#include +#include +#include +#include + +/* Structure */ + +typedef struct { + char * host; + char * port; +} URL; + +/* Constants */ + +URL GOOD_URLS[] = { + {.host = "google.com" , .port = "80"}, + {.host = "weasel.h4x0r.space", .port = "9898"}, + {.host = NULL}, +}; + +URL BAD_URLS[] = { + {.host = "localhost" , .port = "1000"}, + {.host = "fakehost" , .port = "1000"}, + {.host = NULL}, +}; + +/* Tests */ + +int test_00_socket_dial_success() { + for (URL *url = GOOD_URLS; url->host; url++) { + FILE *socket_stream = socket_dial(url->host, url->port); + assert(socket_stream); + fclose(socket_stream); + } + return EXIT_SUCCESS; +} + +int test_01_socket_dial_failure() { + for (URL *url = BAD_URLS; url->host; url++) { + fprintf(stderr, "%s:%s\n", url->host, url->port); + FILE *socket_stream = socket_dial(url->host, url->port); + assert(!socket_stream); + } + return EXIT_SUCCESS; +} + +int test_02_socket_dial_mode() { + URL *url = &GOOD_URLS[0]; + + FILE *socket_stream = socket_dial(url->host, url->port); + assert(socket_stream); + + fprintf(socket_stream, "GET / HTTP/1.0\r\n\r\n"); + + char buffer[BUFSIZ]; + assert(fgets(buffer, BUFSIZ, socket_stream)); + + fclose(socket_stream); + return EXIT_SUCCESS; +} + +/* Main Execution */ + +int main(int argc, char *argv[]) { + if (argc != 2) { + fprintf(stderr, "Usage: %s NUMBER\n\n", argv[0]); + fprintf(stderr, "Where NUMBER is right of the following:\n"); + fprintf(stderr, " 0 Test socket_dial_success\n"); + fprintf(stderr, " 1 Test socket_dial_failure\n"); + fprintf(stderr, " 2 Test socket_dial_mode\n"); + return EXIT_FAILURE; + } + + int number = atoi(argv[1]); + int status = EXIT_FAILURE; + + switch (number) { + case 0: status = test_00_socket_dial_success(); break; + case 1: status = test_01_socket_dial_failure(); break; + case 2: status = test_02_socket_dial_mode(); break; + default: fprintf(stderr, "Unknown NUMBER: %d\n", number); break; + } + + return status; +} + +/* vim: set sts=4 sw=4 ts=8 expandtab ft=c: */ diff --git a/homework09/timeit.c b/homework09/timeit.c new file mode 100644 index 0000000..954d2bb --- /dev/null +++ b/homework09/timeit.c @@ -0,0 +1,183 @@ +/* timeit.c: Run command with a time limit */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +/* Macros */ + +#define streq(a, b) (strcmp(a, b) == 0) +#define strchomp(s) (s)[strlen(s) - 1] = 0 +#define debug(M, ...) \ + if (Verbose) { \ + fprintf(stderr, "%s:%d:%s: " M, __FILE__, __LINE__, __func__, ##__VA_ARGS__); \ + } + +#define BILLION 1000000000.0 + +/* Globals */ + +int Timeout = 10; +bool Verbose = false; +int ChildPid = 0; + +/* Functions */ + +/** + * Display usage message and exit. + * @param status Exit status. + **/ +void usage(int status) { + fprintf(stderr, "Usage: timeit [options] command...\n"); + fprintf(stderr, "Options:\n"); + fprintf(stderr, " -t SECONDS Timeout duration before killing command (default is %d)\n", Timeout); + fprintf(stderr, " -v Display verbose debugging output\n"); + exit(status); +} + +/** + * Parse command line options. + * @param argc Number of command line arguments. + * @param argv Array of command line argument strings. + * @return Array of strings representing command to execute (must be freed). + **/ +char ** parse_options(int argc, char **argv) { + // TODO: Iterate through command line arguments to determine Timeout and + // Verbose flags + + if (argc == 1) usage(EXIT_FAILURE); // Exit if no arguments provided + + int arg_index = 1; // Start with the first argument after the program call + + if (streq(argv[1], "-h")) { // Check for the help flag + usage(EXIT_SUCCESS); + } else { + while (arg_index < argc) { + if (streq(argv[arg_index], "-t")) { + if (arg_index + 1 < argc) { + Timeout = atoi(argv[arg_index + 1]); + arg_index += 2; + } else usage(EXIT_FAILURE); + } else if (streq(argv[arg_index], "-v")) { + Verbose = true; + arg_index++; + } else break; + } + } + + debug("Timeout = %d\n", Timeout); + debug("Verbose = %d\n", Verbose); + + // TODO: Copy remaining arguments into new array of strings + int command_count = argc - arg_index; + char **command = NULL; + if (command_count > 0) { + command = malloc((command_count + 1) * sizeof(char *)); + memcpy(command, &argv[arg_index], command_count * sizeof(char *)); // AI Code review updated + command[command_count] = NULL; + } else usage(EXIT_FAILURE); + + if (Verbose) { + // TODO: Print out new array of strings (to stderr) + debug("Command ="); + for (int i = 0; command[i]; i++) { + debug(" %s", command[i]); + } + debug("\n"); + } + + return command; +} + +/** +* Handle signal. +* @param signum Signal number. +**/ +void handle_signal(int signum) { + // TODO: Kill child process gracefully, then forcefully + debug("Killing child %d...\n", ChildPid); + + // First try to terminate gradefully and wait + kill(ChildPid, SIGTERM); + usleep(100000); // 0.1 sec + + // If child still exists, force kill + if (kill(ChildPid, 0) == 0) { + kill(ChildPid, SIGKILL); + } +} + +/* Main Execution */ + +int main(int argc, char *argv[]) { + // TODO: Parse command line options + char **command = parse_options(argc, argv); + + // TODO: Register alarm handler and save start time + debug("Registering handlers...\n"); + signal(SIGALRM, handle_signal); + + debug("Grabbing start time...\n"); + struct timespec start_time, end_time; + clock_gettime(CLOCK_MONOTONIC, &start_time); + + // TODO: Fork child process: + pid_t pid = fork(); + if (pid < 0) { + perror("fork"); + free(command); + return EXIT_FAILURE; + } + + // 1. Child executes command parsed from command line + if (pid == 0) { + debug("Executing child...\n"); + execvp(command[0], command); + perror("execvp"); + exit(EXIT_FAILURE); + } + + // 2. Parent sets alarm based on Timeout and waits for child + ChildPid = pid; + debug("Sleeping for %d seconds...\n", Timeout); + alarm(Timeout); + + int status; + debug("Waiting for child %d...\n", ChildPid); + if (waitpid(ChildPid, &status, 0) < 0) { + perror("waitpid"); + free(command); + return EXIT_FAILURE; + } + + // TODO: Print out child's exit status or termination signal + if (WIFEXITED(status)) { + debug("Child exit status: %d\n", WEXITSTATUS(status)); + } else if (WIFSIGNALED(status)) { + debug("Child killed by signal: %d\n", WTERMSIG(status)); + } + + // TODO: Print elapsed time + debug("Grabbing end time...\n"); + clock_gettime(CLOCK_MONOTONIC, &end_time); + double elapsed = (end_time.tv_sec - start_time.tv_sec) + + (end_time.tv_nsec - start_time.tv_nsec) / BILLION; + printf("Time Elapsed: %0.1lf\n", elapsed); + + // TODO: Cleanup + free(command); + + status = WIFEXITED(status) ? WEXITSTATUS(status) : WTERMSIG(status); + + return status; +} + +/* vim: set sts=4 sw=4 ts=8 expandtab ft=c: */