diff --git a/kernel/diskfs.c b/kernel/diskfs.c new file mode 100644 index 0000000..e313377 --- /dev/null +++ b/kernel/diskfs.c @@ -0,0 +1,662 @@ +/* +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;ibitmap_blocks;i++) { + diskfs_bitmap_block_read(v,b,i); + for(j=0;jdata[j]!=0xff) { + for(k=0;k<8;k++) { + if(!((1<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<data[bitmap_byte] &= ~(1<disk.inode_blocks;i++) { + diskfs_inode_block_read(v,b,i); + for(j=0;jinodes[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(blockdisk.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(blockdirect[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;iitems[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;iitems[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;iitems[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;idirect[i]); + size += v->block_size; + if(size>=node->size) break; + } + + if(sizesize) { + struct diskfs_block *b = page_alloc(0); + diskfs_data_block_read(v,b,node->indirect); + for(i=0;ipointers[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;iitems[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; +} +