basekernel/kernel/diskfs.c

663 lines
17 KiB
C

/*
Copyright (C) 2015-2019 The University of Notre Dame
This software is distributed under the GNU General Public License.
See the file LICENSE for details.
*/
#include "kernel/types.h"
#include "kernel/error.h"
#include "kmalloc.h"
#include "diskfs.h"
#include "string.h"
#include "fs.h"
#include "fs_internal.h"
#include "bcache.h"
#include "page.h"
/* Read or write a block from the raw device, starting from zero. */
static int diskfs_block_read(struct device *d, struct diskfs_block *b, uint32_t blockno )
{
return bcache_read(d, b->data, 1, blockno) ? DISKFS_BLOCK_SIZE : -1;
}
static int diskfs_block_write(struct device *d, struct diskfs_block *b, uint32_t blockno )
{
return bcache_write(d, b->data, 1, blockno) ? DISKFS_BLOCK_SIZE : -1;
}
/* Read or write a bitmap block, starting from the bitmap offset. */
static int diskfs_bitmap_block_read(struct fs_volume *v, struct diskfs_block *b, uint32_t blockno )
{
if(blockno>=v->disk.bitmap_blocks) return KERROR_OUT_OF_SPACE;
return diskfs_block_read(v->device,b,v->disk.bitmap_start+blockno);
}
static int diskfs_bitmap_block_write(struct fs_volume *v, struct diskfs_block *b, uint32_t blockno )
{
if(blockno>=v->disk.bitmap_blocks) return KERROR_OUT_OF_SPACE;
return diskfs_block_write(v->device,b,v->disk.bitmap_start+blockno);
}
/* Read or write an inode block, starting from the inode block offset. */
static int diskfs_inode_block_read(struct fs_volume *v, struct diskfs_block *b, uint32_t blockno )
{
if(blockno>=v->disk.inode_blocks) return KERROR_OUT_OF_SPACE;
return diskfs_block_read(v->device,b,v->disk.inode_start+blockno);
}
static int diskfs_inode_block_write(struct fs_volume *v, struct diskfs_block *b, uint32_t blockno )
{
if(blockno>=v->disk.inode_blocks) return KERROR_OUT_OF_SPACE;
return diskfs_block_write(v->device,b,v->disk.inode_start+blockno);
}
/* Read or write a data block, starting from the data block offset. */
static int diskfs_data_block_read(struct fs_volume *v, struct diskfs_block *b, uint32_t blockno )
{
if(blockno>=v->disk.data_blocks) return KERROR_OUT_OF_SPACE;
return diskfs_block_read(v->device,b,v->disk.data_start+blockno);
}
static int diskfs_data_block_write(struct fs_volume *v, struct diskfs_block *b, uint32_t blockno )
{
if(blockno>=v->disk.data_blocks) return KERROR_OUT_OF_SPACE;
return diskfs_block_write(v->device,b,v->disk.data_start+blockno);
}
/*
Allocate a new data block by scanning the bitmap.
If available, return the block number.
If nothing available, return zero.
*/
static uint32_t diskfs_data_block_alloc( struct fs_volume *v )
{
struct diskfs_block *b = page_alloc(0);
struct diskfs_superblock *s= &v->disk;
int i, j, k;
for(i=0;i<s->bitmap_blocks;i++) {
diskfs_bitmap_block_read(v,b,i);
for(j=0;j<DISKFS_BLOCK_SIZE;j++) {
if(b->data[j]!=0xff) {
for(k=0;k<8;k++) {
if(!((1<<k) & b->data[j])) {
int blockno = i*DISKFS_BLOCK_SIZE+j*8+k;
// Never allocate block zero;
if(blockno==0) continue;
// Do not exceet the actual number of blocks
if(blockno>=v->disk.data_blocks) break;
b->data[j] |= 1<<k;
diskfs_bitmap_block_write(v,b,i);
page_free(b);
return blockno;
}
}
}
}
}
printf("diskfs: warning: out of space!\n");
page_free(b);
return 0;
}
static void diskfs_data_block_free( struct fs_volume *v, int blockno )
{
struct diskfs_block *b = page_alloc(0);
int bitmap_block = blockno/DISKFS_BLOCK_SIZE;
int bitmap_byte = blockno%DISKFS_BLOCK_SIZE/8;
int bitmap_bit = blockno%DISKFS_BLOCK_SIZE%8;
diskfs_bitmap_block_read(v,b,bitmap_block);
b->data[bitmap_byte] &= ~(1<<bitmap_bit);
diskfs_bitmap_block_write(v,b,bitmap_block);
page_free(b);
}
static int diskfs_inumber_alloc( struct fs_volume *v )
{
struct diskfs_block *b = page_alloc(0);
int i, j;
for(i=0;i<v->disk.inode_blocks;i++) {
diskfs_inode_block_read(v,b,i);
for(j=0;j<DISKFS_INODES_PER_BLOCK;j++) {
if(!b->inodes[j].inuse) {
int inumber = i * DISKFS_INODES_PER_BLOCK + j;
b->inodes[j].inuse = 1;
diskfs_inode_block_write(v,b,i);
page_free(b);
return inumber;
}
}
}
printf("diskfs: warning: out of inodes!\n");
page_free(b);
return 0;
}
static void diskfs_inumber_free( struct fs_volume *v, int inumber )
{
int inode_block = inumber / DISKFS_INODES_PER_BLOCK;
struct diskfs_block *b = page_alloc(0);
diskfs_inode_block_read(v,b,inode_block);
b->inodes[inumber%DISKFS_INODES_PER_BLOCK].inuse = 0;
diskfs_inode_block_write(v,b,inode_block);
page_free(b);
}
int diskfs_inode_load( struct fs_volume *v, int inumber, struct diskfs_inode *inode )
{
struct diskfs_block *b = page_alloc(0);
int inode_block = inumber / DISKFS_INODES_PER_BLOCK;
int inode_position = inumber % DISKFS_INODES_PER_BLOCK;
diskfs_inode_block_read(v,b,inode_block);
memcpy(inode,&b->inodes[inode_position],sizeof(*inode));
page_free(b);
return 1;
}
int diskfs_inode_save( struct fs_volume *v, int inumber, struct diskfs_inode *inode )
{
struct diskfs_block *b = page_alloc(0);
int inode_block = inumber / DISKFS_INODES_PER_BLOCK;
int inode_position = inumber % DISKFS_INODES_PER_BLOCK;
diskfs_inode_block_read(v,b,inode_block);
memcpy(&b->inodes[inode_position],inode,sizeof(*inode));
diskfs_inode_block_write(v,b,inode_block);
page_free(b);
return 1;
}
int diskfs_inode_read( struct fs_dirent *d, struct diskfs_block *b, uint32_t block )
{
int actual;
if(block<DISKFS_DIRECT_POINTERS) {
actual = d->disk.direct[block];
} else {
diskfs_data_block_read(d->volume,b,d->disk.indirect);
actual = b->pointers[block-DISKFS_DIRECT_POINTERS];
}
return diskfs_data_block_read(d->volume,b,actual);
}
int diskfs_inode_write( struct fs_dirent *d, struct diskfs_block *b, uint32_t block )
{
int actual;
struct diskfs_inode *i = &d->disk;
if(block<DISKFS_DIRECT_POINTERS) {
actual = i->direct[block];
if(actual==0) {
actual = diskfs_data_block_alloc(d->volume);
if(actual==0) return KERROR_OUT_OF_SPACE;
i->direct[block] = actual;
diskfs_inode_save(d->volume,d->inumber,i);
}
} else {
struct diskfs_block *iblock = page_alloc(0);
if(i->indirect==0) {
actual = diskfs_data_block_alloc(d->volume);
if(actual==0) {
page_free(iblock);
return KERROR_OUT_OF_SPACE;
}
i->indirect = actual;
diskfs_inode_save(d->volume,d->inumber,i);
memset(iblock,0,DISKFS_BLOCK_SIZE);
diskfs_data_block_write(d->volume,iblock,i->indirect);
}
diskfs_data_block_read(d->volume,iblock,i->indirect);
actual = iblock->pointers[block-DISKFS_DIRECT_POINTERS];
if(actual==0) {
actual = diskfs_data_block_alloc(d->volume);
if(actual==0) {
page_free(iblock);
return KERROR_OUT_OF_SPACE;
}
iblock->pointers[block-DISKFS_DIRECT_POINTERS] = actual;
diskfs_data_block_write(d->volume,iblock,i->indirect);
}
page_free(iblock);
}
return diskfs_data_block_write(d->volume,b,actual);
}
struct fs_dirent * diskfs_dirent_create( struct fs_volume *volume, int inumber, int type )
{
struct fs_dirent *d = kmalloc(sizeof(*d));
memset(d,0,sizeof(*d));
diskfs_inode_load(volume,inumber,&d->disk);
d->volume = volume;
d->size = d->disk.size;
d->inumber = inumber;
d->refcount = 1;
d->isdir = type==DISKFS_ITEM_DIR;
return d;
}
int diskfs_dirent_close( struct fs_dirent *d )
{
// XXX check if inode dirty first
diskfs_inode_save(d->volume,d->inumber,&d->disk);
return 0;
}
/* Returns true if two strings a and b (with lengths) have the same contents. Note that diskfs_item.name is not null-terminated but has diskfs_item.name_length characters. When comparing to a null-terminated string, we must check the length first and then the bytes of the string. */
static int diskfs_name_equals( const char *a, int alength, const char *b, int blength )
{
return alength==blength && !strncmp(a,b,alength);
}
struct fs_dirent * diskfs_dirent_lookup( struct fs_dirent *d, const char *name )
{
struct diskfs_block *b = page_alloc(0);
int i, j;
int nblocks = d->size / DISKFS_BLOCK_SIZE;
if(d->size%DISKFS_BLOCK_SIZE) nblocks++;
int name_length = strlen(name);
for(i=0;i<nblocks;i++) {
diskfs_inode_read(d,b,i);
for(j=0;j<DISKFS_ITEMS_PER_BLOCK;j++) {
struct diskfs_item *r = &b->items[j];
if(r->type!=DISKFS_ITEM_BLANK && diskfs_name_equals(name,name_length,r->name,r->name_length)) {
int inumber = r->inumber;
page_free(b);
return diskfs_dirent_create(d->volume,inumber,r->type);
}
}
}
page_free(b);
return 0;
}
int diskfs_dirent_list( struct fs_dirent *d, char *buffer, int length )
{
struct diskfs_block *b = page_alloc(0);
int nblocks = d->size / DISKFS_BLOCK_SIZE;
if(d->size%DISKFS_BLOCK_SIZE) nblocks++;
int i,j;
int total = 0;
for(i=0;i<nblocks;i++) {
diskfs_inode_read(d,b,i);
for(j=0;j<DISKFS_ITEMS_PER_BLOCK;j++) {
struct diskfs_item *r = &b->items[j];
switch(r->type) {
case DISKFS_ITEM_FILE:
case DISKFS_ITEM_DIR:
memcpy(buffer,r->name,r->name_length);
buffer[r->name_length] = 0;
buffer += r->name_length + 1;
length -= r->name_length + 1;
total += r->name_length + 1;
break;
case DISKFS_ITEM_BLANK:
break;
}
}
}
page_free(b);
return total;
}
int diskfs_dirent_resize( struct fs_dirent *d, uint32_t size )
{
d->size = d->disk.size = size;
return 0;
}
static int diskfs_dirent_add( struct fs_dirent *d, const char *name, int type, int inumber )
{
struct diskfs_block *b = page_alloc(0);
int i, j;
int nblocks = d->size / DISKFS_BLOCK_SIZE;
if(d->size%DISKFS_BLOCK_SIZE) nblocks++;
for(i=0;i<nblocks;i++) {
diskfs_inode_read(d,b,i);
for(j=0;j<DISKFS_ITEMS_PER_BLOCK;j++) {
struct diskfs_item *r = &b->items[j];
if(r->type==DISKFS_ITEM_BLANK) {
r->type = type;
r->inumber = inumber;
r->name_length = strlen(name);
memcpy(r->name,name,r->name_length);
/* Save the modified data block. */
diskfs_inode_write(d,b,i);
/* If this increased the logical size, update that too. */
uint32_t newsize = (i*DISKFS_BLOCK_SIZE) + (j+1)*sizeof(struct diskfs_item);
if(newsize>d->size) {
diskfs_dirent_resize(d,newsize);
diskfs_inode_save(d->volume,d->inumber,&d->disk);
}
page_free(b);
return 0;
}
}
}
memset(b->data,0,DISKFS_BLOCK_SIZE);
struct diskfs_item *r = &b->items[0];
r->inumber = inumber;
r->type = type;
r->name_length = strlen(name);
memcpy(r->name,name,r->name_length);
diskfs_dirent_resize(d,d->size+sizeof(*r));
diskfs_inode_write(d,b,i);
diskfs_inode_save(d->volume,d->inumber,&d->disk);
page_free(b);
return 0;
}
struct fs_dirent * diskfs_dirent_create_file_or_dir( struct fs_dirent *d, const char *name, int type )
{
if(strlen(name)>DISKFS_NAME_MAX) return 0; // KERROR_NAME_TOO_LONG
struct fs_dirent *t = diskfs_dirent_lookup(d,name);
if(t) {
diskfs_dirent_close(t);
return 0;
}
int inumber = diskfs_inumber_alloc(d->volume);
if(inumber==0) {
return 0; // KERROR_OUT_OF_SPACE
}
struct diskfs_inode inode;
memset(&inode,0,sizeof(inode));
inode.inuse = 1;
inode.size = 0;
diskfs_inode_save(d->volume,inumber,&inode);
diskfs_dirent_add(d,name,type,inumber);
return diskfs_dirent_create(d->volume,inumber,type);
}
struct fs_dirent * diskfs_dirent_create_file( struct fs_dirent *d, const char *name )
{
return diskfs_dirent_create_file_or_dir(d,name,DISKFS_ITEM_FILE);
}
struct fs_dirent * diskfs_dirent_create_dir( struct fs_dirent *d, const char *name )
{
return diskfs_dirent_create_file_or_dir(d,name,DISKFS_ITEM_DIR);
}
void diskfs_inode_delete( struct fs_volume *v, struct diskfs_inode *node, int inumber )
{
int size = 0;
int i;
// XXX check for errors in here
for(i=0;i<DISKFS_DIRECT_POINTERS;i++) {
diskfs_data_block_free(v,node->direct[i]);
size += v->block_size;
if(size>=node->size) break;
}
if(size<node->size) {
struct diskfs_block *b = page_alloc(0);
diskfs_data_block_read(v,b,node->indirect);
for(i=0;i<DISKFS_POINTERS_PER_BLOCK;i++) {
diskfs_data_block_free(v,b->pointers[i]);
size += v->block_size;
if(size>=node->size) break;
}
page_free(b);
}
memset(node,sizeof(*node),0);
diskfs_inode_save(v,inumber,node);
diskfs_inumber_free(v,inumber);
}
int diskfs_dirent_remove( struct fs_dirent *d, const char *name )
{
struct diskfs_block *b = page_alloc(0);
int name_length = strlen(name);
int i, j;
int nblocks = d->size / DISKFS_BLOCK_SIZE;
if(d->size%DISKFS_BLOCK_SIZE) nblocks++;
for(i=0;i<nblocks;i++) {
diskfs_inode_read(d,b,i);
for(j=0;j<DISKFS_ITEMS_PER_BLOCK;j++) {
struct diskfs_item *r = &b->items[j];
if(r->type!=DISKFS_ITEM_BLANK && r->name_length==name_length && diskfs_name_equals(name,name_length,r->name,r->name_length)) {
if(r->type==DISKFS_ITEM_DIR) {
struct diskfs_inode inode;
diskfs_inode_load(d->volume,r->inumber,&inode);
if(inode.size>0) {
page_free(b);
return KERROR_NOT_EMPTY;
}
}
int inumber = r->inumber;
r->type = DISKFS_ITEM_BLANK;
diskfs_inode_write(d,b,i);
diskfs_inode_delete(d->volume,&d->disk,inumber);
page_free(b);
return 0;
}
}
}
return KERROR_NOT_FOUND;
}
int diskfs_dirent_write_block( struct fs_dirent *d, const char *data, uint32_t blockno )
{
return diskfs_inode_write(d,(void*)data,blockno);
}
int diskfs_dirent_read_block( struct fs_dirent *d, char *data, uint32_t blockno )
{
return diskfs_inode_read(d,(void*)data,blockno);
}
extern struct fs disk_fs;
struct fs_volume * diskfs_volume_open( struct device *device )
{
struct diskfs_block *b = page_alloc(0);
printf("diskfs: opening device %s unit %d\n",device_name(device),device_unit(device));
diskfs_block_read(device,b,0);
struct diskfs_superblock *sb = &b->superblock;
if(sb->magic!=DISKFS_MAGIC) {
printf("diskfs: no filesystem found!\n");
page_free(b);
return 0;
}
struct fs_volume *v = kmalloc(sizeof(*v));
v->fs = &disk_fs;
v->device = device;
v->block_size = device_block_size(device);
v->refcount = 1;
v->disk = *sb;
page_free(b);
printf("diskfs: %d bitmap blocks, %d inode blocks, %d data blocks\n",
v->disk.bitmap_blocks,
v->disk.inode_blocks,
v->disk.data_blocks);
return v;
}
struct fs_dirent * diskfs_volume_root( struct fs_volume *v )
{
return diskfs_dirent_create(v,0,DISKFS_ITEM_DIR);
}
int diskfs_volume_close( struct fs_volume *v )
{
return 0;
}
int diskfs_volume_format( struct device *device )
{
struct diskfs_block *b = page_alloc(1);
struct diskfs_superblock sb;
int nblocks = device_nblocks(device);
printf("diskfs: formatting device %s unit %d\n",device_name(device),device_unit(device));
sb.magic = DISKFS_MAGIC;
sb.block_size = DISKFS_BLOCK_SIZE;
sb.inode_blocks = 1024 / sizeof(struct diskfs_inode);
int remaining_blocks = nblocks - sb.inode_blocks;
sb.bitmap_blocks = 1 + remaining_blocks / (DISKFS_BLOCK_SIZE*8);
sb.data_blocks = remaining_blocks - sb.bitmap_blocks;
sb.inode_start = 1;
sb.bitmap_start = sb.inode_start + sb.inode_blocks;
sb.data_start = sb.bitmap_start + sb.bitmap_blocks;
printf("diskfs: %d inode blocks, %d bitmap blocks, %d data blocks\n",
sb.inode_blocks, sb.bitmap_blocks, sb.data_blocks );
memset(b,0,DISKFS_BLOCK_SIZE);
b->superblock = sb;
printf("diskfs: writing superblock\n");
diskfs_block_write(device,b,0);
memset(b,0,DISKFS_BLOCK_SIZE);
int i;
printf("diskfs: writing %d inode blocks\n",sb.inode_blocks);
for(i=sb.inode_blocks-1;i>=0;i--) {
diskfs_block_write(device,b,sb.inode_start+i);
}
printf("diskfs: writing %d bitmap blocks\n",sb.bitmap_blocks);
for(i=sb.bitmap_blocks-1;i>=0;i--) {
diskfs_block_write(device,b,sb.bitmap_start+i);
}
printf("diskfs: creating root directory\n");
// Mark the zeroth and first blocks as used.
b->data[0] = 0x03;
diskfs_block_write(device,b,sb.bitmap_start);
// Set up the zeroth inode as the root directory with a single direct block.
memset(b,0,DISKFS_BLOCK_SIZE);
b->inodes[0].inuse = 1;
b->inodes[0].size = sizeof(struct diskfs_item);
b->inodes[0].direct[0] = 1;
diskfs_block_write(device,b,sb.inode_start);
// Create the first directory entry as dot and write it to the first block.
memset(b,0,DISKFS_BLOCK_SIZE);
b->items[0].inumber = 0;
b->items[0].type = DISKFS_ITEM_DIR;
b->items[0].name_length = 1;
b->items[0].name[0] = '.';
diskfs_block_write(device,b,sb.data_start+1);
page_free(b);
printf("diskfs: flushing buffer cache\n");
bcache_flush_device(device);
return 0;
}
struct fs_ops diskfs_ops = {
.volume_open = diskfs_volume_open,
.volume_close = diskfs_volume_close,
.volume_format = diskfs_volume_format,
.volume_root = diskfs_volume_root,
.lookup = diskfs_dirent_lookup,
.mkdir = diskfs_dirent_create_dir,
.mkfile = diskfs_dirent_create_file,
.read_block = diskfs_dirent_read_block,
.write_block = diskfs_dirent_write_block,
.list = diskfs_dirent_list,
.remove = diskfs_dirent_remove,
.resize = diskfs_dirent_resize,
.close = diskfs_dirent_close
};
struct fs disk_fs = {
"diskfs",
&diskfs_ops,
0
};
int diskfs_init(void)
{
fs_register(&disk_fs);
return 0;
}