Final Report for Project 3: File Systems
========================================
## Task 1
We were originally planning on doing an Nth chance clock cache entry replacement policy so that we could vary N until we maximized performance. We also were planning on having the metadata and cache entries stored in separate arrays.
We moved instead to using a LRU replacement policy and a pintos list to store `cache_entry` structs that contain both the data and the metadata. We did this for the following reasons:
1. Pintos lists are much easier to sort, pop from, and append to than arrays
2. There is no memory restriction on cache entry size, so we put all of the data and metadata in the same struct to make everything easier.
Additionally, we moved from one lock for the whole cache to one lock per `cache_entry` instead so that entries could be read from/written to concurrently.
Milo wrote the code for all of Task 1 with conceptual help from Stephanie.
## Task 2
To create the direct, indirect, and doubly indirect pointers, we stored an array of 12 `block_sector_t`'s for the direct pointers, a `block_sector_t` for the indirect pointer, and a `block_sector_t` for the doubly indirect pointer. We put all these members in the `inode_disk` struct. We removed the struct `inode_disk` from the struct inode. We had to modify the byte_to_sector method to account for the Unix FFS approach. We first read the inode's sector from the buffer cache to get the inode_disk, and then we see if we can use direct pointers to read the byte offset. If the byte offset is too large, we then fetch the indirect pointer and check if the byte offset can be handled there, and if not then we get the doubly indirect pointer.
Since we are removing the global file system lock, we added locks for the free map, inode struct, and the list `open_inodes`.
To keep the system in a consistent state, we decided to make block writing atomic, but not undo prior written bytes, as Sam suggested. Basically, we would write to the direct pointers and if we needed more space, wrote to the indirect pointers. If we got a failure writing to the indirect pointers we don't undo the bytes already written to the direct pointers. When allocating new blocks, we used `free_map_allocate` with a count argument of 1 to get around the contiguous allocation issue (only allocating one block at a time).
Aidan and Jack wrote all the code for Task 2 and Milo helped debug.
## Task 3
In order to implement subdirectories and handling relative and absolute file paths in syscalls, we used the `get_next_part` helper function to parse through the given filepath.
We added the boolean `is_dir` to our `struct ionde_disk` in order to track whether the specified object is a directory.
We implemented functionality for the `isdir`, `mkdir`, `chdir`, `inumber`, and `readdir` syscalls. We also updated various other syscalls in order to handle filepaths and directories.
For the `remove` syscall, if the given object is a directory, we check that it does not contain any files or subdirectories other than `.` and `..` and that it is not the root.
For the `read` and `write` syscalls, we verify that the argument passed in is *not* a directory through the `is_dir` boolean in its `struct inode_disk`.
Stephanie wrote all the code for Task 3 with some conceptual and debugging help from Milo.
## Student Testing Report
Didn't get around to it