diff options
Diffstat (limited to 'kernel/fs/s5fs/s5fs_subr.c')
-rw-r--r-- | kernel/fs/s5fs/s5fs_subr.c | 590 |
1 files changed, 590 insertions, 0 deletions
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; + } +} |