Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 25adb24a16 | |||
| 59dd4ecc7d | |||
| ebc75db9d8 | |||
| dfbd984e0a | |||
| 03a4d18968 | |||
| 1af946b63a | |||
| 1a1cb9f122 | |||
| 1432c00d54 | |||
| 8915ce7735 | |||
| bc2a9bfb81 |
28 changed files with 2440 additions and 39 deletions
|
|
@ -13,21 +13,27 @@ all: $(TARGETS)
|
||||||
# TODO: Add rules for libstr.a libstr.so
|
# 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
|
# 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
|
# DO NOT MODIFY BELOW
|
||||||
|
|
|
||||||
105
homework06/str.c
105
homework06/str.c
|
|
@ -5,6 +5,7 @@
|
||||||
#include <ctype.h>
|
#include <ctype.h>
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
/* Functions */
|
/* Functions */
|
||||||
|
|
||||||
|
|
@ -14,7 +15,12 @@
|
||||||
* @param w Pointer to buffer that holds result of conversion
|
* @param w Pointer to buffer that holds result of conversion
|
||||||
**/
|
**/
|
||||||
void str_lower(const char *s, char *w) {
|
void str_lower(const char *s, char *w) {
|
||||||
// TODO
|
while (*s) {
|
||||||
|
*w = tolower(*s);
|
||||||
|
s++;
|
||||||
|
w++;
|
||||||
|
}
|
||||||
|
*w = '\0';
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -23,7 +29,12 @@ void str_lower(const char *s, char *w) {
|
||||||
* @param w Pointer to buffer that holds result of conversion
|
* @param w Pointer to buffer that holds result of conversion
|
||||||
**/
|
**/
|
||||||
void str_upper(const char *s, char *w) {
|
void str_upper(const char *s, char *w) {
|
||||||
// TODO
|
while (*s) {
|
||||||
|
*w = toupper(*s);
|
||||||
|
s++;
|
||||||
|
w++;
|
||||||
|
}
|
||||||
|
*w = '\0';
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -32,17 +43,51 @@ void str_upper(const char *s, char *w) {
|
||||||
* @param w Pointer to buffer that holds result of conversion
|
* @param w Pointer to buffer that holds result of conversion
|
||||||
**/
|
**/
|
||||||
void str_title(const char *s, char *w) {
|
void str_title(const char *s, char *w) {
|
||||||
// TODO
|
if (*s) {
|
||||||
|
*w = toupper(*s);
|
||||||
|
w++;
|
||||||
|
s++;
|
||||||
|
while (*s) {
|
||||||
|
if (!isalpha(*(s - 1))) {
|
||||||
|
*w = toupper(*s);
|
||||||
|
} else {
|
||||||
|
*w = tolower(*s);
|
||||||
|
}
|
||||||
|
s++;
|
||||||
|
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) {
|
void str_rstrip(const char *s, const char *chars, char *w) {
|
||||||
// TODO
|
// Safety checks
|
||||||
|
if (!s || !w) return;
|
||||||
|
|
||||||
|
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 {
|
||||||
|
for (; *chars; chars++)
|
||||||
|
strip_chars[(unsigned char)*chars] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find string length and last non-stripped character
|
||||||
|
while (*p) p++;
|
||||||
|
while (p > (const unsigned char *)s && strip_chars[*(p-1)])
|
||||||
|
p--;
|
||||||
|
|
||||||
|
// Calculate length and copy
|
||||||
|
length = p - (const unsigned char *)s;
|
||||||
|
memcpy(w, s, length);
|
||||||
|
w[length] = '\0';
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -52,7 +97,23 @@ void str_rstrip(const char *s, const char *chars, char *w) {
|
||||||
* @param w Pointer to buffer that holds result of deletion
|
* @param w Pointer to buffer that holds result of deletion
|
||||||
**/
|
**/
|
||||||
void str_delete(const char *s, const char *chars, char *w) {
|
void str_delete(const char *s, const char *chars, char *w) {
|
||||||
// TODO
|
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';
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -63,7 +124,27 @@ void str_delete(const char *s, const char *chars, char *w) {
|
||||||
* @param w Pointer to buffer that holds result of translation
|
* @param w Pointer to buffer that holds result of translation
|
||||||
**/
|
**/
|
||||||
void str_translate(const char *s, const char *from, const char *to, char *w) {
|
void str_translate(const char *s, const char *from, const char *to, char *w) {
|
||||||
// TODO
|
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: */
|
/* vim: set sts=4 sw=4 ts=8 expandtab ft=c: */
|
||||||
|
|
|
||||||
|
|
@ -5,15 +5,16 @@
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
#include <ctype.h> // For isspace
|
||||||
|
|
||||||
/* Constants */
|
/* Constants */
|
||||||
|
|
||||||
enum {
|
enum {
|
||||||
LOWER = 1<<1,
|
LOWER = 1<<1,
|
||||||
UPPER = 0, // TODO: Modify
|
UPPER = 1<<2,
|
||||||
TITLE = 0, // TODO: Modify
|
TITLE = 1<<3,
|
||||||
STRIP = 0, // TODO: Modify
|
STRIP = 1<<4,
|
||||||
DELETE = 0, // TODO: Modify
|
DELETE = 1<<5,
|
||||||
};
|
};
|
||||||
|
|
||||||
/* Functions */
|
/* Functions */
|
||||||
|
|
@ -30,17 +31,91 @@ void usage(int status) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void translate_stream(FILE *stream, const char *set1, const char *set2, int flags) {
|
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 */
|
/* Main Execution */
|
||||||
|
|
||||||
int main(int argc, char *argv[]) {
|
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
|
for (int i = 1; i < argc; i++) {
|
||||||
return EXIT_SUCCESS;
|
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: */
|
/* vim: set sts=4 sw=4 ts=8 expandtab ft=c: */
|
||||||
|
|
||||||
|
|
|
||||||
72
homework07/Makefile
Normal file
72
homework07/Makefile
Normal file
|
|
@ -0,0 +1,72 @@
|
||||||
|
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: node.c
|
||||||
|
$(CC) $(CFLAGS) -c node.c
|
||||||
|
|
||||||
|
list.o: list.c
|
||||||
|
$(CC) $(CFLAGS) -c list.c
|
||||||
|
|
||||||
|
seqit.o: seqit.c
|
||||||
|
$(CC) $(CFLAGS) -c seqit.c
|
||||||
|
|
||||||
|
tailit.o: tailit.c
|
||||||
|
$(CC) $(CFLAGS) -c tailit.c
|
||||||
|
|
||||||
|
seqit: seqit.o list.o node.o
|
||||||
|
$(LD) $(LDFLAGS) -o seqit seqit.o list.o node.o
|
||||||
|
|
||||||
|
tailit: tailit.o list.o node.o
|
||||||
|
$(LD) $(LDFLAGS) -o tailit tailit.o list.o node.o
|
||||||
|
|
||||||
|
#-------------------------------------------------------------------------------
|
||||||
|
# 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
|
||||||
83
homework07/list.c
Normal file
83
homework07/list.c
Normal file
|
|
@ -0,0 +1,83 @@
|
||||||
|
/* 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() {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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) {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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) {
|
||||||
|
Node *appNode = node_create(v, &l->sentinel, l->sentinel.prev);
|
||||||
|
|
||||||
|
l->sentinel.prev->next = appNode;
|
||||||
|
l->sentinel.prev = appNode;
|
||||||
|
l->size++;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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) {
|
||||||
|
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: */
|
||||||
41
homework07/list.h
Normal file
41
homework07/list.h
Normal file
|
|
@ -0,0 +1,41 @@
|
||||||
|
/* list.h: Doubly Linked List Library */
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
/* 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: */
|
||||||
143
homework07/list.unit.c
Normal file
143
homework07/list.unit.c
Normal file
|
|
@ -0,0 +1,143 @@
|
||||||
|
/* list.unit.c: List Structure Unit Test */
|
||||||
|
|
||||||
|
#include "list.h"
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
/* 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: */
|
||||||
40
homework07/node.c
Normal file
40
homework07/node.c
Normal file
|
|
@ -0,0 +1,40 @@
|
||||||
|
/* 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) {
|
||||||
|
Node *new_node = calloc(1, sizeof(Node));
|
||||||
|
|
||||||
|
new_node->value = v;
|
||||||
|
new_node->next = next;
|
||||||
|
new_node->prev = prev;
|
||||||
|
|
||||||
|
return new_node;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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) {
|
||||||
|
if (release) {
|
||||||
|
free(n->value.string);
|
||||||
|
}
|
||||||
|
|
||||||
|
free(n);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* vim: set sts=4 sw=4 ts=8 expandtab ft=c: */
|
||||||
83
homework07/node.unit.c
Normal file
83
homework07/node.unit.c
Normal file
|
|
@ -0,0 +1,83 @@
|
||||||
|
/* node.unit.c: Node Structure Unit Test */
|
||||||
|
|
||||||
|
#include "list.h"
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
/* 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: */
|
||||||
74
homework07/seqit.c
Normal file
74
homework07/seqit.c
Normal file
|
|
@ -0,0 +1,74 @@
|
||||||
|
/* seqit.c: Print a sequence of numbers */
|
||||||
|
|
||||||
|
#include "list.h"
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
/* 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) {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* vim: set sts=4 sw=4 ts=8 expandtab ft=c: */
|
||||||
72
homework07/tailit.c
Normal file
72
homework07/tailit.c
Normal file
|
|
@ -0,0 +1,72 @@
|
||||||
|
/* tailit.c: Output the last part of files */
|
||||||
|
|
||||||
|
#include "list.h"
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
/* 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) {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* vim: set sts=4 sw=4 ts=8 expandtab ft=c: */
|
||||||
72
homework08/Makefile
Normal file
72
homework08/Makefile
Normal file
|
|
@ -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
|
||||||
75
homework08/filter.c
Normal file
75
homework08/filter.c
Normal file
|
|
@ -0,0 +1,75 @@
|
||||||
|
/* filter.c: Filter functions */
|
||||||
|
|
||||||
|
#include "findit.h"
|
||||||
|
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include <fnmatch.h>
|
||||||
|
#include <libgen.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
/* 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: */
|
||||||
112
homework08/filter.unit.c
Normal file
112
homework08/filter.unit.c
Normal file
|
|
@ -0,0 +1,112 @@
|
||||||
|
/* filter.unit.c: filter unit test */
|
||||||
|
|
||||||
|
#include "findit.h"
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
/* 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: */
|
||||||
BIN
homework08/findit
Executable file
BIN
homework08/findit
Executable file
Binary file not shown.
202
homework08/findit.c
Normal file
202
homework08/findit.c
Normal file
|
|
@ -0,0 +1,202 @@
|
||||||
|
/* findit.c: Search for files in a directory hierarchy */
|
||||||
|
|
||||||
|
#include "findit.h"
|
||||||
|
|
||||||
|
#include <dirent.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
/* 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: */
|
||||||
229
homework08/findit.test.sh
Executable file
229
homework08/findit.test.sh
Executable file
|
|
@ -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
|
||||||
121
homework08/list.c
Normal file
121
homework08/list.c
Normal file
|
|
@ -0,0 +1,121 @@
|
||||||
|
/* list.c: Singly Linked List */
|
||||||
|
|
||||||
|
#include "findit.h"
|
||||||
|
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
/* 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: */
|
||||||
205
homework08/list.unit.c
Normal file
205
homework08/list.unit.c
Normal file
|
|
@ -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: */
|
||||||
4
homework09/.gitignore
vendored
Normal file
4
homework09/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
curlit
|
||||||
|
*.o
|
||||||
|
*.sh
|
||||||
|
*.unit
|
||||||
63
homework09/Makefile
Normal file
63
homework09/Makefile
Normal file
|
|
@ -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
|
||||||
187
homework09/curlit.c
Normal file
187
homework09/curlit.c
Normal file
|
|
@ -0,0 +1,187 @@
|
||||||
|
/* curlit.c: Simple HTTP client*/
|
||||||
|
|
||||||
|
#include "socket.h"
|
||||||
|
|
||||||
|
#include <limits.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <time.h>
|
||||||
|
|
||||||
|
#include <netdb.h>
|
||||||
|
|
||||||
|
/* 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: */
|
||||||
70
homework09/socket.c
Normal file
70
homework09/socket.c
Normal file
|
|
@ -0,0 +1,70 @@
|
||||||
|
/* socket.c: TCP Socket Functions */
|
||||||
|
|
||||||
|
#include "socket.h"
|
||||||
|
|
||||||
|
// I commented out the unneeded libraries below
|
||||||
|
// #include <errno.h>
|
||||||
|
// #include <stdlib.h>
|
||||||
|
// #include <string.h>
|
||||||
|
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <netdb.h>
|
||||||
|
#include <netinet/in.h>
|
||||||
|
#include <sys/socket.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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: */
|
||||||
11
homework09/socket.h
Normal file
11
homework09/socket.h
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
/* socket.h */
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
/* Functions */
|
||||||
|
|
||||||
|
FILE * socket_dial(const char *host, const char *port);
|
||||||
|
|
||||||
|
/* vim: set sts=4 sw=4 ts=8 expandtab ft=c: */
|
||||||
91
homework09/socket.unit.c
Normal file
91
homework09/socket.unit.c
Normal file
|
|
@ -0,0 +1,91 @@
|
||||||
|
/* socket.unit.c: Socket unit test */
|
||||||
|
|
||||||
|
#include "socket.h"
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
/* 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: */
|
||||||
183
homework09/timeit.c
Normal file
183
homework09/timeit.c
Normal file
|
|
@ -0,0 +1,183 @@
|
||||||
|
/* timeit.c: Run command with a time limit */
|
||||||
|
|
||||||
|
#include <errno.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <time.h>
|
||||||
|
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <sys/time.h>
|
||||||
|
#include <sys/wait.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
/* 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: */
|
||||||
BIN
reading08/grep
Executable file
BIN
reading08/grep
Executable file
Binary file not shown.
6
reading08/grep.c
Normal file
6
reading08/grep.c
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
int main(int argc, char *argv[]) {
|
||||||
|
// Your pattern matching implementation
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue