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/s5fs |
Created student weenix repository
Diffstat (limited to 'kernel/fs/s5fs')
-rw-r--r-- | kernel/fs/s5fs/s5fs.c | 860 | ||||
-rw-r--r-- | kernel/fs/s5fs/s5fs_subr.c | 590 |
2 files changed, 1450 insertions, 0 deletions
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; + } +} |