metal-cos/sys/fs/o2fs/o2fs.c

666 lines
14 KiB
C

#include <stdbool.h>
#include <stdint.h>
#include <string.h>
#include <errno.h>
#include <sys/kassert.h>
#include <sys/kmem.h>
#include <sys/spinlock.h>
#include <sys/disk.h>
#include <sys/bufcache.h>
#include <sys/vfs.h>
#include <sys/dirent.h>
#include <sys/thread.h>
#include "o2fs.h"
VFS *O2FS_Mount(Disk *disk);
int O2FS_Unmount(VFS *fs);
int O2FS_GetRoot(VFS *fs, VNode **dn);
int O2FS_Lookup(VNode *dn, VNode **fn, const char *name);
int O2FS_Open(VNode *fn);
int O2FS_Close(VNode *fn);
int O2FS_Stat(VNode *fn, struct stat *statinfo);
int O2FS_Read(VNode *fn, void *buf, uint64_t off, uint64_t len);
int O2FS_Write(VNode *fn, void *buf, uint64_t off, uint64_t len);
int O2FS_ReadDir(VNode *fn, void *buf, uint64_t len, uint64_t *off);
static VFSOp O2FSOperations = {
.unmount = O2FS_Unmount,
.getroot = O2FS_GetRoot,
.lookup = O2FS_Lookup,
.open = O2FS_Open,
.close = O2FS_Close,
.stat = O2FS_Stat,
.read = O2FS_Read,
.write = O2FS_Write,
.readdir = O2FS_ReadDir,
};
VFS *
O2FS_Mount(Disk *disk)
{
int status;
VFS *fs = VFS_Alloc();
BufCacheEntry *entry;
SuperBlock *sb;
ASSERT(sizeof(BDirEntry) == 512);
if (!fs)
return NULL;
status = BufCache_Read(disk, 0, &entry);
if (status < 0) {
Alert(o2fs, "Disk cache read failed\n");
return NULL;
}
// Read superblock
sb = entry->buffer;
if (memcmp(sb->magic, SUPERBLOCK_MAGIC, 8) != 0) {
Alert(o2fs, "Invalid file system\n");
BufCache_Release(entry);
return NULL;
}
if (sb->versionMajor != O2FS_VERSION_MAJOR ||
sb->versionMinor != O2FS_VERSION_MINOR) {
Alert(o2fs, "Unsupported file system version\n");
BufCache_Release(entry);
return NULL;
}
// Read bitmap
for (int i = 0; i < sb->bitmapSize; i++) {
ASSERT(i < 16);
BufCacheEntry *bentry;
uint64_t offset = sb->bitmapOffset + i * sb->blockSize;
if (BufCache_Read(disk, offset, &bentry) < 0) {
Alert(o2fs, "Bitmap read failed\n");
for (i = 0; i < 16; i++)
BufCache_Release(fs->bitmap[i]);
BufCache_Release(entry);
return NULL;
}
fs->bitmap[i] = bentry;
}
DLOG(o2fs, "File system mounted\n");
DLOG(o2fs, "Root @ 0x%llx\n", sb->root.offset);
fs->fsptr = entry;
fs->fsval = sb->root.offset;
fs->blksize = sb->blockSize;
// Setup VFS structure
fs->op = &O2FSOperations;
fs->disk = disk;
Spinlock_Init(&fs->lock, "O2FS Lock", SPINLOCK_TYPE_NORMAL);
fs->refCount = 1;
fs->root = NULL;
status = O2FS_GetRoot(fs, &fs->root);
if (status < 0) {
Alert(o2fs, "Mount failed");
BufCache_Release(entry);
return NULL;
}
return fs;
}
int
O2FS_Unmount(VFS *fs)
{
NOT_IMPLEMENTED();
return -1;
}
/**
* O2FSBAlloc --
*
* Allocate a block.
*
* @param [in] vfs VFS Instance.
*
* @return Block number.
*/
uint64_t
O2FSBAlloc(VFS *fs)
{
for (int i = 0; i < 16; i++) {
char *bitmap;
BufCacheEntry *bentry = fs->bitmap[i];
if (fs->bitmap[i] == NULL)
break;
bitmap = bentry->buffer;
// XXX: Check for end of disk
for (int b = 0; b < fs->blksize; b++) {
for (int bi = 0; bi < 8; bi++) {
if (((bitmap[b] >> bi) & 0x1) == 0) {
/* Set bit */
bitmap[b] |= (1 << bi);
/* Write bitmap */
BufCache_Write(bentry);
/*
* Block index is the sum of:
* blksize*8 blocks per bitmap entry
* 8 blocks per bitmap byte
* bit #
*/
uint64_t blk = fs->blksize*8*i + 8*b + bi;
DLOG(o2fs, "BAlloc %lu\n", blk);
return blk;
}
}
}
}
Alert(o2fs, "Out of space!\n");
return 0;
}
/**
* O2FSBFree --
*
* Free a block.
*
* @param [in] vfs VFS Instance.
* @param [in] block Block number.
*/
void
O2FSBFree(VFS *fs, uint64_t block)
{
uint64_t bitoff = block & 0x7;
uint64_t bytoff = (block >> 8) % fs->blksize;
uint64_t bufoff = block / (fs->blksize*8);
DLOG(o2fs, "BFree %lu\n", block);
BufCacheEntry *bentry = fs->bitmap[bufoff];
ASSERT(bentry != NULL);
char *bitmap = bentry->buffer;
/* Mask out the bit */
bitmap[bytoff] &= ~(1 << bitoff);
/* Write the bitmap */
BufCache_Write(bentry);
}
/**
* O2FSLoadVNode --
*
* Load a VNode from the disk given an ObjID.
*
* @param [in] vfs VFS Instance.
* @param [in] oobjid Object ID.
*/
VNode *
O2FSLoadVNode(VFS *fs, ObjID *objid)
{
int status;
VNode *vn;
BNode *bn;
BufCacheEntry *entry;
status = BufCache_Read(fs->disk, objid->offset, &entry);
if (status < 0) {
Alert(o2fs, "disk read error\n");
return NULL;
}
bn = entry->buffer;
if (memcmp(&bn->magic, BNODE_MAGIC, 8) != 0) {
Alert(o2fs, "bad BNode magic\n");
BufCache_Release(entry);
return NULL;
}
if (bn->versionMajor != O2FS_VERSION_MAJOR ||
bn->versionMinor != O2FS_VERSION_MINOR) {
Alert(o2fs, "unsupported BNode version\n");
BufCache_Release(entry);
return NULL;
}
vn = VNode_Alloc();
if (!vn) {
return NULL;
}
vn->op = &O2FSOperations;
vn->disk = fs->disk;
Spinlock_Init(&vn->lock, "VNode Lock", SPINLOCK_TYPE_NORMAL);
vn->refCount = 1;
vn->fsptr = entry;
vn->vfs = fs;
return vn;
}
/**
* O2FSGrowVNode --
*
* Grow a VNode.
*
* @param [in] vfs VFS Instance.
* @param [in] bn BNode for the object.
* @param [in] filesz New file size.
*
* @return 0 on success, otherwise error code.
*/
int
O2FSGrowVNode(VNode *vn, uint64_t filesz)
{
VFS *vfs = vn->vfs;
BufCacheEntry *vnEntry = (BufCacheEntry *)vn->fsptr;
BNode *bn = vnEntry->buffer;
if (filesz > (vfs->blksize * 64))
return -EINVAL;
for (int i = 0; i < ((filesz + vfs->blksize - 1) / vfs->blksize); i++) {
if (bn->direct[i].offset == 0) {
uint64_t blkno = O2FSBAlloc(vfs);
if (blkno == 0) {
return -ENOSPC;
}
bn->direct[i].offset = blkno * vfs->blksize;
}
}
DLOG(o2fs, "Growing: %d\n", filesz);
bn->size = filesz;
BufCache_Write(vnEntry);
return 0;
}
/**
* O2FSRetainVNode --
*
* Increment VNode reference count.
*
* @param [in] vn VNode.
*/
void
O2FSRetainVNode(VNode *vn)
{
vn->refCount++;
}
/**
* O2FSReleaseVNode --
*
* Decrement VNode reference count and release it if reaches zero.
*
* @param [in] vn VNode.
*/
void
O2FSReleaseVNode(VNode *vn)
{
vn->refCount--;
if (vn->refCount == 0) {
BufCache_Release(vn->fsptr);
Spinlock_Destroy(&vn->lock);
VNode_Free(vn);
}
}
/**
* O2FS_GetRoot --
*
* Read the root directory Inode.
*
* @param [in] fs VFS Instance.
* @param [out] dn VNode of the root directory.
*
* @return 0 on success, otherwise error.
*/
int
O2FS_GetRoot(VFS *fs, VNode **dn)
{
int status;
VNode *vn;
BufCacheEntry *entry;
BNode *bn;
if (fs->root) {
fs->root->refCount++;
*dn = fs->root;
return 0;
}
status = BufCache_Read(fs->disk, fs->fsval, &entry);
if (status < 0) {
Alert(o2fs, "disk read error\n");
return status;
}
bn = entry->buffer;
if (memcmp(&bn->magic, BNODE_MAGIC, 8) != 0) {
Alert(o2fs, "bad BNode magic\n");
BufCache_Release(entry);
return -1;
}
if (bn->versionMajor != O2FS_VERSION_MAJOR ||
bn->versionMinor != O2FS_VERSION_MINOR) {
Alert(o2fs, "unsupported BNode version\n");
BufCache_Release(entry);
return -1;
}
vn = VNode_Alloc();
vn->op = &O2FSOperations;
vn->disk = fs->disk;
Spinlock_Init(&vn->lock, "VNode Lock", SPINLOCK_TYPE_NORMAL);
vn->refCount = 1;
vn->fsptr = entry;
vn->vfs = fs;
*dn = vn;
return 0;
}
void
O2FSDumpDirEntry(BDirEntry *entry)
{
VLOG(o2fs, "%16s %08llx %08llx\n", entry->name, entry->objId.offset, entry->size);
}
/**
* O2FS_Lookup --
*
* Lookup a directory entry within a given directory.
*
* @param [in] vn VNode of the directory to look through.
* @param [out] fn VNode of the entry if found.
* @param [in] name Name of the file.
*
* @return 0 on success, otherwise error.
*/
int
O2FS_Lookup(VNode *dn, VNode **fn, const char *name)
{
int status;
VFS *vfs = dn->vfs;
BufCacheEntry *sbEntry = (BufCacheEntry *)vfs->fsptr;
SuperBlock *sb = sbEntry->buffer;
BufCacheEntry *dirEntry = (BufCacheEntry *)dn->fsptr;
BNode *dirBN = dirEntry->buffer;
uint64_t blocks = (dirBN->size + sb->blockSize - 1) / sb->blockSize;
uint64_t b;
DLOG(o2fs, "Lookup %lld %d\n", dirBN->size, blocks);
for (b = 0; b < blocks; b++) {
// Read block
int e;
int entryPerBlock = sb->blockSize / sizeof(BDirEntry);
BufCacheEntry *entry;
BDirEntry *dir;
status = BufCache_Read(dn->disk, dirBN->direct[b].offset, &entry);
if (status < 0)
return status;
dir = (BDirEntry *)entry->buffer;
for (e = 0; e < entryPerBlock; e++) {
if (strcmp((char *)dir[e].magic, BDIR_MAGIC) == 0) {
O2FSDumpDirEntry(&dir[e]);
if (strcmp((char *)dir[e].name, name) == 0) {
*fn = O2FSLoadVNode(vfs, &dir[e].objId);
return 0;
}
}
}
BufCache_Release(entry);
}
return -1;
}
int
O2FS_Open(VNode *fn)
{
return 0;
}
int
O2FS_Close(VNode *fn)
{
return 0;
}
/**
* O2FS_Stat --
*
* Stat a VNode.
*
* @param [in] fn VNode of the file to stat.
* @param [out] statinfo Stat structure.
*
* @return 0 on success.
*/
int
O2FS_Stat(VNode *fn, struct stat *statinfo)
{
VFS *vfs = fn->vfs;
BufCacheEntry *sbEntry = (BufCacheEntry *)vfs->fsptr;
SuperBlock *sb = sbEntry->buffer;
BufCacheEntry *fileEntry = (BufCacheEntry *)fn->fsptr;
BNode *fileBN = fileEntry->buffer;
DLOG(o2fs, "O2FS %p %d\n", fileBN, fileBN->size);
statinfo->st_ino = fileEntry->diskOffset;
statinfo->st_size = fileBN->size;
statinfo->st_blocks = (fileBN->size + sb->blockSize - 1) / sb->blockSize;
statinfo->st_blksize = sb->blockSize;
return 0;
}
/**
* O2FS_Read --
*
* Read from a VNode.
*
* @param [in] fn VNode of the file.
* @param [out] buf Buffer to read into.
* @param [in] off Offset within the file.
* @param [in] len Length of the buffer to read.
*
* @return number of bytes on success, otherwise negative error code.
*/
int
O2FS_Read(VNode *fn, void *buf, uint64_t off, uint64_t len)
{
int status;
VFS *vfs = fn->vfs;
BufCacheEntry *sbEntry = (BufCacheEntry *)vfs->fsptr;
SuperBlock *sb = sbEntry->buffer;
BufCacheEntry *fileEntry = (BufCacheEntry *)fn->fsptr;
BNode *fileBN = fileEntry->buffer;
uint64_t blocks = (fileBN->size + sb->blockSize - 1) / sb->blockSize;
uint64_t readBytes = 0;
DLOG(o2fs, "Read %lld %d\n", fileBN->size, blocks);
if (off > fileBN->size) {
return 0;
}
if (off + len > fileBN->size) {
len = fileBN->size - off;
}
while (1) {
uint64_t b = off / sb->blockSize;
uint64_t bOff = off % sb->blockSize;
uint64_t bLen;
BufCacheEntry *entry;
if (bOff + len > sb->blockSize) {
bLen = sb->blockSize - bOff;
} else {
bLen = len;
}
status = BufCache_Read(fn->disk, fileBN->direct[b].offset, &entry);
if (status < 0)
return status;
DLOG(o2fs, "READ %lx %lx %lld\n", buf, entry->buffer, bLen);
memcpy(buf, entry->buffer + bOff, bLen);
BufCache_Release(entry);
readBytes += bLen;
buf += bLen;
off += bLen;
len -= bLen;
if (len == 0)
break;
}
return readBytes;
}
/**
* O2FS_Write --
*
* Write to a VNode.
*
* @param [in] fn VNode of the file.
* @param [in] buf Buffer to write out.
* @param [in] off Offset within the file.
* @param [in] len Length of the buffer to write.
*
* @return number of bytes on success, otherwise negative error code.
*/
int
O2FS_Write(VNode *fn, void *buf, uint64_t off, uint64_t len)
{
int status;
VFS *vfs = fn->vfs;
BufCacheEntry *sbEntry = (BufCacheEntry *)vfs->fsptr;
SuperBlock *sb = sbEntry->buffer;
BufCacheEntry *fileEntry = (BufCacheEntry *)fn->fsptr;
BNode *fileBN = fileEntry->buffer;
uint64_t blocks = (fileBN->size + sb->blockSize - 1) / sb->blockSize;
uint64_t readBytes = 0;
DLOG(o2fs, "Write %lld %d\n", fileBN->size, blocks);
// XXX: Check permissions
if (fileBN->size < (off+len)) {
status = O2FSGrowVNode(fn, off+len);
if (status < 0)
return status;
}
while (1) {
uint64_t b = off / sb->blockSize;
uint64_t bOff = off % sb->blockSize;
uint64_t bLen;
BufCacheEntry *entry;
if (bOff + len > sb->blockSize) {
bLen = sb->blockSize - bOff;
} else {
bLen = len;
}
status = BufCache_Read(fn->disk, fileBN->direct[b].offset, &entry);
if (status < 0)
return status;
DLOG(o2fs, "WRITE %lx %lx %lld\n", buf, entry->buffer, bLen);
memcpy(entry->buffer + bOff, buf, bLen);
BufCache_Write(entry);
BufCache_Release(entry);
readBytes += bLen;
buf += bLen;
off += bLen;
len -= bLen;
if (len == 0)
break;
}
return readBytes;
}
/**
* O2FS_ReadDir --
*
* Read a directory entry.
*
* @param [in] fn VNode of the directory.
* @param [out] buf Buffer to read the directory entry into.
* @param [in] len Length of the buffer.
* @param [inout] off Offset to start from and return the next offset.
*
* @return 0 on success, otherwise error.
*/
int
O2FS_ReadDir(VNode *fn, void *buf, uint64_t len, uint64_t *off)
{
int count = 0;
int status;
BufCacheEntry *fileEntry = (BufCacheEntry *)fn->fsptr;
BNode *fileBN = fileEntry->buffer;
BDirEntry dirEntry;
struct dirent de;
while (len >= sizeof(de)) {
if (*off == fileBN->size)
return count;
if (*off > fileBN->size)
return -EINVAL;
// XXX: Check offset
status = O2FS_Read(fn, &dirEntry, *off, sizeof(dirEntry));
if (status != sizeof(dirEntry)) {
kprintf("Unexpected error reading directory: %d\n", status);
return -ENOTDIR;
}
if (strncmp((char *)&dirEntry.magic[0], BDIR_MAGIC, sizeof(dirEntry.magic)) != 0) {
return -ENOTDIR;
}
// XXX: Validation and fill in all fields
strcpy(de.d_name, (char *)dirEntry.name);
status = Copy_Out(&de, (uintptr_t)buf, sizeof(de));
if (status != 0)
return status;
*off += sizeof(dirEntry);
buf += sizeof(de);
len -= sizeof(de);
count++;
}
return count;
}