diff options
author | nthnluu <nate1299@me.com> | 2024-01-28 21:20:27 -0500 |
---|---|---|
committer | nthnluu <nate1299@me.com> | 2024-01-28 21:20:27 -0500 |
commit | c63f340d90800895f007de64b7d2d14624263331 (patch) | |
tree | 2c0849fa597dd6da831c8707b6f2603403778d7b /kernel/fs |
Created student weenix repository
Diffstat (limited to 'kernel/fs')
-rw-r--r-- | kernel/fs/Submodules | 1 | ||||
-rw-r--r-- | kernel/fs/file.c | 115 | ||||
-rw-r--r-- | kernel/fs/namev.c | 263 | ||||
-rw-r--r-- | kernel/fs/open.c | 67 | ||||
-rw-r--r-- | kernel/fs/pipe.c | 256 | ||||
-rw-r--r-- | kernel/fs/ramfs/ramfs.c | 852 | ||||
-rw-r--r-- | kernel/fs/s5fs/s5fs.c | 860 | ||||
-rw-r--r-- | kernel/fs/s5fs/s5fs_subr.c | 590 | ||||
-rw-r--r-- | kernel/fs/vfs.c | 222 | ||||
-rw-r--r-- | kernel/fs/vfs_syscall.c | 356 | ||||
-rw-r--r-- | kernel/fs/vnode.c | 250 | ||||
-rw-r--r-- | kernel/fs/vnode_specials.c | 176 |
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; +} |