diff --git a/2-StudentDB/starter/a2-questions.md b/2-StudentDB/starter/a2-questions.md
new file mode 100644
index 0000000000000000000000000000000000000000..572ff6d5007861f4316837962c76584b49ad8b11
--- /dev/null
+++ b/2-StudentDB/starter/a2-questions.md
@@ -0,0 +1,122 @@
+## Assignment 2 Questions
+
+#### Directions
+Please answer the following questions and submit in your repo for the second assignment. Please keep the answers as short and concise as possible.
+
+1. In this assignment I asked you provide an implementation for the `get_student(...)` function because I think it improves the overall design of the database application. After you implemented your solution do you agree that externalizing `get_student(...)` into it's own function is a good design strategy? Briefly describe why or why not.
+
+ > **Answer**: It's a good design strategy because it'll only focus on getting a student's record. also simplify testing being able to focus on correctness.
+
+2. Another interesting aspect of the `get_student(...)` function is how its function prototype requires the caller to provide the storage for the `student_t` structure:
+
+ ```c
+ int get_student(int fd, int id, student_t *s);
+ ```
+
+ Notice that the last parameter is a pointer to storage **provided by the caller** to be used by this function to populate information about the desired student that is queried from the database file. This is a common convention (called pass-by-reference) in the `C` programming language.
+
+ In other programming languages an approach like the one shown below would be more idiomatic for creating a function like `get_student()` (specifically the storage is provided by the `get_student(...)` function itself):
+
+ ```c
+ //Lookup student from the database
+ // IF FOUND: return pointer to student data
+ // IF NOT FOUND: return NULL
+ student_t *get_student(int fd, int id){
+ student_t student;
+ bool student_found = false;
+
+ //code that looks for the student and if
+ //found populates the student structure
+ //The found_student variable will be set
+ //to true if the student is in the database
+ //or false otherwise.
+
+ if (student_found)
+ return &student;
+ else
+ return NULL;
+ }
+ ```
+ Can you think of any reason why the above implementation would be a **very bad idea** using the C programming language? Specifically, address why the above code introduces a subtle bug that could be hard to identify at runtime?
+
+ > **ANSWER:** Yes, the subtle bug is returning a local variable. student is the local variable and uses temporary memory (stack), the prolem is when the function returns the memory is no longer safe to use and the memory is reused somewhere else so it can crash or provide wrong data.
+
+3. Another way the `get_student(...)` function could be implemented is as follows:
+
+ ```c
+ //Lookup student from the database
+ // IF FOUND: return pointer to student data
+ // IF NOT FOUND or memory allocation error: return NULL
+ student_t *get_student(int fd, int id){
+ student_t *pstudent;
+ bool student_found = false;
+
+ pstudent = malloc(sizeof(student_t));
+ if (pstudent == NULL)
+ return NULL;
+
+ //code that looks for the student and if
+ //found populates the student structure
+ //The found_student variable will be set
+ //to true if the student is in the database
+ //or false otherwise.
+
+ if (student_found){
+ return pstudent;
+ }
+ else {
+ free(pstudent);
+ return NULL;
+ }
+ }
+ ```
+ In this implementation the storage for the student record is allocated on the heap using `malloc()` and passed back to the caller when the function returns. What do you think about this alternative implementation of `get_student(...)`? Address in your answer why it work work, but also think about any potential problems it could cause.
+
+ > **ANSWER:** Unlike q2, this code does allocate memory on the heap using malloc() to make sure the returned data is valid after succesfully existing the function. If a student is not found, it returns NULL. The only problem I can think of is the remembering to free() the memory after using the student data so maybe consider only allocate memory when needed.
+
+
+4. Lets take a look at how storage is managed for our simple database. Recall that all student records are stored on disk using the layout of the `student_t` structure (which has a size of 64 bytes). Lets start with a fresh database by deleting the `student.db` file using the command `rm ./student.db`. Now that we have an empty database lets add a few students and see what is happening under the covers. Consider the following sequence of commands:
+
+ ```bash
+ > ./sdbsc -a 1 john doe 345
+ > ls -l ./student.db
+ -rw-r----- 1 bsm23 bsm23 128 Jan 17 10:01 ./student.db
+ > du -h ./student.db
+ 4.0K ./student.db
+ > ./sdbsc -a 3 jane doe 390
+ > ls -l ./student.db
+ -rw-r----- 1 bsm23 bsm23 256 Jan 17 10:02 ./student.db
+ > du -h ./student.db
+ 4.0K ./student.db
+ > ./sdbsc -a 63 jim doe 285
+ > du -h ./student.db
+ 4.0K ./student.db
+ > ./sdbsc -a 64 janet doe 310
+ > du -h ./student.db
+ 8.0K ./student.db
+ > ls -l ./student.db
+ -rw-r----- 1 bsm23 bsm23 4160 Jan 17 10:03 ./student.db
+ ```
+
+ For this question I am asking you to perform some online research to investigate why there is a difference between the size of the file reported by the `ls` command and the actual storage used on the disk reported by the `du` command. Understanding why this happens by design is important since all good systems programmers need to understand things like how linux creates sparse files, and how linux physically stores data on disk using fixed block sizes. Some good google searches to get you started: _"lseek syscall holes and sparse files"_, and _"linux file system blocks"_. After you do some research please answer the following:
+
+ - Please explain why the file size reported by the `ls` command was 128 bytes after adding student with ID=1, 256 after adding student with ID=3, and 4160 after adding the student with ID=64?
+
+ > **ANSWER:** The difference between ls and du is how they measure file size in Linux. ls shows the total file size, including empty gaps (holes), while du shows only the actual disk space used, which is smaller b/c it skips those empty gaps
+
+ - Why did the total storage used on the disk remain unchanged when we added the student with ID=1, ID=3, and ID=63, but increased from 4K to 8K when we added the student with ID=64?
+
+ > **ANSWER:** The total storage stayed the same b/c the filesystem was usiing a single block, and the data fit within that space. No extra disk space was needed. However, when ID=64 was added, the file size increased b/c of a sparse file, where gaps existed. The filesystem had to assign another block too store the new data.
+
+ - Now lets add one more student with a large student ID number and see what happens:
+
+ ```bash
+ > ./sdbsc -a 99999 big dude 205
+ > ls -l ./student.db
+ -rw-r----- 1 bsm23 bsm23 6400000 Jan 17 10:28 ./student.db
+ > du -h ./student.db
+ 12K ./student.db
+ ```
+ We see from above adding a student with a very large student ID (ID=99999) increased the file size to 6400000 as shown by `ls` but the raw storage only increased to 12K as reported by `du`. Can provide some insight into why this happened?
+
+ > **ANSWER:** Adding a student with a large ID creates a sparse file with a big gap between the previous records and the new one. Since each student_t record is 64 bytes, a student with ID 999999 makes the file appear nearly 6.4 million bytes in size
\ No newline at end of file
diff --git a/2-StudentDB/starter/db.h b/2-StudentDB/starter/db.h
new file mode 100644
index 0000000000000000000000000000000000000000..ac7e122c1610c8a5317c6d93bd8762ea1c3dafda
--- /dev/null
+++ b/2-StudentDB/starter/db.h
@@ -0,0 +1,35 @@
+#ifndef __DB_H__
+ #define __DB_H__
+
+// Basic student database record. Note:
+// 1. id must be > 0. A student id==0 means the record has been deleted
+// 2. gpa is an int, should be between 0<=gpa<=500, real gpa is gpa/100.0 this
+// simplifies dealing with floating point types
+// 3. Notice that the student struct was engineered to have a size of
+// 64 bytes. There are reasons for using such a number
+typedef struct student{
+ int id;
+ char fname[24];
+ char lname[32];
+ int gpa;
+} student_t;
+
+//Define limits for sudent ids and allowable GPA ranges. Note GPA values will
+//be stored as integers but printed as floats. For example a GPA of 450 is really
+//that value divided by 100.0 or 4.50.
+#define MIN_STD_ID 1
+#define MAX_STD_ID 100000
+#define MIN_STD_GPA 0
+#define MAX_STD_GPA 500
+
+//some useful constants you should consider using versus hard coding
+//in your program.
+static const student_t EMPTY_STUDENT_RECORD = {0};
+static const int STUDENT_RECORD_SIZE = sizeof(struct student);
+static const int DELETED_STUDENT_ID = 0;
+
+
+#define DB_FILE "student.db" //name of database file
+#define TMP_DB_FILE ".tmp_student.db" //for extra credit
+
+#endif
\ No newline at end of file
diff --git a/2-StudentDB/starter/dblayout.png b/2-StudentDB/starter/dblayout.png
new file mode 100644
index 0000000000000000000000000000000000000000..0aa1ba5aedd5556d13736f0836a36a9ba492626a
Binary files /dev/null and b/2-StudentDB/starter/dblayout.png differ
diff --git a/2-StudentDB/starter/makefile b/2-StudentDB/starter/makefile
new file mode 100644
index 0000000000000000000000000000000000000000..4f6101cc6661facd243dc13f121475c4096dfd5e
--- /dev/null
+++ b/2-StudentDB/starter/makefile
@@ -0,0 +1,28 @@
+# Compiler settings
+CC = gcc
+CFLAGS = -Wall -Wextra -g
+
+# Target executable name
+TARGET = sdbsc
+
+# Find all source and header files
+SRCS = $(wildcard *.c)
+HDRS = $(wildcard *.h)
+
+# Default target
+all: $(TARGET)
+
+# Compile source to executable
+$(TARGET): $(SRCS) $(HDRS)
+ $(CC) $(CFLAGS) -o $(TARGET) $(SRCS)
+
+# Clean up build files
+clean:
+ rm -f $(TARGET)
+ rm -f student.db
+
+test:
+ ./test.sh
+
+# Phony targets
+.PHONY: all clean
\ No newline at end of file
diff --git a/2-StudentDB/starter/sdbsc b/2-StudentDB/starter/sdbsc
new file mode 100755
index 0000000000000000000000000000000000000000..bb265d3b59a73231e08d894bd9c3fa43d03a52f1
Binary files /dev/null and b/2-StudentDB/starter/sdbsc differ
diff --git a/2-StudentDB/starter/sdbsc.c b/2-StudentDB/starter/sdbsc.c
new file mode 100644
index 0000000000000000000000000000000000000000..57397c2d9d68fc592345c8fb20a9cc47a3077c68
--- /dev/null
+++ b/2-StudentDB/starter/sdbsc.c
@@ -0,0 +1,713 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <fcntl.h> //c library for system call file routines
+#include <string.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <stdbool.h>
+
+// database include files
+#include "db.h"
+#include "sdbsc.h"
+
+/*
+ * open_db
+ * dbFile: name of the database file
+ * should_truncate: indicates if opening the file also empties it
+ *
+ * returns: File descriptor on success, or ERR_DB_FILE on failure
+ *
+ * console: Does not produce any console I/O on success
+ * M_ERR_DB_OPEN on error
+ *
+ */
+int open_db(char *dbFile, bool should_truncate)
+{
+ // Set permissions: rw-rw----
+ // see sys/stat.h for constants
+ mode_t mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP;
+
+ // open the file if it exists for Read and Write,
+ // create it if it does not exist
+ int flags = O_RDWR | O_CREAT;
+
+ if (should_truncate)
+ flags |= O_TRUNC;
+
+ // Now open file
+ int fd = open(dbFile, flags, mode);
+
+ if (fd == -1)
+ {
+ printf(M_ERR_DB_OPEN);
+ return ERR_DB_FILE;
+ }
+
+ return fd;
+}
+
+/*
+ * get_student
+ * fd: linux file descriptor
+ * id: the student id we are looking forname of the
+ * *s: a pointer where the located (if found) student data will be
+ * copied
+ *
+ * returns: NO_ERROR student located and copied into *s
+ * ERR_DB_FILE database file I/O issue
+ * SRCH_NOT_FOUND student was not located in the database
+ *
+ * console: Does not produce any console I/O used by other functions
+ */
+int get_student(int fd, int id, student_t *s) {
+ if (id < 1 || id > MAX_STD_ID) {
+ return SRCH_NOT_FOUND; //check to see if student id exist
+ }
+
+ ssize_t read_size;
+ //tells me the # of bytes
+ int seek_position = id * sizeof(student_t);
+
+ if (lseek(fd, seek_position, SEEK_SET) == -1) {
+ return ERR_DB_FILE;
+ }
+
+ read_size = read(fd, s, sizeof(student_t)); //if read() fails, return -1
+ if (read_size == -1) {
+ return ERR_DB_FILE;
+ }
+
+ return (read_size == sizeof(student_t) && memcmp(s, &EMPTY_STUDENT_RECORD, sizeof(student_t)) != 0) ? NO_ERROR : SRCH_NOT_FOUND;
+}
+
+/*
+ * add_student
+ * fd: linux file descriptor
+ * id: student id (range is defined in db.h )
+ * fname: student first name
+ * lname: student last name
+ * gpa: GPA as an integer (range defined in db.h)
+ *
+ * Adds a new student to the database. After calculating the index for the
+ * student, check if there is another student already at that location. A good
+ * way is to use something like memcmp() to ensure that the location for this
+ * student contains all zero byes indicating the space is empty.
+ *
+ * returns: NO_ERROR student added to database
+ * ERR_DB_FILE database file I/O issue
+ * ERR_DB_OP database operation logically failed (aka student
+ * already exists)
+ *
+ *
+ * console: M_STD_ADDED on success
+ * M_ERR_DB_ADD_DUP student already exists
+ * M_ERR_DB_READ error reading or seeking the database file
+ * M_ERR_DB_WRITE error writing to db file (adding student)
+ *
+ */
+int add_student(int fd, int id, char *fname, char *lname, int gpa) {
+ student_t student;
+ ssize_t byteCount;
+ int filePosition = id * sizeof(student_t);
+
+ if (lseek(fd, filePosition, SEEK_SET) == -1) {
+ printf(M_ERR_DB_READ);
+ return ERR_DB_FILE;
+ }
+
+ byteCount = read(fd, &student, sizeof(student_t));
+ if (byteCount == -1) {
+ printf(M_ERR_DB_READ);
+ return ERR_DB_FILE;
+ }
+
+ if (byteCount == sizeof(student_t) && memcmp(&student, &EMPTY_STUDENT_RECORD, sizeof(student_t)) != 0) {
+ printf(M_ERR_DB_ADD_DUP, id);
+ return ERR_DB_OP;
+ }
+
+ student.id = id;
+ strncpy(student.fname, fname, sizeof(student.fname) - 1);
+ student.fname[sizeof(student.fname) - 1] = '\0';
+ strncpy(student.lname, lname, sizeof(student.lname) - 1);
+ student.lname[sizeof(student.lname) - 1] = '\0';
+ student.gpa = gpa;
+
+ //is the file large enough?
+ struct stat st;
+ if (fstat(fd, &st) == 0) { //calcualtess the target file size
+ off_t targetSize = (id + 1) * sizeof(student_t);
+ if (st.st_size < targetSize) {
+ if (ftruncate(fd, targetSize) == -1) {
+ printf(M_ERR_DB_WRITE);
+ return ERR_DB_FILE;
+ }
+ }
+ } //reset the files position
+ if (lseek(fd, filePosition, SEEK_SET) == -1) {
+ printf(M_ERR_DB_WRITE);
+ return ERR_DB_FILE;
+ }
+
+ if (write(fd, &student, sizeof(student_t)) != sizeof(student_t)) {
+ printf(M_ERR_DB_WRITE);
+ return ERR_DB_FILE;
+ }
+
+ fsync(fd); //not sure if neeeded but recommended if dealing with records
+
+ printf(M_STD_ADDED, id);
+ return NO_ERROR;
+}
+
+
+/*
+ * del_student
+ * fd: linux file descriptor
+ * id: student id to be deleted
+ *
+ * Removes a student to the database. Use the get_student() function to
+ * locate the student to be deleted. If there is a student at that location
+ * write an empty student record - see EMPTY_STUDENT_RECORD from db.h at
+ * that location.
+ *
+ * returns: NO_ERROR student deleted from database
+ * ERR_DB_FILE database file I/O issue
+ * ERR_DB_OP database operation logically failed (aka student
+ * not in database)
+ *
+ *
+ * console: M_STD_DEL_MSG on success
+ * M_STD_NOT_FND_MSG student not in database, cant be deleted
+ * M_ERR_DB_READ error reading or seeking the database file
+ * M_ERR_DB_WRITE error writing to db file (adding student)
+ *
+ */
+int del_student(int fd, int id) {
+ student_t student;
+ ssize_t byteCount;
+ int filePosition = id * sizeof(student_t);
+
+ if (lseek(fd, filePosition, SEEK_SET) == -1) {
+ printf(M_ERR_DB_READ);
+ return ERR_DB_FILE;
+ }
+
+ byteCount = read(fd, &student, sizeof(student_t));
+ if (byteCount == -1) {
+ printf(M_ERR_DB_READ);
+ return ERR_DB_FILE;
+ }
+
+ if (byteCount != sizeof(student_t) || memcmp(&student, &EMPTY_STUDENT_RECORD, sizeof(student_t)) == 0) {
+ printf(M_STD_NOT_FND_MSG, id);
+ return ERR_DB_OP;
+ }
+
+ if (lseek(fd, filePosition, SEEK_SET) == -1) {
+ printf(M_ERR_DB_WRITE);
+ return ERR_DB_FILE;
+ }
+
+ student_t empty_student = EMPTY_STUDENT_RECORD;
+ if (write(fd, &empty_student, sizeof(student_t)) != sizeof(student_t)) {
+ printf(M_ERR_DB_WRITE);
+ return ERR_DB_FILE;
+ }
+
+ printf(M_STD_DEL_MSG, id);
+ return NO_ERROR;
+}
+
+/*
+ * count_db_records
+ * fd: linux file descriptor
+ *
+ * Counts the number of records in the database. Start by reading the
+ * database at the beginning, and continue reading individual records
+ * until you it EOF. EOF is when the read() syscall returns 0. Check
+ * if a slot is empty or previously deleted by investigating if all of
+ * the bytes in the record read are zeros - I would suggest using memory
+ * compare memcmp() for this. Create a counter variable and initialize it
+ * to zero, every time a non-zero record is read increment the counter.
+ *
+ * returns: <number> returns the number of records in db on success
+ * ERR_DB_FILE database file I/O issue
+ * ERR_DB_OP database operation logically failed (aka student
+ * not in database)
+ *
+ *
+ * console: M_DB_RECORD_CNT on success, to report the number of students in db
+ * M_DB_EMPTY on success if the record count in db is zero
+ * M_ERR_DB_READ error reading or seeking the database file
+ * M_ERR_DB_WRITE error writing to db file (adding student)
+ *
+ */
+// int count_db_records(int fd)
+// {
+// // TODO
+// printf(M_NOT_IMPL);
+// return NOT_IMPLEMENTED_YET;
+// }
+
+int count_db_records(int fd) {
+ student_t student;
+ ssize_t byteCount;
+ int record_count = 0;
+
+ if (lseek(fd, 0, SEEK_SET) == -1) {
+ printf(M_ERR_DB_READ);
+ return ERR_DB_FILE;
+ }
+
+ while ((byteCount = read(fd, &student, sizeof(student_t))) == sizeof(student_t)) {
+ if (memcmp(&student, &EMPTY_STUDENT_RECORD, sizeof(student_t)) != 0) {
+ record_count++;
+ }
+ }
+ if (byteCount == -1) {
+ printf(M_ERR_DB_READ);
+ return ERR_DB_FILE;
+ }
+ if (record_count == 0) {
+ printf("%s\n", M_DB_EMPTY);
+ } else {
+ printf(M_DB_RECORD_CNT, record_count);
+ }
+
+ return record_count;
+}
+
+
+/*
+ * print_db
+ * fd: linux file descriptor
+ *
+ * Prints all records in the database. Start by reading the
+ * database at the beginning, and continue reading individual records
+ * until you it EOF. EOF is when the read() syscall returns 0. Check
+ * if a slot is empty or previously deleted by investigating if all of
+ * the bytes in the record read are zeros - I would suggest using memory
+ * compare memcmp() for this. Be careful as the database might be empty.
+ * on the first real row encountered print the header for the required output:
+ *
+ * printf(STUDENT_PRINT_HDR_STRING, "ID",
+ * "FIRST_NAME", "LAST_NAME", "GPA");
+ *
+ * then for each valid record encountered print the required output:
+ *
+ * printf(STUDENT_PRINT_FMT_STRING, student.id, student.fname,
+ * student.lname, calculated_gpa_from_student);
+ *
+ * The code above assumes you are reading student records into a local
+ * variable named student that is of type student_t. Also dont forget that
+ * the GPA in the student structure is an int, to convert it into a real
+ * gpa divide by 100.0 and store in a float variable.
+ *
+ * returns: NO_ERROR on success
+ * ERR_DB_FILE database file I/O issue
+ *
+ *
+ * console: <see above> on success, print table or database empty
+ * M_ERR_DB_READ error reading or seeking the database file
+ *
+ */
+int print_db(int fd) {
+ student_t student;
+ ssize_t byteCount;
+ int foundRecords = 0;
+
+ if (lseek(fd, 0, SEEK_SET) == -1) {
+ printf(M_ERR_DB_READ);
+ return ERR_DB_FILE;
+ }
+ //loop over file
+ while ((byteCount = read(fd, &student, sizeof(student_t))) == sizeof(student_t)) {
+ if (memcmp(&student, &EMPTY_STUDENT_RECORD, sizeof(student_t)) != 0) {
+ if (foundRecords == 0) {
+ printf(STUDENT_PRINT_HDR_STRING, "ID", "FIRST_NAME", "LAST_NAME", "GPA");
+ } //turn into round gpa
+ float gpaScore = student.gpa / 100.0;
+ printf(STUDENT_PRINT_FMT_STRING, student.id, student.fname, student.lname, gpaScore);
+ foundRecords++;
+ }
+ }
+
+ if (foundRecords == 0) { //databse empty
+ printf(M_DB_EMPTY);
+ }
+ if (byteCount == -1) {
+ printf(M_ERR_DB_READ);
+ return ERR_DB_FILE;
+ }
+
+ return NO_ERROR;
+}
+
+
+/*
+ * print_student
+ * *s: a pointer to a student_t structure that should
+ * contain a valid student to be printed
+ *
+ * Start by ensuring that provided student pointer is valid. To do this
+ * make sure it is not NULL and that s->id is not zero. After ensuring
+ * that the student is valid, print it the exact way that is described
+ * in the print_db() function by first printing the header then the
+ * student data:
+ *
+ * printf(STUDENT_PRINT_HDR_STRING, "ID",
+ * "FIRST NAME", "LAST_NAME", "GPA");
+ *
+ * printf(STUDENT_PRINT_FMT_STRING, s->id, s->fname,
+ * student.lname, calculated_gpa_from_s);
+ *
+ * Dont forget that the GPA in the student structure is an int, to convert
+ * it into a real gpa divide by 100.0 and store in a float variable.
+ *
+ * returns: nothing, this is a void function
+ *
+ *
+ * console: <see above> on success, print table or database empty
+ * M_ERR_STD_PRINT if the function argument s is NULL or if
+ * s->id is zero
+ *
+ */
+void print_student(student_t *s) {
+ if (s == NULL || s->id == 0) {
+ printf(M_ERR_STD_PRINT);
+ return;
+ }
+ printf(STUDENT_PRINT_HDR_STRING, "ID", "FIRST_NAME", "LAST_NAME", "GPA");
+ float gpaScore = s->gpa / 100.0;
+ printf(STUDENT_PRINT_FMT_STRING, s->id, s->fname, s->lname, gpaScore);
+}
+
+
+/*
+ * NOTE IMPLEMENTING THIS FUNCTION IS EXTRA CREDIT
+ *
+ * compress_db
+ * fd: linux file descriptor
+ *
+ * This assignment takes advantage of the way Linux handles sparse files
+ * on disk. Thus if there is a large hole between student records, Linux
+ * will not use any physical storage. However, when a database record is
+ * deleted storage is used to write a blank - see EMPTY_STUDENT_RECORD from
+ * db.h - record.
+ *
+ * Since Linux provides no way to delete data in the middle of a file, and
+ * deleted records take up physical storage, this function will compress the
+ * database by rewriting a new database file that only includes valid student
+ * records. There are a number of ways to do this, but since this is extra credit
+ * you need to figure this out on your own.
+ *
+ * At a high level create a temporary database file then copy all valid students from
+ * the active database (passed in via fd) to the temporary file. When this is done
+ * rename the temporary database file to the name of the real database file. See
+ * the constants in db.h for required file names:
+ *
+ * #define DB_FILE "student.db" //name of database file
+ * #define TMP_DB_FILE ".tmp_student.db" //for extra credit
+ *
+ * Note that you are passed in the fd of the database file to be compressed,
+ * it is very likely you will need to close it to overwrite it with the
+ * compressed version of the file. To ensure the caller can work with the
+ * compressed file after you create it, it is a good design to return the fd
+ * of the new compressed file from this function
+ *
+ * returns: <number> returns the fd of the compressed database file
+ * ERR_DB_FILE database file I/O issue
+ *
+ *
+ * console: M_DB_COMPRESSED_OK on success, the db was successfully compressed.
+ * M_ERR_DB_OPEN error when opening/creating temporary database file.
+ * this error should also be returned after you
+ * compressed the database file and if you are unable
+ * to open it to pass the fd back to the caller
+ * M_ERR_DB_CREATE error creating the db file. For instance the
+ * inability to copy the temporary file back as
+ * the primary database file.
+ * M_ERR_DB_READ error reading or seeking the the db or tempdb file
+ * M_ERR_DB_WRITE error writing to db or tempdb file (adding student)
+ *
+ */
+int compress_db(int fd) {
+ int temp_fd; //temporary database
+ student_t student;
+
+ //open temp file, make it if non-existed then truncate
+ temp_fd = open(TMP_DB_FILE, O_RDWR | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR);
+ if (temp_fd == -1) {
+ printf(M_ERR_DB_CREATE);
+ return ERR_DB_FILE;
+ }
+
+ if (lseek(fd, 0, SEEK_SET) == -1) {
+ printf(M_ERR_DB_READ);
+ close(temp_fd);
+ return ERR_DB_FILE;
+ }
+ //read each student from db
+ while (read(fd, &student, sizeof(student_t)) == sizeof(student_t)) {
+ if (memcmp(&student, &EMPTY_STUDENT_RECORD, sizeof(student_t)) != 0) {
+ if (write(temp_fd, &student, sizeof(student_t)) != sizeof(student_t)) {
+ printf(M_ERR_DB_WRITE);
+ close(temp_fd);
+ return ERR_DB_FILE;
+ }
+ }
+ }
+
+ close(fd);
+ close(temp_fd);
+ //rename
+ if (rename(TMP_DB_FILE, DB_FILE) == -1) {
+ printf(M_ERR_DB_CREATE);
+ return ERR_DB_FILE;
+ }
+
+ printf(M_DB_COMPRESSED_OK);
+ return open_db(DB_FILE, false);
+}
+
+
+/*
+ * validate_range
+ * id: proposed student id
+ * gpa: proposed gpa
+ *
+ * This function validates that the id and gpa are in the allowable ranges
+ * as per the specifications. It checks if the values are within the
+ * inclusive range using constents in db.h
+ *
+ * returns: NO_ERROR on success, both ID and GPA are in range
+ * EXIT_FAIL_ARGS if either ID or GPA is out of range
+ *
+ * console: This function does not produce any output
+ *
+ */
+int validate_range(int id, int gpa)
+{
+
+ if ((id < MIN_STD_ID) || (id > MAX_STD_ID))
+ return EXIT_FAIL_ARGS;
+
+ if ((gpa < MIN_STD_GPA) || (gpa > MAX_STD_GPA))
+ return EXIT_FAIL_ARGS;
+
+ return NO_ERROR;
+}
+
+/*
+ * usage
+ * exename: the name of the executable from argv[0]
+ *
+ * Prints this programs expected usage
+ *
+ * returns: nothing, this is a void function
+ *
+ * console: This function prints the usage information
+ *
+ */
+void usage(char *exename)
+{
+ printf("usage: %s -[h|a|c|d|f|p|z] options. Where:\n", exename);
+ printf("\t-h: prints help\n");
+ printf("\t-a id first_name last_name gpa(as 3 digit int): adds a student\n");
+ printf("\t-c: counts the records in the database\n");
+ printf("\t-d id: deletes a student\n");
+ printf("\t-f id: finds and prints a student in the database\n");
+ printf("\t-p: prints all records in the student database\n");
+ printf("\t-x: compress the database file [EXTRA CREDIT]\n");
+ printf("\t-z: zero db file (remove all records)\n");
+}
+
+// Welcome to main()
+int main(int argc, char *argv[])
+{
+ char opt; // user selected option
+ int fd; // file descriptor of database files
+ int rc; // return code from various operations
+ int exit_code; // exit code to shell
+ int id; // userid from argv[2]
+ int gpa; // gpa from argv[5]
+
+ // space for a student structure which we will get back from
+ // some of the functions we will be writing such as get_student(),
+ // and print_student().
+ student_t student = {0};
+
+ // This function must have at least one arg, and the arg must start
+ // with a dash
+ if ((argc < 2) || (*argv[1] != '-'))
+ {
+ usage(argv[0]);
+ exit(1);
+ }
+
+ // The option is the first character after the dash for example
+ //-h -a -c -d -f -p -x -z
+ opt = (char)*(argv[1] + 1); // get the option flag
+
+ // handle the help flag and then exit normally
+ if (opt == 'h')
+ {
+ usage(argv[0]);
+ exit(EXIT_OK);
+ }
+
+ // now lets open the file and continue if there is no error
+ // note we are not truncating the file using the second
+ // parameter
+ fd = open_db(DB_FILE, false);
+ if (fd < 0)
+ {
+ exit(EXIT_FAIL_DB);
+ }
+
+ // set rc to the return code of the operation to ensure the program
+ // use that to determine the proper exit_code. Look at the header
+ // sdbsc.h for expected values.
+
+ exit_code = EXIT_OK;
+ switch (opt)
+ {
+ case 'a':
+ // arv[0] arv[1] arv[2] arv[3] arv[4] arv[5]
+ // prog_name -a id first_name last_name gpa
+ //-------------------------------------------------------
+ // example: prog_name -a 1 John Doe 341
+ if (argc != 6)
+ {
+ usage(argv[0]);
+ exit_code = EXIT_FAIL_ARGS;
+ break;
+ }
+
+ // convert id and gpa to ints from argv. For this assignment assume
+ // they are valid numbers
+ id = atoi(argv[2]);
+ gpa = atoi(argv[5]);
+
+ exit_code = validate_range(id, gpa);
+ if (exit_code == EXIT_FAIL_ARGS)
+ {
+ printf(M_ERR_STD_RNG);
+ break;
+ }
+
+ rc = add_student(fd, id, argv[3], argv[4], gpa);
+ if (rc < 0)
+ exit_code = EXIT_FAIL_DB;
+
+ break;
+
+ case 'c':
+ // arv[0] arv[1]
+ // prog_name -c
+ //-----------------
+ // example: prog_name -c
+ rc = count_db_records(fd);
+ if (rc < 0)
+ exit_code = EXIT_FAIL_DB;
+ break;
+
+ case 'd':
+ // arv[0] arv[1] arv[2]
+ // prog_name -d id
+ //-------------------------
+ // example: prog_name -d 100
+ if (argc != 3)
+ {
+ usage(argv[0]);
+ exit_code = EXIT_FAIL_ARGS;
+ break;
+ }
+ id = atoi(argv[2]);
+ rc = del_student(fd, id);
+ if (rc < 0)
+ exit_code = EXIT_FAIL_DB;
+
+ break;
+
+ case 'f':
+ // arv[0] arv[1] arv[2]
+ // prog_name -f id
+ //-------------------------
+ // example: prog_name -f 100
+ if (argc != 3)
+ {
+ usage(argv[0]);
+ exit_code = EXIT_FAIL_ARGS;
+ break;
+ }
+ id = atoi(argv[2]);
+ rc = get_student(fd, id, &student);
+
+ switch (rc)
+ {
+ case NO_ERROR:
+ print_student(&student);
+ break;
+ case SRCH_NOT_FOUND:
+ printf(M_STD_NOT_FND_MSG, id);
+ exit_code = EXIT_FAIL_DB;
+ break;
+ default:
+ printf(M_ERR_DB_READ);
+ exit_code = EXIT_FAIL_DB;
+ break;
+ }
+ break;
+
+ case 'p':
+ // arv[0] arv[1]
+ // prog_name -p
+ //-----------------
+ // example: prog_name -p
+ rc = print_db(fd);
+ if (rc < 0)
+ exit_code = EXIT_FAIL_DB;
+ break;
+
+ case 'x':
+ // arv[0] arv[1]
+ // prog_name -x
+ //-----------------
+ // example: prog_name -x
+
+ // remember compress_db returns a fd of the compressed database.
+ // we close it after this switch statement
+ fd = compress_db(fd);
+ if (fd < 0)
+ exit_code = EXIT_FAIL_DB;
+ break;
+
+ case 'z':
+ // arv[0] arv[1]
+ // prog_name -x
+ //-----------------
+ // example: prog_name -x
+ // HINT: close the db file, we already have fd
+ // and reopen db indicating truncate=true
+ close(fd);
+ fd = open_db(DB_FILE, true);
+ if (fd < 0)
+ {
+ exit_code = EXIT_FAIL_DB;
+ break;
+ }
+ printf(M_DB_ZERO_OK);
+ exit_code = EXIT_OK;
+ break;
+ default:
+ usage(argv[0]);
+ exit_code = EXIT_FAIL_ARGS;
+ }
+
+ // dont forget to close the file before exiting, and setting the
+ // proper exit code - see the header file for expected values
+ close(fd);
+ exit(exit_code);
+}
diff --git a/2-StudentDB/starter/sdbsc.dSYM/Contents/Info.plist b/2-StudentDB/starter/sdbsc.dSYM/Contents/Info.plist
new file mode 100644
index 0000000000000000000000000000000000000000..b0e4048d8e21d51c50870c6d6436056a0093011e
--- /dev/null
+++ b/2-StudentDB/starter/sdbsc.dSYM/Contents/Info.plist
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+ <dict>
+ <key>CFBundleDevelopmentRegion</key>
+ <string>English</string>
+ <key>CFBundleIdentifier</key>
+ <string>com.apple.xcode.dsym.sdbsc</string>
+ <key>CFBundleInfoDictionaryVersion</key>
+ <string>6.0</string>
+ <key>CFBundlePackageType</key>
+ <string>dSYM</string>
+ <key>CFBundleSignature</key>
+ <string>????</string>
+ <key>CFBundleShortVersionString</key>
+ <string>1.0</string>
+ <key>CFBundleVersion</key>
+ <string>1</string>
+ </dict>
+</plist>
diff --git a/2-StudentDB/starter/sdbsc.dSYM/Contents/Resources/DWARF/sdbsc b/2-StudentDB/starter/sdbsc.dSYM/Contents/Resources/DWARF/sdbsc
new file mode 100644
index 0000000000000000000000000000000000000000..b10370575645bbf73790e9438d18e96575524a43
Binary files /dev/null and b/2-StudentDB/starter/sdbsc.dSYM/Contents/Resources/DWARF/sdbsc differ
diff --git a/2-StudentDB/starter/sdbsc.dSYM/Contents/Resources/Relocations/x86_64/sdbsc.yml b/2-StudentDB/starter/sdbsc.dSYM/Contents/Resources/Relocations/x86_64/sdbsc.yml
new file mode 100644
index 0000000000000000000000000000000000000000..7bd017a9208d54ddabd958723d98af36132c2323
--- /dev/null
+++ b/2-StudentDB/starter/sdbsc.dSYM/Contents/Resources/Relocations/x86_64/sdbsc.yml
@@ -0,0 +1,18 @@
+---
+triple: 'x86_64-apple-darwin'
+binary-path: sdbsc
+relocations:
+ - { offsetInCU: 0x26, offset: 0x26, size: 0x8, addend: 0x0, symName: _open_db, symObjAddr: 0x0, symBinAddr: 0x100002B10, symSize: 0x80 }
+ - { offsetInCU: 0x4D, offset: 0x4D, size: 0x8, addend: 0x0, symName: _EMPTY_STUDENT_RECORD, symObjAddr: 0x13EC, symBinAddr: 0x100003B38, symSize: 0x0 }
+ - { offsetInCU: 0xBE, offset: 0xBE, size: 0x8, addend: 0x0, symName: _open_db, symObjAddr: 0x0, symBinAddr: 0x100002B10, symSize: 0x80 }
+ - { offsetInCU: 0x11E, offset: 0x11E, size: 0x8, addend: 0x0, symName: _get_student, symObjAddr: 0x80, symBinAddr: 0x100002B90, symSize: 0xE0 }
+ - { offsetInCU: 0x17E, offset: 0x17E, size: 0x8, addend: 0x0, symName: _add_student, symObjAddr: 0x160, symBinAddr: 0x100002C70, symSize: 0x270 }
+ - { offsetInCU: 0x23C, offset: 0x23C, size: 0x8, addend: 0x0, symName: _del_student, symObjAddr: 0x3D0, symBinAddr: 0x100002EE0, symSize: 0x1F0 }
+ - { offsetInCU: 0x2B0, offset: 0x2B0, size: 0x8, addend: 0x0, symName: _count_db_records, symObjAddr: 0x5C0, symBinAddr: 0x1000030D0, symSize: 0x130 }
+ - { offsetInCU: 0x30B, offset: 0x30B, size: 0x8, addend: 0x0, symName: _print_db, symObjAddr: 0x6F0, symBinAddr: 0x100003200, symSize: 0x190 }
+ - { offsetInCU: 0x384, offset: 0x384, size: 0x8, addend: 0x0, symName: _print_student, symObjAddr: 0x880, symBinAddr: 0x100003390, symSize: 0xC0 }
+ - { offsetInCU: 0x3B9, offset: 0x3B9, size: 0x8, addend: 0x0, symName: _compress_db, symObjAddr: 0x940, symBinAddr: 0x100003450, symSize: 0x1B0 }
+ - { offsetInCU: 0x404, offset: 0x404, size: 0x8, addend: 0x0, symName: _validate_range, symObjAddr: 0xAF0, symBinAddr: 0x100003600, symSize: 0x60 }
+ - { offsetInCU: 0x43D, offset: 0x43D, size: 0x8, addend: 0x0, symName: _usage, symObjAddr: 0xB50, symBinAddr: 0x100003660, symSize: 0xA0 }
+ - { offsetInCU: 0x463, offset: 0x463, size: 0x8, addend: 0x0, symName: _main, symObjAddr: 0xBF0, symBinAddr: 0x100003700, symSize: 0x3C4 }
+...
diff --git a/2-StudentDB/starter/sdbsc.h b/2-StudentDB/starter/sdbsc.h
new file mode 100644
index 0000000000000000000000000000000000000000..79b08e7775c8adea56f6027b22570813086aad8f
--- /dev/null
+++ b/2-StudentDB/starter/sdbsc.h
@@ -0,0 +1,64 @@
+#ifndef __SDB_H__
+
+#include "db.h" //get student record type
+
+//prototypes for functions go below for this assignment
+int open_db(char *dbFile, bool should_truncate);
+int add_student(int fd, int id, char *fname, char *lname, int gpa);
+int get_student(int fd, int id, student_t *s);
+int del_student(int fd, int id);
+int compress_db(int fd);
+void print_student(student_t *s);
+int validate_range(int id, int gpa);
+int count_db_records(int fd);
+int print_db(int fd);
+void usage(char *);
+
+//error codes to be returned from individual functions
+// NO_ERROR is returned if there are no errors
+// ERR_DB_FILE is returned if there is are any issues with the database file itself
+// ERR_DB_OP is returned if an operation did not work aka add or delete a student
+// SRCH_NOT_FOUND is returned if the student is not found (get_student, and del_student)
+#define NO_ERROR 0
+#define ERR_DB_FILE -1
+#define ERR_DB_OP -2
+#define SRCH_NOT_FOUND -3
+#define NOT_IMPLEMENTED_YET 0
+
+
+//error codes to be returned to the shell
+// EXIT_OK program executed without error
+// EXIT_FAIL_DB a database operation failed
+// EXIT_FAIL_ARGS one or more arguments to program were not valid
+// EXIT_NOT_IMPL the operation has not been implemented yet
+#define EXIT_OK 0
+#define EXIT_FAIL_DB 1
+#define EXIT_FAIL_ARGS 2
+#define EXIT_NOT_IMPL 3
+
+//Output messages
+#define M_ERR_STD_RNG "Cant add student, either ID or GPA out of allowable range!\n"
+#define M_ERR_DB_CREATE "Error creating DB file, exiting!\n"
+#define M_ERR_DB_OPEN "Error opening DB file, exiting!\n"
+#define M_ERR_DB_READ "Error reading DB file, exiting!\n"
+#define M_ERR_DB_WRITE "Error writing DB file, exiting!\n"
+#define M_ERR_DB_ADD_DUP "Cant add student with ID=%d, already exists in db.\n"
+#define M_ERR_STD_PRINT "Cant print student. Student is NULL or ID is zero\n"
+
+#define M_STD_ADDED "Student %d added to database.\n"
+#define M_STD_DEL_MSG "Student %d was deleted from database.\n"
+#define M_STD_NOT_FND_MSG "Student %d was not found in database.\n"
+#define M_DB_COMPRESSED_OK "Database successfully compressed!\n"
+#define M_DB_ZERO_OK "All database records removed!\n"
+#define M_DB_EMPTY "Database contains no student records.\n"
+#define M_DB_RECORD_CNT "Database contains %d student record(s).\n"
+#define M_NOT_IMPL "The requested operation is not implemented yet!\n"
+
+//useful format strings for print students
+//For example to print the header in the required output:
+// printf(STUDENT_PRINT_HDR_STRING, "ID","FIRST NAME",
+// "LAST_NAME", "GPA");
+#define STUDENT_PRINT_HDR_STRING "%-6s %-24s %-32s %-3s\n"
+#define STUDENT_PRINT_FMT_STRING "%-6d %-24.24s %-32.32s %-3.2f\n"
+
+#endif
\ No newline at end of file
diff --git a/2-StudentDB/starter/student.db b/2-StudentDB/starter/student.db
new file mode 100644
index 0000000000000000000000000000000000000000..7bbdd80cdd5ce2c733b1a45675acf10340bc2567
Binary files /dev/null and b/2-StudentDB/starter/student.db differ
diff --git a/2-StudentDB/starter/test.sh b/2-StudentDB/starter/test.sh
new file mode 100755
index 0000000000000000000000000000000000000000..e7aadbf98fd63040fa530a3b918c36b3ccf427c7
--- /dev/null
+++ b/2-StudentDB/starter/test.sh
@@ -0,0 +1,207 @@
+#!/usr/bin/env bats
+
+# The setup function runs before every test
+setup_file() {
+ # Delete the student.db file if it exists
+ if [ -f "student.db" ]; then
+ rm "student.db"
+ fi
+}
+
+@test "Check if database is empty to start" {
+ run ./sdbsc -p
+ [ "$status" -eq 0 ]
+ [ "$output" = "Database contains no student records." ]
+}
+
+@test "Add a student 1 to db" {
+ run ./sdbsc -a 1 john doe 345
+ [ "$status" -eq 0 ]
+ [ "${lines[0]}" = "Student 1 added to database." ]
+}
+
+@test "Add more students to db" {
+ run ./sdbsc -a 3 jane doe 390
+ [ "$status" -eq 0 ]
+ [ "${lines[0]}" = "Student 3 added to database." ] || {
+ echo "Failed Output: $output"
+ return 1
+ }
+
+ run ./sdbsc -a 63 jim doe 285
+ [ "$status" -eq 0 ]
+ [ "${lines[0]}" = "Student 63 added to database." ] || {
+ echo "Failed Output: $output"
+ return 1
+ }
+
+ run ./sdbsc -a 64 janet doe 310
+ [ "$status" -eq 0 ]
+ [ "${lines[0]}" = "Student 64 added to database." ] || {
+ echo "Failed Output: $output"
+ return 1
+ }
+
+ run ./sdbsc -a 99999 big dude 205
+ [ "$status" -eq 0 ]
+ [ "${lines[0]}" = "Student 99999 added to database." ] || {
+ echo "Failed Output: $output"
+ return 1
+ }
+}
+
+@test "Check student count" {
+ run ./sdbsc -c
+ [ "$status" -eq 0 ]
+ [ "${lines[0]}" = "Database contains 5 student record(s)." ] || {
+ echo "Failed Output: $output"
+ return 1
+ }
+}
+
+@test "Make sure adding duplicate student fails" {
+ run ./sdbsc -a 63 dup student 300
+ [ "$status" -eq 1 ] || {
+ echo "Expecting status of 1, got: $status"
+ return 1
+ }
+ [ "${lines[0]}" = "Cant add student with ID=63, already exists in db." ] || {
+ echo "Failed Output: $output"
+ return 1
+ }
+}
+
+#EDIT TEST for macOS
+@test "Make sure the file size is correct at this time" {
+ run stat -f "%z" ./student.db
+ [ "$status" -eq 0 ]
+ [ "${lines[0]}" = "6400000" ] || {
+ echo "Failed Output: $output"
+ echo "Expected: 64000000"
+ return 1
+ }
+}
+
+# OLD TEST GIVEN - this is for Linux
+# @test "Make sure the file size is correct at this time" {
+# run stat --format="%s" ./student.db
+# [ "$status" -eq 0 ]
+# [ "${lines[0]}" = "6400000" ] || {
+# echo "Failed Output: $output"
+# echo "Expected: 64000000"
+# return 1
+# }
+# }
+
+@test "Find student 3 in db" {
+ run ./sdbsc -f 3
+
+ # Ensure the command ran successfully
+ [ "$status" -eq 0 ]
+
+ # Use echo with -n to avoid adding extra newline and normalize spaces
+ normalized_output=$(echo -n "${lines[1]}" | tr -s '[:space:]' ' ')
+
+ # Define the expected output
+ expected_output="3 jane doe 3.90"
+
+ # Compare the normalized output with the expected output
+ [ "$normalized_output" = "$expected_output" ] || {
+ echo "Failed Output: $normalized_output"
+ echo "Expected: $expected_output"
+ return 1
+ }
+}
+
+@test "Try looking up non-existent student" {
+ run ./sdbsc -f 4
+ [ "$status" -eq 1 ] || {
+ echo "Expecting status of 1, got: $status"
+ return 1
+ }
+ [ "${lines[0]}" = "Student 4 was not found in database." ] || {
+ echo "Failed Output: $output"
+ return 1
+ }
+}
+
+@test "Delete student 64 in db" {
+ run ./sdbsc -d 64
+ [ "$status" -eq 0 ]
+ [ "${lines[0]}" = "Student 64 was deleted from database." ] || {
+ echo "Failed Output: $output"
+ return 1
+ }
+}
+
+@test "Try deleting non-existent student" {
+ run ./sdbsc -d 65
+ [ "$status" -eq 1 ] || {
+ echo "Expecting status of 1, got: $status"
+ return 1
+ }
+ [ "${lines[0]}" = "Student 65 was not found in database." ] || {
+ echo "Failed Output: $output"
+ return 1
+ }
+}
+
+@test "Check student count again, should be 4 now" {
+ run ./sdbsc -c
+ [ "$status" -eq 0 ]
+ [ "${lines[0]}" = "Database contains 4 student record(s)." ] || {
+ echo "Failed Output: $output"
+ return 1
+ }
+}
+
+@test "Print student records" {
+ # Run the command
+ run ./sdbsc -p
+
+ # Ensure the command ran successfully
+ [ "$status" -eq 0 ]
+
+ # Normalize the output by replacing multiple spaces with a single space
+ normalized_output=$(echo -n "$output" | tr -s '[:space:]' ' ')
+
+ # Define the expected output (normalized)
+ expected_output="ID FIRST_NAME LAST_NAME GPA 1 john doe 3.45 3 jane doe 3.90 63 jim doe 2.85 99999 big dude 2.05"
+
+ # Compare the normalized output
+ [ "$normalized_output" = "$expected_output" ] || {
+ echo "Failed Output: $normalized_output"
+ echo "Expected Output: $expected_output"
+ return 1
+ }
+}
+
+
+@test "Compress db - try 1" {
+ skip
+ run ./sdbsc -x
+ [ "$status" -eq 0 ]
+ [ "${lines[0]}" = "Database successfully compressed!" ] || {
+ echo "Failed Output: $output"
+ return 1
+ }
+}
+
+
+@test "Delete student 99999 in db" {
+ run ./sdbsc -d 99999
+ [ "$status" -eq 0 ]
+ [ "${lines[0]}" = "Student 99999 was deleted from database." ] || {
+ echo "Failed Output: $output"
+ return 1
+ }
+}
+
+@test "Compress db again - try 2" {
+ run ./sdbsc -x
+ [ "$status" -eq 0 ]
+ [ "${lines[0]}" = "Database successfully compressed!" ] || {
+ echo "Failed Output: $output"
+ return 1
+ }
+}
\ No newline at end of file
diff --git a/2-StudentDB/starter/testload.sh b/2-StudentDB/starter/testload.sh
new file mode 100755
index 0000000000000000000000000000000000000000..2f36a925ad16ce879f04bc1382c53b7e18f2790e
--- /dev/null
+++ b/2-StudentDB/starter/testload.sh
@@ -0,0 +1,6 @@
+#! /bin/bash
+./sdbsc -a 1 john doe 3.45
+./sdbsc -a 3 jane doe 3.90
+./sdbsc -a 63 jim doe 2.85
+./sdbsc -a 64 janet doe 3.10
+./sdbsc -a 99999 big dude 2.05
\ No newline at end of file
diff --git a/SysProg-Class b/SysProg-Class
index 8a062aa9b6f6b10003eef49ef73ec199c0ae1183..6352ca409e67e2ee1df7f35cda529bcd2b59fdc4 160000
--- a/SysProg-Class
+++ b/SysProg-Class
@@ -1 +1 @@
-Subproject commit 8a062aa9b6f6b10003eef49ef73ec199c0ae1183
+Subproject commit 6352ca409e67e2ee1df7f35cda529bcd2b59fdc4