aboutsummaryrefslogtreecommitdiff
path: root/kernel/fs
diff options
context:
space:
mode:
Diffstat (limited to 'kernel/fs')
-rw-r--r--kernel/fs/Submodules1
-rw-r--r--kernel/fs/file.c115
-rw-r--r--kernel/fs/namev.c263
-rw-r--r--kernel/fs/open.c67
-rw-r--r--kernel/fs/pipe.c256
-rw-r--r--kernel/fs/ramfs/ramfs.c852
-rw-r--r--kernel/fs/s5fs/s5fs.c860
-rw-r--r--kernel/fs/s5fs/s5fs_subr.c590
-rw-r--r--kernel/fs/vfs.c222
-rw-r--r--kernel/fs/vfs_syscall.c356
-rw-r--r--kernel/fs/vnode.c250
-rw-r--r--kernel/fs/vnode_specials.c176
12 files changed, 4008 insertions, 0 deletions
diff --git a/kernel/fs/Submodules b/kernel/fs/Submodules
new file mode 100644
index 0000000..a6a93cb
--- /dev/null
+++ b/kernel/fs/Submodules
@@ -0,0 +1 @@
+ramfs s5fs
diff --git a/kernel/fs/file.c b/kernel/fs/file.c
new file mode 100644
index 0000000..4e79a3d
--- /dev/null
+++ b/kernel/fs/file.c
@@ -0,0 +1,115 @@
+#include "fs/file.h"
+#include "fs/vfs.h"
+#include "fs/vnode.h"
+#include "kernel.h"
+#include "mm/slab.h"
+#include "util/debug.h"
+#include "util/string.h"
+
+static slab_allocator_t *file_allocator;
+
+void file_init(void)
+{
+ file_allocator = slab_allocator_create("file", sizeof(file_t));
+}
+
+void fref(file_t *f)
+{
+ KASSERT(f->f_mode <= FMODE_MAX_VALUE && f->f_vnode);
+
+ f->f_refcount++;
+
+ if (f->f_vnode)
+ {
+ dbg(DBG_FREF, "fref: 0x%p, 0x%p ino %u, up to %lu\n", f,
+ f->f_vnode->vn_fs, f->f_vnode->vn_vno, f->f_refcount);
+ }
+ else
+ {
+ dbg(DBG_FREF, "fref: 0x%p up to %lu\n", f, f->f_refcount);
+ }
+}
+
+/*
+ * Create a file, initialize its members, vref the vnode, call acquire() on the
+ * vnode if the function pointer is non-NULL, and set the file descriptor in
+ * curproc->p_files.
+ *
+ * On successful return, the vnode's refcount should be incremented by one,
+ * the file's refcount should be 1, and curproc->p_files[fd] should point to
+ * the file being returned.
+ */
+file_t *fcreate(int fd, vnode_t *vnode, unsigned int mode)
+{
+ KASSERT(!curproc->p_files[fd]);
+ file_t *file = slab_obj_alloc(file_allocator);
+ if (!file)
+ return NULL;
+ memset(file, 0, sizeof(file_t));
+ file->f_mode = mode;
+
+ vref(file->f_vnode = vnode);
+ if (vnode->vn_ops->acquire)
+ vnode->vn_ops->acquire(vnode, file);
+
+ curproc->p_files[fd] = file;
+ fref(file);
+ return file;
+}
+
+/*
+ * Perform bounds checking on the fd, use curproc->p_files to get the file,
+ * fref it if it exists, and return.
+ */
+file_t *fget(int fd)
+{
+ if (fd < 0 || fd >= NFILES)
+ return NULL;
+ file_t *file = curproc->p_files[fd];
+ if (file)
+ fref(file);
+ return file;
+}
+
+/*
+ * Decrement the refcount, and set *filep to NULL.
+ *
+ * If the refcount drops to 0, call release on the vnode if the function pointer
+ * is non-null, vput() file's vnode, and free the file memory.
+ *
+ * Regardless of the ending refcount, *filep == NULL on return.
+ */
+void fput(file_t **filep)
+{
+ file_t *file = *filep;
+ *filep = NULL;
+
+ KASSERT(file && file->f_mode <= FMODE_MAX_VALUE);
+ KASSERT(file->f_refcount > 0);
+ if (file->f_refcount != 1)
+ KASSERT(file->f_vnode);
+
+ file->f_refcount--;
+
+ if (file->f_vnode)
+ {
+ dbg(DBG_FREF, "fput: 0x%p, 0x%p ino %u, down to %lu\n", file,
+ file->f_vnode->vn_fs, file->f_vnode->vn_vno, file->f_refcount);
+ }
+ else
+ {
+ dbg(DBG_FREF, "fput: 0x%p down to %lu\n", file, file->f_refcount);
+ }
+
+ if (!file->f_refcount)
+ {
+ if (file->f_vnode)
+ {
+ vlock(file->f_vnode);
+ if (file->f_vnode->vn_ops->release)
+ file->f_vnode->vn_ops->release(file->f_vnode, file);
+ vput_locked(&file->f_vnode);
+ }
+ slab_obj_free(file_allocator, file);
+ }
+}
diff --git a/kernel/fs/namev.c b/kernel/fs/namev.c
new file mode 100644
index 0000000..9e55892
--- /dev/null
+++ b/kernel/fs/namev.c
@@ -0,0 +1,263 @@
+#include "errno.h"
+#include "globals.h"
+#include "kernel.h"
+#include <fs/dirent.h>
+
+#include "util/debug.h"
+#include "util/string.h"
+
+#include "fs/fcntl.h"
+#include "fs/stat.h"
+#include "fs/vfs.h"
+#include "fs/vnode.h"
+
+/*
+ * Get the parent of a directory. dir must not be locked.
+ */
+long namev_get_parent(vnode_t *dir, vnode_t **out)
+{
+ vlock(dir);
+ long ret = namev_lookup(dir, "..", 2, out);
+ vunlock(dir);
+ return ret;
+}
+
+/*
+ * Determines if vnode a is a descendant of vnode b.
+ * Returns 1 if true, 0 otherwise.
+ */
+long namev_is_descendant(vnode_t *a, vnode_t *b)
+{
+ vref(a);
+ vnode_t *cur = a;
+ vnode_t *next = NULL;
+ while (cur != NULL)
+ {
+ if (cur->vn_vno == b->vn_vno)
+ {
+ vput(&cur);
+ return 1;
+ }
+ else if (cur->vn_vno == cur->vn_fs->fs_root->vn_vno)
+ {
+ /* we've reached the root node. */
+ vput(&cur);
+ return 0;
+ }
+
+ /* backup the filesystem tree */
+ namev_get_parent(cur, &next);
+ vnode_t *tmp = cur;
+ cur = next;
+ vput(&tmp);
+ }
+
+ return 0;
+}
+
+/* Wrapper around dir's vnode operation lookup. dir must be locked on entry and
+ * upon return.
+ *
+ * Upon success, return 0 and return the found vnode using res_vnode, or:
+ * - ENOTDIR: dir does not have a lookup operation or is not a directory
+ * - Propagate errors from the vnode operation lookup
+ *
+ * Hints:
+ * Take a look at ramfs_lookup(), which adds a reference to res_vnode but does
+ * not touch any locks. In most cases, this means res_vnode will be unlocked
+ * upon return. However, there is a case where res_vnode would actually be
+ * locked after calling dir's lookup function (i.e. looking up '.'). You
+ * shouldn't deal with any locking in namev_lookup(), but you should be aware of
+ * this special case when writing other functions that use namev_lookup().
+ * Because you are the one writing nearly all of the calls to namev_lookup(), it
+ * is up to you both how you handle all inputs (i.e. dir or name is null,
+ * namelen is 0), and whether namev_lookup() even gets called with a bad input.
+ */
+long namev_lookup(vnode_t *dir, const char *name, size_t namelen,
+ vnode_t **res_vnode)
+{
+ NOT_YET_IMPLEMENTED("VFS: ***none***");
+ return ret;
+}
+
+/*
+ * Find the next meaningful token in a string representing a path.
+ *
+ * Returns the token and sets `len` to be the token's length.
+ *
+ * Once all tokens have been returned, the next char* returned is either NULL
+ * or "" (the empty string). In order to handle both, if you're calling
+ * this in a loop, we suggest terminating the loop once the value returned
+ * in len is 0
+ *
+ * Example usage:
+ * - "/dev/null"
+ * ==> *search would point to the first character of "/null"
+ * ==> *len would be 3 (as "dev" is of length 3)
+ * ==> namev_tokenize would return a pointer to the
+ * first character of "dev/null"
+ *
+ * - "a/b/c"
+ * ==> *search would point to the first character of "/b/c"
+ * ==> *len would be 1 (as "a" is of length 1)
+ * ==> namev_tokenize would return a pointer to the first character
+ * of "a/b/c"
+ *
+ * We highly suggest testing this function outside of Weenix; for instance
+ * using an online compiler or compiling and testing locally to fully
+ * understand its behavior. See handout for an example.
+ */
+static const char *namev_tokenize(const char **search, size_t *len)
+{
+ const char *begin;
+
+ if (*search == NULL)
+ {
+ *len = 0;
+ return NULL;
+ }
+
+ KASSERT(NULL != *search);
+
+ /* Skip initial '/' to find the beginning of the token. */
+ while (**search == '/')
+ {
+ (*search)++;
+ }
+
+ /* Determine the length of the token by searching for either the
+ * next '/' or the end of the path. */
+ begin = *search;
+ *len = 0;
+ while (**search && **search != '/')
+ {
+ (*len)++;
+ (*search)++;
+ }
+
+ if (!**search)
+ {
+ *search = NULL;
+ }
+
+ return begin;
+}
+
+/*
+ * Parse path and return in `res_vnode` the vnode corresponding to the directory
+ * containing the basename (last element) of path. `base` must not be locked on
+ * entry or on return. `res_vnode` must not be locked on return. Return via `name`
+ * and `namelen` the basename of path.
+ *
+ * Return 0 on success, or:
+ * - EINVAL: path refers to an empty string
+ * - Propagate errors from namev_lookup()
+ *
+ * Hints:
+ * - When *calling* namev_dir(), if it is unclear what to pass as the `base`, you
+ * should use `curproc->p_cwd` (think about why this makes sense).
+ * - `curproc` is a global variable that represents the current running process
+ * (a proc_t struct), which has a field called p_cwd.
+ * - The first parameter, base, is the vnode from which to start resolving
+ * path, unless path starts with a '/', in which case you should start at
+ * the root vnode, vfs_root_fs.fs_root.
+ * - Use namev_lookup() to handle each individual lookup. When looping, be
+ * careful about locking and refcounts, and make sure to clean up properly
+ * upon failure.
+ * - namev_lookup() should return with the found vnode unlocked, unless the
+ * found vnode is the same as the given directory (e.g. "/./."). Be mindful
+ * of this special case, and any locking/refcounting that comes with it.
+ * - When parsing the path, you do not need to implement hand-over-hand
+ * locking. That is, when calling `namev_lookup(dir, path, pathlen, &out)`,
+ * it is safe to put away and unlock dir before locking `out`.
+ * - You are encouraged to use namev_tokenize() to help parse path.
+ * - Whether you're using the provided base or the root vnode, you will have
+ * to explicitly lock and reference your starting vnode before using it.
+ * - Don't allocate memory to return name. Just set name to point into the
+ * correct part of path.
+ *
+ * Example usage:
+ * - "/a/.././//b/ccc/" ==> res_vnode = vnode for b, name = "ccc", namelen = 3
+ * - "tmp/..//." ==> res_vnode = base, name = ".", namelen = 1
+ * - "/dev/null" ==> rev_vnode = vnode for /dev, name = "null", namelen = 4
+ * For more examples of expected behavior, you can try out the command line
+ * utilities `dirname` and `basename` on your virtual machine or a Brown
+ * department machine.
+ */
+long namev_dir(vnode_t *base, const char *path, vnode_t **res_vnode,
+ const char **name, size_t *namelen)
+{
+ NOT_YET_IMPLEMENTED("VFS: ***none***");
+ return 0;
+}
+
+/*
+ * Open the file specified by `base` and `path`, or create it, if necessary.
+ * Return the file's vnode via `res_vnode`, which should be returned unlocked
+ * and with an added reference.
+ *
+ * Return 0 on success, or:
+ * - EINVAL: O_CREAT is specified but path implies a directory
+ * - ENAMETOOLONG: path basename is too long
+ * - ENOTDIR: Attempting to open a regular file as a directory
+ * - Propagate errors from namev_dir() and namev_lookup()
+ *
+ * Hints:
+ * - A path ending in '/' implies that the basename is a directory.
+ * - Use namev_dir() to get the directory containing the basename.
+ * - Use namev_lookup() to try to obtain the desired vnode.
+ * - If namev_lookup() fails and O_CREAT is specified in oflags, use
+ * the parent directory's vnode operation mknod to create the vnode.
+ * Use the basename info from namev_dir(), and the mode and devid
+ * provided to namev_open().
+ * - Use the macro S_ISDIR() to check if a vnode actually is a directory.
+ * - Use the macro NAME_LEN to check the basename length. Check out
+ * ramfs_mknod() to confirm that the name should be null-terminated.
+ */
+long namev_open(vnode_t *base, const char *path, int oflags, int mode,
+ devid_t devid, struct vnode **res_vnode)
+{
+ NOT_YET_IMPLEMENTED("VFS: ***none***");
+ return 0;
+}
+
+/*
+ * Wrapper around namev_open with O_RDONLY and 0 mode/devid
+ */
+long namev_resolve(vnode_t *base, const char *path, vnode_t **res_vnode)
+{
+ return namev_open(base, path, O_RDONLY, 0, 0, res_vnode);
+}
+
+#ifdef __GETCWD__
+/* Finds the name of 'entry' in the directory 'dir'. The name is writen
+ * to the given buffer. On success 0 is returned. If 'dir' does not
+ * contain 'entry' then -ENOENT is returned. If the given buffer cannot
+ * hold the result then it is filled with as many characters as possible
+ * and a null terminator, -ERANGE is returned.
+ *
+ * Files can be uniquely identified within a file system by their
+ * inode numbers. */
+int lookup_name(vnode_t *dir, vnode_t *entry, char *buf, size_t size)
+{
+ NOT_YET_IMPLEMENTED("GETCWD: ***none***");
+ return -ENOENT;
+}
+
+NOT_YET_IMPLEMENTED("GETCWD: ***none***");
+
+/* Used to find the absolute path of the directory 'dir'. Since
+ * directories cannot have more than one link there is always
+ * a unique solution. The path is writen to the given buffer.
+ * On success 0 is returned. On error this function returns a
+ * negative error code. See the man page for getcwd(3) for
+ * possible errors. Even if an error code is returned the buffer
+ * will be filled with a valid string which has some partial
+ * information about the wanted path. */
+ssize_t lookup_dirpath(vnode_t *dir, char *buf, size_t osize)
+{
+ NOT_YET_IMPLEMENTED("GETCWD: ***none***");
+
+ return -ENOENT;
+}
+#endif /* __GETCWD__ */
diff --git a/kernel/fs/open.c b/kernel/fs/open.c
new file mode 100644
index 0000000..fa6fe12
--- /dev/null
+++ b/kernel/fs/open.c
@@ -0,0 +1,67 @@
+#include "errno.h"
+#include "fs/fcntl.h"
+#include "fs/file.h"
+#include "fs/vfs.h"
+#include "fs/vfs_syscall.h"
+#include "fs/vnode.h"
+#include "globals.h"
+#include "util/debug.h"
+#include <fs/vnode.h>
+
+// NOTE: IF DOING MULTI-THREADED PROCS, NEED TO SYNCHRONIZE ACCESS TO FILE
+// DESCRIPTORS, AND, MORE GENERALLY SPEAKING, p_files, IN PARTICULAR IN THIS
+// FUNCTION AND ITS CALLERS.
+/*
+ * Go through curproc->p_files and find the first null entry.
+ * If one exists, set fd to that index and return 0.
+ *
+ * Error cases get_empty_fd is responsible for generating:
+ * - EMFILE: no empty file descriptor
+ */
+long get_empty_fd(int *fd)
+{
+ for (*fd = 0; *fd < NFILES; (*fd)++)
+ {
+ if (!curproc->p_files[*fd])
+ {
+ return 0;
+ }
+ }
+ *fd = -1;
+ return -EMFILE;
+}
+
+/*
+ * Open the file at the provided path with the specified flags.
+ *
+ * Returns the file descriptor on success, or error cases:
+ * - EINVAL: Invalid oflags
+ * - EISDIR: Trying to open a directory with write access
+ * - ENXIO: Blockdev or chardev vnode does not have an actual underlying device
+ * - ENOMEM: Not enough kernel memory (if fcreate() fails)
+ *
+ * Hints:
+ * 1) Use get_empty_fd() to get an available fd.
+ * 2) Use namev_open() with oflags, mode S_IFREG, and devid 0.
+ * 3) Check for EISDIR and ENXIO errors.
+ * 4) Convert oflags (O_RDONLY, O_WRONLY, O_RDWR, O_APPEND) into corresponding
+ * file access flags (FMODE_READ, FMODE_WRITE, FMODE_APPEND).
+ * 5) Use fcreate() to create and initialize the corresponding file descriptor
+ * with the vnode from 2) and the mode from 4).
+ *
+ * When checking oflags, you only need to check that the read and write
+ * permissions are consistent. However, because O_RDONLY is 0 and O_RDWR is 2,
+ * there's no way to tell if both were specified. So, you really only need
+ * to check if O_WRONLY and O_RDWR were specified.
+ *
+ * If O_TRUNC specified and the vnode represents a regular file, make sure to call the
+ * the vnode's truncate routine (to reduce the size of the file to 0).
+ *
+ * If a vnode represents a chardev or blockdev, then the appropriate field of
+ * the vnode->vn_dev union will point to the device. Otherwise, the union will be NULL.
+ */
+long do_open(const char *filename, int oflags)
+{
+ NOT_YET_IMPLEMENTED("VFS: ***none***");
+ return -1;
+}
diff --git a/kernel/fs/pipe.c b/kernel/fs/pipe.c
new file mode 100644
index 0000000..b1d365f
--- /dev/null
+++ b/kernel/fs/pipe.c
@@ -0,0 +1,256 @@
+/*
+ * FILE: pipe.c
+ * AUTH: eric
+ * DESC: Implementation of pipe(2) system call.
+ * DATE: Thu Dec 26 17:08:34 2013
+ */
+
+#include "errno.h"
+#include "globals.h"
+
+#include "fs/file.h"
+#include "fs/pipe.h"
+#include "fs/stat.h"
+#include "fs/vfs.h"
+#include "fs/vfs_syscall.h"
+#include "fs/vnode.h"
+
+#include "mm/kmalloc.h"
+#include "mm/slab.h"
+
+#include "util/debug.h"
+#include "util/string.h"
+
+#define PIPE_BUF_SIZE 4096
+
+static void pipe_read_vnode(fs_t *fs, vnode_t *vnode);
+
+static void pipe_delete_vnode(fs_t *fs, vnode_t *vnode);
+
+static fs_ops_t pipe_fsops = {.read_vnode = pipe_read_vnode,
+ .delete_vnode = pipe_delete_vnode,
+ .umount = NULL};
+
+static fs_t pipe_fs = {.fs_dev = "pipe",
+ .fs_type = "pipe",
+ .fs_ops = &pipe_fsops,
+ .fs_root = NULL,
+ .fs_i = NULL};
+
+static long pipe_read(vnode_t *vnode, size_t pos, void *buf, size_t count);
+
+static long pipe_write(vnode_t *vnode, size_t pos, const void *buf,
+ size_t count);
+
+static long pipe_stat(vnode_t *vnode, stat_t *ss);
+
+static long pipe_acquire(vnode_t *vnode, file_t *file);
+
+static long pipe_release(vnode_t *vnode, file_t *file);
+
+static vnode_ops_t pipe_vops = {
+ .read = pipe_read,
+ .write = pipe_write,
+ .mmap = NULL,
+ .mknod = NULL,
+ .lookup = NULL,
+ .link = NULL,
+ .unlink = NULL,
+ .mkdir = NULL,
+ .rmdir = NULL,
+ .readdir = NULL,
+ .stat = pipe_stat,
+ .acquire = pipe_acquire,
+ .release = pipe_release,
+ .get_pframe = NULL,
+ .fill_pframe = NULL,
+ .flush_pframe = NULL,
+};
+
+/* struct pipe defines some data specific to pipes. One of these
+ should be present in the vn_i field of each pipe vnode. */
+typedef struct pipe
+{
+ /* Buffer for data in the pipe, which has been written but not yet read. */
+ char *pv_buf;
+ /*
+ * Position of the head and number of characters in the buffer. You can
+ * write in characters at position head so long as size does not grow beyond
+ * the pipe buffer size.
+ */
+ off_t pv_head;
+ size_t pv_size;
+ /* Number of file descriptors using this pipe for read and write. */
+ int pv_readers;
+ int pv_writers;
+ /*
+ * Mutexes for reading and writing. Without these, readers might get non-
+ * contiguous reads in a single call (for example, if they empty the buffer
+ * but still have more to read, then the writer continues writing, waking up
+ * a different thread first) and similarly for writers.
+ */
+ kmutex_t pv_rdlock;
+ kmutex_t pv_wrlock;
+ /*
+ * Waitqueues for threads attempting to read from an empty buffer, or
+ * write to a full buffer. When the pipe becomes non-empty (or non-full)
+ * then the corresponding waitq should be broadcasted on to make sure all
+ * of the threads get a chance to go.
+ */
+ ktqueue_t pv_read_waitq;
+ ktqueue_t pv_write_waitq;
+} pipe_t;
+
+#define VNODE_TO_PIPE(vn) ((pipe_t *)((vn)->vn_i))
+
+static slab_allocator_t *pipe_allocator = NULL;
+static int next_pno = 0;
+
+void pipe_init(void)
+{
+ pipe_allocator = slab_allocator_create("pipe", sizeof(pipe_t));
+ KASSERT(pipe_allocator);
+}
+
+/*
+ * Create a pipe struct here. You are going to need to allocate all
+ * of the necessary structs and buffers, and then initialize all of
+ * the necessary fields (head, size, readers, writers, and the locks
+ * and queues.)
+ */
+static pipe_t *pipe_create(void)
+{
+ NOT_YET_IMPLEMENTED("PIPES: ***none***");
+ return NULL;
+}
+
+/*
+ * Free all necessary memory.
+ */
+static void pipe_destroy(pipe_t *pipe)
+{
+ NOT_YET_IMPLEMENTED("PIPES: ***none***");
+}
+
+/* pipefs vnode operations */
+static void pipe_read_vnode(fs_t *fs, vnode_t *vnode)
+{
+ vnode->vn_ops = &pipe_vops;
+ vnode->vn_mode = S_IFIFO;
+ vnode->vn_len = 0;
+ vnode->vn_i = NULL;
+}
+
+static void pipe_delete_vnode(fs_t *fs, vnode_t *vnode)
+{
+ pipe_t *p = VNODE_TO_PIPE(vnode);
+ if (p)
+ {
+ pipe_destroy(p);
+ }
+}
+
+/*
+ * Gets a new vnode representing a pipe. The reason
+ * why we don't just do this setup in pipe_read_vnode
+ * is that the creation of the pipe data might fail, since
+ * there is memory allocation going on in there. Thus,
+ * we split it into two steps, the first of which relies on
+ * pipe_read_vnode to do some setup, and then the pipe_create
+ * call, at which point we can safely vput the allocated
+ * vnode if pipe_create fails.
+ */
+static vnode_t *pget(void)
+{
+ NOT_YET_IMPLEMENTED("PIPES: ***none***");
+ return NULL;
+}
+
+/*
+ * An implementation of the pipe(2) system call. You really
+ * only have to worry about a few things:
+ * o Running out of memory when allocating the vnode, at which
+ * point you should fail with ENOMEM;
+ * o Running out of file descriptors, in which case you should
+ * fail with EMFILE.
+ * Once all of the structures are set up, just put the read-end
+ * file descriptor of the pipe into pipefd[0], and the write-end
+ * descriptor into pipefd[1].
+ */
+int do_pipe(int pipefd[2])
+{
+ NOT_YET_IMPLEMENTED("PIPES: ***none***");
+ return -ENOTSUP;
+}
+
+/*
+ * When reading from a pipe, you should make sure there are enough characters in
+ * the buffer to read. If there are, grab them and move up the tail by
+ * subtracting from size. offset is ignored. Also, remember to take the reader
+ * lock to prevent other threads from reading while you are waiting for more
+ * characters.
+ *
+ * This might block, e.g. if there are no or not enough characters to read.
+ * It might be the case that there are no more writers and we aren't done
+ * reading. However, in situations like this, there is no way to open the pipe
+ * for writing again so no more writers will ever put characters in the pipe.
+ * The reader should just take as much as it needs (or barring that, as much as
+ * it can get) and return with a partial buffer.
+ */
+static long pipe_read(vnode_t *vnode, size_t pos, void *buf, size_t count)
+{
+ NOT_YET_IMPLEMENTED("PIPES: ***none***");
+ return -EINVAL;
+}
+
+/*
+ * Writing to a pipe is the dual of reading: if there is room, we can write our
+ * data and go, but if not, we have to wait until there is more room and alert
+ * any potential readers. Like above, you should take the writer lock to make
+ * sure your write is contiguous.
+ *
+ * If there are no more readers, we have a broken pipe, and should fail with
+ * the EPIPE error number.
+ */
+static long pipe_write(vnode_t *vnode, size_t pos, const void *buf,
+ size_t count)
+{
+ NOT_YET_IMPLEMENTED("PIPES: ***none***");
+ return -EINVAL;
+}
+
+/*
+ * It's still possible to stat a pipe using the fstat call, which takes a file
+ * descriptor. Pipes don't have too much information, though. The only ones that
+ * matter here are st_mode and st_ino, though you want to zero out some of the
+ * others.
+ */
+static long pipe_stat(vnode_t *vnode, stat_t *ss)
+{
+ NOT_YET_IMPLEMENTED("PIPES: ***none***");
+ return -EINVAL;
+}
+
+/*
+ * If someone is opening the read end of the pipe, we need to increment
+ * the reader count, and the same for the writer count if a file open
+ * for writing is acquiring this vnode. This count needs to be accurate
+ * for correct reading and writing behavior.
+ */
+static long pipe_acquire(vnode_t *vnode, file_t *file)
+{
+ NOT_YET_IMPLEMENTED("PIPES: ***none***");
+ return 0;
+}
+
+/*
+ * Subtract from the reader or writer count as necessary here. If either
+ * count hits zero, you are going to need to wake up the other group of
+ * threads so they can either return with their partial read or notice
+ * the broken pipe.
+ */
+static long pipe_release(vnode_t *vnode, file_t *file)
+{
+ NOT_YET_IMPLEMENTED("PIPES: ***none***");
+ return 0;
+}
diff --git a/kernel/fs/ramfs/ramfs.c b/kernel/fs/ramfs/ramfs.c
new file mode 100644
index 0000000..72547c4
--- /dev/null
+++ b/kernel/fs/ramfs/ramfs.c
@@ -0,0 +1,852 @@
+/*
+ * This is a special filesystem designed to be a test filesystem before s5fs has
+ * been written. It is an in-memory filesystem that supports almost all of the
+ * vnode operations. It has the following restrictions:
+ *
+ * o File sizes are limited to a single page (4096 bytes) in order
+ * to keep the code simple.
+ *
+ * o There is no support for fill_pframe, etc.
+ *
+ * o There is a maximum directory size limit
+ *
+ * o There is a maximum number of files/directories limit
+ */
+
+#include "fs/ramfs/ramfs.h"
+#include "errno.h"
+#include "fs/dirent.h"
+#include "fs/stat.h"
+#include "fs/vfs.h"
+#include "fs/vnode.h"
+#include "globals.h"
+#include "kernel.h"
+#include "mm/kmalloc.h"
+#include "mm/slab.h"
+#include "util/debug.h"
+#include "util/string.h"
+
+/*
+ * Filesystem operations
+ */
+static void ramfs_read_vnode(fs_t *fs, vnode_t *vn);
+
+static void ramfs_delete_vnode(fs_t *fs, vnode_t *vn);
+
+static long ramfs_umount(fs_t *fs);
+
+static fs_ops_t ramfs_ops = {.read_vnode = ramfs_read_vnode,
+ .delete_vnode = ramfs_delete_vnode,
+ .umount = ramfs_umount};
+
+/*
+ * vnode operations
+ */
+static ssize_t ramfs_read(vnode_t *file, size_t offset, void *buf,
+ size_t count);
+
+static ssize_t ramfs_write(vnode_t *file, size_t offset, const void *buf,
+ size_t count);
+
+/* getpage */
+static ssize_t ramfs_create(vnode_t *dir, const char *name, size_t name_len,
+ vnode_t **result);
+
+static ssize_t ramfs_mknod(struct vnode *dir, const char *name, size_t name_len,
+ int mode, devid_t devid, struct vnode **out);
+
+static ssize_t ramfs_lookup(vnode_t *dir, const char *name, size_t namelen,
+ vnode_t **out);
+
+static long ramfs_link(vnode_t *dir, const char *name, size_t namelen,
+ vnode_t *child);
+
+static ssize_t ramfs_unlink(vnode_t *dir, const char *name, size_t name_len);
+
+static ssize_t ramfs_rename(vnode_t *olddir, const char *oldname,
+ size_t oldnamelen, vnode_t *newdir,
+ const char *newname, size_t newnamelen);
+
+static ssize_t ramfs_mkdir(vnode_t *dir, const char *name, size_t name_len,
+ struct vnode **out);
+
+static ssize_t ramfs_rmdir(vnode_t *dir, const char *name, size_t name_len);
+
+static ssize_t ramfs_readdir(vnode_t *dir, size_t offset, struct dirent *d);
+
+static ssize_t ramfs_stat(vnode_t *file, stat_t *buf);
+
+static void ramfs_truncate_file(vnode_t *file);
+
+static vnode_ops_t ramfs_dir_vops = {.read = NULL,
+ .write = NULL,
+ .mmap = NULL,
+ .mknod = ramfs_mknod,
+ .lookup = ramfs_lookup,
+ .link = ramfs_link,
+ .unlink = ramfs_unlink,
+ .rename = ramfs_rename,
+ .mkdir = ramfs_mkdir,
+ .rmdir = ramfs_rmdir,
+ .readdir = ramfs_readdir,
+ .stat = ramfs_stat,
+ .acquire = NULL,
+ .release = NULL,
+ .get_pframe = NULL,
+ .fill_pframe = NULL,
+ .flush_pframe = NULL,
+ .truncate_file = NULL};
+
+static vnode_ops_t ramfs_file_vops = {.read = ramfs_read,
+ .write = ramfs_write,
+ .mmap = NULL,
+ .mknod = NULL,
+ .lookup = NULL,
+ .link = NULL,
+ .unlink = NULL,
+ .mkdir = NULL,
+ .rmdir = NULL,
+ .stat = ramfs_stat,
+ .acquire = NULL,
+ .release = NULL,
+ .get_pframe = NULL,
+ .fill_pframe = NULL,
+ .flush_pframe = NULL,
+ .truncate_file = ramfs_truncate_file};
+
+/*
+ * The ramfs 'inode' structure
+ */
+typedef struct ramfs_inode
+{
+ size_t rf_size; /* Total file size */
+ ino_t rf_ino; /* Inode number */
+ char *rf_mem; /* Memory for this file (1 page) */
+ ssize_t rf_mode; /* Type of file */
+ ssize_t rf_linkcount; /* Number of links to this file */
+} ramfs_inode_t;
+
+#define RAMFS_TYPE_DATA 0
+#define RAMFS_TYPE_DIR 1
+#define RAMFS_TYPE_CHR 2
+#define RAMFS_TYPE_BLK 3
+
+#define VNODE_TO_RAMFSINODE(vn) ((ramfs_inode_t *)(vn)->vn_i)
+#define VNODE_TO_RAMFS(vn) ((ramfs_t *)(vn)->vn_fs->fs_i)
+#define VNODE_TO_DIRENT(vn) ((ramfs_dirent_t *)VNODE_TO_RAMFSINODE(vn)->rf_mem)
+
+/*
+ * ramfs filesystem structure
+ */
+#define RAMFS_MAX_FILES 64
+
+typedef struct ramfs
+{
+ ramfs_inode_t *rfs_inodes[RAMFS_MAX_FILES]; /* Array of all files */
+} ramfs_t;
+
+/*
+ * For directories, we simply store an array of (ino, name) pairs in the
+ * memory portion of the inode.
+ */
+typedef struct ramfs_dirent
+{
+ ssize_t rd_ino; /* Inode number of this entry */
+ char rd_name[NAME_LEN]; /* Name of this entry */
+} ramfs_dirent_t;
+
+#define RAMFS_MAX_DIRENT ((size_t)(PAGE_SIZE / sizeof(ramfs_dirent_t)))
+
+/* Helper functions */
+static ssize_t ramfs_alloc_inode(fs_t *fs, ssize_t type, devid_t devid)
+{
+ ramfs_t *rfs = (ramfs_t *)fs->fs_i;
+ KASSERT((RAMFS_TYPE_DATA == type) || (RAMFS_TYPE_DIR == type) ||
+ (RAMFS_TYPE_CHR == type) || (RAMFS_TYPE_BLK == type));
+ /* Find a free inode */
+ ssize_t i;
+ for (i = 0; i < RAMFS_MAX_FILES; i++)
+ {
+ if (NULL == rfs->rfs_inodes[i])
+ {
+ ramfs_inode_t *inode;
+ if (NULL == (inode = kmalloc(sizeof(ramfs_inode_t))))
+ {
+ return -ENOSPC;
+ }
+
+ if (RAMFS_TYPE_CHR == type || RAMFS_TYPE_BLK == type)
+ {
+ /* Don't need any space in memory, so put devid in here */
+ inode->rf_mem = (char *)(uint64_t)devid;
+ }
+ else
+ {
+ /* We allocate space for the file's contents immediately */
+ if (NULL == (inode->rf_mem = page_alloc()))
+ {
+ kfree(inode);
+ return -ENOSPC;
+ }
+ memset(inode->rf_mem, 0, PAGE_SIZE);
+ }
+ inode->rf_size = 0;
+ inode->rf_ino = i;
+ inode->rf_mode = type;
+ inode->rf_linkcount = 1;
+
+ /* Install in table and return */
+ rfs->rfs_inodes[i] = inode;
+ return i;
+ }
+ }
+ return -ENOSPC;
+}
+
+/*
+ * Function implementations
+ */
+
+long ramfs_mount(struct fs *fs)
+{
+ /* Allocate filesystem */
+ ramfs_t *rfs = kmalloc(sizeof(ramfs_t));
+ if (NULL == rfs)
+ {
+ return -ENOMEM;
+ }
+
+ memset(rfs->rfs_inodes, 0, sizeof(rfs->rfs_inodes));
+
+ fs->fs_i = rfs;
+ fs->fs_ops = &ramfs_ops;
+
+ /* Set up root inode */
+ ssize_t root_ino;
+ if (0 > (root_ino = ramfs_alloc_inode(fs, RAMFS_TYPE_DIR, 0)))
+ {
+ return root_ino;
+ }
+
+ slab_allocator_t *allocator =
+ slab_allocator_create("ramfs_node", sizeof(vnode_t));
+ fs->fs_vnode_allocator = allocator;
+ KASSERT(allocator);
+
+ KASSERT(0 == root_ino);
+ ramfs_inode_t *root = rfs->rfs_inodes[root_ino];
+
+ /* Set up '.' and '..' in the root directory */
+ ramfs_dirent_t *rootdent = (ramfs_dirent_t *)root->rf_mem;
+ rootdent->rd_ino = 0;
+ strcpy(rootdent->rd_name, ".");
+ rootdent++;
+ rootdent->rd_ino = 0;
+ strcpy(rootdent->rd_name, "..");
+
+ /* Increase root inode size accordingly */
+ root->rf_size = 2 * sizeof(ramfs_dirent_t);
+
+ /* Put the root in the inode table */
+ rfs->rfs_inodes[0] = root;
+
+ /* And vget the root vnode */
+ fs->fs_root = vget(fs, 0);
+
+ return 0;
+}
+
+static void ramfs_read_vnode(fs_t *fs, vnode_t *vn)
+{
+ ramfs_t *rfs = VNODE_TO_RAMFS(vn);
+ ramfs_inode_t *inode = rfs->rfs_inodes[vn->vn_vno];
+ KASSERT(inode && inode->rf_ino == vn->vn_vno);
+
+ inode->rf_linkcount++;
+
+ vn->vn_i = inode;
+ vn->vn_len = inode->rf_size;
+
+ switch (inode->rf_mode)
+ {
+ case RAMFS_TYPE_DATA:
+ vn->vn_mode = S_IFREG;
+ vn->vn_ops = &ramfs_file_vops;
+ break;
+ case RAMFS_TYPE_DIR:
+ vn->vn_mode = S_IFDIR;
+ vn->vn_ops = &ramfs_dir_vops;
+ break;
+ case RAMFS_TYPE_CHR:
+ vn->vn_mode = S_IFCHR;
+ vn->vn_ops = NULL;
+ vn->vn_devid = (devid_t)(uint64_t)(inode->rf_mem);
+ break;
+ case RAMFS_TYPE_BLK:
+ vn->vn_mode = S_IFBLK;
+ vn->vn_ops = NULL;
+ vn->vn_devid = (devid_t)(uint64_t)(inode->rf_mem);
+ break;
+ default:
+ panic("inode %ld has unknown/invalid type %ld!!\n",
+ (ssize_t)vn->vn_vno, (ssize_t)inode->rf_mode);
+ }
+}
+
+static void ramfs_delete_vnode(fs_t *fs, vnode_t *vn)
+{
+ ramfs_inode_t *inode = VNODE_TO_RAMFSINODE(vn);
+ ramfs_t *rfs = VNODE_TO_RAMFS(vn);
+
+ if (0 == --inode->rf_linkcount)
+ {
+ KASSERT(rfs->rfs_inodes[vn->vn_vno] == inode);
+
+ rfs->rfs_inodes[vn->vn_vno] = NULL;
+ if (inode->rf_mode == RAMFS_TYPE_DATA ||
+ inode->rf_mode == RAMFS_TYPE_DIR)
+ {
+ page_free(inode->rf_mem);
+ }
+ /* otherwise, inode->rf_mem is a devid */
+
+ kfree(inode);
+ }
+}
+
+static ssize_t ramfs_umount(fs_t *fs)
+{
+ /* We don't need to do any flushing or anything as everything is in memory.
+ * Just free all of our allocated memory */
+ ramfs_t *rfs = (ramfs_t *)fs->fs_i;
+
+ vput(&fs->fs_root);
+
+ /* Free all the inodes */
+ ssize_t i;
+ for (i = 0; i < RAMFS_MAX_FILES; i++)
+ {
+ if (NULL != rfs->rfs_inodes[i])
+ {
+ if (NULL != rfs->rfs_inodes[i]->rf_mem &&
+ (rfs->rfs_inodes[i]->rf_mode == RAMFS_TYPE_DATA ||
+ rfs->rfs_inodes[i]->rf_mode == RAMFS_TYPE_DIR))
+ {
+ page_free(rfs->rfs_inodes[i]->rf_mem);
+ }
+ kfree(rfs->rfs_inodes[i]);
+ }
+ }
+
+ return 0;
+}
+
+static ssize_t ramfs_create(vnode_t *dir, const char *name, size_t name_len,
+ vnode_t **result)
+{
+ vnode_t *vn;
+ size_t i;
+ ramfs_dirent_t *entry;
+
+ /* Look for space in the directory */
+ entry = VNODE_TO_DIRENT(dir);
+ for (i = 0; i < RAMFS_MAX_DIRENT; i++, entry++)
+ {
+ if (!entry->rd_name[0])
+ {
+ break;
+ }
+ }
+
+ if (i == RAMFS_MAX_DIRENT)
+ {
+ return -ENOSPC;
+ }
+
+ /* Allocate an inode */
+ ssize_t ino;
+ if (0 > (ino = ramfs_alloc_inode(dir->vn_fs, RAMFS_TYPE_DATA, 0)))
+ {
+ return ino;
+ }
+
+ /* Get a vnode, set entry in directory */
+ vn = vget(dir->vn_fs, (ino_t)ino);
+
+ entry->rd_ino = vn->vn_vno;
+ strncpy(entry->rd_name, name, MIN(name_len, NAME_LEN - 1));
+ entry->rd_name[MIN(name_len, NAME_LEN - 1)] = '\0';
+
+ VNODE_TO_RAMFSINODE(dir)->rf_size += sizeof(ramfs_dirent_t);
+
+ *result = vn;
+
+ return 0;
+}
+
+static ssize_t ramfs_mknod(struct vnode *dir, const char *name, size_t name_len,
+ int mode, devid_t devid, struct vnode **out)
+{
+ size_t i;
+ ramfs_dirent_t *entry;
+
+ /* Look for space in the directory */
+ entry = VNODE_TO_DIRENT(dir);
+ for (i = 0; i < RAMFS_MAX_DIRENT; i++, entry++)
+ {
+ if (!entry->rd_name[0])
+ {
+ break;
+ }
+ }
+
+ if (i == RAMFS_MAX_DIRENT)
+ {
+ return -ENOSPC;
+ }
+
+ ssize_t ino;
+ if (S_ISCHR(mode))
+ {
+ ino = ramfs_alloc_inode(dir->vn_fs, RAMFS_TYPE_CHR, devid);
+ }
+ else if (S_ISBLK(mode))
+ {
+ ino = ramfs_alloc_inode(dir->vn_fs, RAMFS_TYPE_BLK, devid);
+ }
+ else if (S_ISREG(mode))
+ {
+ ino = ramfs_alloc_inode(dir->vn_fs, RAMFS_TYPE_DATA, devid);
+ }
+ else
+ {
+ panic("Invalid mode!\n");
+ }
+
+ if (ino < 0)
+ {
+ return ino;
+ }
+
+ /* Set entry in directory */
+ entry->rd_ino = ino;
+ strncpy(entry->rd_name, name, MIN(name_len, NAME_LEN - 1));
+ entry->rd_name[MIN(name_len, NAME_LEN - 1)] = '\0';
+
+ VNODE_TO_RAMFSINODE(dir)->rf_size += sizeof(ramfs_dirent_t);
+
+ vnode_t *child = vget(dir->vn_fs, ino);
+
+ dbg(DBG_VFS, "creating ino(%ld), vno(%d) with path: %s\n", ino,
+ child->vn_vno, entry->rd_name);
+
+ KASSERT(child);
+ *out = child;
+ return 0;
+}
+
+static ssize_t ramfs_lookup(vnode_t *dir, const char *name, size_t namelen,
+ vnode_t **out)
+{
+ size_t i;
+ ramfs_inode_t *inode = VNODE_TO_RAMFSINODE(dir);
+ ramfs_dirent_t *entry = (ramfs_dirent_t *)inode->rf_mem;
+
+ for (i = 0; i < RAMFS_MAX_DIRENT; i++, entry++)
+ {
+ if (name_match(entry->rd_name, name, namelen))
+ {
+ if (dir->vn_vno != entry->rd_ino)
+ {
+ fs_t *fs = (dir)->vn_fs;
+ *out = vget(fs, entry->rd_ino);
+ }
+ else
+ {
+ vref(dir);
+ *out = dir;
+ }
+ return 0;
+ }
+ }
+
+ return -ENOENT;
+}
+
+static ssize_t ramfs_find_dirent(vnode_t *dir, const char *name,
+ size_t namelen)
+{
+ size_t i;
+ ramfs_inode_t *inode = VNODE_TO_RAMFSINODE(dir);
+ ramfs_dirent_t *entry = (ramfs_dirent_t *)inode->rf_mem;
+
+ for (i = 0; i < RAMFS_MAX_DIRENT; i++, entry++)
+ {
+ if (name_match(entry->rd_name, name, namelen))
+ {
+ return entry->rd_ino;
+ }
+ }
+
+ return -ENOENT;
+}
+
+static ssize_t ramfs_append_dirent(vnode_t *dir, const char *name,
+ size_t namelen, vnode_t *child)
+{
+ vnode_t *vn;
+ size_t i;
+ ramfs_dirent_t *entry;
+
+ KASSERT(child->vn_fs == dir->vn_fs);
+
+ /* Look for space in the directory */
+ entry = VNODE_TO_DIRENT(dir);
+ for (i = 0; i < RAMFS_MAX_DIRENT; i++, entry++)
+ {
+ if (name_match(entry->rd_name, name, namelen))
+ {
+ return -EEXIST;
+ }
+
+ if (!entry->rd_name[0])
+ {
+ break;
+ }
+ }
+
+ if (i == RAMFS_MAX_DIRENT)
+ {
+ return -ENOSPC;
+ }
+
+ /* Set entry in parent */
+ entry->rd_ino = child->vn_vno;
+ strncpy(entry->rd_name, name, MIN(namelen, NAME_LEN - 1));
+ entry->rd_name[MIN(namelen, NAME_LEN - 1)] = '\0';
+
+ VNODE_TO_RAMFSINODE(dir)->rf_size += sizeof(ramfs_dirent_t);
+
+ /* Increase linkcount */
+ VNODE_TO_RAMFSINODE(child)->rf_linkcount++;
+
+ return 0;
+}
+
+static ssize_t ramfs_delete_dirent(vnode_t *dir, const char *name,
+ size_t namelen, vnode_t *child)
+{
+ int found = 0;
+ size_t i;
+ ramfs_dirent_t *entry = VNODE_TO_DIRENT(dir);
+ for (i = 0; i < RAMFS_MAX_DIRENT; i++, entry++)
+ {
+ if (name_match(entry->rd_name, name, namelen))
+ {
+ found = 1;
+ entry->rd_name[0] = '\0';
+ break;
+ }
+ }
+
+ if (!found)
+ {
+ return -EEXIST;
+ }
+
+ VNODE_TO_RAMFSINODE(dir)->rf_size -= sizeof(ramfs_dirent_t);
+ VNODE_TO_RAMFSINODE(child)->rf_linkcount--;
+
+ return 0;
+}
+
+static long ramfs_link(vnode_t *dir, const char *name, size_t namelen,
+ vnode_t *child)
+{
+ return ramfs_append_dirent(dir, name, namelen, child);
+}
+
+static ssize_t ramfs_unlink(vnode_t *dir, const char *name, size_t namelen)
+{
+ ssize_t ret;
+ size_t i;
+ ramfs_dirent_t *entry;
+
+ vnode_t *vn = dir;
+
+ long ino = ramfs_find_dirent(dir, name, namelen);
+ if (ino < 0)
+ {
+ return ino;
+ }
+
+ vnode_t *child = vget_locked(dir->vn_fs, (ino_t)ino);
+ KASSERT(!S_ISDIR(child->vn_mode) && "handled at VFS level");
+
+ ret = ramfs_delete_dirent(dir, name, namelen, child);
+ KASSERT(ret == 0);
+
+ vput_locked(&child);
+
+ return 0;
+}
+
+static ssize_t ramfs_rename(vnode_t *olddir, const char *oldname,
+ size_t oldnamelen, vnode_t *newdir,
+ const char *newname, size_t newnamelen)
+{
+ long ino = ramfs_find_dirent(olddir, oldname, oldnamelen);
+ if (ino < 0)
+ {
+ return ino;
+ }
+
+ vnode_t *oldvn = vget_locked(olddir->vn_fs, (ino_t)ino);
+ if (S_ISDIR(oldvn->vn_mode))
+ {
+ vput_locked(&oldvn);
+ return -EPERM;
+ }
+ if (S_ISDIR(oldvn->vn_mode))
+ {
+ vput_locked(&oldvn);
+ return -EISDIR;
+ }
+
+ /* Determine if an entry corresponding to `newname` already exists */
+ ino = ramfs_find_dirent(newdir, newname, newnamelen);
+ if (ino != -ENOENT)
+ {
+ if (ino < 0)
+ {
+ return ino;
+ }
+ return -EEXIST;
+ }
+
+ ssize_t ret = ramfs_append_dirent(newdir, newname, newnamelen, oldvn);
+ if (ret < 0)
+ {
+ vput_locked(&oldvn);
+ return ret;
+ }
+
+ ret = ramfs_delete_dirent(olddir, oldname, oldnamelen, oldvn);
+ vput_locked(&oldvn);
+
+ return ret;
+}
+
+static ssize_t ramfs_mkdir(vnode_t *dir, const char *name, size_t name_len,
+ struct vnode **out)
+{
+ vnode_t *vn;
+ size_t i;
+ ramfs_dirent_t *entry;
+
+ /* Look for space in the directory */
+ entry = VNODE_TO_DIRENT(dir);
+ for (i = 0; i < RAMFS_MAX_DIRENT; i++, entry++)
+ {
+ if (!entry->rd_name[0])
+ {
+ break;
+ }
+ }
+
+ if (i == RAMFS_MAX_DIRENT)
+ {
+ return -ENOSPC;
+ }
+
+ /* Allocate an inode */
+ ssize_t ino;
+ if (0 > (ino = ramfs_alloc_inode(dir->vn_fs, RAMFS_TYPE_DIR, 0)))
+ {
+ return ino;
+ }
+
+ /* Set entry in parent */
+ entry->rd_ino = ino;
+ strncpy(entry->rd_name, name, MIN(name_len, NAME_LEN - 1));
+ entry->rd_name[MIN(name_len, NAME_LEN - 1)] = '\0';
+
+ VNODE_TO_RAMFSINODE(dir)->rf_size += sizeof(ramfs_dirent_t);
+
+ /* Set up '.' and '..' in the directory */
+ entry = (ramfs_dirent_t *)VNODE_TO_RAMFS(dir)->rfs_inodes[ino]->rf_mem;
+ entry->rd_ino = ino;
+ strcpy(entry->rd_name, ".");
+ entry++;
+ entry->rd_ino = dir->vn_vno;
+ strcpy(entry->rd_name, "..");
+
+ /* Increase inode size accordingly */
+ VNODE_TO_RAMFS(dir)->rfs_inodes[ino]->rf_size = 2 * sizeof(ramfs_dirent_t);
+
+ /* This probably can't fail... (unless OOM :/) */
+ *out = vget(dir->vn_fs, ino);
+
+ return 0;
+}
+
+static ssize_t ramfs_rmdir(vnode_t *dir, const char *name, size_t name_len)
+{
+ ssize_t ret;
+ size_t i;
+ ramfs_dirent_t *entry;
+
+ KASSERT(!name_match(".", name, name_len) &&
+ !name_match("..", name, name_len));
+
+ long ino = ramfs_find_dirent(dir, name, name_len);
+ if (ino < 0)
+ {
+ return ino;
+ }
+
+ vnode_t *child = vget_locked(dir->vn_fs, (ino_t)ino);
+ if (!S_ISDIR(child->vn_mode))
+ {
+ vput_locked(&child);
+ return -ENOTDIR;
+ }
+
+ /* We have to make sure that this directory is empty */
+ entry = VNODE_TO_DIRENT(child);
+ for (i = 0; i < RAMFS_MAX_DIRENT; i++, entry++)
+ {
+ if (!strcmp(entry->rd_name, ".") || !strcmp(entry->rd_name, ".."))
+ {
+ continue;
+ }
+
+ if (entry->rd_name[0])
+ {
+ vput_locked(&child);
+ return -ENOTEMPTY;
+ }
+ }
+
+ /* Finally, remove the entry from the parent directory */
+ entry = VNODE_TO_DIRENT(dir);
+ for (i = 0; i < RAMFS_MAX_DIRENT; i++, entry++)
+ {
+ if (name_match(entry->rd_name, name, name_len))
+ {
+ entry->rd_name[0] = '\0';
+ break;
+ }
+ }
+ VNODE_TO_RAMFSINODE(dir)->rf_size -= sizeof(ramfs_dirent_t);
+
+ VNODE_TO_RAMFSINODE(child)->rf_linkcount--;
+ vput_locked(&child);
+
+ return 0;
+}
+
+static ssize_t ramfs_read(vnode_t *file, size_t offset, void *buf,
+ size_t count)
+{
+ ssize_t ret;
+ ramfs_inode_t *inode = VNODE_TO_RAMFSINODE(file);
+
+ KASSERT(!S_ISDIR(file->vn_mode));
+
+ if (offset > inode->rf_size)
+ {
+ ret = 0;
+ }
+ else if (offset + count > inode->rf_size)
+ {
+ ret = inode->rf_size - offset;
+ }
+ else
+ {
+ ret = count;
+ }
+
+ memcpy(buf, inode->rf_mem + offset, ret);
+ return ret;
+}
+
+static ssize_t ramfs_write(vnode_t *file, size_t offset, const void *buf,
+ size_t count)
+{
+ ssize_t ret;
+ ramfs_inode_t *inode = VNODE_TO_RAMFSINODE(file);
+
+ KASSERT(!S_ISDIR(file->vn_mode));
+
+ ret = MIN((size_t)count, (size_t)PAGE_SIZE - offset);
+ memcpy(inode->rf_mem + offset, buf, ret);
+
+ KASSERT(file->vn_len == inode->rf_size);
+ file->vn_len = MAX(file->vn_len, offset + ret);
+ inode->rf_size = file->vn_len;
+
+ return ret;
+}
+
+static ssize_t ramfs_readdir(vnode_t *dir, size_t offset, struct dirent *d)
+{
+ ssize_t ret = 0;
+ ramfs_dirent_t *dir_entry, *targ_entry;
+
+ KASSERT(S_ISDIR(dir->vn_mode));
+ KASSERT(0 == offset % sizeof(ramfs_dirent_t));
+
+ dir_entry = VNODE_TO_DIRENT(dir);
+ dir_entry = (ramfs_dirent_t *)(((char *)dir_entry) + offset);
+ targ_entry = dir_entry;
+
+ while ((offset < (size_t)(RAMFS_MAX_DIRENT * sizeof(ramfs_dirent_t))) &&
+ (!targ_entry->rd_name[0]))
+ {
+ ++targ_entry;
+ offset += sizeof(ramfs_dirent_t);
+ }
+
+ if (offset >= (size_t)(RAMFS_MAX_DIRENT * sizeof(ramfs_dirent_t)))
+ {
+ return 0;
+ }
+
+ ret = sizeof(ramfs_dirent_t) +
+ (targ_entry - dir_entry) * sizeof(ramfs_dirent_t);
+
+ d->d_ino = targ_entry->rd_ino;
+ d->d_off = 0; /* unused */
+ strncpy(d->d_name, targ_entry->rd_name, NAME_LEN - 1);
+ d->d_name[NAME_LEN - 1] = '\0';
+ return ret;
+}
+
+static ssize_t ramfs_stat(vnode_t *file, stat_t *buf)
+{
+ ramfs_inode_t *i = VNODE_TO_RAMFSINODE(file);
+ memset(buf, 0, sizeof(stat_t));
+ buf->st_mode = file->vn_mode;
+ buf->st_ino = (ssize_t)file->vn_vno;
+ buf->st_dev = 0;
+ if (file->vn_mode == S_IFCHR || file->vn_mode == S_IFBLK)
+ {
+ buf->st_rdev = (ssize_t)i->rf_mem;
+ }
+ buf->st_nlink = i->rf_linkcount - 1;
+ buf->st_size = (ssize_t)i->rf_size;
+ buf->st_blksize = (ssize_t)PAGE_SIZE;
+ buf->st_blocks = 1;
+
+ return 0;
+}
+
+static void ramfs_truncate_file(vnode_t *file)
+{
+ KASSERT(S_ISREG(file->vn_mode) && "This routine should only be called for regular files");
+ ramfs_inode_t *i = VNODE_TO_RAMFSINODE(file);
+ i->rf_size = 0;
+ file->vn_len = 0;
+ memset(i->rf_mem, 0, PAGE_SIZE);
+} \ No newline at end of file
diff --git a/kernel/fs/s5fs/s5fs.c b/kernel/fs/s5fs/s5fs.c
new file mode 100644
index 0000000..3790c1a
--- /dev/null
+++ b/kernel/fs/s5fs/s5fs.c
@@ -0,0 +1,860 @@
+#include "errno.h"
+#include "globals.h"
+#include "kernel.h"
+#include <mm/slab.h>
+
+#include "util/debug.h"
+#include "util/printf.h"
+#include "util/string.h"
+
+#include "proc/kmutex.h"
+
+#include "fs/dirent.h"
+#include "fs/file.h"
+#include "fs/s5fs/s5fs.h"
+#include "fs/s5fs/s5fs_subr.h"
+#include "fs/stat.h"
+
+#include "mm/kmalloc.h"
+
+static long s5_check_super(s5_super_t *super);
+
+static long s5fs_check_refcounts(fs_t *fs);
+
+static void s5fs_read_vnode(fs_t *fs, vnode_t *vn);
+
+static void s5fs_delete_vnode(fs_t *fs, vnode_t *vn);
+
+static long s5fs_umount(fs_t *fs);
+
+static void s5fs_sync(fs_t *fs);
+
+static ssize_t s5fs_read(vnode_t *vnode, size_t pos, void *buf, size_t len);
+
+static ssize_t s5fs_write(vnode_t *vnode, size_t pos, const void *buf,
+ size_t len);
+
+static long s5fs_mmap(vnode_t *file, mobj_t **ret);
+
+static long s5fs_mknod(struct vnode *dir, const char *name, size_t namelen,
+ int mode, devid_t devid, struct vnode **out);
+
+static long s5fs_lookup(vnode_t *dir, const char *name, size_t namelen,
+ vnode_t **out);
+
+static long s5fs_link(vnode_t *dir, const char *name, size_t namelen,
+ vnode_t *child);
+
+static long s5fs_unlink(vnode_t *vdir, const char *name, size_t namelen);
+
+static long s5fs_rename(vnode_t *olddir, const char *oldname, size_t oldnamelen,
+ vnode_t *newdir, const char *newname,
+ size_t newnamelen);
+
+static long s5fs_mkdir(vnode_t *dir, const char *name, size_t namelen,
+ struct vnode **out);
+
+static long s5fs_rmdir(vnode_t *parent, const char *name, size_t namelen);
+
+static long s5fs_readdir(vnode_t *vnode, size_t pos, struct dirent *d);
+
+static long s5fs_stat(vnode_t *vnode, stat_t *ss);
+
+static void s5fs_truncate_file(vnode_t *vnode);
+
+static long s5fs_release(vnode_t *vnode, file_t *file);
+
+static long s5fs_get_pframe(vnode_t *vnode, size_t pagenum, long forwrite,
+ pframe_t **pfp);
+
+static long s5fs_fill_pframe(vnode_t *vnode, pframe_t *pf);
+
+static long s5fs_flush_pframe(vnode_t *vnode, pframe_t *pf);
+
+fs_ops_t s5fs_fsops = {.read_vnode = s5fs_read_vnode,
+ .delete_vnode = s5fs_delete_vnode,
+ .umount = s5fs_umount,
+ .sync = s5fs_sync};
+
+static vnode_ops_t s5fs_dir_vops = {.read = NULL,
+ .write = NULL,
+ .mmap = NULL,
+ .mknod = s5fs_mknod,
+ .lookup = s5fs_lookup,
+ .link = s5fs_link,
+ .unlink = s5fs_unlink,
+ .rename = s5fs_rename,
+ .mkdir = s5fs_mkdir,
+ .rmdir = s5fs_rmdir,
+ .readdir = s5fs_readdir,
+ .stat = s5fs_stat,
+ .acquire = NULL,
+ .release = NULL,
+ .get_pframe = s5fs_get_pframe,
+ .fill_pframe = s5fs_fill_pframe,
+ .flush_pframe = s5fs_flush_pframe,
+ .truncate_file = NULL};
+
+static vnode_ops_t s5fs_file_vops = {.read = s5fs_read,
+ .write = s5fs_write,
+ .mmap = s5fs_mmap,
+ .mknod = NULL,
+ .lookup = NULL,
+ .link = NULL,
+ .unlink = NULL,
+ .mkdir = NULL,
+ .rmdir = NULL,
+ .readdir = NULL,
+ .stat = s5fs_stat,
+ .acquire = NULL,
+ .release = NULL,
+ .get_pframe = s5fs_get_pframe,
+ .fill_pframe = s5fs_fill_pframe,
+ .flush_pframe = s5fs_flush_pframe,
+ .truncate_file = s5fs_truncate_file};
+
+
+static mobj_ops_t s5fs_mobj_ops = {.get_pframe = NULL,
+ .fill_pframe = blockdev_fill_pframe,
+ .flush_pframe = blockdev_flush_pframe,
+ .destructor = NULL};
+
+/*
+ * Initialize the passed-in fs_t. The only members of fs_t that are initialized
+ * before the call to s5fs_mount are fs_dev and fs_type ("s5fs"). You must
+ * initialize everything else: fs_vnode_allocator, fs_i, fs_ops, fs_root.
+ *
+ * Initialize the block device for the s5fs_t that is created, and copy
+ * the super block from disk into memory.
+ */
+long s5fs_mount(fs_t *fs)
+{
+ int num;
+
+ KASSERT(fs);
+
+ if (sscanf(fs->fs_dev, "disk%d", &num) != 1)
+ {
+ return -EINVAL;
+ }
+
+ blockdev_t *dev = blockdev_lookup(MKDEVID(DISK_MAJOR, num));
+ if (!dev)
+ return -EINVAL;
+
+ slab_allocator_t *allocator =
+ slab_allocator_create("s5_node", sizeof(s5_node_t));
+ fs->fs_vnode_allocator = allocator;
+
+ s5fs_t *s5fs = (s5fs_t *)kmalloc(sizeof(s5fs_t));
+
+ if (!s5fs)
+ {
+ slab_allocator_destroy(fs->fs_vnode_allocator);
+ fs->fs_vnode_allocator = NULL;
+ return -ENOMEM;
+ }
+
+ mobj_init(&s5fs->s5f_mobj, MOBJ_FS, &s5fs_mobj_ops);
+ s5fs->s5f_bdev = dev;
+
+#ifndef OLD
+ pframe_t *pf;
+ s5_get_meta_disk_block(s5fs, S5_SUPER_BLOCK, 0, &pf);
+ memcpy(&s5fs->s5f_super, pf->pf_addr, sizeof(s5_super_t));
+ s5_release_disk_block(&pf);
+#endif
+
+ if (s5_check_super(&s5fs->s5f_super))
+ {
+ kfree(s5fs);
+ slab_allocator_destroy(fs->fs_vnode_allocator);
+ fs->fs_vnode_allocator = NULL;
+ return -EINVAL;
+ }
+
+ kmutex_init(&s5fs->s5f_mutex);
+
+ s5fs->s5f_fs = fs;
+
+ fs->fs_i = s5fs;
+ fs->fs_ops = &s5fs_fsops;
+ fs->fs_root = vget(fs, s5fs->s5f_super.s5s_root_inode);
+ // vunlock(fs->fs_root);
+
+ return 0;
+}
+
+/* Initialize a vnode and inode by reading its corresponding inode info from
+ * disk.
+ *
+ * Hints:
+ * - To read the inode from disk, you will need to use the following:
+ * - VNODE_TO_S5NODE to obtain the s5_node_t with the inode corresponding
+ * to the provided vnode
+ * - FS_TO_S5FS to obtain the s5fs object
+ * - S5_INODE_BLOCK(vn->v_vno) to determine the block number of the block that
+ * contains the inode info
+ * - s5_get_disk_block and s5_release_disk_block to handle the disk block
+ * - S5_INODE_OFFSET to find the desired inode within the disk block
+ * containing it (returns the offset that the inode is stored within the block)
+ * - You should initialize the s5_node_t's inode field by reading directly from
+ * the inode on disk by using the page frame returned from s5_get_disk_block. Also
+ * make sure to initialize the dirtied_inode field.
+ * - Using the inode info, you need to initialize the following vnode fields:
+ * vn_len, vn_mode, and vn_ops using the fields found in the s5_inode struct.
+ * - See stat.h for vn_mode values.
+ * - For character and block devices:
+ * 1) Initialize vn_devid by reading the inode's s5_indirect_block field.
+ * 2) Set vn_ops to NULL.
+ */
+static void s5fs_read_vnode(fs_t *fs, vnode_t *vn)
+{
+ NOT_YET_IMPLEMENTED("S5FS: ***none***");
+}
+
+/* Clean up the inode corresponding to the given vnode.
+ *
+ * Hints:
+ * - This function is called in the following way:
+ * mobj_put -> vnode_destructor -> s5fs_delete_vnode.
+ * - Cases to consider:
+ * 1) The inode is no longer in use (linkcount == 0), so free it using
+ * s5_free_inode.
+ * 2) The inode is dirty, so write it back to disk.
+ * 3) The inode is unchanged, so do nothing.
+ */
+static void s5fs_delete_vnode(fs_t *fs, vnode_t *vn)
+{
+ NOT_YET_IMPLEMENTED("S5FS: ***none***");
+}
+
+/*
+ * See umount in vfs.h
+ *
+ * Check reference counts and the super block.
+ * Put the fs_root.
+ * Write the super block out to disk.
+ * Flush the underlying memory object.
+ */
+static long s5fs_umount(fs_t *fs)
+{
+ s5fs_t *s5fs = FS_TO_S5FS(fs);
+ blockdev_t *bd = s5fs->s5f_bdev;
+
+ if (s5fs_check_refcounts(fs))
+ {
+ panic(
+ "s5fs_umount: WARNING: linkcount corruption "
+ "discovered in fs on block device with major %d "
+ "and minor %d!!\n",
+ MAJOR(bd->bd_id), MINOR(bd->bd_id));
+ }
+ if (s5_check_super(&s5fs->s5f_super))
+ {
+ panic(
+ "s5fs_umount: WARNING: corrupted superblock "
+ "discovered on fs on block device with major %d "
+ "and minor %d!!\n",
+ MAJOR(bd->bd_id), MINOR(bd->bd_id));
+ }
+
+ vput(&fs->fs_root);
+
+ s5fs_sync(fs);
+ kfree(s5fs);
+ return 0;
+}
+
+static void s5fs_sync(fs_t *fs)
+{
+#ifdef FIXME
+ s5fs_t *s5fs = FS_TO_S5FS(fs);
+ #ifdef OLD
+ mobj_t *mobj = S5FS_TO_VMOBJ(s5fs);
+ #endif
+ mobj_t *mobj = 0; // XXX FIX ME
+
+ mobj_lock(mobj);
+
+ pframe_t *pf;
+ mobj_get_pframe(mobj, S5_SUPER_BLOCK, 1, &pf);
+ memcpy(pf->pf_addr, &s5fs->s5f_super, sizeof(s5_super_t));
+ pframe_release(&pf);
+
+ mobj_flush(S5FS_TO_VMOBJ(s5fs));
+ mobj_unlock(S5FS_TO_VMOBJ(s5fs));
+#endif
+}
+
+/* Wrapper around s5_read_file. */
+static ssize_t s5fs_read(vnode_t *vnode, size_t pos, void *buf, size_t len)
+{
+ KASSERT(!S_ISDIR(vnode->vn_mode) && "should be handled at the VFS level");
+ NOT_YET_IMPLEMENTED("S5FS: ***none***");
+ return -1;
+}
+
+/* Wrapper around s5_write_file. */
+static ssize_t s5fs_write(vnode_t *vnode, size_t pos, const void *buf,
+ size_t len)
+{
+ KASSERT(!S_ISDIR(vnode->vn_mode) && "should be handled at the VFS level");
+ NOT_YET_IMPLEMENTED("S5FS: ***none***");
+ return -1;
+}
+
+/*
+ * Any error handling should have been done before this function was called.
+ * Simply add a reference to the underlying mobj and return it through ret.
+ */
+static long s5fs_mmap(vnode_t *file, mobj_t **ret)
+{
+ NOT_YET_IMPLEMENTED("VM: ***none***");
+ return 0;
+}
+
+/* Allocate and initialize an inode and its corresponding vnode.
+ *
+ * dir - The directory in which to make the new inode
+ * name - The name of the new inode
+ * namelen - Name length
+ * mode - vn_mode of the new inode, see S_IF{} macros in stat.h
+ * devid - devid of the new inode for special devices
+ * out - Upon success, out must point to the newly created vnode
+ * Upon failure, out must be unchanged
+ *
+ * Return 0 on success, or:
+ * - ENOTSUP: mode is not S_IFCHR, S_BLK, or S_ISREG
+ * - Propagate errors from s5_alloc_inode and s5_link
+ *
+ * Hints:
+ * - Use mode to determine the S5_TYPE_{} for the inode.
+ * - Use s5_alloc_inode is allocate a new inode.
+ * - Use vget to obtain the vnode corresponding to the newly created inode.
+ * - Use s5_link to link the newly created inode/vnode to the parent directory.
+ * - You will need to clean up the vnode using vput in the case that
+ * the link operation fails.
+ */
+static long s5fs_mknod(struct vnode *dir, const char *name, size_t namelen,
+ int mode, devid_t devid, struct vnode **out)
+{
+ KASSERT(S_ISDIR(dir->vn_mode) && "should be handled at the VFS level");
+ NOT_YET_IMPLEMENTED("S5FS: ***none***");
+ return -1;
+}
+
+/* Search for a given entry within a directory.
+ *
+ * dir - The directory in which to search
+ * name - The name to search for
+ * namelen - Name length
+ * ret - Upon success, ret must point to the found vnode
+ *
+ * Return 0 on success, or:
+ * - Propagate errors from s5_find_dirent
+ *
+ * Hints:
+ * - Use s5_find_dirent, vget, and vref.
+ * - vref can be used in the case where the vnode you're looking for happens
+ * to be dir itself.
+ */
+long s5fs_lookup(vnode_t *dir, const char *name, size_t namelen,
+ vnode_t **ret)
+{
+ NOT_YET_IMPLEMENTED("S5FS: ***none***");
+ return -1;
+}
+
+/* Wrapper around s5_link.
+ *
+ * Return whatever s5_link returns, or:
+ * - EISDIR: child is a directory
+ */
+static long s5fs_link(vnode_t *dir, const char *name, size_t namelen,
+ vnode_t *child)
+{
+ KASSERT(S_ISDIR(dir->vn_mode) && "should be handled at the VFS level");
+ NOT_YET_IMPLEMENTED("S5FS: ***none***");
+ return -1;
+}
+
+/* Remove the directory entry in dir corresponding to name and namelen.
+ *
+ * Return 0 on success, or:
+ * - Propagate errors from s5_find_dirent
+ *
+ * Hints:
+ * - Use s5_find_dirent and s5_remove_dirent.
+ * - You will probably want to use vget_locked and vput_locked to protect the
+ * found vnode. Make sure your implementation of s5_remove_dirent knows what
+ * to expect.
+ */
+static long s5fs_unlink(vnode_t *dir, const char *name, size_t namelen)
+{
+ KASSERT(S_ISDIR(dir->vn_mode) && "should be handled at the VFS level");
+ KASSERT(!name_match(".", name, namelen));
+ KASSERT(!name_match("..", name, namelen));
+ NOT_YET_IMPLEMENTED("S5FS: ***none***");
+ return -1;
+}
+
+/* Change the name or location of a file.
+ *
+ * olddir - The directory in which the file currently resides
+ * oldname - The old name of the file
+ * oldnamelen - Length of the old name
+ * newdir - The directory in which to place the file
+ * newname - The new name of the file
+ * newnamelen - Length of the new name
+ *
+ * Return 0 on success, or:
+ * - ENAMETOOLONG: newname is >= NAME_LEN
+ * - ENOTDIR: newdir is not a directory
+ * - EISDIR: newname is a directory
+ * - Propagate errors from s5_find_dirent and s5_link
+ *
+ * Steps:
+ * 1) Use s5_find_dirent and vget_locked to obtain the vnode corresponding to old name.
+ * 2) If newdir already contains an entry for newname:
+ * a) Compare node numbers and do nothing if old name and new name refer to the same inode
+ * b) Check if new-name is a directory
+ * c) Remove the previously existing entry for new name using s5_remove_dirent
+ * d) Link the new direct using s5_link
+ * 3) If there is no entry for newname, use s5_link to add a link to the old node at new name
+ * 4) Use s5_remove_dirent to remove old name’s entry in olddir
+ *
+ *
+ * Hints:
+ * - olddir and newdir should be locked on entry and not unlocked during the
+ * duration of this function. Any other vnodes locked should be unlocked and
+ * put before return.
+ * - Be careful with locking! Because you are making changes to the vnodes,
+ * you should always be using vget_locked and vput_locked. Be sure to clean
+ * up properly in error/special cases.
+ * - You DO NOT need to support renaming of directories in Weenix. If you were to support this
+ * in the s5fs layer (which is not extra credit), you can use the following routine:
+ * 1) Use s5_find_dirent and vget_locked to obtain the vnode corresponding to old name.
+ * 2) If newer already contains an entry for newname:
+ * a) Compare node numbers and do nothing if old name and new name refer to the same inode
+ * b) Check if new-name is a directory
+ * c) Remove the previously existing entry for new name using s5_remove_dirent
+ * d) Link the new direct using s5_link
+ * 3) If there is no entry for newname, use s5_link to add a link to the old node at new name
+ * 4) Use s5_remove_dirent to remove old name’s entry in olddir
+ */
+static long s5fs_rename(vnode_t *olddir, const char *oldname, size_t oldnamelen,
+ vnode_t *newdir, const char *newname,
+ size_t newnamelen)
+{
+ NOT_YET_IMPLEMENTED("S5FS: ***none***");
+ return -1;
+}
+
+/* Create a directory.
+ *
+ * dir - The directory in which to create the new directory
+ * name - The name of the new directory
+ * namelen - Name length of the new directory
+ * out - On success, must point to the new directory, unlocked
+ * On failure, must be unchanged
+ *
+ * Return 0 on success, or:
+ * - Propagate errors from s5_alloc_inode and s5_link
+ *
+ * Steps:
+ * 1) Allocate an inode.
+ * 2) Get the child directory vnode.
+ * 3) Create the "." entry.
+ * 4) Create the ".." entry.
+ * 5) Create the name/namelen entry in the parent (that corresponds
+ * to the new directory)
+ *
+ * Hints:
+ * - If you run into any errors, you must undo previous steps.
+ * - You may assume/assert that undo operations do not fail.
+ * - It may help to assert that linkcounts are correct.
+ */
+static long s5fs_mkdir(vnode_t *dir, const char *name, size_t namelen,
+ struct vnode **out)
+{
+ KASSERT(S_ISDIR((dir)->vn_mode) && "should be handled at the VFS level");
+ NOT_YET_IMPLEMENTED("S5FS: ***none***");
+ return -1;
+}
+
+/* Remove a directory.
+ *
+ * Return 0 on success, or:
+ * - ENOTDIR: The specified entry is not a directory
+ * - ENOTEMPTY: The directory to be removed has entries besides "." and ".."
+ * - Propagate errors from s5_find_dirent
+ *
+ * Hints:
+ * - If you are confident you are managing directory entries properly, you can
+ * check for ENOTEMPTY by simply checking the length of the directory to be
+ * removed. An empty directory has two entries: "." and "..".
+ * - Remove the three entries created in s5fs_mkdir.
+ */
+static long s5fs_rmdir(vnode_t *parent, const char *name, size_t namelen)
+{
+ KASSERT(!name_match(".", name, namelen));
+ KASSERT(!name_match("..", name, namelen));
+ KASSERT(S_ISDIR(parent->vn_mode) && "should be handled at the VFS level");
+ NOT_YET_IMPLEMENTED("S5FS: ***none***");
+ return -1;
+}
+
+/* Read a directory entry.
+ *
+ * vnode - The directory from which to read an entry
+ * pos - The position within the directory to start reading from
+ * d - Caller-allocated dirent that must be properly initialized on
+ * successful return
+ *
+ * Return bytes read on success, or:
+ * - Propagate errors from s5_read_file
+ *
+ * Hints:
+ * - Use s5_read_file to read an s5_dirent_t. To do so, you can create a local
+ * s5_dirent_t variable and use that as the buffer to pass into s5_read_file.
+ * - Be careful that you read into an s5_dirent_t and populate the provided
+ * dirent_t properly.
+ */
+static long s5fs_readdir(vnode_t *vnode, size_t pos, struct dirent *d)
+{
+ KASSERT(S_ISDIR(vnode->vn_mode) && "should be handled at the VFS level");
+ NOT_YET_IMPLEMENTED("S5FS: ***none***");
+ return -1;
+}
+
+/* Get file status.
+ *
+ * vnode - The vnode of the file in question
+ * ss - Caller-allocated stat_t struct that must be initialized on success
+ *
+ * This function should not fail.
+ *
+ * Hint:
+ * - Initialize st_blocks using s5_inode_blocks.
+ * - Initialize st_mode using the corresponding vnode modes in stat.h.
+ * - Initialize st_rdev with the devid of special devices.
+ * - Initialize st_ino with the inode number.
+ * - Initialize st_nlink with the linkcount.
+ * - Initialize st_blksize with S5_BLOCK_SIZE.
+ * - Initialize st_size with the size of the file.
+ * - Initialize st_dev with the bd_id of the s5fs block device.
+ * - Set all other fields to 0.
+ */
+static long s5fs_stat(vnode_t *vnode, stat_t *ss)
+{
+ NOT_YET_IMPLEMENTED("S5FS: ***none***");
+ return -1;
+}
+
+/**
+ * Truncate the vnode and inode length to be 0.
+ *
+ * file - the vnode, whose size should be truncated
+ *
+ * This routine should only be called from do_open via
+ * vn_ops in the case that a regular file is opened with the
+ * O_TRUNC flag specified.
+ */
+static void s5fs_truncate_file(vnode_t *file)
+{
+ KASSERT(S_ISREG(file->vn_mode) && "This routine should only be called for regular files");
+ file->vn_len = 0;
+ s5_node_t* s5_node = VNODE_TO_S5NODE(file);
+ s5_inode_t* s5_inode = &s5_node->inode;
+ // setting the size of the inode to be 0 as well
+ s5_inode->s5_un.s5_size = 0;
+ s5_node->dirtied_inode = 1;
+
+ // Call subroutine to free the blocks that were used
+ vlock(file);
+ s5_remove_blocks(s5_node);
+ vunlock(file);
+}
+
+#ifdef OLD
+/*
+ * Wrapper around mobj_get_pframe. Remember to lock the memory object around
+ * the call to mobj_get_pframe. Assert that the get_pframe does not fail.
+ */
+inline void s5_get_disk_block(s5fs_t *s5fs, blocknum_t blocknum, long forwrite,
+ pframe_t **pfp)
+{
+ mobj_lock(S5FS_TO_VMOBJ(s5fs));
+ long ret = mobj_get_pframe(S5FS_TO_VMOBJ(s5fs), blocknum, forwrite, pfp);
+ mobj_unlock(S5FS_TO_VMOBJ(s5fs));
+ KASSERT(!ret && *pfp);
+}
+#endif
+
+/*
+ * Wrapper around device's read_block function; first looks up block in file-system cache.
+ * If not there, allocates and fills a page frame.
+ * Used for meta blocks, thus location is passed in.
+ */
+inline void s5_get_meta_disk_block(s5fs_t *s5fs, uint64_t blocknum, long forwrite,
+ pframe_t **pfp)
+{
+ mobj_lock(&s5fs->s5f_mobj);
+ mobj_find_pframe(&s5fs->s5f_mobj, blocknum, pfp);
+ if (*pfp)
+ {
+ // block is cached
+ mobj_unlock(&s5fs->s5f_mobj);
+ return;
+ }
+ mobj_create_pframe(&s5fs->s5f_mobj, blocknum, blocknum, pfp);
+ pframe_t *pf = *pfp;
+ pf->pf_addr = page_alloc();
+ KASSERT(pf->pf_addr);
+
+ blockdev_t *bd = s5fs->s5f_bdev;
+ long ret = bd->bd_ops->read_block(bd, pf->pf_addr, (blocknum_t)pf->pf_loc, 1);
+ pf->pf_dirty |= forwrite; // needed?
+ KASSERT (!ret);
+ mobj_unlock(&s5fs->s5f_mobj);
+ KASSERT(!ret && *pfp);
+}
+
+/*
+ * Wrapper around device's read_block function; allocates and fills a page frame.
+ * Assumes cache has already been searched.
+ * Used for file blocks, thus file block number is supplied.
+ */
+static inline void s5_get_file_disk_block(vnode_t *vnode, uint64_t blocknum, uint64_t loc, long forwrite,
+ pframe_t **pfp)
+{
+ //mobj_lock(&vnode->vn_mobj);
+ mobj_create_pframe(&vnode->vn_mobj, blocknum, loc, pfp);
+ //mobj_unlock(&vnode->vn_mobj);
+ pframe_t *pf = *pfp;
+ pf->pf_addr = page_alloc();
+ KASSERT(pf->pf_addr);
+ blockdev_t *bd = VNODE_TO_S5FS(vnode)->s5f_bdev;
+ long ret = bd->bd_ops->read_block(bd, pf->pf_addr, pf->pf_loc, 1);
+ pf->pf_dirty |= forwrite; // needed?
+ KASSERT (!ret);
+}
+
+/* Wrapper around pframe_release.
+ *
+ * Note: All pframe_release does is unlock the pframe. Why aren't we actually
+ * writing anything back yet? Because the pframe remains associated with
+ * whatever mobj we provided when we originally called mobj_get_pframe. If
+ * anyone tries to access the pframe later, Weenix will just give them the
+ * cached page frame from the mobj. If the pframe is ever freed (most likely on
+ * shutdown), then it will be written back to disk: mobj_flush_pframe ->
+ * blockdev_flush_pframe.
+ */
+inline void s5_release_disk_block(pframe_t **pfp) { pframe_release(pfp); }
+
+/*
+ * This is where the abstraction of vnode file block/page --> disk block is
+ * finally implemented. Check that the requested page lies within vnode->vn_len.
+ *
+ * Of course, you will want to use s5_file_block_to_disk_block. Pay attention
+ * to what the forwrite argument to s5fs_get_pframe means for the alloc argument
+ * in s5_file_block_to_disk_block.
+ *
+ * If the disk block for the corresponding file block is sparse, you should use
+ * mobj_default_get_pframe on the vnode's own memory object. This will trickle
+ * down to s5fs_fill_pframe if the pframe is not already resident.
+ *
+ * Otherwise, if the disk block is NOT sparse, you will want to simply use
+ * s5_get_disk_block. NOTE: in this case, you also need to make sure you free
+ * the pframe that resides in the vnode itself for the requested pagenum. To
+ * do so, you will want to use mobj_find_pframe and mobj_free_pframe.
+ *
+ * Given the above design, we s5fs itself does not need to implement
+ * flush_pframe. Any pframe that will be written to (forwrite = 1) should always
+ * have a disk block backing it on successful return. Thus, the page frame will
+ * reside in the block device of the filesystem, where the flush_pframe is
+ * already implemented. We do, however, need to implement fill_pframe for sparse
+ * blocks.
+ */
+static long s5fs_get_pframe(vnode_t *vnode, uint64_t pagenum, long forwrite,
+ pframe_t **pfp)
+{
+#ifdef OLD
+ if (vnode->vn_len <= pagenum * PAGE_SIZE)
+ return -EINVAL;
+ long loc =
+ s5_file_block_to_disk_block(VNODE_TO_S5NODE(vnode), pagenum, forwrite);
+ if (loc < 0)
+ return loc;
+ if (loc)
+ {
+ mobj_find_pframe(&vnode->vn_mobj, pagenum, pfp);
+ if (*pfp)
+ {
+ mobj_free_pframe(&vnode->vn_mobj, pfp);
+ }
+ s5_get_disk_block(VNODE_TO_S5FS(vnode), (blocknum_t)loc, forwrite, pfp);
+ return 0;
+ }
+ else
+ {
+ KASSERT(!forwrite);
+ return mobj_default_get_pframe(&vnode->vn_mobj, pagenum, forwrite, pfp);
+ }
+#endif
+
+ if (vnode->vn_len <= pagenum * PAGE_SIZE)
+ return -EINVAL;
+ mobj_find_pframe(&vnode->vn_mobj, pagenum, pfp);
+ if (*pfp)
+ {
+ // block is cached
+ return 0;
+ }
+ int new;
+ long loc = s5_file_block_to_disk_block(VNODE_TO_S5NODE(vnode), pagenum, forwrite, &new);
+ if (loc < 0)
+ return loc;
+ if (loc) {
+ // block is mapped
+ if (new) {
+ // block didn't previously exist, thus its current contents are meaningless
+ *pfp = s5_cache_and_clear_block(&vnode->vn_mobj, pagenum, loc);
+ } else {
+ // block must be read from disk
+ s5_get_file_disk_block(vnode, pagenum, loc, forwrite, pfp);
+ }
+ return 0;
+ }
+ else
+ {
+ // block is in a sparse region of the file
+ KASSERT(!forwrite);
+ return mobj_default_get_pframe(&vnode->vn_mobj, pagenum, forwrite, pfp);
+ }
+}
+
+/*
+ * According the documentation for s5fs_get_pframe, this only gets called when
+ * the file block for a given page number is sparse. In other words, pf
+ * corresponds to a sparse block.
+ */
+static long s5fs_fill_pframe(vnode_t *vnode, pframe_t *pf)
+{
+ memset(pf->pf_addr, 0, PAGE_SIZE);
+ return 0;
+}
+
+/*
+ * Verify the superblock. 0 on success; -1 on failure.
+ */
+static long s5_check_super(s5_super_t *super)
+{
+ if (!(super->s5s_magic == S5_MAGIC &&
+ (super->s5s_free_inode < super->s5s_num_inodes ||
+ super->s5s_free_inode == (uint32_t)-1) &&
+ super->s5s_root_inode < super->s5s_num_inodes))
+ {
+ return -1;
+ }
+ if (super->s5s_version != S5_CURRENT_VERSION)
+ {
+ dbg(DBG_PRINT,
+ "Filesystem is version %d; "
+ "only version %d is supported.\n",
+ super->s5s_version, S5_CURRENT_VERSION);
+ return -1;
+ }
+ return 0;
+}
+
+/*
+ * Calculate refcounts on the filesystem.
+ */
+static void calculate_refcounts(int *counts, vnode_t *vnode)
+{
+ long ret;
+
+ size_t pos = 0;
+ dirent_t dirent;
+ vnode_t *child;
+
+ while ((ret = s5fs_readdir(vnode, pos, &dirent)) > 0)
+ {
+ counts[dirent.d_ino]++;
+ dbg(DBG_S5FS, "incrementing count of inode %d to %d\n", dirent.d_ino,
+ counts[dirent.d_ino]);
+ if (counts[dirent.d_ino] == 1)
+ {
+ child = vget_locked(vnode->vn_fs, dirent.d_ino);
+ if (S_ISDIR(child->vn_mode))
+ {
+ calculate_refcounts(counts, child);
+ }
+ vput_locked(&child);
+ }
+ pos += ret;
+ }
+
+ KASSERT(!ret);
+}
+
+/*
+ * Verify refcounts on the filesystem. 0 on success; -1 on failure.
+ */
+long s5fs_check_refcounts(fs_t *fs)
+{
+ s5fs_t *s5fs = (s5fs_t *)fs->fs_i;
+ int *refcounts;
+ long ret = 0;
+
+ refcounts = kmalloc(s5fs->s5f_super.s5s_num_inodes * sizeof(int));
+ KASSERT(refcounts);
+ memset(refcounts, 0, s5fs->s5f_super.s5s_num_inodes * sizeof(int));
+
+ vlock(fs->fs_root);
+ refcounts[fs->fs_root->vn_vno]++;
+ calculate_refcounts(refcounts, fs->fs_root);
+ refcounts[fs->fs_root->vn_vno]--;
+
+ vunlock(fs->fs_root);
+
+ dbg(DBG_PRINT,
+ "Checking refcounts of s5fs filesystem on block "
+ "device with major %d, minor %d\n",
+ MAJOR(s5fs->s5f_bdev->bd_id), MINOR(s5fs->s5f_bdev->bd_id));
+
+ for (uint32_t i = 0; i < s5fs->s5f_super.s5s_num_inodes; i++)
+ {
+ if (!refcounts[i])
+ {
+ continue;
+ }
+
+ vnode_t *vn = vget(fs, i);
+ KASSERT(vn);
+ s5_node_t *sn = VNODE_TO_S5NODE(vn);
+
+ if (refcounts[i] != sn->inode.s5_linkcount)
+ {
+ dbg(DBG_PRINT, " Inode %d, expecting %d, found %d\n", i,
+ refcounts[i], sn->inode.s5_linkcount);
+ ret = -1;
+ }
+ vput(&vn);
+ }
+
+ dbg(DBG_PRINT,
+ "Refcount check of s5fs filesystem on block "
+ "device with major %d, minor %d completed %s.\n",
+ MAJOR(s5fs->s5f_bdev->bd_id), MINOR(s5fs->s5f_bdev->bd_id),
+ (ret ? "UNSUCCESSFULLY" : "successfully"));
+
+ kfree(refcounts);
+ return ret;
+}
+
+static long s5fs_flush_pframe(vnode_t *vnode, pframe_t *pf) {
+ return blockdev_flush_pframe(&((s5fs_t *)vnode->vn_fs->fs_i)->s5f_mobj, pf);
+} \ No newline at end of file
diff --git a/kernel/fs/s5fs/s5fs_subr.c b/kernel/fs/s5fs/s5fs_subr.c
new file mode 100644
index 0000000..c972d7c
--- /dev/null
+++ b/kernel/fs/s5fs/s5fs_subr.c
@@ -0,0 +1,590 @@
+#include "fs/s5fs/s5fs_subr.h"
+#include "drivers/blockdev.h"
+#include "errno.h"
+#include "fs/s5fs/s5fs.h"
+#include "fs/stat.h"
+#include "fs/vfs.h"
+#include "fs/vnode.h"
+#include "kernel.h"
+#include "mm/pframe.h"
+#include "proc/kmutex.h"
+#include "util/debug.h"
+#include "util/string.h"
+#include <fs/s5fs/s5fs.h>
+
+static void s5_free_block(s5fs_t *s5fs, blocknum_t block);
+
+static long s5_alloc_block(s5fs_t *s5fs);
+
+static inline void s5_lock_super(s5fs_t *s5fs)
+{
+ kmutex_lock(&s5fs->s5f_mutex);
+}
+
+static inline void s5_unlock_super(s5fs_t *s5fs)
+{
+ kmutex_unlock(&s5fs->s5f_mutex);
+}
+
+/* Helper function to obtain inode info from disk given an inode number.
+ *
+ * s5fs - The file system (it will usually be obvious what to pass for this
+ * parameter)
+ * ino - Inode number to fetch
+ * forwrite - Set if you intend to write any fields in the s5_inode_t, clear
+ * if you only intend to read
+ * pfp - Return parameter for a page frame that will contain the disk
+ * block of the desired inode
+ * inodep - Return parameter for the s5_inode_t corresponding to the desired
+ * inode
+ */
+static inline void s5_get_inode(s5fs_t *s5fs, ino_t ino, long forwrite,
+ pframe_t **pfp, s5_inode_t **inodep)
+{
+ s5_get_meta_disk_block(s5fs, S5_INODE_BLOCK(ino), forwrite, pfp);
+ *inodep = (s5_inode_t *)(*pfp)->pf_addr + S5_INODE_OFFSET(ino);
+ KASSERT((*inodep)->s5_number == ino);
+}
+
+/* Release an inode by releasing the page frame of the disk block containing the
+ * inode. See comments above s5_release_disk_block to see why we don't write
+ * anything back yet.
+ *
+ * pfp - The page frame containing the inode
+ * inodep - The inode to be released
+ *
+ * On return, pfp and inodep both point to NULL.
+ */
+static inline void s5_release_inode(pframe_t **pfp, s5_inode_t **inodep)
+{
+ KASSERT((s5_inode_t *)(*pfp)->pf_addr +
+ S5_INODE_OFFSET((*inodep)->s5_number) ==
+ *inodep);
+ *inodep = NULL;
+ s5_release_disk_block(pfp);
+}
+
+/* Helper function to obtain a specific block of a file.
+ *
+ * sn - The s5_node representing the file in question
+ * blocknum - The offset of the desired block relative to the beginning of the
+ * file, i.e. index 8000 is block 1 of the file, even though it may
+ * not be block 1 of the disk
+ * forwrite - Set if you intend to write to the block, clear if you only intend
+ * to read
+ * pfp - Return parameter for a page frame containing the block data
+ */
+static inline long s5_get_file_block(s5_node_t *sn, size_t blocknum,
+ long forwrite, pframe_t **pfp)
+{
+ return sn->vnode.vn_mobj.mo_ops.get_pframe(&sn->vnode.vn_mobj, blocknum,
+ forwrite, pfp);
+}
+
+/* Release the page frame associated with a file block. See comments above
+ * s5_release_disk_block to see why we don't write anything back yet.
+ *
+ * On return, pfp points to NULL.
+ */
+static inline void s5_release_file_block(pframe_t **pfp)
+{
+ pframe_release(pfp);
+}
+
+#ifdef OLD
+/* Given a file and a file block number, return the disk block number of the
+ * desired file block.
+ *
+ * sn - The s5_node representing the file
+ * file_blocknum - The offset of the desired block relative to the beginning of
+ * the file
+ * alloc - If set, allocate the block / indirect block as necessary
+ * If clear, don't allocate sparse blocks
+ *
+ * Return a disk block number on success, or:
+ * - 0: The block is sparse, and alloc is clear, OR
+ * The indirect block would contain the block, but the indirect block is
+ * sparse, and alloc is clear
+ * - EINVAL: The specified block number is greater than or equal to
+ * S5_MAX_FILE_BLOCKS
+ * - Propagate errors from s5_alloc_block.
+ *
+ * Hints:
+ * - Use the file inode's s5_direct_blocks and s5_indirect_block to perform the
+ * translation.
+ * - Use s5_alloc_block to allocate blocks.
+ * - Be sure to mark the inode as dirty when appropriate, i.e. when you are
+ * making changes to the actual s5_inode_t struct. Hint: Does allocating a
+ * direct block dirty the inode? What about allocating the indirect block?
+ * Finally, what about allocating a block pointed to by the indirect block?
+ * - Cases to consider:
+ * 1) file_blocknum < S_NDIRECT_BLOCKS
+ * 2) Indirect block is not allocated but alloc is set. Be careful not to
+ * leak a block in an error case!
+ * 3) Indirect block is allocated. The desired block may be sparse, and you
+ * may have to allocate it.
+ * 4) The indirect block has not been allocated and alloc is clear.
+ */
+long s5_file_block_to_disk_block(s5_node_t *sn, size_t file_blocknum,
+ int alloc)
+{
+ NOT_YET_IMPLEMENTED("S5FS: ***none***");
+ return -1;
+}
+#endif
+
+
+long s5_file_block_to_disk_block(s5_node_t *sn, size_t file_blocknum,
+ int alloc, int *newp)
+{
+ NOT_YET_IMPLEMENTED("S5FS: ***none***");
+ return -1;
+}
+
+pframe_t *s5_cache_and_clear_block(mobj_t *mo, long block, long loc) {
+ pframe_t *pf;
+ mobj_create_pframe(mo, block, loc, &pf);
+ pf->pf_addr = page_alloc();
+ memset(pf->pf_addr, 0, PAGE_SIZE);
+ pf->pf_dirty = 1; // XXX do this later
+ return pf;
+}
+
+/* Read from a file.
+ *
+ * sn - The s5_node representing the file to read from
+ * pos - The position to start reading from
+ * buf - The buffer to read into
+ * len - The number of bytes to read
+ *
+ * Return the number of bytes read, or:
+ * - Propagate errors from s5_get_file_block (do not return a partial
+ * read). As in, if s5_get_file_block returns an error,
+ * the call to s5_read_file should fail.
+ *
+ * Hints:
+ * - Do not directly call s5_file_block_to_disk_block. To obtain pframes with
+ * the desired blocks, use s5_get_file_block and s5_release_file_block.
+ * - Be sure to handle all edge cases regarding pos and len relative to the
+ * length of the actual file. (If pos is greater than or equal to the length
+ * of the file, then s5_read_file should return 0).
+ */
+ssize_t s5_read_file(s5_node_t *sn, size_t pos, char *buf, size_t len)
+{
+ NOT_YET_IMPLEMENTED("S5FS: ***none***");
+ return -1;
+}
+
+/* Write to a file.
+ *
+ * sn - The s5_node representing the file to write to
+ * pos - The position to start writing to
+ * buf - The buffer to write from
+ * len - The number of bytes to write
+ *
+ * Return the number of bytes written, or:
+ * - EFBIG: pos was beyond S5_MAX_FILE_SIZE
+ * - Propagate errors from s5_get_file_block (that is, do not return a partial
+ * write)
+ *
+ * Hints:
+ * - You should return -EFBIG only if the provided pos was invalid. Otherwise,
+ * it is okay to make a partial write up to the maximum file size.
+ * - Use s5_get_file_block and s5_release_file_block to obtain pframes with
+ * the desired blocks.
+ * - Because s5_get_file_block calls s5fs_get_pframe, which checks the length
+ * of the vnode, you may have to update the vnode's length before you call
+ * s5_get_file_block. In this case, you should also update the inode's
+ * s5_size and mark the inode dirty.
+ * - If, midway through writing, you run into an error with s5_get_file_block,
+ * it is okay to merely undo your most recent changes while leaving behind
+ * writes you've already made to other blocks, before returning the error.
+ * That is, it is okay to make a partial write that the caller does not know
+ * about, as long as the file's length is consistent with what you've
+ * actually written so far.
+ * - You should maintain the vn_len of the vnode and the s5_un.s5_size field of the
+ * inode to be the same.
+ */
+ssize_t s5_write_file(s5_node_t *sn, size_t pos, const char *buf, size_t len)
+{
+ NOT_YET_IMPLEMENTED("S5FS: ***none***");
+ return -1;
+}
+
+#ifdef OLD
+/* Allocate one block from the filesystem.
+ *
+ * Return the block number of the newly allocated block, or:
+ * - ENOSPC: There are no more free blocks
+ *
+ * Hints:
+ * - Protect access to the super block using s5_lock_super and s5_unlock super.
+ * - Recall that the free block list is a linked list of blocks containing disk
+ * block numbers of free blocks. Each node contains S5_NBLKS_PER_FNODE block
+ * numbers, where the last entry is a pointer to the next node in the linked
+ * list, or -1 if there are no more free blocks remaining. The super block's
+ * s5s_free_blocks is the first node of this linked list.
+ * - The super block's s5s_nfree member is the number of blocks that are free
+ * within s5s_free_blocks. You could use it as an index into the
+ * s5s_free_blocks array. Be sure to update the field appropriately.
+ * - When s5s_free_blocks runs out (i.e. s5s_nfree == 0), refill it by
+ * collapsing the next node of the free list into the super block. Exactly
+ * when you do this is up to you.
+ * - You should initialize the block's contents to 0. Specifically,
+ * when you use s5_alloc_block to allocate an indirect block,
+ * as your implementation of s5_file_block_to_disk_block probably expects
+ * sparse blocks to be represented by a 0.
+ * - You may find it helpful to take a look at the implementation of
+ * s5_free_block below.
+ * - You may assume/assert that any pframe calls succeed.
+ */
+static long s5_alloc_block(s5fs_t *s5fs)
+{
+ NOT_YET_IMPLEMENTED("S5FS: ***none***");
+ return -1;
+}
+#endif
+
+static long s5_alloc_block(s5fs_t *s5fs)
+{
+ NOT_YET_IMPLEMENTED("S5FS: ***none***");
+ return -1;
+}
+
+/*
+ * The exact opposite of s5_alloc_block: add blockno to the free list of the
+ * filesystem. This should never fail. You may assert that any pframe calls
+ * succeed.
+ *
+ * Don't forget to protect access to the super block, update s5s_nfree, and
+ * expand the linked list correctly if the super block can no longer hold any
+ * more free blocks in its s5s_free_blocks array according to s5s_nfree.
+ */
+static void s5_free_block(s5fs_t *s5fs, blocknum_t blockno)
+{
+ s5_lock_super(s5fs);
+ s5_super_t *s = &s5fs->s5f_super;
+ dbg(DBG_S5FS, "freeing disk block %d\n", blockno);
+ KASSERT(blockno);
+ KASSERT(s->s5s_nfree < S5_NBLKS_PER_FNODE);
+
+ if (s->s5s_nfree == S5_NBLKS_PER_FNODE - 1)
+ {
+ // FIX THIS! Don't need to read prior contents
+ pframe_t *pf;
+ s5_get_meta_disk_block(s5fs, blockno, 1, &pf);
+ memcpy(pf->pf_addr, s->s5s_free_blocks, sizeof(s->s5s_free_blocks));
+ s5_release_disk_block(&pf);
+
+ s->s5s_nfree = 0;
+ s->s5s_free_blocks[S5_NBLKS_PER_FNODE - 1] = blockno;
+ }
+ else
+ {
+ s->s5s_free_blocks[s->s5s_nfree++] = blockno;
+ }
+ s5_unlock_super(s5fs);
+}
+
+/*
+ * Allocate one inode from the filesystem. You will need to use the super block
+ * s5s_free_inode member. You must initialize the on-disk contents of the
+ * allocated inode according to the arguments type and devid.
+ *
+ * Recall that the free inode list is a linked list. Each free inode contains a
+ * link to the next free inode. The super block s5s_free_inode must always point
+ * to the next free inode, or contain -1 to indicate no more inodes are
+ * available.
+ *
+ * Don't forget to protect access to the super block and update s5s_free_inode.
+ *
+ * You should use s5_get_inode and s5_release_inode.
+ *
+ * On success, return the newly allocated inode number.
+ * On failure, return -ENOSPC.
+ */
+long s5_alloc_inode(s5fs_t *s5fs, uint16_t type, devid_t devid)
+{
+ KASSERT((S5_TYPE_DATA == type) || (S5_TYPE_DIR == type) ||
+ (S5_TYPE_CHR == type) || (S5_TYPE_BLK == type));
+
+ s5_lock_super(s5fs);
+ uint32_t new_ino = s5fs->s5f_super.s5s_free_inode;
+ if (new_ino == (uint32_t)-1)
+ {
+ s5_unlock_super(s5fs);
+ return -ENOSPC;
+ }
+
+ pframe_t *pf;
+ s5_inode_t *inode;
+ s5_get_inode(s5fs, new_ino, 1, &pf, &inode);
+
+ s5fs->s5f_super.s5s_free_inode = inode->s5_un.s5_next_free;
+ KASSERT(inode->s5_un.s5_next_free != inode->s5_number);
+
+ inode->s5_un.s5_size = 0;
+ inode->s5_type = type;
+ inode->s5_linkcount = 0;
+ memset(inode->s5_direct_blocks, 0, sizeof(inode->s5_direct_blocks));
+ inode->s5_indirect_block =
+ (S5_TYPE_CHR == type || S5_TYPE_BLK == type) ? devid : 0;
+
+ s5_release_inode(&pf, &inode);
+ s5_unlock_super(s5fs);
+
+ dbg(DBG_S5FS, "allocated inode %d\n", new_ino);
+ return new_ino;
+}
+
+/*
+ * Free the inode by:
+ * 1) adding the inode to the free inode linked list (opposite of
+ * s5_alloc_inode), and 2) freeing all blocks being used by the inode.
+ *
+ * The suggested order of operations to avoid deadlock, is:
+ * 1) lock the super block
+ * 2) get the inode to be freed
+ * 3) update the free inode linked list
+ * 4) copy the blocks to be freed from the inode onto the stack
+ * 5) release the inode
+ * 6) unlock the super block
+ * 7) free all direct blocks
+ * 8) get the indirect block
+ * 9) copy the indirect block array onto the stack
+ * 10) release the indirect block
+ * 11) free the indirect blocks
+ * 12) free the indirect block itself
+ */
+void s5_free_inode(s5fs_t *s5fs, ino_t ino)
+{
+ pframe_t *pf;
+ s5_inode_t *inode;
+ s5_lock_super(s5fs);
+ s5_get_inode(s5fs, ino, 1, &pf, &inode);
+
+ uint32_t direct_blocks_to_free[S5_NDIRECT_BLOCKS];
+ uint32_t indirect_block_to_free;
+ if (inode->s5_type == S5_TYPE_DATA || inode->s5_type == S5_TYPE_DIR)
+ {
+ indirect_block_to_free = inode->s5_indirect_block;
+ memcpy(direct_blocks_to_free, inode->s5_direct_blocks,
+ sizeof(direct_blocks_to_free));
+ }
+ else
+ {
+ KASSERT(inode->s5_type == S5_TYPE_BLK || inode->s5_type == S5_TYPE_CHR);
+ indirect_block_to_free = 0;
+ memset(direct_blocks_to_free, 0, sizeof(direct_blocks_to_free));
+ }
+
+ inode->s5_un.s5_next_free = s5fs->s5f_super.s5s_free_inode;
+ inode->s5_type = S5_TYPE_FREE;
+ s5fs->s5f_super.s5s_free_inode = inode->s5_number;
+
+ s5_release_inode(&pf, &inode);
+ s5_unlock_super(s5fs);
+
+ for (unsigned i = 0; i < S5_NDIRECT_BLOCKS; i++)
+ {
+ if (direct_blocks_to_free[i])
+ {
+ s5_free_block(s5fs, direct_blocks_to_free[i]);
+ }
+ }
+ if (indirect_block_to_free)
+ {
+ uint32_t indirect_blocks_to_free[S5_NIDIRECT_BLOCKS];
+
+ s5_get_meta_disk_block(s5fs, indirect_block_to_free, 0, &pf);
+ KASSERT(S5_BLOCK_SIZE == PAGE_SIZE);
+ memcpy(indirect_blocks_to_free, pf->pf_addr, S5_BLOCK_SIZE);
+ s5_release_disk_block(&pf);
+
+ for (unsigned i = 0; i < S5_NIDIRECT_BLOCKS; i++)
+ {
+ if (indirect_blocks_to_free[i])
+ {
+ s5_free_block(s5fs, indirect_blocks_to_free[i]);
+ }
+ }
+ s5_free_block(s5fs, indirect_block_to_free);
+ }
+ dbg(DBG_S5FS, "freed inode %d\n", ino);
+}
+
+/* Return the inode number corresponding to the directory entry specified by
+ * name and namelen within a given directory.
+ *
+ * sn - The directory to search in
+ * name - The name to search for
+ * namelen - Length of name
+ * filepos - If non-NULL, use filepos to return the starting position of the
+ * directory entry
+ *
+ * Return the desired inode number, or:
+ * - ENOENT: Could not find a directory entry with the specified name
+ *
+ * Hints:
+ * - Use s5_read_file in increments of sizeof(s5_dirent_t) to read successive
+ * directory entries and compare them against name and namelen.
+ * - To avoid reading beyond the end of the directory, check if the return
+ * value of s5_read_file is 0
+ * - You could optimize this function by using s5_get_file_block (rather than
+ * s5_read_file) to ensure you do not read beyond the length of the file,
+ * but doing so is optional.
+ */
+long s5_find_dirent(s5_node_t *sn, const char *name, size_t namelen,
+ size_t *filepos)
+{
+ KASSERT(S_ISDIR(sn->vnode.vn_mode) && "should be handled at the VFS level");
+ KASSERT(S5_BLOCK_SIZE == PAGE_SIZE && "be wary, thee");
+ NOT_YET_IMPLEMENTED("S5FS: ***none***");
+ return -1;
+}
+
+/* Remove the directory entry specified by name and namelen from the directory
+ * sn.
+ *
+ * child - The found directory entry must correspond to the caller-provided
+ * child
+ *
+ * No return value. This function should never fail. You should assert that
+ * anything which could be incorrect is correct, and any function calls which
+ * could fail succeed.
+ *
+ * Hints:
+ * - Assert that the directory exists.
+ * - Assert that the found directory entry corresponds to child.
+ * - Ensure that the remaining directory entries in the file are contiguous. To
+ * do this, you should:
+ * - Overwrite the removed entry with the last directory entry.
+ * - Truncate the length of the directory by sizeof(s5_dirent_t).
+ * - Make sure you are only using s5_dirent_t, and not dirent_t structs.
+ * - Decrement the child's linkcount, because you have removed the directory's
+ * link to the child.
+ * - Mark the inodes as dirtied.
+ * - Use s5_find_dirent to find the position of the entry being removed.
+ */
+void s5_remove_dirent(s5_node_t *sn, const char *name, size_t namelen,
+ s5_node_t *child)
+{
+ vnode_t *dir = &sn->vnode;
+ s5_inode_t *inode = &sn->inode;
+ NOT_YET_IMPLEMENTED("S5FS: ***none***");
+}
+
+/* Replace a directory entry.
+ *
+ * sn - The directory to search within
+ * name - The name of the old directory entry
+ * namelen - Length of the old directory entry name
+ * old - The s5_node corresponding to the old directory entry
+ * new - The s5_node corresponding to the new directory entry
+ *
+ * No return value. Similar to s5_remove_dirent, this function should never
+ * fail. You should assert that everything behaves correctly.
+ *
+ * Hints:
+ * - Assert that the directory exists, that the directory entry exists, and
+ * that it corresponds to the old s5_node.
+ * - When forming the new directory entry, use the same name and namelen from
+ * before, but use the inode number from the new s5_node.
+ * - Update linkcounts and dirty inodes appropriately.
+ *
+ * s5_replace_dirent is NOT necessary to implement. It's only useful if
+ * you're planning on implementing the renaming of directories (which you shouldn't
+ * attempt until after the rest of S5FS is done).
+ */
+void s5_replace_dirent(s5_node_t *sn, const char *name, size_t namelen,
+ s5_node_t *old, s5_node_t *new)
+{
+ vnode_t *dir = &sn->vnode;
+ s5_inode_t *inode = &sn->inode;
+ NOT_YET_IMPLEMENTED("S5FS: ***none***");
+}
+
+/* Create a directory entry.
+ *
+ * dir - The directory within which to create a new entry
+ * name - The name of the new entry
+ * namelen - Length of the new entry name
+ * child - The s5_node holding the inode which the new entry should represent
+ *
+ * Return 0 on success, or:
+ * - EEXIST: The directory entry already exists
+ * - Propagate errors from s5_write_file
+ *
+ * Hints:
+ * - Update linkcounts and mark inodes dirty appropriately.
+ * - You may wish to assert at the end of s5_link that the directory entry
+ * exists and that its inode is, as expected, the inode of child.
+ */
+long s5_link(s5_node_t *dir, const char *name, size_t namelen,
+ s5_node_t *child)
+{
+ KASSERT(kmutex_owns_mutex(&dir->vnode.vn_mobj.mo_mutex));
+
+ NOT_YET_IMPLEMENTED("S5FS: ***none***");
+ return -1;
+}
+
+/* Return the number of file blocks allocated for sn. This means any
+ * file blocks that are not sparse, direct or indirect. If the indirect
+ * block itself is allocated, that must also count. This function should not
+ * fail.
+ *
+ * Hint:
+ * - You may wish to assert that the special character / block files do not
+ * have any blocks allocated to them. Remember, the s5_indirect_block for
+ * these special files is actually the device id.
+ */
+long s5_inode_blocks(s5_node_t *sn)
+{
+ NOT_YET_IMPLEMENTED("S5FS: ***none***");
+ return -1;
+}
+
+/**
+ * Given a s5_node_t, frees the associated direct blocks and
+ * the indirect blocks if they exist.
+ *
+ * Should only be called from the truncate_file routine.
+ */
+void s5_remove_blocks(s5_node_t *sn)
+{
+ // Free the blocks used by the node
+ // First, free the the direct blocks
+ s5fs_t* s5fs = VNODE_TO_S5FS(&sn->vnode);
+ s5_inode_t* s5_inode = &sn->inode;
+ for (unsigned i = 0; i < S5_NDIRECT_BLOCKS; i++)
+ {
+ if (s5_inode->s5_direct_blocks[i])
+ {
+ s5_free_block(s5fs, s5_inode->s5_direct_blocks[i]);
+ }
+ }
+
+ memset(s5_inode->s5_direct_blocks, 0, sizeof(s5_inode->s5_direct_blocks));
+
+ // Get the indirect blocks and free them, if they exist
+ if (s5_inode->s5_indirect_block)
+ {
+ pframe_t *pf;
+ s5_get_meta_disk_block(s5fs, s5_inode->s5_indirect_block, 0, &pf);
+ uint32_t *blocknum_ptr = pf->pf_addr;
+
+ for (unsigned i = 0; i < S5_NIDIRECT_BLOCKS; i++)
+ {
+ if (blocknum_ptr[i])
+ {
+ s5_free_block(s5fs, blocknum_ptr[i]);
+ }
+ }
+
+ s5_release_disk_block(&pf);
+ // Free the indirect block itself
+ s5_free_block(s5fs, s5_inode->s5_indirect_block);
+ s5_inode->s5_indirect_block = 0;
+ }
+}
diff --git a/kernel/fs/vfs.c b/kernel/fs/vfs.c
new file mode 100644
index 0000000..3f5ed15
--- /dev/null
+++ b/kernel/fs/vfs.c
@@ -0,0 +1,222 @@
+#include "errno.h"
+#include "globals.h"
+#include "kernel.h"
+#include "util/string.h"
+#include <fs/s5fs/s5fs.h>
+#include <fs/vnode.h>
+
+#include "fs/file.h"
+#include "fs/ramfs/ramfs.h"
+
+#include "mm/kmalloc.h"
+#include "mm/slab.h"
+#include "util/debug.h"
+
+#ifdef __S5FS__
+#include "fs/s5fs/s5fs.h"
+#endif
+
+#ifdef __MOUNTING__
+/* The fs listed here are only the non-root file systems */
+list_t mounted_fs_list;
+
+/*
+ * Implementing this function is not required and strongly discouraged unless
+ * you are absolutley sure your Weenix is perfect.
+ *
+ * The purpose of this function is to set up the pointers between the file
+ * system struct and the vnode of the mount point. Remember to watch your
+ * reference counts. (The exception here is when the vnode's vn_mount field
+ * points to the mounted file system's root we do not increment the reference
+ * count on the file system's root vnode. The file system is already keeping
+ * a reference to the vnode which will not go away until the file system is
+ * unmounted. If we kept a second such reference it would conflict with the
+ * behavior of vfs_is_in_use(), make sure you understand why.)
+ *
+ * Once everything is set up add the file system to the list of mounted file
+ * systems.
+ *
+ * Remember proper error handling.
+ *
+ * This function is not meant to mount the root file system.
+ */
+int vfs_mount(struct vnode *mtpt, fs_t *fs)
+{
+ NOT_YET_IMPLEMENTED("MOUNTING: ***none***");
+ return -EINVAL;
+}
+
+/*
+ * Implementing this function is not required and strongly discouraged unless
+ * you are absolutley sure your Weenix is perfect.
+ *
+ * The purpose of this function is to undo the setup done in vfs_mount(). Also
+ * you should call the underlying file system's umount() function. Make sure
+ * to keep track of reference counts. You should also kfree the fs struct at
+ * the end of this method.
+ *
+ * Remember proper error handling. You might want to make sure that you do not
+ * try to call this function on the root file system (this function is not meant
+ * to unmount the root file system).
+ */
+int vfs_umount(fs_t *fs)
+{
+ NOT_YET_IMPLEMENTED("MOUNTING: ***none***");
+ return -EINVAL;
+}
+#endif /* __MOUNTING__ */
+
+fs_t vfs_root_fs = {
+ .fs_dev = VFS_ROOTFS_DEV,
+ .fs_type = VFS_ROOTFS_TYPE,
+ .vnode_list = LIST_INITIALIZER(vfs_root_fs.vnode_list),
+ .vnode_list_mutex = KMUTEX_INITIALIZER(vfs_root_fs.vnode_list_mutex),
+ .fs_vnode_allocator = NULL,
+ .fs_i = NULL,
+ .fs_ops = NULL,
+ .fs_root = NULL,
+};
+
+/*
+ * Call mountfunc on vfs_root_fs and set curproc->p_cwd (reference count!)
+ */
+void vfs_init()
+{
+ long err = mountfunc(&vfs_root_fs);
+ if (err)
+ {
+ panic(
+ "Failed to mount root fs of type \"%s\" on device "
+ "\"%s\" with errno of %ld\n",
+ vfs_root_fs.fs_type, vfs_root_fs.fs_dev, -err);
+ }
+
+ vlock(vfs_root_fs.fs_root);
+ vref(curproc->p_cwd = vfs_root_fs.fs_root);
+ vunlock(vfs_root_fs.fs_root);
+
+#ifdef __MOUNTING__
+ list_init(&mounted_fs_list);
+ fs->fs_mtpt = vfs_root_fs.fs_root;
+#endif
+}
+
+/*
+ * Wrapper around the sync call() to vfs_root_fs using fs_ops
+ */
+void do_sync()
+{
+ vfs_root_fs.fs_ops->sync(&vfs_root_fs);
+#ifdef __MOUNTING__
+ // if implementing mounting, just sync() all the mounted FS's as well
+#endif
+}
+
+/*
+ *
+ */
+long vfs_shutdown()
+{
+ dbg(DBG_VFS, "shutting down vfs\n");
+ long ret = 0;
+
+#ifdef __MOUNTING__
+ list_iterate(&mounted_fs_list, mtfs, fs_t, fs_link)
+ {
+ ret = vfs_umount(mtfs);
+ KASSERT(!ret);
+ }
+#endif
+
+ if (vfs_is_in_use(&vfs_root_fs))
+ {
+ panic("vfs_shutdown: found active vnodes in root filesystem");
+ }
+
+ if (vfs_root_fs.fs_ops->umount)
+ {
+ ret = vfs_root_fs.fs_ops->umount(&vfs_root_fs);
+ }
+ else
+ {
+ // vlock(vfs_root_fs.fs_root);
+ vput(&vfs_root_fs.fs_root);
+ }
+
+ if (vfs_count_active_vnodes(&vfs_root_fs))
+ {
+ panic(
+ "vfs_shutdown: vnodes still in use after unmounting root "
+ "filesystem");
+ }
+ return ret;
+}
+
+long mountfunc(fs_t *fs)
+{
+ static const struct
+ {
+ char *fstype;
+
+ long (*mountfunc)(fs_t *);
+ } types[] = {
+#ifdef __S5FS__
+ {"s5fs", s5fs_mount},
+#endif
+ {"ramfs", ramfs_mount},
+ };
+
+ for (unsigned int i = 0; i < sizeof(types) / sizeof(types[0]); i++)
+ {
+ if (strcmp(fs->fs_type, types[i].fstype) == 0)
+ {
+ return types[i].mountfunc(fs);
+ }
+ }
+
+ return -EINVAL;
+}
+
+/*
+ * A filesystem is in use if the total number of vnode refcounts for that
+ * filesystem > 1. The singular refcount in a fs NOT in use comes from fs_root.
+ *
+ * Error cases vfs_is_in_use is responsible for generating:
+ * - EBUSY: if the filesystem is in use
+ */
+long vfs_is_in_use(fs_t *fs)
+{
+ long ret = 0;
+ // kmutex_lock(&fs->vnode_list_mutex);
+ list_iterate(&fs->vnode_list, vn, vnode_t, vn_link)
+ {
+ vlock(vn);
+ size_t expected_refcount = vn->vn_fs->fs_root == vn ? 1 : 0;
+ size_t refcount = vn->vn_mobj.mo_refcount;
+ vunlock(vn);
+ if (refcount != expected_refcount)
+ {
+ dbg(DBG_VFS,
+ "vnode %d still in use with %d references and %lu mobj "
+ "references (expected %lu)\n",
+ vn->vn_vno, vn->vn_mobj.mo_refcount, refcount,
+ expected_refcount);
+ ret = -EBUSY;
+ // break;
+ }
+ }
+ // kmutex_unlock(&fs->vnode_list_mutex);
+ return ret;
+}
+
+/*
+ * Return the size of fs->vnode_list
+ */
+size_t vfs_count_active_vnodes(fs_t *fs)
+{
+ size_t count = 0;
+ kmutex_lock(&fs->vnode_list_mutex);
+ list_iterate(&fs->vnode_list, vn, vnode_t, vn_link) { count++; }
+ kmutex_unlock(&fs->vnode_list_mutex);
+ return count;
+}
diff --git a/kernel/fs/vfs_syscall.c b/kernel/fs/vfs_syscall.c
new file mode 100644
index 0000000..d2f018c
--- /dev/null
+++ b/kernel/fs/vfs_syscall.c
@@ -0,0 +1,356 @@
+#include "fs/vfs_syscall.h"
+#include "errno.h"
+#include "fs/fcntl.h"
+#include "fs/file.h"
+#include "fs/lseek.h"
+#include "fs/vfs.h"
+#include "fs/vnode.h"
+#include "globals.h"
+#include "kernel.h"
+#include "util/debug.h"
+#include "util/string.h"
+#include <limits.h>
+
+/*
+ * Read len bytes into buf from the fd's file using the file's vnode operation
+ * read.
+ *
+ * Return the number of bytes read on success, or:
+ * - EBADF: fd is invalid or is not open for reading
+ * - EISDIR: fd refers to a directory
+ * - Propagate errors from the vnode operation read
+ *
+ * Hints:
+ * - Be sure to update the file's position appropriately.
+ * - Lock/unlock the file's vnode when calling its read operation.
+ */
+ssize_t do_read(int fd, void *buf, size_t len)
+{
+ NOT_YET_IMPLEMENTED("VFS: ***none***");
+ return -1;
+}
+
+/*
+ * Write len bytes from buf into the fd's file using the file's vnode operation
+ * write.
+ *
+ * Return the number of bytes written on success, or:
+ * - EBADF: fd is invalid or is not open for writing
+ * - Propagate errors from the vnode operation read
+ *
+ * Hints:
+ * - Check out `man 2 write` for details about how to handle the FMODE_APPEND
+ * flag.
+ * - Be sure to update the file's position appropriately.
+ * - Lock/unlock the file's vnode when calling its write operation.
+ */
+ssize_t do_write(int fd, const void *buf, size_t len)
+{
+ NOT_YET_IMPLEMENTED("VFS: ***none***");
+ return -1;
+}
+
+/*
+ * Close the file descriptor fd.
+ *
+ * Return 0 on success, or:
+ * - EBADF: fd is invalid or not open
+ *
+ * Hints:
+ * Check `proc.h` to see if there are any helpful fields in the
+ * proc_t struct for checking if the file associated with the fd is open.
+ * Consider what happens when we open a file and what counts as closing it
+ */
+long do_close(int fd)
+{
+ NOT_YET_IMPLEMENTED("VFS: ***none***");
+ return -1;
+}
+
+/*
+ * Duplicate the file descriptor fd.
+ *
+ * Return the new file descriptor on success, or:
+ * - EBADF: fd is invalid or not open
+ * - Propagate errors from get_empty_fd()
+ *
+ * Hint: Use get_empty_fd() to obtain an available file descriptor.
+ */
+long do_dup(int fd)
+{
+ NOT_YET_IMPLEMENTED("VFS: ***none***");
+ return -1;
+}
+
+/*
+ * Duplicate the file descriptor ofd using the new file descriptor nfd. If nfd
+ * was previously open, close it.
+ *
+ * Return nfd on success, or:
+ * - EBADF: ofd is invalid or not open, or nfd is invalid
+ *
+ * Hint: You don't need to do anything if ofd and nfd are the same.
+ * (If supporting MTP, this action must be atomic)
+ */
+long do_dup2(int ofd, int nfd)
+{
+ NOT_YET_IMPLEMENTED("VFS: ***none***");
+ return -1;
+}
+
+/*
+ * Create a file specified by mode and devid at the location specified by path.
+ *
+ * Return 0 on success, or:
+ * - EINVAL: Mode is not S_IFCHR, S_IFBLK, or S_IFREG
+ * - Propagate errors from namev_open()
+ *
+ * Hints:
+ * - Create the file by calling namev_open() with the O_CREAT flag.
+ * - Be careful about refcounts after calling namev_open(). The newly created
+ * vnode should have no references when do_mknod returns. The underlying
+ * filesystem is responsible for maintaining references to the inode, which
+ * will prevent it from being destroyed, even if the corresponding vnode is
+ * cleaned up.
+ * - You don't need to handle EEXIST (this would be handled within namev_open,
+ * but doing so would likely cause problems elsewhere)
+ */
+long do_mknod(const char *path, int mode, devid_t devid)
+{
+ NOT_YET_IMPLEMENTED("VFS: ***none***");
+ return -1;
+}
+
+/*
+ * Create a directory at the location specified by path.
+ *
+ * Return 0 on success, or:
+ * - ENAMETOOLONG: The last component of path is too long
+ * - ENOTDIR: The parent of the directory to be created is not a directory
+ * - EEXIST: A file located at path already exists
+ * - Propagate errors from namev_dir(), namev_lookup(), and the vnode
+ * operation mkdir
+ *
+ * Hints:
+ * 1) Use namev_dir() to find the parent of the directory to be created.
+ * 2) Use namev_lookup() to check that the directory does not already exist.
+ * 3) Use the vnode operation mkdir to create the directory.
+ * - Compare against NAME_LEN to determine if the basename is too long.
+ * Check out ramfs_mkdir() to confirm that the basename will be null-
+ * terminated.
+ * - Be careful about locking and refcounts after calling namev_dir() and
+ * namev_lookup().
+ */
+long do_mkdir(const char *path)
+{
+ NOT_YET_IMPLEMENTED("VFS: ***none***");
+ return -1;
+}
+
+/*
+ * Delete a directory at path.
+ *
+ * Return 0 on success, or:
+ * - EINVAL: Attempting to rmdir with "." as the final component
+ * - ENOTEMPTY: Attempting to rmdir with ".." as the final component
+ * - ENOTDIR: The parent of the directory to be removed is not a directory
+ * - ENAMETOOLONG: the last component of path is too long
+ * - Propagate errors from namev_dir() and the vnode operation rmdir
+ *
+ * Hints:
+ * - Use namev_dir() to find the parent of the directory to be removed.
+ * - Be careful about refcounts from calling namev_dir().
+ * - Use the parent directory's rmdir operation to remove the directory.
+ * - Lock/unlock the vnode when calling its rmdir operation.
+ */
+long do_rmdir(const char *path)
+{
+ NOT_YET_IMPLEMENTED("VFS: ***none***");
+ return -1;
+}
+
+/*
+ * Remove the link between path and the file it refers to.
+ *
+ * Return 0 on success, or:
+ * - ENOTDIR: the parent of the file to be unlinked is not a directory
+ * - ENAMETOOLONG: the last component of path is too long
+ * - Propagate errors from namev_dir() and the vnode operation unlink
+ *
+ * Hints:
+ * - Use namev_dir() and be careful about refcounts.
+ * - Lock/unlock the parent directory when calling its unlink operation.
+ */
+long do_unlink(const char *path)
+{
+ NOT_YET_IMPLEMENTED("VFS: ***none***");
+ return -1;
+}
+
+/*
+ * Create a hard link newpath that refers to the same file as oldpath.
+ *
+ * Return 0 on success, or:
+ * - EPERM: oldpath refers to a directory
+ * - ENAMETOOLONG: The last component of newpath is too long
+ * - ENOTDIR: The parent of the file to be linked is not a directory
+ *
+ * Hints:
+ * 1) Use namev_resolve() on oldpath to get the target vnode.
+ * 2) Use namev_dir() on newpath to get the directory vnode.
+ * 3) Use vlock_in_order() to lock the directory and target vnodes.
+ * 4) Use the directory vnode's link operation to create a link to the target.
+ * 5) Use vunlock_in_order() to unlock the vnodes.
+ * 6) Make sure to clean up references added from calling namev_resolve() and
+ * namev_dir().
+ */
+long do_link(const char *oldpath, const char *newpath)
+{
+ NOT_YET_IMPLEMENTED("VFS: ***none***");
+ return -1;
+}
+
+/* Rename a file or directory.
+ *
+ * Return 0 on success, or:
+ * - ENOTDIR: the parent of either path is not a directory
+ * - ENAMETOOLONG: the last component of either path is too long
+ * - Propagate errors from namev_dir() and the vnode operation rename
+ *
+ * You DO NOT need to support renaming of directories.
+ * Steps:
+ * 1. namev_dir oldpath --> olddir vnode
+ * 2. namev_dir newpath --> newdir vnode
+ * 4. Lock the olddir and newdir in ancestor-first order (see `vlock_in_order`)
+ * 5. Use the `rename` vnode operation
+ * 6. Unlock the olddir and newdir
+ * 8. vput the olddir and newdir vnodes
+ *
+ * Alternatively, you can allow do_rename() to rename directories if
+ * __RENAMEDIR__ is set in Config.mk. As with all extra credit
+ * projects this is harder and you will get no extra credit (but you
+ * will get our admiration). Please make sure the normal version works first.
+ * Steps:
+ * 1. namev_dir oldpath --> olddir vnode
+ * 2. namev_dir newpath --> newdir vnode
+ * 3. Lock the global filesystem `vnode_rename_mutex`
+ * 4. Lock the olddir and newdir in ancestor-first order (see `vlock_in_order`)
+ * 5. Use the `rename` vnode operation
+ * 6. Unlock the olddir and newdir
+ * 7. Unlock the global filesystem `vnode_rename_mutex`
+ * 8. vput the olddir and newdir vnodes
+ *
+ * P.S. This scheme /probably/ works, but we're not 100% sure.
+ */
+long do_rename(const char *oldpath, const char *newpath)
+{
+ NOT_YET_IMPLEMENTED("VFS: ***none***");
+ return -1;
+}
+
+/* Set the current working directory to the directory represented by path.
+ *
+ * Returns 0 on success, or:
+ * - ENOTDIR: path does not refer to a directory
+ * - Propagate errors from namev_resolve()
+ *
+ * Hints:
+ * - Use namev_resolve() to get the vnode corresponding to path.
+ * - Pay attention to refcounts!
+ * - Remember that p_cwd should not be locked upon return from this function.
+ * - (If doing MTP, must protect access to p_cwd)
+ */
+long do_chdir(const char *path)
+{
+ NOT_YET_IMPLEMENTED("VFS: ***none***");
+ return -1;
+}
+
+/*
+ * Read a directory entry from the file specified by fd into dirp.
+ *
+ * Return sizeof(dirent_t) on success, or:
+ * - EBADF: fd is invalid or is not open
+ * - ENOTDIR: fd does not refer to a directory
+ * - Propagate errors from the vnode operation readdir
+ *
+ * Hints:
+ * - Use the vnode operation readdir.
+ * - Be sure to update file position according to readdir's return value.
+ * - On success (readdir return value is strictly positive), return
+ * sizeof(dirent_t).
+ */
+ssize_t do_getdent(int fd, struct dirent *dirp)
+{
+ NOT_YET_IMPLEMENTED("VFS: ***none***");
+ return -1;
+}
+
+/*
+ * Set the position of the file represented by fd according to offset and
+ * whence.
+ *
+ * Return the new file position, or:
+ * - EBADF: fd is invalid or is not open
+ * - EINVAL: whence is not one of SEEK_SET, SEEK_CUR, or SEEK_END;
+ * or, the resulting file offset would be negative
+ *
+ * Hints:
+ * - See `man 2 lseek` for details about whence.
+ * - Be sure to protect the vnode if you have to access its vn_len.
+ */
+off_t do_lseek(int fd, off_t offset, int whence)
+{
+ NOT_YET_IMPLEMENTED("VFS: ***none***");
+ return -1;
+}
+
+/* Use buf to return the status of the file represented by path.
+ *
+ * Return 0 on success, or:
+ * - Propagate errors from namev_resolve() and the vnode operation stat.
+ */
+long do_stat(const char *path, stat_t *buf)
+{
+ NOT_YET_IMPLEMENTED("VFS: ***none***");
+ return -1;
+}
+
+#ifdef __MOUNTING__
+/*
+ * Implementing this function is not required and strongly discouraged unless
+ * you are absolutely sure your Weenix is perfect.
+ *
+ * This is the syscall entry point into vfs for mounting. You will need to
+ * create the fs_t struct and populate its fs_dev and fs_type fields before
+ * calling vfs's mountfunc(). mountfunc() will use the fields you populated
+ * in order to determine which underlying filesystem's mount function should
+ * be run, then it will finish setting up the fs_t struct. At this point you
+ * have a fully functioning file system, however it is not mounted on the
+ * virtual file system, you will need to call vfs_mount to do this.
+ *
+ * There are lots of things which can go wrong here. Make sure you have good
+ * error handling. Remember the fs_dev and fs_type buffers have limited size
+ * so you should not write arbitrary length strings to them.
+ */
+int do_mount(const char *source, const char *target, const char *type)
+{
+ NOT_YET_IMPLEMENTED("MOUNTING: ***none***");
+ return -EINVAL;
+}
+
+/*
+ * Implementing this function is not required and strongly discouraged unless
+ * you are absolutley sure your Weenix is perfect.
+ *
+ * This function delegates all of the real work to vfs_umount. You should not
+ * worry about freeing the fs_t struct here, that is done in vfs_umount. All
+ * this function does is figure out which file system to pass to vfs_umount and
+ * do good error checking.
+ */
+int do_umount(const char *target)
+{
+ NOT_YET_IMPLEMENTED("MOUNTING: ***none***");
+ return -EINVAL;
+}
+#endif
diff --git a/kernel/fs/vnode.c b/kernel/fs/vnode.c
new file mode 100644
index 0000000..91fee09
--- /dev/null
+++ b/kernel/fs/vnode.c
@@ -0,0 +1,250 @@
+#include "fs/vnode.h"
+#include "errno.h"
+#include "fs/stat.h"
+#include "fs/vfs.h"
+#include "kernel.h"
+#include "mm/slab.h"
+#include "util/debug.h"
+#include "util/string.h"
+#include <fs/vnode_specials.h>
+
+#define MOBJ_TO_VNODE(o) CONTAINER_OF((o), vnode_t, vn_mobj)
+
+static long vnode_get_pframe(mobj_t *o, uint64_t pagenum, long forwrite,
+ pframe_t **pfp);
+static long vnode_fill_pframe(mobj_t *o, pframe_t *pf);
+static long vnode_flush_pframe(mobj_t *o, pframe_t *pf);
+static void vnode_destructor(mobj_t *o);
+
+static mobj_ops_t vnode_mobj_ops = {.get_pframe = vnode_get_pframe,
+ .fill_pframe = vnode_fill_pframe,
+ .flush_pframe = vnode_flush_pframe,
+ .destructor = vnode_destructor};
+
+/**
+ * locks the vnodes in the order of their inode number,
+ * in the case that they are the same vnode, then only one vnode is locked.
+ *
+ * this scheme prevents the A->B/B->A locking problem, but it only
+ * works only if the `vlock_in_order` function is used in all cases where 2
+ * nodes must be locked.
+ */
+void vlock_in_order(vnode_t *a, vnode_t *b)
+{
+ /* these vnode's must be on the same filesystem */
+ KASSERT(a->vn_fs == b->vn_fs);
+
+ if (a->vn_vno == b->vn_vno)
+ {
+ vlock(a);
+ return;
+ }
+
+ /* */
+ if (S_ISDIR(a->vn_mode) && S_ISDIR(b->vn_mode))
+ {
+ if (namev_is_descendant(a, b))
+ {
+ vlock(b);
+ vlock(a);
+ return;
+ }
+ else if (namev_is_descendant(b, a))
+ {
+ vlock(a);
+ vlock(b);
+ return;
+ }
+ }
+ else if (S_ISDIR(a->vn_mode))
+ {
+ vlock(a);
+ vlock(b);
+ }
+ else if (S_ISDIR(b->vn_mode))
+ {
+ vlock(b);
+ vlock(a);
+ }
+ else if (a->vn_vno < b->vn_vno)
+ {
+ vlock(a);
+ vlock(b);
+ }
+ else
+ {
+ vlock(b);
+ vlock(a);
+ }
+}
+
+void vunlock_in_order(vnode_t *a, vnode_t *b)
+{
+ if (a->vn_vno == b->vn_vno)
+ {
+ vunlock(a);
+ return;
+ }
+
+ vunlock(a);
+ vunlock(b);
+}
+
+void await_vnode_loaded(vnode_t *vnode)
+{
+ /* blocks until the vnode's vn_state is loaded */
+ while (vnode->vn_state != VNODE_LOADED)
+ {
+ sched_sleep_on(&vnode->vn_waitq);
+ }
+ KASSERT(vnode->vn_state == VNODE_LOADED);
+}
+
+void notify_vnode_loaded(vnode_t *vn)
+{
+ /* set the state to loaded and release all waiters */
+ vn->vn_state = VNODE_LOADED;
+ sched_broadcast_on(&vn->vn_waitq);
+}
+
+void vnode_init(vnode_t *vn, fs_t *fs, ino_t ino, int state)
+{
+ vn->vn_state = VNODE_LOADING;
+ vn->vn_fs = fs;
+ vn->vn_vno = ino;
+ sched_queue_init(&vn->vn_waitq);
+ mobj_init(&vn->vn_mobj, MOBJ_VNODE, &vnode_mobj_ops);
+ KASSERT(vn->vn_mobj.mo_refcount);
+}
+
+vnode_t *__vget(fs_t *fs, ino_t ino, int get_locked)
+{
+find:
+ kmutex_lock(&fs->vnode_list_mutex);
+ list_iterate(&fs->vnode_list, vn, vnode_t, vn_link)
+ {
+ if (vn->vn_vno == ino)
+ {
+ if (atomic_inc_not_zero(&vn->vn_mobj.mo_refcount))
+ {
+ /* reference acquired, we can release the per-FS list */
+ kmutex_unlock(&fs->vnode_list_mutex);
+ await_vnode_loaded(vn);
+ if (get_locked)
+ {
+ vlock(vn);
+ }
+ return vn;
+ }
+ else
+ {
+ /* count must be 0, wait and try again later */
+ kmutex_unlock(&fs->vnode_list_mutex);
+ sched_yield();
+ goto find;
+ }
+ }
+ }
+
+ /* vnode does not exist, must allocate one */
+ dbg(DBG_VFS, "creating vnode %d\n", ino);
+ vnode_t *vn = slab_obj_alloc(fs->fs_vnode_allocator);
+ KASSERT(vn);
+ memset(vn, 0, sizeof(vnode_t));
+
+ /* initialize the vnode state */
+ vnode_init(vn, fs, ino, VNODE_LOADING);
+
+ /* add the vnode to the per-FS list, lock the vnode, and release the list
+ * (unblocking other `vget` calls) */
+ list_insert_tail(&fs->vnode_list, &vn->vn_link);
+ vlock(vn);
+ kmutex_unlock(&fs->vnode_list_mutex);
+
+ /* load the vnode */
+ vn->vn_fs->fs_ops->read_vnode(vn->vn_fs, vn);
+ if (S_ISCHR(vn->vn_mode) || S_ISBLK(vn->vn_mode))
+ {
+ init_special_vnode(vn);
+ }
+
+ /* notify potential waiters that the vnode is ready for use and return */
+ notify_vnode_loaded(vn);
+ if (!get_locked)
+ {
+ vunlock(vn);
+ }
+ return vn;
+}
+
+inline vnode_t *vget(fs_t *fs, ino_t ino) { return __vget(fs, ino, 0); }
+
+inline vnode_t *vget_locked(fs_t *fs, ino_t ino) { return __vget(fs, ino, 1); }
+
+inline void vref(vnode_t *vn) { mobj_ref(&vn->vn_mobj); }
+
+inline void vlock(vnode_t *vn) { mobj_lock(&vn->vn_mobj); }
+
+inline void vunlock(vnode_t *vn) { mobj_unlock(&vn->vn_mobj); }
+
+inline void vput(struct vnode **vnp)
+{
+ vnode_t *vn = *vnp;
+ *vnp = NULL;
+ mobj_t *mobj = &vn->vn_mobj;
+ mobj_put(&mobj);
+}
+
+inline void vput_locked(struct vnode **vnp)
+{
+ vunlock(*vnp);
+ vput(vnp);
+}
+
+static long vnode_get_pframe(mobj_t *o, uint64_t pagenum, long forwrite,
+ pframe_t **pfp)
+{
+ vnode_t *vnode = MOBJ_TO_VNODE(o);
+ KASSERT(vnode->vn_ops->get_pframe);
+ return vnode->vn_ops->get_pframe(vnode, pagenum, forwrite, pfp);
+}
+
+static long vnode_fill_pframe(mobj_t *o, pframe_t *pf)
+{
+ vnode_t *vnode = MOBJ_TO_VNODE(o);
+ KASSERT(vnode->vn_ops->fill_pframe);
+ return vnode->vn_ops->fill_pframe(vnode, pf);
+}
+
+static long vnode_flush_pframe(mobj_t *o, pframe_t *pf)
+{
+ vnode_t *vnode = MOBJ_TO_VNODE(o);
+ KASSERT(vnode->vn_ops->flush_pframe);
+ return vnode->vn_ops->flush_pframe(vnode, pf);
+}
+
+static void vnode_destructor(mobj_t *o)
+{
+ vnode_t *vn = MOBJ_TO_VNODE(o);
+ dbg(DBG_VFS, "destroying vnode %d\n", vn->vn_vno);
+
+ /* lock, flush, and delete the vnode */
+ KASSERT(!o->mo_refcount);
+ vlock(vn);
+ KASSERT(!o->mo_refcount);
+ KASSERT(!kmutex_has_waiters(&o->mo_mutex));
+ mobj_flush(o);
+ if (vn->vn_fs->fs_ops->delete_vnode)
+ {
+ vn->vn_fs->fs_ops->delete_vnode(vn->vn_fs, vn);
+ }
+ KASSERT(!kmutex_has_waiters(&o->mo_mutex));
+ vunlock(vn);
+
+ /* remove the vnode from the list and free it*/
+ kmutex_lock(&vn->vn_fs->vnode_list_mutex);
+ KASSERT(list_link_is_linked(&vn->vn_link));
+ list_remove(&vn->vn_link);
+ kmutex_unlock(&vn->vn_fs->vnode_list_mutex);
+ slab_obj_free(vn->vn_fs->fs_vnode_allocator, vn);
+}
diff --git a/kernel/fs/vnode_specials.c b/kernel/fs/vnode_specials.c
new file mode 100644
index 0000000..a6c38a3
--- /dev/null
+++ b/kernel/fs/vnode_specials.c
@@ -0,0 +1,176 @@
+#include <errno.h>
+#include <fs/stat.h>
+#include <fs/vfs.h>
+#include <fs/vnode.h>
+#include <util/debug.h>
+
+static long special_file_stat(vnode_t *file, stat_t *ss);
+
+static ssize_t chardev_file_read(vnode_t *file, size_t pos, void *buf,
+ size_t count);
+
+static ssize_t chardev_file_write(vnode_t *file, size_t pos, const void *buf,
+ size_t count);
+
+static long chardev_file_mmap(vnode_t *file, mobj_t **ret);
+
+static long chardev_file_fill_pframe(vnode_t *file, pframe_t *pf);
+
+static long chardev_file_flush_pframe(vnode_t *file, pframe_t *pf);
+
+static vnode_ops_t chardev_spec_vops = {
+ .read = chardev_file_read,
+ .write = chardev_file_write,
+ .mmap = chardev_file_mmap,
+ .mknod = NULL,
+ .lookup = NULL,
+ .link = NULL,
+ .unlink = NULL,
+ .mkdir = NULL,
+ .rmdir = NULL,
+ .readdir = NULL,
+ .stat = special_file_stat,
+ .get_pframe = NULL,
+ .fill_pframe = chardev_file_fill_pframe,
+ .flush_pframe = chardev_file_flush_pframe,
+};
+
+static ssize_t blockdev_file_read(vnode_t *file, size_t pos, void *buf,
+ size_t count);
+
+static ssize_t blockdev_file_write(vnode_t *file, size_t pos, const void *buf,
+ size_t count);
+
+static long blockdev_file_mmap(vnode_t *file, mobj_t **ret);
+
+static long blockdev_file_fill_pframe(vnode_t *file, pframe_t *pf);
+
+static long blockdev_file_flush_pframe(vnode_t *file, pframe_t *pf);
+
+static vnode_ops_t blockdev_spec_vops = {
+ .read = blockdev_file_read,
+ .write = blockdev_file_write,
+ .mmap = blockdev_file_mmap,
+ .mknod = NULL,
+ .lookup = NULL,
+ .link = NULL,
+ .unlink = NULL,
+ .mkdir = NULL,
+ .rmdir = NULL,
+ .readdir = NULL,
+ .stat = special_file_stat,
+ .get_pframe = NULL,
+ .fill_pframe = blockdev_file_fill_pframe,
+ .flush_pframe = blockdev_file_flush_pframe,
+};
+
+void init_special_vnode(vnode_t *vn)
+{
+ if (S_ISCHR(vn->vn_mode))
+ {
+ vn->vn_ops = &chardev_spec_vops;
+ vn->vn_dev.chardev = chardev_lookup(vn->vn_devid);
+ }
+ else
+ {
+ KASSERT(S_ISBLK(vn->vn_mode));
+ vn->vn_ops = &blockdev_spec_vops;
+ vn->vn_dev.blockdev = blockdev_lookup(vn->vn_devid);
+ }
+}
+
+static long special_file_stat(vnode_t *file, stat_t *ss)
+{
+ KASSERT(file->vn_fs->fs_root->vn_ops->stat != NULL);
+ // call the containing file system's stat routine
+ return file->vn_fs->fs_root->vn_ops->stat(file, ss);
+}
+
+/*
+ * Make a read by deferring to the underlying chardev and its read operation.
+ *
+ * Returns what the chardev's read returned.
+ *
+ * Hint: Watch out! chardev_file_read and chardev_file_write are indirectly
+ * called in do_read and do_write, respectively, as the read/write ops for
+ * chardev-type vnodes. This means that the vnode file should be locked
+ * upon entry to this function.
+ *
+ * However, tty_read and tty_write, the read/write ops for the tty chardev,
+ * are potentially blocking. To avoid deadlock, you should unlock the file
+ * before calling the chardev's read, and lock it again after. If you fail
+ * to do this, a shell reading from /dev/tty0 for instance, will block all
+ * access to the /dev/tty0 vnode. This means that if someone runs `ls /dev/`,
+ * while a shell is reading from `/dev/tty0`, the `ls` call will hang.
+ *
+ * Also, if a vnode represents a chardev, you can access the chardev using
+ * vnode->vn_dev.chardev.
+ *
+ */
+static ssize_t chardev_file_read(vnode_t *file, size_t pos, void *buf,
+ size_t count)
+{
+ NOT_YET_IMPLEMENTED("VFS: ***none***");
+ return 0;
+}
+
+/*
+ * Make a write by deferring to the underlying chardev and its write operation.
+ *
+ * Return what the chardev's write returned.
+ *
+ * See the comments from chardev_file_read above for hints.
+ *
+ */
+static long chardev_file_write(vnode_t *file, size_t pos, const void *buf,
+ size_t count)
+{
+ NOT_YET_IMPLEMENTED("VFS: ***none***");
+ return 0;
+}
+
+/*
+ * For this and the following chardev functions, simply defer to the underlying
+ * chardev's corresponding operations.
+ */
+static long chardev_file_mmap(vnode_t *file, mobj_t **ret)
+{
+ NOT_YET_IMPLEMENTED("VM: ***none***");
+ return 0;
+}
+
+static long chardev_file_fill_pframe(vnode_t *file, pframe_t *pf)
+{
+ NOT_YET_IMPLEMENTED("VM: ***none***");
+ return 0;
+}
+
+static long chardev_file_flush_pframe(vnode_t *file, pframe_t *pf)
+{
+ NOT_YET_IMPLEMENTED("VM: ***none***");
+ return 0;
+}
+
+static ssize_t blockdev_file_read(vnode_t *file, size_t pos, void *buf,
+ size_t count)
+{
+ return -ENOTSUP;
+}
+
+static long blockdev_file_write(vnode_t *file, size_t pos, const void *buf,
+ size_t count)
+{
+ return -ENOTSUP;
+}
+
+static long blockdev_file_mmap(vnode_t *file, mobj_t **ret) { return -ENOTSUP; }
+
+static long blockdev_file_fill_pframe(vnode_t *file, pframe_t *pf)
+{
+ return -ENOTSUP;
+}
+
+static long blockdev_file_flush_pframe(vnode_t *file, pframe_t *pf)
+{
+ return -ENOTSUP;
+}