From bc2a9bfb8108f37b43adf21598ebd88685beec3d Mon Sep 17 00:00:00 2001 From: Owen Dorweiler Date: Sat, 22 Mar 2025 20:47:26 -0400 Subject: [PATCH 01/10] Homework 06: str --- homework06/Makefile | 18 ++++--- homework06/str.c | 116 +++++++++++++++++++++++++++++++++++++++----- reading08/grep | Bin 0 -> 20288 bytes reading08/grep.c | 6 +++ 4 files changed, 121 insertions(+), 19 deletions(-) create mode 100755 reading08/grep create mode 100644 reading08/grep.c diff --git a/homework06/Makefile b/homework06/Makefile index 8b7bc93..d3e8dda 100644 --- a/homework06/Makefile +++ b/homework06/Makefile @@ -13,21 +13,27 @@ all: $(TARGETS) # TODO: Add rules for libstr.a libstr.so #------------------------------------------------------------------------------- -str.o: +str.o: str.c + $(CC) $(CFLAGS) -c -o $@ $< -libstr.so: +libstr.so: str.o + $(LD) $(LDFLAGS) -shared -o $@ $< -libstr.a: +libstr.a: str.o + $(AR) $(ARFLAGS) $@ $< #------------------------------------------------------------------------------- # TODO: Add rules for trit.dynamic trit.static #------------------------------------------------------------------------------- -trit.o: +trit.o: trit.c + $(CC) $(CFLAGS) -c -o $@ $< -trit.dynamic: +trit.dynamic: trit.o + $(LD) $(LDFLAGS) -o $@ $< -lstr -trit.static: +trit.static: trit.o libstr.a + $(LD) -o $@ $< libstr.a #------------------------------------------------------------------------------- # DO NOT MODIFY BELOW diff --git a/homework06/str.c b/homework06/str.c index c05daa3..e4e5fb3 100644 --- a/homework06/str.c +++ b/homework06/str.c @@ -13,17 +13,27 @@ * @param s String to convert * @param w Pointer to buffer that holds result of conversion **/ -void str_lower(const char *s, char *w) { - // TODO -} + void str_lower(const char *s, char *w) { + while (*s) { + *w = tolower(*s); + s++; + w++; + } + *w = '\0'; + } /** * Convert string to uppercase. * @param s String to convert * @param w Pointer to buffer that holds result of conversion **/ -void str_upper(const char *s, char *w) { - // TODO +void str_upper(const char *s, char *w) { + while (*s) { + *w = toupper(*s); + s++; + w++; + } + *w = '\0'; } /** @@ -31,8 +41,22 @@ void str_upper(const char *s, char *w) { * @param s String to convert * @param w Pointer to buffer that holds result of conversion **/ -void str_title(const char *s, char *w) { - // TODO +void str_title(const char *s, char *w) { + if (*s) { + *w = toupper(*s); + w++; + s++; + while (*s) { + if (!isalpha(*(s - 1))) { + *w = toupper(*s); + } else { + *w = tolower(*s); + } + s++; + w++; + } + } + *w = '\0'; } /** @@ -41,8 +65,38 @@ void str_title(const char *s, char *w) { * @param chars Characters to strip (if NULL, then all whitespace) * @param w Pointer to buffer that holds result of strip **/ -void str_rstrip(const char *s, const char *chars, char *w) { - // TODO +void str_rstrip(const char *s, const char *chars, char *w) { + bool strip_chars[256] = {0}; + + // Create the lookup table + if (chars == NULL) { + // If chars is NULL, strip whitespace + strip_chars[' '] = true; + strip_chars['\t'] = true; + strip_chars['\n'] = true; + } else { + // Otherwise, strip specified characters + while (*chars) { + strip_chars[(unsigned char)*chars] = true; + chars++; + } + } + + // Copy s to w + const char *src = s; + char *dst = w; + while (*src) { + *dst++ = *src++; + } + *dst = '\0'; + + if (dst > w) { + dst--; + while (dst >= w && strip_chars[(unsigned char)*dst]) { + dst--; + } + *(dst + 1) = '\0'; + } } /** @@ -51,8 +105,24 @@ void str_rstrip(const char *s, const char *chars, char *w) { * @param chars Characters to delete * @param w Pointer to buffer that holds result of deletion **/ -void str_delete(const char *s, const char *chars, char *w) { - // TODO +void str_delete(const char *s, const char *chars, char *w) { + bool del_chars[256] = {0}; + + // Create lookup table + while (*chars) { + del_chars[(unsigned char)*chars] = true; + chars++; + } + + // Copy non-deleted chars to w + while (*s) { + if (!del_chars[(unsigned char)*s]) { + *w = *s; + w++; + } + s++; + } + *w = '\0'; } /** @@ -62,8 +132,28 @@ void str_delete(const char *s, const char *chars, char *w) { * @param to String with corresponding translation characters * @param w Pointer to buffer that holds result of translation **/ -void str_translate(const char *s, const char *from, const char *to, char *w) { - // TODO +void str_translate(const char *s, const char *from, const char *to, char *w) { + unsigned char translate_table[256]; + int i; + + for (i = 0; i < 256; i++) { + translate_table[i] = i; + } + + // Create the translation map + while (*from && *to) { + translate_table[(unsigned char)*from] = *to; + from++; + to++; + } + + // Translate + while (*s) { + *w = translate_table[(unsigned char)*s]; + s++; + w++; + } + *w = '\0'; } /* vim: set sts=4 sw=4 ts=8 expandtab ft=c: */ diff --git a/reading08/grep b/reading08/grep new file mode 100755 index 0000000000000000000000000000000000000000..3620e3371693859db8c05b1ec8b361ecf3664890 GIT binary patch literal 20288 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 N=7?o5$px)p|6d0IpI!g} literal 0 HcmV?d00001 diff --git a/reading08/grep.c b/reading08/grep.c new file mode 100644 index 0000000..e1f497c --- /dev/null +++ b/reading08/grep.c @@ -0,0 +1,6 @@ +#include + +int main(int argc, char *argv[]) { + // Your pattern matching implementation + return 0; +} From 8915ce77352bd49632ef191384ced8696122da75 Mon Sep 17 00:00:00 2001 From: Owen Dorweiler Date: Sat, 22 Mar 2025 20:47:48 -0400 Subject: [PATCH 02/10] Homework 06: trit --- homework06/trit.c | 103 +++++++++++++++++++++++++++++++++++++++------- 1 file changed, 89 insertions(+), 14 deletions(-) diff --git a/homework06/trit.c b/homework06/trit.c index 3a9301f..3a55994 100644 --- a/homework06/trit.c +++ b/homework06/trit.c @@ -5,15 +5,16 @@ #include #include #include +#include // For isspace /* Constants */ enum { - LOWER = 1<<1, - UPPER = 0, // TODO: Modify - TITLE = 0, // TODO: Modify - STRIP = 0, // TODO: Modify - DELETE = 0, // TODO: Modify + LOWER = 1<<1, + UPPER = 1<<2, + TITLE = 1<<3, + STRIP = 1<<4, + DELETE = 1<<5, }; /* Functions */ @@ -21,26 +22,100 @@ enum { void usage(int status) { fprintf(stderr, "Usage: trit SET1 SET2\n\n"); fprintf(stderr, "Post Translation filters:\n\n"); - fprintf(stderr, " -l Convert to lowercase\n"); - fprintf(stderr, " -u Convert to uppercase\n"); - fprintf(stderr, " -t Convert to titlecase\n"); - fprintf(stderr, " -s Strip trailing whitespace\n"); - fprintf(stderr, " -d Delete letters in SET1\n"); + fprintf(stderr, " -l Convert to lowercase\n"); + fprintf(stderr, " -u Convert to uppercase\n"); + fprintf(stderr, " -t Convert to titlecase\n"); + fprintf(stderr, " -s Strip trailing whitespace\n"); + fprintf(stderr, " -d Delete letters in SET1\n"); exit(status); } void translate_stream(FILE *stream, const char *set1, const char *set2, int flags) { - // TODO + char line[256]; + + while (fgets(line, sizeof(line), stream) != NULL) { + // Remove newline char if present + size_t len = strlen(line); + if (len > 0 && line[len - 1] == '\n') { + line[len - 1] = '\0'; + } + + // Translate or delete part of string + char processed_line[1024]; + strcpy(processed_line, line); + + if (flags & DELETE) { + str_delete(processed_line, set1, line); + strcpy(processed_line, line); + set2 = ""; + } else if (set1 != NULL && set2 != NULL) { + str_translate(processed_line, set1, set2, line); + strcpy(processed_line, line); + } + + // Filters + if (flags & LOWER) { + str_lower(processed_line, line); + strcpy(processed_line, line); + } + + if (flags & UPPER) { + str_upper(processed_line, line); + strcpy(processed_line, line); + } + + if (flags & TITLE) { + str_title(processed_line, line); + strcpy(processed_line, line); + } + + if (flags & STRIP) { + str_rstrip(processed_line, " \t\r\f\v", line); // all the whitespace characters I can think of (has no newline) + strcpy(processed_line, line); + } + + printf("%s\n", processed_line); + } } /* Main Execution */ int main(int argc, char *argv[]) { - // TODO: Parse command line arguments + int flags = 0; + char *set1 = NULL; + char *set2 = NULL; + int positional_args_count = 0; - // TODO: Translate standard input - return EXIT_SUCCESS; + for (int i = 1; i < argc; i++) { + if (strcmp(argv[i], "-l") == 0) { + flags |= LOWER; + } else if (strcmp(argv[i], "-u") == 0) { + flags |= UPPER; + } else if (strcmp(argv[i], "-t") == 0) { + flags |= TITLE; + } else if (strcmp(argv[i], "-d") == 0) { + flags |= DELETE; + } else if (strcmp(argv[i], "-s") == 0) { + flags |= STRIP; + } else if (strcmp(argv[i], "-h") == 0) { + usage(0); + } else { + positional_args_count++; + if (positional_args_count == 1) { + set1 = argv[i]; + } else if (positional_args_count == 2) { + set2 = argv[i]; + } else { + usage(1); + } + } + } + + translate_stream(stdin, set1, set2, flags); + + return 0; } +/* Main Execution */ /* vim: set sts=4 sw=4 ts=8 expandtab ft=c: */ From 1432c00d549460442a794bebcfcf328c1ba17c78 Mon Sep 17 00:00:00 2001 From: Owen Dorweiler Date: Wed, 26 Mar 2025 17:55:52 -0400 Subject: [PATCH 03/10] Update str.c and README based on AI code review --- homework06/str.c | 49 ++++++++++++++++++++---------------------------- 1 file changed, 20 insertions(+), 29 deletions(-) diff --git a/homework06/str.c b/homework06/str.c index e4e5fb3..68d2d08 100644 --- a/homework06/str.c +++ b/homework06/str.c @@ -5,6 +5,7 @@ #include #include #include +#include /* Functions */ @@ -59,44 +60,34 @@ void str_title(const char *s, char *w) { *w = '\0'; } -/** - * Strip characters from back of string (if present). - * @param s String to strip - * @param chars Characters to strip (if NULL, then all whitespace) - * @param w Pointer to buffer that holds result of strip - **/ void str_rstrip(const char *s, const char *chars, char *w) { - bool strip_chars[256] = {0}; + // Safety checks + if (!s || !w) return; - // Create the lookup table - if (chars == NULL) { - // If chars is NULL, strip whitespace - strip_chars[' '] = true; + bool strip_chars[256] = {0}; + const unsigned char *p = (const unsigned char *)s; + size_t length = 0; + + // Build lookup table + if (!chars) { + // Strip standard whitespace + strip_chars[' '] = true; strip_chars['\t'] = true; strip_chars['\n'] = true; } else { - // Otherwise, strip specified characters - while (*chars) { + for (; *chars; chars++) strip_chars[(unsigned char)*chars] = true; - chars++; - } } - // Copy s to w - const char *src = s; - char *dst = w; - while (*src) { - *dst++ = *src++; - } - *dst = '\0'; + // Find string length and last non-stripped character + while (*p) p++; + while (p > (const unsigned char *)s && strip_chars[*(p-1)]) + p--; - if (dst > w) { - dst--; - while (dst >= w && strip_chars[(unsigned char)*dst]) { - dst--; - } - *(dst + 1) = '\0'; - } + // Calculate length and copy + length = p - (const unsigned char *)s; + memcpy(w, s, length); + w[length] = '\0'; } /** From 1a1cb9f122f2eb10b21c93d635af81f06b5381d5 Mon Sep 17 00:00:00 2001 From: Owen Dorweiler Date: Thu, 27 Mar 2025 12:36:49 -0400 Subject: [PATCH 04/10] Homework 07: Initial Import --- homework07/Makefile | 66 +++++++++++++++++++ homework07/list.c | 50 ++++++++++++++ homework07/list.h | 41 ++++++++++++ homework07/list.unit.c | 143 +++++++++++++++++++++++++++++++++++++++++ homework07/node.c | 31 +++++++++ homework07/node.unit.c | 83 ++++++++++++++++++++++++ homework07/seqit.c | 34 ++++++++++ homework07/tailit.c | 34 ++++++++++ 8 files changed, 482 insertions(+) create mode 100644 homework07/Makefile create mode 100644 homework07/list.c create mode 100644 homework07/list.h create mode 100644 homework07/list.unit.c create mode 100644 homework07/node.c create mode 100644 homework07/node.unit.c create mode 100644 homework07/seqit.c create mode 100644 homework07/tailit.c diff --git a/homework07/Makefile b/homework07/Makefile new file mode 100644 index 0000000..d86360b --- /dev/null +++ b/homework07/Makefile @@ -0,0 +1,66 @@ +CC= gcc +CFLAGS= -Wall -g -std=gnu99 +LD= gcc +LDFLAGS= -L. +TARGETS= seqit tailit + +all: $(TARGETS) + +#-------------------------------------------------------------------------------- +# TODO: Add rules for node.o, list.o, seqit.o, tailit.o, seqit, tailit +#-------------------------------------------------------------------------------- + +node.o: + +list.o: + +seqit.o: + +tailit.o: + +seqit: + +tailit: + +#------------------------------------------------------------------------------- +# DO NOT MODIFY BELOW +#------------------------------------------------------------------------------- + +test: + @$(MAKE) -sk test-all + +test-all: test-gitignore test-node test-list test-seqit test-tailit + +test-gitignore: + @echo "*.o" >> .gitignore + @echo "*.sh" >> .gitignore + @echo "*.unit" >> .gitignore + +node.unit: node.unit.c node.o + $(LD) $(LDFLAGS) -o node.unit node.unit.c node.o + +test-node: node.unit + @curl -sLO https://www3.nd.edu/~pbui/teaching/cse.20289.sp25/static/txt/homework07/node.unit.sh + @chmod +x node.unit.sh + @./node.unit.sh + +list.unit: list.unit.c node.o list.o + $(LD) $(LDFLAGS) -o list.unit list.unit.c node.o list.o + +test-list: list.unit + @curl -sLO https://www3.nd.edu/~pbui/teaching/cse.20289.sp25/static/txt/homework07/list.unit.sh + @chmod +x list.unit.sh + @./list.unit.sh + +test-seqit: seqit + @curl -sLO https://www3.nd.edu/~pbui/teaching/cse.20289.sp25/static/txt/homework07/seqit.test.sh + @chmod +x seqit.test.sh + @./seqit.test.sh + +test-tailit: tailit + @curl -sLO https://www3.nd.edu/~pbui/teaching/cse.20289.sp25/static/txt/homework07/tailit.test.sh + @chmod +x tailit.test.sh + @./tailit.test.sh + +clean: + @rm -f *.o *.unit *.unit.sh *.test.sh seqit tailit diff --git a/homework07/list.c b/homework07/list.c new file mode 100644 index 0000000..303c7e3 --- /dev/null +++ b/homework07/list.c @@ -0,0 +1,50 @@ +/* list.c: List Structure */ + +#include "list.h" + +/* List Functions */ + +/** + * Create a List structure. + * + * @return Pointer to new List structure (must be deleted later). + **/ +List * list_create() { + // TODO + return NULL; +} + +/** + * Delete List structure. + * + * @param l Pointer to List structure. + * @param release Whether or not to release the string values. + **/ +void list_delete(List *l, bool release) { + // TODO +} + +/** + * Add new Value to back of List structure. + * + * @param l Pointer to List structure. + * @param v Value to add to back of List structure. + **/ +void list_append(List *l, Value v) { + // TODO +} + +/** + * Remove Value at specified index from List structure. + * + * @param l Pointer to List structure. + * @param index Index of Value to remove from List structure. + * + * @return Value at index in List structure (-1 if index is out of bounds). + **/ +Value list_pop(List *l, size_t index) { + // TODO + return (Value)-1L; +} + +/* vim: set sts=4 sw=4 ts=8 expandtab ft=c: */ diff --git a/homework07/list.h b/homework07/list.h new file mode 100644 index 0000000..78e51d0 --- /dev/null +++ b/homework07/list.h @@ -0,0 +1,41 @@ +/* list.h: Doubly Linked List Library */ + +#pragma once + +#include +#include +#include + +/* Value Union */ + +typedef union { + int64_t number; // Value as number + char *string; // Value as string +} Value; + +/* Node Structure */ + +typedef struct Node Node; +struct Node { + Value value; // Value + Node *next; // Pointer to next Node + Node *prev; // Pointer to previous Node +}; + +Node * node_create(Value v, Node *next, Node *prev); +void node_delete(Node *n, bool release); + +/* List Structure */ + +typedef struct { + Node sentinel; // Sentinel Node at head and tail of List + size_t size; // Number of Nodes in List +} List; + +List * list_create(); +void list_delete(List *l, bool release); + +void list_append(List *l, Value v); +Value list_pop(List *l, size_t index); + +/* vim: set sts=4 sw=4 ts=8 expandtab ft=c: */ diff --git a/homework07/list.unit.c b/homework07/list.unit.c new file mode 100644 index 0000000..b565b1c --- /dev/null +++ b/homework07/list.unit.c @@ -0,0 +1,143 @@ +/* list.unit.c: List Structure Unit Test */ + +#include "list.h" + +#include +#include +#include +#include +#include + +/* Constants */ + +const int64_t NUMBERS[] = {5, 7, 4, -1}; +const char *STRINGS[] = {"Orange", "South Bend", "Eau Claire", NULL}; + +/* Tests */ + +int test_00_list_create() { + List *l = list_create(); + assert(l); + assert(l->sentinel.value.number == 0); + assert(l->sentinel.next == &l->sentinel); + assert(l->sentinel.prev == &l->sentinel); + assert(l->size == 0); + free(l); + return EXIT_SUCCESS; +} + +int test_01_list_delete() { + List *l0 = list_create(); + assert(l0); + list_delete(l0, false); + + List *l1 = list_create(); + assert(l1); + for (const int64_t *n = NUMBERS; *n >= 0; n++) { + l1->sentinel.next = node_create((Value)*n, l1->sentinel.next, &l1->sentinel); + l1->size++; + } + list_delete(l1, false); + + List *l2 = list_create(); + assert(l2); + for (const char **s = STRINGS; *s; s++) { + l2->sentinel.next = node_create((Value)strdup(*s), l2->sentinel.next, &l2->sentinel); + l2->size++; + } + list_delete(l2, true); + return EXIT_SUCCESS; +} + +int test_02_list_append() { + List *l1 = list_create(); + assert(l1); + for (size_t i = 0; NUMBERS[i] >= 0; i++) { + list_append(l1, (Value)NUMBERS[i]); + assert(l1->size == (i + 1)); + } + assert(l1->sentinel.next->value.number == NUMBERS[0]); + assert(l1->sentinel.next->next->value.number == NUMBERS[1]); + assert(l1->sentinel.next->next->next->value.number == NUMBERS[2]); + assert(l1->sentinel.prev->value.number == NUMBERS[2]); + assert(l1->sentinel.prev->prev->value.number == NUMBERS[1]); + assert(l1->sentinel.prev->prev->prev->value.number == NUMBERS[0]); + list_delete(l1, false); + + List *l2 = list_create(); + assert(l2); + for (size_t i = 0; STRINGS[i]; i++) { + list_append(l2, (Value)strdup(STRINGS[i])); + assert(l2->size == (i + 1)); + } + assert(strcmp(l2->sentinel.next->value.string , STRINGS[0]) == 0); + assert(strcmp(l2->sentinel.next->next->value.string , STRINGS[1]) == 0); + assert(strcmp(l2->sentinel.next->next->next->value.string, STRINGS[2]) == 0); + assert(strcmp(l2->sentinel.prev->value.string , STRINGS[2]) == 0); + assert(strcmp(l2->sentinel.prev->prev->value.string , STRINGS[1]) == 0); + assert(strcmp(l2->sentinel.prev->prev->prev->value.string, STRINGS[0]) == 0); + list_delete(l2, true); + return EXIT_SUCCESS; +} + +int test_03_list_pop() { + List *l1 = list_create(); + assert(l1); + for (size_t i = 0; NUMBERS[i] >= 0; i++) { + list_append(l1, (Value)NUMBERS[i]); + assert(l1->size == (i + 1)); + } + + Value v1 = list_pop(l1, 1); + assert(v1.number == NUMBERS[1]); + assert(l1->sentinel.next->value.number == NUMBERS[0]); + assert(l1->sentinel.next->next->value.number == NUMBERS[2]); + assert(l1->sentinel.prev->value.number == NUMBERS[2]); + assert(l1->sentinel.prev->prev->value.number == NUMBERS[0]); + assert(l1->size == 2); + + Value v2 = list_pop(l1, 1); + assert(v2.number == NUMBERS[2]); + assert(l1->sentinel.next->value.number == NUMBERS[0]); + assert(l1->sentinel.prev->value.number == NUMBERS[0]); + assert(l1->size == 1); + + Value vX= list_pop(l1, 1); + assert(vX.number == -1L); + + Value v0 = list_pop(l1, 0); + assert(v0.number == NUMBERS[0]); + assert(l1->size == 0); + + list_delete(l1, false); + 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 list_create\n"); + fprintf(stderr, " 1 Test list_delete\n"); + fprintf(stderr, " 2 Test list_append\n"); + fprintf(stderr, " 3 Test list_pop\n"); + return EXIT_FAILURE; + } + + int number = atoi(argv[1]); + int status = EXIT_FAILURE; + + switch (number) { + case 0: status = test_00_list_create(); break; + case 1: status = test_01_list_delete(); break; + case 2: status = test_02_list_append(); break; + case 3: status = test_03_list_pop(); 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/homework07/node.c b/homework07/node.c new file mode 100644 index 0000000..bbcc8d2 --- /dev/null +++ b/homework07/node.c @@ -0,0 +1,31 @@ +/* node.c: Node Structure */ + +#include "list.h" + +/* Node Functions */ + +/** + * Create a Node structure. + * + * @param v Value (Number or String). + * @param next Pointer to next Node structure. + * @param prev Pointer to previous Node structure. + * + * @return Pointer to new Node structure (must be deleted later). + **/ +Node * node_create(Value v, Node *next, Node *prev) { + // TODO + return NULL; +} + +/** + * Delete Node structure and its contents. + * + * @param n Pointer to Node structure. + * @param release Whether or not to free the string value. + **/ +void node_delete(Node *n, bool release) { + // TODO +} + +/* vim: set sts=4 sw=4 ts=8 expandtab ft=c: */ diff --git a/homework07/node.unit.c b/homework07/node.unit.c new file mode 100644 index 0000000..7f0bfcc --- /dev/null +++ b/homework07/node.unit.c @@ -0,0 +1,83 @@ +/* node.unit.c: Node Structure Unit Test */ + +#include "list.h" + +#include +#include +#include +#include +#include + +/* Tests */ + +int test_00_node_create() { + Value v0 = {.number = 42}; + Node *n0 = node_create(v0, NULL, NULL); + assert(n0); + assert(n0->value.number == v0.number); + assert(n0->next == NULL); + assert(n0->prev == NULL); + + Value v1 = {.string = "better now"}; + Node *n1 = node_create(v1, n0, NULL); + assert(n1); + assert(n1->value.string == v1.string); + assert(n1->next == n0); + assert(n1->prev == NULL); + + Node *n2 = node_create((Value)strdup(v1.string), n0, n1); + assert(n2); + assert(strcmp(n2->value.string, v1.string) == 0); + assert(n2->next == n0); + assert(n2->prev == n1); + + free(n0); + free(n1); + free(n2->value.string); + free(n2); + return EXIT_SUCCESS; +} + +int test_01_node_delete() { + Value v0 = {.number = 42}; + Node *n0 = node_create(v0, NULL, NULL); + assert(n0); + + Value v1 = {.string = "better now"}; + Node *n1 = node_create(v1, n0, NULL); + assert(n1); + + Node *n2 = node_create((Value)strdup(v1.string), n0, n1); + assert(n2); + + node_delete(n0, false); + node_delete(n1, false); + node_delete(n2, 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"); + 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; + 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/homework07/seqit.c b/homework07/seqit.c new file mode 100644 index 0000000..4842f3f --- /dev/null +++ b/homework07/seqit.c @@ -0,0 +1,34 @@ +/* seqit.c: Print a sequence of numbers */ + +#include "list.h" + +#include +#include +#include + +/* Functions */ + +void usage(int status) { + fprintf(stderr, "Usage: seqit LAST\n"); + fprintf(stderr, " seqit FIRST LAST\n"); + fprintf(stderr, " seqit FIRST INCREMENT LAST\n"); + exit(status); +} + +List *generate_sequence(ssize_t first, ssize_t increment, ssize_t last) { + // TODO + return NULL; +} + +/* Main Execution */ + +int main(int argc, char *argv[]) { + // TODO: Parse command line arguments + + // TODO: Generate sequence + + // TODO: Print out sequence + return EXIT_SUCCESS; +} + +/* vim: set sts=4 sw=4 ts=8 expandtab ft=c: */ diff --git a/homework07/tailit.c b/homework07/tailit.c new file mode 100644 index 0000000..59cc4de --- /dev/null +++ b/homework07/tailit.c @@ -0,0 +1,34 @@ +/* tailit.c: Output the last part of files */ + +#include "list.h" + +#include +#include +#include +#include + +/* Functions */ + +void usage(int status) { + fprintf(stderr, "Usage: tailit [-n NUMBER]\n\n"); + fprintf(stderr, " -n NUMBER Output the last NUMBER of lines (default is 10)\n"); + exit(status); +} + +List *tail_stream(FILE *stream, size_t limit) { + // TODO + return NULL; +} + +/* Main Execution */ + +int main(int argc, char *argv[]) { + // TODO: Parse command line arguments + + // TODO: Construct tail of stream + + // TODO: Print out tail + return EXIT_SUCCESS; +} + +/* vim: set sts=4 sw=4 ts=8 expandtab ft=c: */ From 1af946b63a0f02e127a02d309a465b46af24ab3c Mon Sep 17 00:00:00 2001 From: Owen Dorweiler Date: Sat, 29 Mar 2025 17:05:05 -0400 Subject: [PATCH 05/10] Homework 07: Makefile --- homework07/Makefile | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/homework07/Makefile b/homework07/Makefile index d86360b..d85917a 100644 --- a/homework07/Makefile +++ b/homework07/Makefile @@ -10,17 +10,23 @@ all: $(TARGETS) # TODO: Add rules for node.o, list.o, seqit.o, tailit.o, seqit, tailit #-------------------------------------------------------------------------------- -node.o: +node.o: node.c + $(CC) $(CFLAGS) -c node.c -list.o: +list.o: list.c + $(CC) $(CFLAGS) -c list.c -seqit.o: +seqit.o: seqit.c + $(CC) $(CFLAGS) -c seqit.c -tailit.o: +tailit.o: tailit.c + $(CC) $(CFLAGS) -c tailit.c -seqit: +seqit: seqit.o list.o node.o + $(LD) $(LDFLAGS) -o seqit seqit.o list.o node.o -tailit: +tailit: tailit.o list.o node.o + $(LD) $(LDFLAGS) -o tailit tailit.o list.o node.o #------------------------------------------------------------------------------- # DO NOT MODIFY BELOW From 03a4d18968a1440ec66ec26168f90dde333aaf16 Mon Sep 17 00:00:00 2001 From: Owen Dorweiler Date: Sat, 29 Mar 2025 17:05:18 -0400 Subject: [PATCH 06/10] Homework 07: node.c --- homework07/node.c | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/homework07/node.c b/homework07/node.c index bbcc8d2..7f04932 100644 --- a/homework07/node.c +++ b/homework07/node.c @@ -14,8 +14,13 @@ * @return Pointer to new Node structure (must be deleted later). **/ Node * node_create(Value v, Node *next, Node *prev) { - // TODO - return NULL; + Node *new_node = calloc(1, sizeof(Node)); + + new_node->value = v; + new_node->next = next; + new_node->prev = prev; + + return new_node; } /** @@ -25,7 +30,11 @@ Node * node_create(Value v, Node *next, Node *prev) { * @param release Whether or not to free the string value. **/ void node_delete(Node *n, bool release) { - // TODO + if (release) { + free(n->value.string); + } + + free(n); } /* vim: set sts=4 sw=4 ts=8 expandtab ft=c: */ From dfbd984e0a0d3b4513549d116fa6a737b0aa325a Mon Sep 17 00:00:00 2001 From: Owen Dorweiler Date: Sat, 29 Mar 2025 17:05:36 -0400 Subject: [PATCH 07/10] Homework 07: list.c --- homework07/list.c | 47 ++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 40 insertions(+), 7 deletions(-) diff --git a/homework07/list.c b/homework07/list.c index 303c7e3..e56dd6e 100644 --- a/homework07/list.c +++ b/homework07/list.c @@ -10,8 +10,12 @@ * @return Pointer to new List structure (must be deleted later). **/ List * list_create() { - // TODO - return NULL; + List *newList = calloc(1, sizeof(List)); // Use calloc to set everything to 0 + + newList->sentinel.next = &newList->sentinel; + newList->sentinel.prev = &newList->sentinel; + + return newList; } /** @@ -21,7 +25,15 @@ List * list_create() { * @param release Whether or not to release the string values. **/ void list_delete(List *l, bool release) { - // TODO + Node *current = l->sentinel.next; // Start with first node after sentinel node + + while (current != &l->sentinel) { + Node *next = current->next; + node_delete(current, release); // Call node_delete and pass in release variable + current = next; + } + + free(l); } /** @@ -31,7 +43,11 @@ void list_delete(List *l, bool release) { * @param v Value to add to back of List structure. **/ void list_append(List *l, Value v) { - // TODO + Node *appNode = node_create(v, &l->sentinel, l->sentinel.prev); + + l->sentinel.prev->next = appNode; + l->sentinel.prev = appNode; + l->size++; } /** @@ -42,9 +58,26 @@ void list_append(List *l, Value v) { * * @return Value at index in List structure (-1 if index is out of bounds). **/ -Value list_pop(List *l, size_t index) { - // TODO - return (Value)-1L; +Value list_pop(List *l, size_t index) { + if (index >= l->size) { + return (Value)-1L; + } + + // Find the node based on index + Node *current = l->sentinel.next; + for (size_t i = 0; i < index; i++) { + current = current->next; + } + + // Unlink the node + current->prev->next = current->next; + current->next->prev = current->prev; + + Value v = current->value; + node_delete(current, false); + l->size--; + + return v; } /* vim: set sts=4 sw=4 ts=8 expandtab ft=c: */ From ebc75db9d8a01891b2c0fcb0d9e7ee1d1c7f1087 Mon Sep 17 00:00:00 2001 From: Owen Dorweiler Date: Sat, 29 Mar 2025 17:05:45 -0400 Subject: [PATCH 08/10] Homework 07: seqit.c --- homework07/seqit.c | 44 ++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 42 insertions(+), 2 deletions(-) diff --git a/homework07/seqit.c b/homework07/seqit.c index 4842f3f..418ca61 100644 --- a/homework07/seqit.c +++ b/homework07/seqit.c @@ -16,18 +16,58 @@ void usage(int status) { } List *generate_sequence(ssize_t first, ssize_t increment, ssize_t last) { - // TODO - return NULL; + List* sequence = list_create(); + + ssize_t current = first; + if (increment > 0) { + while (current <= last) { + list_append(sequence, (Value){.number = current}); + current += increment; + } + } else { + while (current >= last) { + list_append(sequence, (Value){.number = current}); + current += increment; + } + } + + return sequence; } /* Main Execution */ int main(int argc, char *argv[]) { // TODO: Parse command line arguments + ssize_t first = 1, increment = 1, last = 1; // Set initial values + + switch (argc) { + case 2: + last = atoll(argv[1]); + break; + case 3: + first = atoll(argv[1]); + last = atoll(argv[2]); + break; + case 4: + first = atoll(argv[1]); + increment = atoll(argv[2]); + last = atoll(argv[3]); + break; + default: + usage(EXIT_FAILURE); + } // TODO: Generate sequence + List *sequence = generate_sequence(first, increment, last); // TODO: Print out sequence + while (sequence->size > 0) { + Value v = list_pop(sequence, 0); + printf("%ld\n", v.number); + } + + // Delete the list + list_delete(sequence, false); return EXIT_SUCCESS; } From 59dd4ecc7d827d95c44345d6ad21eb934d69d4bf Mon Sep 17 00:00:00 2001 From: Owen Dorweiler Date: Sat, 29 Mar 2025 17:05:56 -0400 Subject: [PATCH 09/10] Homework 07: tailit.c --- homework07/tailit.c | 42 ++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 40 insertions(+), 2 deletions(-) diff --git a/homework07/tailit.c b/homework07/tailit.c index 59cc4de..1b1025b 100644 --- a/homework07/tailit.c +++ b/homework07/tailit.c @@ -16,18 +16,56 @@ void usage(int status) { } List *tail_stream(FILE *stream, size_t limit) { - // TODO - return NULL; + List *sequence = list_create(); + char buffer[BUFSIZ]; + + while (fgets(buffer, sizeof(buffer), stream) != NULL) { + char *line = strdup(buffer); + list_append(sequence, (Value){.string = line}); + + // If over limit, remove the oldest line (head) + if (sequence->size > limit) { + Value old = list_pop(sequence, 0); + free(old.string); // Free the popped line's string + } + } + + return sequence; } /* Main Execution */ int main(int argc, char *argv[]) { + size_t limit = 10; // Set baseline limit + // TODO: Parse command line arguments + if (argc == 2) { + if (strcmp(argv[1], "-h") == 0) { + usage(EXIT_SUCCESS); + } else { + usage(EXIT_FAILURE); + } + } else if (argc == 3) { + if (strcmp(argv[1], "-n") == 0) { + limit = (size_t)atoi(argv[2]); + } else { + usage(EXIT_FAILURE); + } + } else if (argc > 3) { + usage(EXIT_FAILURE); + } // TODO: Construct tail of stream + List *lines = tail_stream(stdin, limit); // TODO: Print out tail + while (lines->size > 0) { + Value v = list_pop(lines, 0); + printf("%s", v.string); + free(v.string); + } + + list_delete(lines, false); return EXIT_SUCCESS; } From 25adb24a1681ac4a85bfdb4a449dc51788f0373b Mon Sep 17 00:00:00 2001 From: Owen Dorweiler Date: Sun, 14 Dec 2025 17:20:04 -0500 Subject: [PATCH 10/10] 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: */