491 lines
12 KiB
C
491 lines
12 KiB
C
#include <inc/string.h>
|
|
#include <inc/partition.h>
|
|
|
|
#include "fs.h"
|
|
|
|
// --------------------------------------------------------------
|
|
// Super block
|
|
// --------------------------------------------------------------
|
|
|
|
// Validate the file system super-block.
|
|
void
|
|
check_super(void)
|
|
{
|
|
if (super->s_magic != FS_MAGIC)
|
|
panic("bad file system magic number");
|
|
|
|
if (super->s_nblocks > DISKSIZE/BLKSIZE)
|
|
panic("file system is too large");
|
|
|
|
cprintf("superblock is good\n");
|
|
}
|
|
|
|
// --------------------------------------------------------------
|
|
// Free block bitmap
|
|
// --------------------------------------------------------------
|
|
|
|
// Check to see if the block bitmap indicates that block 'blockno' is free.
|
|
// Return 1 if the block is free, 0 if not.
|
|
bool
|
|
block_is_free(uint32_t blockno)
|
|
{
|
|
if (super == 0 || blockno >= super->s_nblocks)
|
|
return 0;
|
|
if (bitmap[blockno / 32] & (1 << (blockno % 32)))
|
|
return 1;
|
|
return 0;
|
|
}
|
|
|
|
// Mark a block free in the bitmap
|
|
void
|
|
free_block(uint32_t blockno)
|
|
{
|
|
// Blockno zero is the null pointer of block numbers.
|
|
if (blockno == 0)
|
|
panic("attempt to free zero block");
|
|
bitmap[blockno/32] |= 1<<(blockno%32);
|
|
}
|
|
|
|
// Search the bitmap for a free block and allocate it. When you
|
|
// allocate a block, immediately flush the changed bitmap block
|
|
// to disk.
|
|
//
|
|
// Return block number allocated on success,
|
|
// -E_NO_DISK if we are out of blocks.
|
|
//
|
|
// Hint: use free_block as an example for manipulating the bitmap.
|
|
int
|
|
alloc_block(void)
|
|
{
|
|
// The bitmap consists of one or more blocks. A single bitmap block
|
|
// contains the in-use bits for BLKBITSIZE blocks. There are
|
|
// super->s_nblocks blocks in the disk altogether.
|
|
uint32_t* search = bitmap + 1;
|
|
for(uint32_t block = 1; block < super->s_nblocks; block++) {
|
|
if(bitmap[block / 32] & (1 << (block % 32))) {
|
|
bitmap[block / 32] &= ~(1 << (block % 32));
|
|
flush_block(&bitmap[block / 32]);
|
|
return block;
|
|
}
|
|
}
|
|
|
|
return -E_NO_DISK;
|
|
}
|
|
|
|
// Validate the file system bitmap.
|
|
//
|
|
// Check that all reserved blocks -- 0, 1, and the bitmap blocks themselves --
|
|
// are all marked as in-use.
|
|
void
|
|
check_bitmap(void)
|
|
{
|
|
uint32_t i;
|
|
|
|
// Make sure all bitmap blocks are marked in-use
|
|
for (i = 0; i * BLKBITSIZE < super->s_nblocks; i++)
|
|
assert(!block_is_free(2+i));
|
|
|
|
// Make sure the reserved and root blocks are marked in-use.
|
|
assert(!block_is_free(0));
|
|
assert(!block_is_free(1));
|
|
|
|
cprintf("bitmap is good\n");
|
|
}
|
|
|
|
// --------------------------------------------------------------
|
|
// File system structures
|
|
// --------------------------------------------------------------
|
|
|
|
|
|
|
|
// Initialize the file system
|
|
void
|
|
fs_init(void)
|
|
{
|
|
static_assert(sizeof(struct File) == 256);
|
|
|
|
// Find a JOS disk. Use the second IDE disk (number 1) if available
|
|
if (ide_probe_disk1())
|
|
ide_set_disk(1);
|
|
else
|
|
ide_set_disk(0);
|
|
bc_init();
|
|
|
|
// Set "super" to point to the super block.
|
|
super = diskaddr(1);
|
|
check_super();
|
|
|
|
// Set "bitmap" to the beginning of the first bitmap block.
|
|
bitmap = diskaddr(2);
|
|
check_bitmap();
|
|
|
|
}
|
|
|
|
// Find the disk block number slot for the 'filebno'th block in file 'f'.
|
|
// Set '*ppdiskbno' to point to that slot.
|
|
// The slot will be one of the f->f_direct[] entries,
|
|
// or an entry in the indirect block.
|
|
// When 'alloc' is set, this function will allocate an indirect block
|
|
// if necessary.
|
|
//
|
|
// Returns:
|
|
// 0 on success (but note that *ppdiskbno might equal 0).
|
|
// -E_NOT_FOUND if the function needed to allocate an indirect block, but
|
|
// alloc was 0.
|
|
// -E_NO_DISK if there's no space on the disk for an indirect block.
|
|
// -E_INVAL if filebno is out of range (it's >= NDIRECT + NINDIRECT).
|
|
//
|
|
// Analogy: This is like pgdir_walk for files.
|
|
// Hint: Don't forget to clear any block you allocate.
|
|
static int
|
|
file_block_walk(struct File *f, uint32_t filebno, uint32_t **ppdiskbno, bool alloc)
|
|
{
|
|
// LAB 5: Your code here.
|
|
int newblock;
|
|
if(filebno < NDIRECT) {
|
|
*ppdiskbno = &f->f_direct[filebno];
|
|
return 0;
|
|
} else if(filebno < NDIRECT + NINDIRECT) {
|
|
filebno -= NDIRECT;
|
|
if(!f->f_indirect) {
|
|
if(!alloc) return -E_NOT_FOUND;
|
|
if((newblock = alloc_block()) < 0) return newblock;
|
|
f->f_indirect = newblock;
|
|
}
|
|
|
|
uint32_t* indirect = diskaddr(f->f_indirect);
|
|
*ppdiskbno = &indirect[filebno];
|
|
return 0;
|
|
}
|
|
return -E_INVAL;
|
|
}
|
|
|
|
// Set *blk to the address in memory where the filebno'th
|
|
// block of file 'f' would be mapped.
|
|
//
|
|
// Returns 0 on success, < 0 on error. Errors are:
|
|
// -E_NO_DISK if a block needed to be allocated but the disk is full.
|
|
// -E_INVAL if filebno is out of range.
|
|
//
|
|
// Hint: Use file_block_walk and alloc_block.
|
|
int
|
|
file_get_block(struct File *f, uint32_t filebno, char **blk)
|
|
{
|
|
// LAB 5: Your code here.
|
|
uint32_t* blockno;
|
|
int r;
|
|
|
|
if ((r = file_block_walk(f, filebno, &blockno, 1)) < 0)
|
|
return r;
|
|
|
|
if(*blockno == 0) {
|
|
if((r = alloc_block()) < 0) return r;
|
|
*blockno = r;
|
|
}
|
|
|
|
*blk = diskaddr(*blockno);
|
|
return 0;
|
|
}
|
|
|
|
// Try to find a file named "name" in dir. If so, set *file to it.
|
|
//
|
|
// Returns 0 and sets *file on success, < 0 on error. Errors are:
|
|
// -E_NOT_FOUND if the file is not found
|
|
static int
|
|
dir_lookup(struct File *dir, const char *name, struct File **file)
|
|
{
|
|
int r;
|
|
uint32_t i, j, nblock;
|
|
char *blk;
|
|
struct File *f;
|
|
|
|
// Search dir for name.
|
|
// We maintain the invariant that the size of a directory-file
|
|
// is always a multiple of the file system's block size.
|
|
assert((dir->f_size % BLKSIZE) == 0);
|
|
nblock = dir->f_size / BLKSIZE;
|
|
for (i = 0; i < nblock; i++) {
|
|
if ((r = file_get_block(dir, i, &blk)) < 0)
|
|
return r;
|
|
f = (struct File*) blk;
|
|
for (j = 0; j < BLKFILES; j++)
|
|
if (strcmp(f[j].f_name, name) == 0) {
|
|
*file = &f[j];
|
|
return 0;
|
|
}
|
|
}
|
|
return -E_NOT_FOUND;
|
|
}
|
|
|
|
// Set *file to point at a free File structure in dir. The caller is
|
|
// responsible for filling in the File fields.
|
|
static int
|
|
dir_alloc_file(struct File *dir, struct File **file)
|
|
{
|
|
int r;
|
|
uint32_t nblock, i, j;
|
|
char *blk;
|
|
struct File *f;
|
|
|
|
assert((dir->f_size % BLKSIZE) == 0);
|
|
nblock = dir->f_size / BLKSIZE;
|
|
for (i = 0; i < nblock; i++) {
|
|
if ((r = file_get_block(dir, i, &blk)) < 0)
|
|
return r;
|
|
f = (struct File*) blk;
|
|
for (j = 0; j < BLKFILES; j++)
|
|
if (f[j].f_name[0] == '\0') {
|
|
*file = &f[j];
|
|
return 0;
|
|
}
|
|
}
|
|
dir->f_size += BLKSIZE;
|
|
if ((r = file_get_block(dir, i, &blk)) < 0)
|
|
return r;
|
|
f = (struct File*) blk;
|
|
*file = &f[0];
|
|
return 0;
|
|
}
|
|
|
|
// Skip over slashes.
|
|
static const char*
|
|
skip_slash(const char *p)
|
|
{
|
|
while (*p == '/')
|
|
p++;
|
|
return p;
|
|
}
|
|
|
|
// Evaluate a path name, starting at the root.
|
|
// On success, set *pf to the file we found
|
|
// and set *pdir to the directory the file is in.
|
|
// If we cannot find the file but find the directory
|
|
// it should be in, set *pdir and copy the final path
|
|
// element into lastelem.
|
|
static int
|
|
walk_path(const char *path, struct File **pdir, struct File **pf, char *lastelem)
|
|
{
|
|
const char *p;
|
|
char name[MAXNAMELEN];
|
|
struct File *dir, *f;
|
|
int r;
|
|
|
|
// if (*path != '/')
|
|
// return -E_BAD_PATH;
|
|
path = skip_slash(path);
|
|
f = &super->s_root;
|
|
dir = 0;
|
|
name[0] = 0;
|
|
|
|
if (pdir)
|
|
*pdir = 0;
|
|
*pf = 0;
|
|
while (*path != '\0') {
|
|
dir = f;
|
|
p = path;
|
|
while (*path != '/' && *path != '\0')
|
|
path++;
|
|
if (path - p >= MAXNAMELEN)
|
|
return -E_BAD_PATH;
|
|
memmove(name, p, path - p);
|
|
name[path - p] = '\0';
|
|
path = skip_slash(path);
|
|
|
|
if (dir->f_type != FTYPE_DIR)
|
|
return -E_NOT_FOUND;
|
|
|
|
if ((r = dir_lookup(dir, name, &f)) < 0) {
|
|
if (r == -E_NOT_FOUND && *path == '\0') {
|
|
if (pdir)
|
|
*pdir = dir;
|
|
if (lastelem)
|
|
strcpy(lastelem, name);
|
|
*pf = 0;
|
|
}
|
|
return r;
|
|
}
|
|
}
|
|
|
|
if (pdir)
|
|
*pdir = dir;
|
|
*pf = f;
|
|
return 0;
|
|
}
|
|
|
|
// --------------------------------------------------------------
|
|
// File operations
|
|
// --------------------------------------------------------------
|
|
|
|
// Create "path". On success set *pf to point at the file and return 0.
|
|
// On error return < 0.
|
|
int
|
|
file_create(const char *path, struct File **pf)
|
|
{
|
|
char name[MAXNAMELEN];
|
|
int r;
|
|
struct File *dir, *f;
|
|
|
|
if ((r = walk_path(path, &dir, &f, name)) == 0)
|
|
return -E_FILE_EXISTS;
|
|
if (r != -E_NOT_FOUND || dir == 0)
|
|
return r;
|
|
if ((r = dir_alloc_file(dir, &f)) < 0)
|
|
return r;
|
|
|
|
strcpy(f->f_name, name);
|
|
*pf = f;
|
|
file_flush(dir);
|
|
return 0;
|
|
}
|
|
|
|
// Open "path". On success set *pf to point at the file and return 0.
|
|
// On error return < 0.
|
|
int
|
|
file_open(const char *path, struct File **pf)
|
|
{
|
|
return walk_path(path, 0, pf, 0);
|
|
}
|
|
|
|
// Read count bytes from f into buf, starting from seek position
|
|
// offset. This meant to mimic the standard pread function.
|
|
// Returns the number of bytes read, < 0 on error.
|
|
ssize_t
|
|
file_read(struct File *f, void *buf, size_t count, off_t offset)
|
|
{
|
|
int r, bn;
|
|
off_t pos;
|
|
char *blk;
|
|
|
|
if (offset >= f->f_size)
|
|
return 0;
|
|
|
|
count = MIN(count, f->f_size - offset);
|
|
|
|
for (pos = offset; pos < offset + count; ) {
|
|
if ((r = file_get_block(f, pos / BLKSIZE, &blk)) < 0)
|
|
return r;
|
|
bn = MIN(BLKSIZE - pos % BLKSIZE, offset + count - pos);
|
|
memmove(buf, blk + pos % BLKSIZE, bn);
|
|
pos += bn;
|
|
buf += bn;
|
|
}
|
|
|
|
return count;
|
|
}
|
|
|
|
|
|
// Write count bytes from buf into f, starting at seek position
|
|
// offset. This is meant to mimic the standard pwrite function.
|
|
// Extends the file if necessary.
|
|
// Returns the number of bytes written, < 0 on error.
|
|
int
|
|
file_write(struct File *f, const void *buf, size_t count, off_t offset)
|
|
{
|
|
int r, bn;
|
|
off_t pos;
|
|
char *blk;
|
|
|
|
// Extend file if necessary
|
|
if (offset + count > f->f_size)
|
|
if ((r = file_set_size(f, offset + count)) < 0)
|
|
return r;
|
|
|
|
for (pos = offset; pos < offset + count; ) {
|
|
if ((r = file_get_block(f, pos / BLKSIZE, &blk)) < 0)
|
|
return r;
|
|
bn = MIN(BLKSIZE - pos % BLKSIZE, offset + count - pos);
|
|
memmove(blk + pos % BLKSIZE, buf, bn);
|
|
pos += bn;
|
|
buf += bn;
|
|
}
|
|
|
|
return count;
|
|
}
|
|
|
|
// Remove a block from file f. If it's not there, just silently succeed.
|
|
// Returns 0 on success, < 0 on error.
|
|
static int
|
|
file_free_block(struct File *f, uint32_t filebno)
|
|
{
|
|
int r;
|
|
uint32_t *ptr;
|
|
|
|
if ((r = file_block_walk(f, filebno, &ptr, 0)) < 0)
|
|
return r;
|
|
if (*ptr) {
|
|
free_block(*ptr);
|
|
*ptr = 0;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
// Remove any blocks currently used by file 'f',
|
|
// but not necessary for a file of size 'newsize'.
|
|
// For both the old and new sizes, figure out the number of blocks required,
|
|
// and then clear the blocks from new_nblocks to old_nblocks.
|
|
// If the new_nblocks is no more than NDIRECT, and the indirect block has
|
|
// been allocated (f->f_indirect != 0), then free the indirect block too.
|
|
// (Remember to clear the f->f_indirect pointer so you'll know
|
|
// whether it's valid!)
|
|
// Do not change f->f_size.
|
|
static void
|
|
file_truncate_blocks(struct File *f, off_t newsize)
|
|
{
|
|
int r;
|
|
uint32_t bno, old_nblocks, new_nblocks;
|
|
|
|
old_nblocks = (f->f_size + BLKSIZE - 1) / BLKSIZE;
|
|
new_nblocks = (newsize + BLKSIZE - 1) / BLKSIZE;
|
|
for (bno = new_nblocks; bno < old_nblocks; bno++)
|
|
if ((r = file_free_block(f, bno)) < 0)
|
|
cprintf("warning: file_free_block: %e", r);
|
|
|
|
if (new_nblocks <= NDIRECT && f->f_indirect) {
|
|
free_block(f->f_indirect);
|
|
f->f_indirect = 0;
|
|
}
|
|
}
|
|
|
|
// Set the size of file f, truncating or extending as necessary.
|
|
int
|
|
file_set_size(struct File *f, off_t newsize)
|
|
{
|
|
if (f->f_size > newsize)
|
|
file_truncate_blocks(f, newsize);
|
|
f->f_size = newsize;
|
|
flush_block(f);
|
|
return 0;
|
|
}
|
|
|
|
// Flush the contents and metadata of file f out to disk.
|
|
// Loop over all the blocks in file.
|
|
// Translate the file block number into a disk block number
|
|
// and then check whether that disk block is dirty. If so, write it out.
|
|
void
|
|
file_flush(struct File *f)
|
|
{
|
|
int i;
|
|
uint32_t *pdiskbno;
|
|
|
|
for (i = 0; i < (f->f_size + BLKSIZE - 1) / BLKSIZE; i++) {
|
|
if (file_block_walk(f, i, &pdiskbno, 0) < 0 ||
|
|
pdiskbno == NULL || *pdiskbno == 0)
|
|
continue;
|
|
flush_block(diskaddr(*pdiskbno));
|
|
}
|
|
flush_block(f);
|
|
if (f->f_indirect)
|
|
flush_block(diskaddr(f->f_indirect));
|
|
}
|
|
|
|
|
|
// Sync the entire file system. A big hammer.
|
|
void
|
|
fs_sync(void)
|
|
{
|
|
int i;
|
|
for (i = 1; i < super->s_nblocks; i++)
|
|
flush_block(diskaddr(i));
|
|
}
|
|
|