metal-cos/sys/fs/o2fs/o2fs.c
2015-01-30 23:02:18 -08:00

387 lines
8.0 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/diskcache.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();
DiskCacheEntry *entry;
SuperBlock *sb;
ASSERT(sizeof(BDirEntry) == 512);
if (!fs)
return NULL;
status = DiskCache_Read(disk, 0, &entry);
if (status < 0) {
Alert(o2fs, "O2FS: Disk cache read failed\n");
return NULL;
}
// Read superblock
sb = entry->buffer;
if (memcmp(sb->magic, SUPERBLOCK_MAGIC, 8) != 0) {
Alert(o2fs, "O2FS: Invalid file system\n");
DiskCache_Release(entry);
return NULL;
}
if (sb->versionMajor != O2FS_VERSION_MAJOR ||
sb->versionMinor != O2FS_VERSION_MINOR) {
Alert(o2fs, "O2FS: Unsupported file system version\n");
DiskCache_Release(entry);
return NULL;
}
Log(o2fs, "O2FS: File system mounted\n");
Log(o2fs, "O2FS: Root @ 0x%llx\n", sb->root.offset);
fs->fsptr = entry;
fs->fsval = sb->root.offset;
// 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, "O2FS: Mount failed");
DiskCache_Release(entry);
return NULL;
}
return fs;
}
int
O2FS_Unmount(VFS *fs)
{
NOT_IMPLEMENTED();
return -1;
}
VNode *
O2FSLoadVNode(VFS *fs, ObjID *objid)
{
int status;
VNode *vn;
BNode *bn;
DiskCacheEntry *entry;
status = DiskCache_Read(fs->disk, objid->offset, &entry);
if (status < 0) {
Alert(o2fs, "O2FS: disk read error\n");
return NULL;
}
bn = entry->buffer;
if (memcmp(&bn->magic, BNODE_MAGIC, 8) != 0) {
Alert(o2fs, "O2FS: bad BNode magic\n");
DiskCache_Release(entry);
return NULL;
}
if (bn->versionMajor != O2FS_VERSION_MAJOR ||
bn->versionMinor != O2FS_VERSION_MINOR) {
Alert(o2fs, "O2FS: unsupported BNode version\n");
DiskCache_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;
}
void
O2FSRetainVNode(VNode *vn)
{
vn->refCount++;
}
void
O2FSReleaseVNode(VNode *vn)
{
vn->refCount--;
if (vn->refCount == 0) {
DiskCache_Release(vn->fsptr);
Spinlock_Destroy(&vn->lock);
VNode_Free(vn);
}
}
int
O2FS_GetRoot(VFS *fs, VNode **dn)
{
int status;
VNode *vn;
DiskCacheEntry *entry;
BNode *bn;
if (fs->root) {
fs->root->refCount++;
*dn = fs->root;
return 0;
}
status = DiskCache_Read(fs->disk, fs->fsval, &entry);
if (status < 0) {
Alert(o2fs, "O2FS: disk read error\n");
return status;
}
bn = entry->buffer;
if (memcmp(&bn->magic, BNODE_MAGIC, 8) != 0) {
Alert(o2fs, "O2FS: bad BNode magic\n");
DiskCache_Release(entry);
return -1;
}
if (bn->versionMajor != O2FS_VERSION_MAJOR ||
bn->versionMinor != O2FS_VERSION_MINOR) {
Alert(o2fs, "O2FS: unsupported BNode version\n");
DiskCache_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);
}
int
O2FS_Lookup(VNode *dn, VNode **fn, const char *name)
{
int status;
VFS *vfs = dn->vfs;
DiskCacheEntry *sbEntry = (DiskCacheEntry *)vfs->fsptr;
SuperBlock *sb = sbEntry->buffer;
DiskCacheEntry *dirEntry = (DiskCacheEntry *)dn->fsptr;
BNode *dirBN = dirEntry->buffer;
uint64_t blocks = (dirBN->size + sb->blockSize - 1) / sb->blockSize;
uint64_t b;
VLOG(o2fs, "O2FS: LOOKUP %lld %d\n", dirBN->size, blocks);
for (b = 0; b < blocks; b++) {
// Read block
int e;
int entryPerBlock = sb->blockSize / sizeof(BDirEntry);
DiskCacheEntry *entry;
BDirEntry *dir;
status = DiskCache_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;
}
}
}
DiskCache_Release(entry);
}
return -1;
}
int
O2FS_Open(VNode *fn)
{
return 0;
}
int
O2FS_Close(VNode *fn)
{
return 0;
}
int
O2FS_Stat(VNode *fn, struct stat *statinfo)
{
VFS *vfs = fn->vfs;
DiskCacheEntry *sbEntry = (DiskCacheEntry *)vfs->fsptr;
SuperBlock *sb = sbEntry->buffer;
DiskCacheEntry *fileEntry = (DiskCacheEntry *)fn->fsptr;
BNode *fileBN = fileEntry->buffer;
statinfo->st_size = fileBN->size;
// XXX: support other fields
statinfo->st_blocks = (fileBN->size + sb->blockSize - 1) / sb->blockSize;
statinfo->st_blksize = sb->blockSize;
return 0;
}
int
O2FS_Read(VNode *fn, void *buf, uint64_t off, uint64_t len)
{
int status;
VFS *vfs = fn->vfs;
DiskCacheEntry *sbEntry = (DiskCacheEntry *)vfs->fsptr;
SuperBlock *sb = sbEntry->buffer;
DiskCacheEntry *fileEntry = (DiskCacheEntry *)fn->fsptr;
BNode *fileBN = fileEntry->buffer;
uint64_t blocks = (fileBN->size + sb->blockSize - 1) / sb->blockSize;
uint64_t readBytes = 0;
VLOG(o2fs, "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;
DiskCacheEntry *entry;
if (bOff + len > sb->blockSize) {
bLen = sb->blockSize - bOff;
} else {
bLen = len;
}
status = DiskCache_Read(fn->disk, fileBN->direct[b].offset, &entry);
if (status < 0)
return status;
memcpy(buf, entry->buffer + bOff, bLen);
DiskCache_Release(entry);
readBytes += bLen;
buf += bLen;
off += bLen;
len -= bLen;
if (len == 0)
break;
}
return readBytes;
}
int
O2FS_Write(VNode *fn, void *buf, uint64_t off, uint64_t len)
{
NOT_IMPLEMENTED();
return -EINVAL;
}
int
O2FS_ReadDir(VNode *fn, void *buf, uint64_t len, uint64_t *off)
{
int count = 0;
int status;
DiskCacheEntry *fileEntry = (DiskCacheEntry *)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.");
return status;
}
// XXX: Validation and fill in all fields
strcpy(de.d_name, (char *)dirEntry.name);
status = CopyOut(&de, (uintptr_t)buf, sizeof(de));
if (status != 0)
return status;
*off += sizeof(dirEntry);
buf += sizeof(de);
len -= sizeof(de);
count++;
}
return count;
}