diff --git a/kernel-src/fs/ext2.c b/kernel-src/fs/ext2.c index d2d7f254..86cf2533 100644 --- a/kernel-src/fs/ext2.c +++ b/kernel-src/fs/ext2.c @@ -2,6 +2,7 @@ #include #include #include +#include #include #include #include @@ -358,6 +359,7 @@ static int freestructure(ext2fs_t *fs, uintmax_t id, bool inode) { int bmindex = index % 8; // 0 means free + __assert(bm[bmoffset] & (1 << bmindex)); bm[bmoffset] &= ~(1 << bmindex); // sync bitmap back into disk @@ -513,6 +515,7 @@ static int allocandset(ext2fs_t *fs, ext2node_t *node, uintmax_t setoffset, bloc } node->inode.sectcount += INODE_SECTSPERBLOCK(fs); + *newvalue = block; return e; } @@ -523,6 +526,14 @@ static int inodeallocateindirect(ext2fs_t *fs, ext2node_t *node, int indirect) { if (e) return e; + __assert(fs->blocksize <= UTIL_ZEROBUFFERSIZE); + size_t writec; + e = vfs_write(fs->backing, util_zerobuffer, fs->blocksize, BLOCK_GETDISKOFFSET(fs, v), &writec, 0); + if (e) { + freestructure(fs, v, false); // TODO if error mark as unclean + return e; + } + switch (indirect) { case 1: node->inode.singlypointer = v; @@ -587,8 +598,8 @@ static int setinodeblock(ext2fs_t *fs, ext2node_t *node, uintmax_t index, blockp node->inode.directpointer[directindex] = block; } else { size_t count; - blockptr_t doublyptr; - blockptr_t singlyptr; + blockptr_t doublyptr = 0; + blockptr_t singlyptr = 0; int e = 0; @@ -707,6 +718,8 @@ static int rwblocks(ext2fs_t *fs, ext2node_t *node, void *buffer, size_t count, if (e) return e; + __assert(block); + size_t donecount; e = write ? vfs_write(fs->backing, bufferp, fs->blocksize, BLOCK_GETDISKOFFSET(fs, block), &donecount, 0) : @@ -727,6 +740,8 @@ static int rwblock(ext2fs_t *fs, ext2node_t *node, void *buffer, size_t count, u if (e) return e; + __assert(block); + size_t donecount; e = write ? vfs_write(fs->backing, buffer, count, BLOCK_GETDISKOFFSET(fs, block) + offset, &donecount, 0) : @@ -838,7 +853,7 @@ static int insertdent(ext2fs_t *fs, ext2node_t *node, char *name, int inode, int uintmax_t offset = 0; size_t truesize, freesize; while (offset < inodesize) { - splitdent = (ext2dent_t *)((uintptr_t)dirbuffer + offset); + splitdent = (ext2dent_t *)((uintptr_t)dirbuffer + offset); // TODO unused dents truesize = ROUND_UP(sizeof(ext2dent_t) + splitdent->namelen, 4); freesize = splitdent->size - truesize; @@ -873,6 +888,123 @@ static int insertdent(ext2fs_t *fs, ext2node_t *node, char *name, int inode, int return err; } +static int removedent(ext2fs_t *fs, ext2node_t *node, char *name, int *inode) { + // XXX loading the whole dir into memory at once isn't the best idea but works for now + void *dirbuffer = vmm_map(NULL, INODE_SIZE(&node->inode) + 1, VMM_FLAGS_ALLOCATE, BUFFER_MAP_FLAGS, NULL); // + 1 to make sure there is at least one page mapped + if (dirbuffer == NULL) + return ENOMEM; + + int err = rwbytes(fs, node, dirbuffer, INODE_SIZE(&node->inode), 0, false); + if (err) + goto cleanup; + + uintmax_t offset = 0; + int inodefound = 0; + size_t namelen = strlen(name); + uintmax_t lastsize = 0; + ext2dent_t *dent = NULL; + + // iterate through directory to find the inode number and dent offset + while (offset < INODE_SIZE(&node->inode)) { + dent = (ext2dent_t *)((uintptr_t)dirbuffer + offset); + + if (namelen == dent->namelen && dent->inode && strncmp(name, dent->name, dent->namelen) == 0) { + inodefound = dent->inode; + break; + } + + __assert(dent->size); + lastsize = dent->size; + offset += dent->size; + } + + if (inodefound == 0) { + err = ENOENT; + goto cleanup; + } + + *inode = inodefound; + ext2dent_t *lastdent = (ext2dent_t *)((uintptr_t)dent - lastsize); + + if (offset % fs->blocksize) { // can expand last dent size? + lastdent->size += dent->size; + err = rwbytes(fs, node, lastdent, lastdent->size, offset - lastsize, true); + } else { // nope, set current as unused + dent->inode = 0; + dent->name[0] = '\0'; + err = rwbytes(fs, node, dent, dent->size, offset, true); + } + + cleanup: + if (dirbuffer) + vmm_unmap(dirbuffer, INODE_SIZE(&node->inode) + 1, 0); + + return err; +} + +// depth of 0 means singly, 1 means doubly and 2 means triply +static int freeindirect(ext2fs_t *fs, uintmax_t block, int depth) { + blockptr_t *buffer = alloc(fs->blocksize); + if (buffer == NULL) + return ENOMEM; + + size_t readc; + int err = vfs_read(fs->backing, buffer, fs->blocksize, BLOCK_GETDISKOFFSET(fs, block), &readc, 0); + if (err) + goto cleanup; + + size_t blocksinindirect = BLOCKS_IN_INDIRECT(fs); + + for (int i = 0; i < blocksinindirect; ++i) { + if (buffer[i] == 0) + continue; + + if (depth) + err = freeindirect(fs, buffer[i], depth - 1); + else + err = freestructure(fs, buffer[i], false); + + if (err) + goto cleanup; + } + + err = freestructure(fs, block, false); + + cleanup: + free(buffer); + return err; +} + +static void freeinode(ext2fs_t *fs, inode_t *inode, int id) { + // TODO if any error happens here, set fs as unclean + int err = 0; + + int type = INODE_TYPEPERM_TYPE(inode->typeperm); + if (type == INODE_TYPE_DIR || type == INODE_TYPE_REGULAR || (type == INODE_TYPE_SYMLINK && INODE_SIZE(inode) > 60)) { + for (int i = 0; i < 12; ++i) { + uintmax_t block = inode->directpointer[i]; + if (block) + err |= freestructure(fs, block, false); + } + + if (inode->singlypointer) + err |= freeindirect(fs, inode->singlypointer, 0); + + if (inode->doublypointer) + err |= freeindirect(fs, inode->doublypointer, 1); + + if (inode->triplypointer) + err |= freeindirect(fs, inode->triplypointer, 2); + } + + inode->dtime = timekeeper_time().s; + err |= writeinode(fs, inode, id); + + err |= freestructure(fs, id, true); + if (type == INODE_TYPE_DIR) + err |= changedircount(fs, INODE_GETGROUP(fs, id), -1); +} + static int ext2_open(vnode_t **vnode, int flags, cred_t *cred) { // TODO device files return 0; @@ -965,10 +1097,9 @@ static int ext2_lookup(vnode_t *vnode, char *name, vnode_t **result, cred_t *cre } else { // it is, use the vnode from the table newnode = tablev; + VOP_HOLD(&newnode->vnode); } - VOP_HOLD(&newnode->vnode); - MUTEX_RELEASE(&fs->inodetablelock); *result = &newnode->vnode; @@ -977,6 +1108,7 @@ static int ext2_lookup(vnode_t *vnode, char *name, vnode_t **result, cred_t *cre if (err && newnode) { vnode_t *tmp = (vnode_t *)newnode; VOP_RELEASE(tmp); + MUTEX_RELEASE(&fs->inodetablelock); } VOP_UNLOCK(vnode); @@ -1179,13 +1311,13 @@ static int ext2_create(vnode_t *parent, char *name, vattr_t *attr, int type, vno EXT2NODE_INIT(newnode, &vnops, 0, type, parent->vfs, 0); int inodetmp; + uintmax_t id = 0; int err = findindir(fs, node, name, &inodetmp); if (err == 0) err = EEXIST; if (err != ENOENT) goto cleanup; - uintmax_t id = 0; err = allocatestructure(fs, &id, true); if (err) goto cleanup; @@ -1242,8 +1374,175 @@ static int ext2_create(vnode_t *parent, char *name, vattr_t *attr, int type, vno return err; } -static int ext2_symlink(vnode_t *parentvnode, char *name, vattr_t *attr, char *path, cred_t *cred) { - return ENOSYS; +// XXX this implementation isn't atomic in the create -> write target step +static int ext2_symlink(vnode_t *vnode, char *name, vattr_t *attr, char *path, cred_t *cred) { + ext2fs_t *fs = (ext2fs_t *)vnode->vfs; + size_t linklen = strlen(path); + + vnode_t *newvnode; + int err = ext2_create(vnode, name, attr, V_TYPE_LINK, &newvnode, cred); + if (err) + return err; + + VOP_LOCK(vnode); + VOP_LOCK(newvnode); + ext2node_t *newnode = (ext2node_t *)newvnode; + + if (linklen <= 60) { + // store it in the inode + memcpy(&newnode->inode.directpointer, path, linklen); + } else { + // or in the file + err = resizeinode(fs, newnode, linklen); + if (err) + goto cleanup; + + err = rwbytes(fs, newnode, path, linklen, 0, true); + if (err) + goto cleanup; + // TODO in case of error, unlink + } + + INODE_SETSIZE(&newnode->inode, linklen); + // TODO in case of error, unlink + err = writeinode(fs, &newnode->inode, newnode->id); + + VOP_RELEASE(newvnode); + + cleanup: + VOP_UNLOCK(newvnode); + VOP_UNLOCK(vnode); + return err; +} + +#define MMAPTMPFLAGS (ARCH_MMU_FLAGS_READ | ARCH_MMU_FLAGS_NOEXEC | ARCH_MMU_FLAGS_WRITE) + +static int ext2_mmap(vnode_t *node, void *addr, uintmax_t offset, int flags, cred_t *cred) { + void *paddr = pmm_alloc(1, PMM_SECTION_DEFAULT); + if (paddr == NULL) + return ENOMEM; + + if (arch_mmu_map(_cpu()->vmmctx->pagetable, paddr, addr, MMAPTMPFLAGS) == false) { + pmm_free(paddr, 1); + return ENOMEM; + } + + size_t readc; + int e = ext2_read(node, addr, PAGE_SIZE, offset, flags, &readc, cred); + if (e) { + arch_mmu_unmap(_cpu()->vmmctx->pagetable, addr); + pmm_free(paddr, 1); + return e; + } + + memset((void *)((uintptr_t)addr + readc), 0, PAGE_SIZE - readc); + + arch_mmu_remap(_cpu()->vmmctx->pagetable, paddr, addr, vnodeflagstommuflags(flags)); + + return 0; +} + +static int ext2_munmap(vnode_t *node, void *addr, uintmax_t offset, int flags, cred_t *cred) { + if (flags & V_FFLAGS_SHARED) { + size_t wc; + // TODO no growing flag + __assert(ext2_write(node, addr, PAGE_SIZE, offset, flags, &wc, cred) == 0); + } + + void *paddr = arch_mmu_getphysical(_cpu()->vmmctx->pagetable, addr); + __assert(paddr); + arch_mmu_unmap(_cpu()->vmmctx->pagetable, addr); + pmm_free(paddr, 1); + return 0; +} + +static int ext2_unlink(vnode_t *vnode, char *name, cred_t *cred) { + ext2node_t *node = (ext2node_t *)vnode; + ext2fs_t *fs = (ext2fs_t *)vnode->vfs; + VOP_LOCK(vnode); + + int inode = 0; + int err = removedent(fs, node, name, &inode); + if (err) + goto cleanup; + + bool isdir = false; + void *v; + MUTEX_ACQUIRE(&fs->inodetablelock, false); + err = hashtable_get(&fs->inodetable, &v, &inode, sizeof(inode)); + if (err == ENOENT) { + inode_t buff; + err = readinode(fs, &buff, inode); // TODO if error mark as unclean + + if (INODE_TYPEPERM_TYPE(buff.typeperm) == INODE_TYPE_DIR) { // account for . entry + isdir = true; + buff.links -= 1; + } + + if (--buff.links == 0) + freeinode(fs, &buff, inode); + else + err = writeinode(fs, &buff, inode); // TODO if error mark as unclean + + MUTEX_RELEASE(&fs->inodetablelock); + } else if (err) { + MUTEX_RELEASE(&fs->inodetablelock); // TODO mark as unclean + goto cleanup; + } else { + ext2node_t *unlinknode = v; + MUTEX_RELEASE(&fs->inodetablelock); + + VOP_LOCK(&unlinknode->vnode); + if (INODE_TYPEPERM_TYPE(unlinknode->inode.typeperm) == INODE_TYPE_DIR) { // account for . entry + isdir = 1; + unlinknode->inode.links -= 1; + } + + int nlinks = --unlinknode->inode.links; + err = writeinode(fs, &unlinknode->inode, unlinknode->id); // TODO mark as unclean + VOP_UNLOCK(&unlinknode->vnode); + if (nlinks == 0) { + vnode_t *unlinkvnode = v; + // do an operation to remove from memory and inode table if theres no more refcounts nor links. + // this will also unallocate the inode and blocks + VOP_RELEASE(unlinkvnode); + } + } + + if (isdir) { // account for .. entry in unlinked dir + --node->inode.links; + writeinode(fs, &node->inode, node->id); // TODO unclean if error + } + + cleanup: + VOP_UNLOCK(vnode); + return err; +} + +static int ext2_inactive(vnode_t *vnode) { + ext2node_t *node = (ext2node_t *)vnode; + ext2fs_t *fs = (ext2fs_t *)vnode->vfs; + + if (node->inode.links) { + // TODO there is still no *normal* situation where this would happen, so it has + // not been implemented yet. the node still has links on the filesystem but is being + // released from memory due to only being referenced by the inode table. + // this was done likely to free up memory (and the structures and data will still remain on disk) + // the inode table lock is nescessarily being held here by the function that called VOP_RELEASE + __assert(!"ext2 VOP_INACTIVE() with hardlink count >0 is currently unimplemented"); + } else { + // the node does not have any more links on the file system nor references and + // we should free it completely from the filesystem. + MUTEX_ACQUIRE(&fs->inodetablelock, false); + __assert(hashtable_remove(&fs->inodetable, &node->id, sizeof(node->id)) == 0); + MUTEX_RELEASE(&fs->inodetablelock); + + freeinode(fs, &node->inode, node->id); + + slab_free(nodecache, node); + } + + return 0; } // TODO move root variable to vfs @@ -1369,13 +1668,6 @@ static vfsops_t vfsops = { .root = ext2_root }; - /* - int (*unlink)(vnode_t *node, char *name, cred_t *cred); - int (*inactive)(vnode_t *node); - int (*mmap)(vnode_t *node, void *addr, uintmax_t offset, int flags, cred_t *cred); - int (*munmap)(vnode_t *node, void *addr, uintmax_t offset, int flags, cred_t *cred); - */ - static vops_t vnops = { .write = ext2_write, .read = ext2_read, @@ -1391,7 +1683,11 @@ static vops_t vnops = { .resize = ext2_resize, .link = ext2_link, .create = ext2_create, - .symlink = ext2_symlink + .symlink = ext2_symlink, + .mmap = ext2_mmap, + .munmap = ext2_munmap, + .unlink = ext2_unlink, + .inactive = ext2_inactive }; void ext2_init() {