diff --git a/Assignment2/a2-questions.md b/Assignment2/a2-questions.md
new file mode 100644
index 0000000000000000000000000000000000000000..a06cc1c8fb2e0eac4865330b9e732b00354d5c31
--- /dev/null
+++ b/Assignment2/a2-questions.md
@@ -0,0 +1,129 @@
+## 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**:  Yes, externalizing get_student(...) is a good choice. It formalize the logic for retrieving a student, reducing code duplication and improving ease of use. Other functions (e.g., `del_student()`, `print_student()`) reuse it a lot, ensuring consistent behavior when accessing student records.
+
+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:** This implementation is dangerous because student is a local variable stored on the stack. When the function returns, the stack is deallocated, making `&student` a dangling pointer. This leads to undefined behavior.
+
+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:** This approach works because malloc() allocates memory on the heap, ensuring the student record remains valid after the function returns. However, it can cause memory issues:
+    > - The caller must free() the memory, which can be annoying sometimes, or it causes memory leaks.
+    > - If the function is called frequently, excessive heap allocations may degrade performance.
+    > Managing memory explicitly like the current implementation reduce that potential threat.
+
+
+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 database uses sparse file allocation. Each student record is 64 bytes, and their position is based on `id * sizeof(student_t)`.
+        > - ID=1 → First record at offset 1 * 64 = 64 (File size: 128 bytes).
+        > - ID=3 → Third record at offset 3 * 64 = 192 (File size: 256 bytes).
+        > - ID=64 → 64th record at offset 64 * 64 = 4096 (File size: 4160 bytes).
+        > Linux does not store zeros physically in sparse files, so the actual disk usage is just enough to hold the largest id. 
+
+    -   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:** Before ID=64, all records fit within the first 4 KB block, adding ID=64 moved into a new 4 KB block, causing real storage allocation to increase to 8 KB.
+
+    - 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:**  `ls` reports the full file size. `du` only reports actual allocated blocks, so when ID 99999 is added, it is stored in another `4K` block, so actual disk usage only increase by `4K` since Linux does not allocate blocks for empty regions.
\ No newline at end of file
diff --git a/Assignment2/db.h b/Assignment2/db.h
new file mode 100644
index 0000000000000000000000000000000000000000..ac7e122c1610c8a5317c6d93bd8762ea1c3dafda
--- /dev/null
+++ b/Assignment2/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/Assignment2/dblayout.png b/Assignment2/dblayout.png
new file mode 100644
index 0000000000000000000000000000000000000000..0aa1ba5aedd5556d13736f0836a36a9ba492626a
Binary files /dev/null and b/Assignment2/dblayout.png differ
diff --git a/Assignment2/makefile b/Assignment2/makefile
new file mode 100644
index 0000000000000000000000000000000000000000..4f6101cc6661facd243dc13f121475c4096dfd5e
--- /dev/null
+++ b/Assignment2/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/Assignment2/sdbsc b/Assignment2/sdbsc
new file mode 100755
index 0000000000000000000000000000000000000000..b462bc0d36d7a217a6bf046b9ab7227604446c86
Binary files /dev/null and b/Assignment2/sdbsc differ
diff --git a/Assignment2/sdbsc.c b/Assignment2/sdbsc.c
new file mode 100644
index 0000000000000000000000000000000000000000..559d2cd3b551559986ae230128dd2c4239148d17
--- /dev/null
+++ b/Assignment2/sdbsc.c
@@ -0,0 +1,690 @@
+#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)
+    {
+        // Handle the error
+        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)
+{
+    // TODO
+    off_t offset = id * STUDENT_RECORD_SIZE;
+    if (lseek(fd, offset, SEEK_SET) == -1) {
+        return ERR_DB_FILE;
+    }
+
+    ssize_t bytes_read = read(fd, s, STUDENT_RECORD_SIZE);
+    if (bytes_read != STUDENT_RECORD_SIZE) {
+        return ERR_DB_FILE;
+    }
+
+    if (memcmp(s, &EMPTY_STUDENT_RECORD, STUDENT_RECORD_SIZE) == 0) {
+        return SRCH_NOT_FOUND;
+    }
+
+    return NO_ERROR;
+}
+
+/*
+ *  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)
+{
+    // TODO
+    off_t offset = id * STUDENT_RECORD_SIZE;
+    student_t exist;
+
+    if (lseek(fd, offset, SEEK_SET) == -1) {
+        printf(M_ERR_DB_READ);
+        return ERR_DB_FILE;
+    }
+
+    ssize_t bytes_read = read(fd, &exist, STUDENT_RECORD_SIZE);
+    if (bytes_read == 0) {
+        memset(&exist, 0, sizeof(exist));
+    } else if (bytes_read != STUDENT_RECORD_SIZE) {
+        printf(M_ERR_DB_READ);
+        return ERR_DB_FILE;
+    }
+
+    if (memcmp(&exist, &EMPTY_STUDENT_RECORD, STUDENT_RECORD_SIZE) != 0) {
+        printf(M_ERR_DB_ADD_DUP, id);
+        return ERR_DB_OP;
+    }
+
+    student_t new_student = {0};
+    new_student.id = id;
+    strncpy(new_student.fname, fname, sizeof(new_student.fname) - 1);
+    strncpy(new_student.lname, lname, sizeof(new_student.lname) - 1);
+    new_student.gpa = gpa;
+
+    if (lseek(fd, offset, SEEK_SET) == -1) {
+        printf(M_ERR_DB_WRITE);
+        return ERR_DB_FILE;
+    }
+
+    if (write(fd, &new_student, STUDENT_RECORD_SIZE) != STUDENT_RECORD_SIZE) {
+        printf(M_ERR_DB_WRITE);
+        return ERR_DB_FILE;
+    }
+
+    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)
+{
+    // TODO
+    student_t student;
+    int result = get_student(fd, id, &student);
+
+    if (result == SRCH_NOT_FOUND) {
+        printf(M_STD_NOT_FND_MSG, id);
+        return ERR_DB_OP;
+    } else if (result != NO_ERROR) {
+        return ERR_DB_FILE;
+    }
+
+    off_t offset = id * STUDENT_RECORD_SIZE;
+    if (lseek(fd, offset, SEEK_SET) == -1) {
+        printf(M_ERR_DB_WRITE);
+        return ERR_DB_FILE;
+    }
+
+    if (write(fd, &EMPTY_STUDENT_RECORD, STUDENT_RECORD_SIZE) != STUDENT_RECORD_SIZE) {
+        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
+    student_t student;
+    int count = 0;
+
+    if (lseek(fd, 0, SEEK_SET) == -1) {
+        printf(M_ERR_DB_READ);
+        return ERR_DB_FILE;
+    }
+
+    while (read(fd, &student, STUDENT_RECORD_SIZE) == STUDENT_RECORD_SIZE) {
+        if (memcmp(&student, &EMPTY_STUDENT_RECORD, STUDENT_RECORD_SIZE) != 0) {
+            count++;
+        }
+    }
+
+    if (count == 0) {
+        printf(M_DB_EMPTY);
+    } else {
+        printf(M_DB_RECORD_CNT, count);
+    }
+
+    return 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)
+{
+    // TODO
+    student_t student;
+    bool records_available = false;
+
+    if (lseek(fd, 0, SEEK_SET) == -1) {
+        printf(M_ERR_DB_READ);
+        return ERR_DB_FILE;
+    }
+
+    while (read(fd, &student, STUDENT_RECORD_SIZE) == STUDENT_RECORD_SIZE) {
+        if (memcmp(&student, &EMPTY_STUDENT_RECORD, STUDENT_RECORD_SIZE) != 0) {
+            if (!records_available) {
+                printf(STUDENT_PRINT_HDR_STRING, "ID", "FIRST_NAME", "LAST_NAME", "GPA");
+                records_available = true;
+            }
+            printf(STUDENT_PRINT_FMT_STRING, student.id, student.fname, student.lname, student.gpa / 100.0);
+        }
+    }
+
+    if (!records_available) {
+        printf(M_DB_EMPTY);
+    }
+
+    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)
+{
+    // TODO
+    if (s == NULL || s->id == 0) {
+        printf(M_ERR_STD_PRINT);
+        return;
+    }
+
+    printf(STUDENT_PRINT_HDR_STRING, "ID", "FIRST NAME", "LAST NAME", "GPA");
+    printf(STUDENT_PRINT_FMT_STRING, s->id, s->fname, s->lname, s->gpa / 100.0);
+}
+
+/*
+ *  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)
+{
+    // TODO
+    close(fd);
+
+    fd = open(DB_FILE, O_RDONLY);
+    if (fd == -1) {
+        printf(M_ERR_DB_OPEN);
+        return ERR_DB_FILE;
+    }
+
+    // Open or make a temp database file in write mode
+    int tmp_fd = open(TMP_DB_FILE, O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP);
+    if (tmp_fd == -1) {
+        printf(M_ERR_DB_CREATE);
+        close(fd);
+        return ERR_DB_FILE;
+    }
+
+    student_t student;
+    while (read(fd, &student, STUDENT_RECORD_SIZE) == STUDENT_RECORD_SIZE) {
+        // Copy only non-empty records 
+        if (memcmp(&student, &EMPTY_STUDENT_RECORD, STUDENT_RECORD_SIZE) != 0) {
+            if (write(tmp_fd, &student, STUDENT_RECORD_SIZE) != STUDENT_RECORD_SIZE) {
+                printf(M_ERR_DB_WRITE);
+                close(fd);
+                close(tmp_fd);
+                return ERR_DB_FILE;
+            }
+        }
+    }
+
+    close(fd);
+    close(tmp_fd);
+
+    // Replace old database with the compressed one
+    if (rename(TMP_DB_FILE, DB_FILE) == -1) {
+        printf(M_ERR_DB_CREATE);
+        return ERR_DB_FILE;
+    }
+
+    fd = open(DB_FILE, O_RDWR);
+    if (fd == -1) {
+        printf(M_ERR_DB_OPEN);
+        return ERR_DB_FILE;
+    }
+
+    printf(M_DB_COMPRESSED_OK);
+    return fd;
+}
+
+/*
+ *  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/Assignment2/sdbsc.h b/Assignment2/sdbsc.h
new file mode 100644
index 0000000000000000000000000000000000000000..79b08e7775c8adea56f6027b22570813086aad8f
--- /dev/null
+++ b/Assignment2/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/Assignment2/student.db b/Assignment2/student.db
new file mode 100644
index 0000000000000000000000000000000000000000..7bbdd80cdd5ce2c733b1a45675acf10340bc2567
Binary files /dev/null and b/Assignment2/student.db differ
diff --git a/Assignment2/test.sh b/Assignment2/test.sh
new file mode 100755
index 0000000000000000000000000000000000000000..a013095b177c4e809244e4472d235f0370581355
--- /dev/null
+++ b/Assignment2/test.sh
@@ -0,0 +1,195 @@
+#!/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
+    }
+}
+
+@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/Assignment2/testload.sh b/Assignment2/testload.sh
new file mode 100755
index 0000000000000000000000000000000000000000..2f36a925ad16ce879f04bc1382c53b7e18f2790e
--- /dev/null
+++ b/Assignment2/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