diff --git a/GNUmakefile b/GNUmakefile index c7e8242..2b85d1d 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -141,6 +141,7 @@ include boot/Makefrag include kern/Makefrag include lib/Makefrag include user/Makefrag +include fs/Makefrag CPUS ?= 1 @@ -149,6 +150,8 @@ QEMUOPTS = -drive file=$(OBJDIR)/kern/kernel.img,index=0,media=disk,format=raw - QEMUOPTS += $(shell if $(QEMU) -nographic -help | grep -q '^-D '; then echo '-D qemu.log'; fi) IMAGES = $(OBJDIR)/kern/kernel.img QEMUOPTS += -smp $(CPUS) +QEMUOPTS += -drive file=$(OBJDIR)/fs/fs.img,index=1,media=disk,format=raw +IMAGES += $(OBJDIR)/fs/fs.img QEMUOPTS += $(QEMUEXTRA) .gdbinit: .gdbinit.tmpl diff --git a/conf/lab.mk b/conf/lab.mk index 74db46c..428f188 100644 --- a/conf/lab.mk +++ b/conf/lab.mk @@ -1,2 +1,2 @@ -LAB=4 -PACKAGEDATE=Mon Oct 8 21:31:51 PDT 2018 +LAB=5 +PACKAGEDATE=Wed Oct 24 20:44:37 EDT 2018 diff --git a/fs/Makefrag b/fs/Makefrag new file mode 100644 index 0000000..748e065 --- /dev/null +++ b/fs/Makefrag @@ -0,0 +1,77 @@ + +OBJDIRS += fs + +FSOFILES := $(OBJDIR)/fs/ide.o \ + $(OBJDIR)/fs/bc.o \ + $(OBJDIR)/fs/fs.o \ + $(OBJDIR)/fs/serv.o \ + $(OBJDIR)/fs/test.o \ + +USERAPPS := $(OBJDIR)/user/init + +FSIMGTXTFILES := fs/newmotd \ + fs/motd + + +USERAPPS := $(USERAPPS) \ + $(OBJDIR)/user/cat \ + $(OBJDIR)/user/echo \ + $(OBJDIR)/user/init \ + $(OBJDIR)/user/ls \ + $(OBJDIR)/user/lsfd \ + $(OBJDIR)/user/num \ + $(OBJDIR)/user/forktree \ + $(OBJDIR)/user/primes \ + $(OBJDIR)/user/primespipe \ + $(OBJDIR)/user/sh \ + $(OBJDIR)/user/testfdsharing \ + $(OBJDIR)/user/testkbd \ + $(OBJDIR)/user/testpipe \ + $(OBJDIR)/user/testpteshare \ + $(OBJDIR)/user/testshell \ + $(OBJDIR)/user/hello \ + $(OBJDIR)/user/faultio \ + +FSIMGTXTFILES := $(FSIMGTXTFILES) \ + fs/lorem \ + fs/script \ + fs/testshell.key \ + fs/testshell.sh + + +FSIMGFILES := $(FSIMGTXTFILES) $(USERAPPS) + +$(OBJDIR)/fs/%.o: fs/%.c fs/fs.h inc/lib.h $(OBJDIR)/.vars.USER_CFLAGS + @echo + cc[USER] $< + @mkdir -p $(@D) + $(V)$(CC) -nostdinc $(USER_CFLAGS) -c -o $@ $< + +$(OBJDIR)/fs/fs: $(FSOFILES) $(OBJDIR)/lib/entry.o $(OBJDIR)/lib/libjos.a user/user.ld + @echo + ld $@ + $(V)mkdir -p $(@D) + $(V)$(LD) -o $@ $(ULDFLAGS) $(LDFLAGS) -nostdlib \ + $(OBJDIR)/lib/entry.o $(FSOFILES) \ + -L$(OBJDIR)/lib -ljos $(GCC_LIB) + $(V)$(OBJDUMP) -S $@ >$@.asm + +# How to build the file system image +$(OBJDIR)/fs/fsformat: fs/fsformat.c + @echo + mk $(OBJDIR)/fs/fsformat + $(V)mkdir -p $(@D) + $(V)$(NCC) $(NATIVE_CFLAGS) -o $(OBJDIR)/fs/fsformat fs/fsformat.c + +$(OBJDIR)/fs/clean-fs.img: $(OBJDIR)/fs/fsformat $(FSIMGFILES) + @echo + mk $(OBJDIR)/fs/clean-fs.img + $(V)mkdir -p $(@D) + $(V)$(OBJDIR)/fs/fsformat $(OBJDIR)/fs/clean-fs.img 1024 $(FSIMGFILES) + +$(OBJDIR)/fs/fs.img: $(OBJDIR)/fs/clean-fs.img + @echo + cp $(OBJDIR)/fs/clean-fs.img $@ + $(V)cp $(OBJDIR)/fs/clean-fs.img $@ + +all: $(OBJDIR)/fs/fs.img + +#all: $(addsuffix .sym, $(USERAPPS)) + +#all: $(addsuffix .asm, $(USERAPPS)) + diff --git a/fs/bc.c b/fs/bc.c new file mode 100644 index 0000000..e3922c4 --- /dev/null +++ b/fs/bc.c @@ -0,0 +1,151 @@ + +#include "fs.h" + +// Return the virtual address of this disk block. +void* +diskaddr(uint32_t blockno) +{ + if (blockno == 0 || (super && blockno >= super->s_nblocks)) + panic("bad block number %08x in diskaddr", blockno); + return (char*) (DISKMAP + blockno * BLKSIZE); +} + +// Is this virtual address mapped? +bool +va_is_mapped(void *va) +{ + return (uvpd[PDX(va)] & PTE_P) && (uvpt[PGNUM(va)] & PTE_P); +} + +// Is this virtual address dirty? +bool +va_is_dirty(void *va) +{ + return (uvpt[PGNUM(va)] & PTE_D) != 0; +} + +// Fault any disk block that is read in to memory by +// loading it from disk. +static void +bc_pgfault(struct UTrapframe *utf) +{ + void *addr = (void *) utf->utf_fault_va; + uint32_t blockno = ((uint32_t)addr - DISKMAP) / BLKSIZE; + int r; + + // Check that the fault was within the block cache region + if (addr < (void*)DISKMAP || addr >= (void*)(DISKMAP + DISKSIZE)) + panic("page fault in FS: eip %08x, va %08x, err %04x", + utf->utf_eip, addr, utf->utf_err); + + // Sanity check the block number. + if (super && blockno >= super->s_nblocks) + panic("reading non-existent block %08x\n", blockno); + + // Allocate a page in the disk map region, read the contents + // of the block from the disk into that page. + // Hint: first round addr to page boundary. fs/ide.c has code to read + // the disk. + // + // LAB 5: you code here: + + // Clear the dirty bit for the disk block page since we just read the + // block from disk + if ((r = sys_page_map(0, addr, 0, addr, uvpt[PGNUM(addr)] & PTE_SYSCALL)) < 0) + panic("in bc_pgfault, sys_page_map: %e", r); + + // Check that the block we read was allocated. (exercise for + // the reader: why do we do this *after* reading the block + // in?) + if (bitmap && block_is_free(blockno)) + panic("reading free block %08x\n", blockno); +} + +// Flush the contents of the block containing VA out to disk if +// necessary, then clear the PTE_D bit using sys_page_map. +// If the block is not in the block cache or is not dirty, does +// nothing. +// Hint: Use va_is_mapped, va_is_dirty, and ide_write. +// Hint: Use the PTE_SYSCALL constant when calling sys_page_map. +// Hint: Don't forget to round addr down. +void +flush_block(void *addr) +{ + uint32_t blockno = ((uint32_t)addr - DISKMAP) / BLKSIZE; + + if (addr < (void*)DISKMAP || addr >= (void*)(DISKMAP + DISKSIZE)) + panic("flush_block of bad va %08x", addr); + + // LAB 5: Your code here. + panic("flush_block not implemented"); +} + +// Test that the block cache works, by smashing the superblock and +// reading it back. +static void +check_bc(void) +{ + struct Super backup; + + // back up super block + memmove(&backup, diskaddr(1), sizeof backup); + + // smash it + strcpy(diskaddr(1), "OOPS!\n"); + flush_block(diskaddr(1)); + assert(va_is_mapped(diskaddr(1))); + assert(!va_is_dirty(diskaddr(1))); + + // clear it out + sys_page_unmap(0, diskaddr(1)); + assert(!va_is_mapped(diskaddr(1))); + + // read it back in + assert(strcmp(diskaddr(1), "OOPS!\n") == 0); + + // fix it + memmove(diskaddr(1), &backup, sizeof backup); + flush_block(diskaddr(1)); + + // Now repeat the same experiment, but pass an unaligned address to + // flush_block. + + // back up super block + memmove(&backup, diskaddr(1), sizeof backup); + + // smash it + strcpy(diskaddr(1), "OOPS!\n"); + + // Pass an unaligned address to flush_block. + flush_block(diskaddr(1) + 20); + assert(va_is_mapped(diskaddr(1))); + + // Skip the !va_is_dirty() check because it makes the bug somewhat + // obscure and hence harder to debug. + //assert(!va_is_dirty(diskaddr(1))); + + // clear it out + sys_page_unmap(0, diskaddr(1)); + assert(!va_is_mapped(diskaddr(1))); + + // read it back in + assert(strcmp(diskaddr(1), "OOPS!\n") == 0); + + // fix it + memmove(diskaddr(1), &backup, sizeof backup); + flush_block(diskaddr(1)); + + cprintf("block cache is good\n"); +} + +void +bc_init(void) +{ + struct Super super; + set_pgfault_handler(bc_pgfault); + check_bc(); + + // cache the super block by reading it once + memmove(&super, diskaddr(1), sizeof super); +} + diff --git a/fs/fs.c b/fs/fs.c new file mode 100644 index 0000000..45ecaf8 --- /dev/null +++ b/fs/fs.c @@ -0,0 +1,456 @@ +#include +#include + +#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. + + // LAB 5: Your code here. + panic("alloc_block not implemented"); + 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. + panic("file_block_walk not implemented"); +} + +// 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. + panic("file_get_block not implemented"); +} + +// 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)); +} + diff --git a/fs/fs.h b/fs/fs.h new file mode 100644 index 0000000..0350d78 --- /dev/null +++ b/fs/fs.h @@ -0,0 +1,49 @@ +#include +#include + +#define SECTSIZE 512 // bytes per disk sector +#define BLKSECTS (BLKSIZE / SECTSIZE) // sectors per block + +/* Disk block n, when in memory, is mapped into the file system + * server's address space at DISKMAP + (n*BLKSIZE). */ +#define DISKMAP 0x10000000 + +/* Maximum disk size we can handle (3GB) */ +#define DISKSIZE 0xC0000000 + +struct Super *super; // superblock +uint32_t *bitmap; // bitmap blocks mapped in memory + +/* ide.c */ +bool ide_probe_disk1(void); +void ide_set_disk(int diskno); +void ide_set_partition(uint32_t first_sect, uint32_t nsect); +int ide_read(uint32_t secno, void *dst, size_t nsecs); +int ide_write(uint32_t secno, const void *src, size_t nsecs); + +/* bc.c */ +void* diskaddr(uint32_t blockno); +bool va_is_mapped(void *va); +bool va_is_dirty(void *va); +void flush_block(void *addr); +void bc_init(void); + +/* fs.c */ +void fs_init(void); +int file_get_block(struct File *f, uint32_t file_blockno, char **pblk); +int file_create(const char *path, struct File **f); +int file_open(const char *path, struct File **f); +ssize_t file_read(struct File *f, void *buf, size_t count, off_t offset); +int file_write(struct File *f, const void *buf, size_t count, off_t offset); +int file_set_size(struct File *f, off_t newsize); +void file_flush(struct File *f); +int file_remove(const char *path); +void fs_sync(void); + +/* int map_block(uint32_t); */ +bool block_is_free(uint32_t blockno); +int alloc_block(void); + +/* test.c */ +void fs_test(void); + diff --git a/fs/fsformat.c b/fs/fsformat.c new file mode 100644 index 0000000..4dab07a --- /dev/null +++ b/fs/fsformat.c @@ -0,0 +1,244 @@ +/* + * JOS file system format + */ + +// We don't actually want to define off_t! +#define off_t xxx_off_t +#define bool xxx_bool +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#undef off_t +#undef bool + +// Prevent inc/types.h, included from inc/fs.h, +// from attempting to redefine types defined in the host's inttypes.h. +#define JOS_INC_TYPES_H +// Typedef the types that inc/mmu.h needs. +typedef uint32_t physaddr_t; +typedef uint32_t off_t; +typedef int bool; + +#include +#include + +#define ROUNDUP(n, v) ((n) - 1 + (v) - ((n) - 1) % (v)) +#define MAX_DIR_ENTS 128 + +struct Dir +{ + struct File *f; + struct File *ents; + int n; +}; + +uint32_t nblocks; +char *diskmap, *diskpos; +struct Super *super; +uint32_t *bitmap; + +void +panic(const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + fputc('\n', stderr); + abort(); +} + +void +readn(int f, void *out, size_t n) +{ + size_t p = 0; + while (p < n) { + ssize_t m = read(f, out + p, n - p); + if (m < 0) + panic("read: %s", strerror(errno)); + if (m == 0) + panic("read: Unexpected EOF"); + p += m; + } +} + +uint32_t +blockof(void *pos) +{ + return ((char*)pos - diskmap) / BLKSIZE; +} + +void * +alloc(uint32_t bytes) +{ + void *start = diskpos; + diskpos += ROUNDUP(bytes, BLKSIZE); + if (blockof(diskpos) >= nblocks) + panic("out of disk blocks"); + return start; +} + +void +opendisk(const char *name) +{ + int r, diskfd, nbitblocks; + + if ((diskfd = open(name, O_RDWR | O_CREAT, 0666)) < 0) + panic("open %s: %s", name, strerror(errno)); + + if ((r = ftruncate(diskfd, 0)) < 0 + || (r = ftruncate(diskfd, nblocks * BLKSIZE)) < 0) + panic("truncate %s: %s", name, strerror(errno)); + + if ((diskmap = mmap(NULL, nblocks * BLKSIZE, PROT_READ|PROT_WRITE, + MAP_SHARED, diskfd, 0)) == MAP_FAILED) + panic("mmap %s: %s", name, strerror(errno)); + + close(diskfd); + + diskpos = diskmap; + alloc(BLKSIZE); + super = alloc(BLKSIZE); + super->s_magic = FS_MAGIC; + super->s_nblocks = nblocks; + super->s_root.f_type = FTYPE_DIR; + strcpy(super->s_root.f_name, "/"); + + nbitblocks = (nblocks + BLKBITSIZE - 1) / BLKBITSIZE; + bitmap = alloc(nbitblocks * BLKSIZE); + memset(bitmap, 0xFF, nbitblocks * BLKSIZE); +} + +void +finishdisk(void) +{ + int r, i; + + for (i = 0; i < blockof(diskpos); ++i) + bitmap[i/32] &= ~(1<<(i%32)); + + if ((r = msync(diskmap, nblocks * BLKSIZE, MS_SYNC)) < 0) + panic("msync: %s", strerror(errno)); +} + +void +finishfile(struct File *f, uint32_t start, uint32_t len) +{ + int i; + f->f_size = len; + len = ROUNDUP(len, BLKSIZE); + for (i = 0; i < len / BLKSIZE && i < NDIRECT; ++i) + f->f_direct[i] = start + i; + if (i == NDIRECT) { + uint32_t *ind = alloc(BLKSIZE); + f->f_indirect = blockof(ind); + for (; i < len / BLKSIZE; ++i) + ind[i - NDIRECT] = start + i; + } +} + +void +startdir(struct File *f, struct Dir *dout) +{ + dout->f = f; + dout->ents = malloc(MAX_DIR_ENTS * sizeof *dout->ents); + dout->n = 0; +} + +struct File * +diradd(struct Dir *d, uint32_t type, const char *name) +{ + struct File *out = &d->ents[d->n++]; + if (d->n > MAX_DIR_ENTS) + panic("too many directory entries"); + strcpy(out->f_name, name); + out->f_type = type; + return out; +} + +void +finishdir(struct Dir *d) +{ + int size = d->n * sizeof(struct File); + struct File *start = alloc(size); + memmove(start, d->ents, size); + finishfile(d->f, blockof(start), ROUNDUP(size, BLKSIZE)); + free(d->ents); + d->ents = NULL; +} + +void +writefile(struct Dir *dir, const char *name) +{ + int r, fd; + struct File *f; + struct stat st; + const char *last; + char *start; + + if ((fd = open(name, O_RDONLY)) < 0) + panic("open %s: %s", name, strerror(errno)); + if ((r = fstat(fd, &st)) < 0) + panic("stat %s: %s", name, strerror(errno)); + if (!S_ISREG(st.st_mode)) + panic("%s is not a regular file", name); + if (st.st_size >= MAXFILESIZE) + panic("%s too large", name); + + last = strrchr(name, '/'); + if (last) + last++; + else + last = name; + + f = diradd(dir, FTYPE_REG, last); + start = alloc(st.st_size); + readn(fd, start, st.st_size); + finishfile(f, blockof(start), st.st_size); + close(fd); +} + +void +usage(void) +{ + fprintf(stderr, "Usage: fsformat fs.img NBLOCKS files...\n"); + exit(2); +} + +int +main(int argc, char **argv) +{ + int i; + char *s; + struct Dir root; + + assert(BLKSIZE % sizeof(struct File) == 0); + + if (argc < 3) + usage(); + + nblocks = strtol(argv[2], &s, 0); + if (*s || s == argv[2] || nblocks < 2 || nblocks > 1024) + usage(); + + opendisk(argv[1]); + + startdir(&super->s_root, &root); + for (i = 3; i < argc; i++) + writefile(&root, argv[i]); + finishdir(&root); + + finishdisk(); + return 0; +} + diff --git a/fs/ide.c b/fs/ide.c new file mode 100644 index 0000000..2d8b4bf --- /dev/null +++ b/fs/ide.c @@ -0,0 +1,112 @@ +/* + * Minimal PIO-based (non-interrupt-driven) IDE driver code. + * For information about what all this IDE/ATA magic means, + * see the materials available on the class references page. + */ + +#include "fs.h" +#include + +#define IDE_BSY 0x80 +#define IDE_DRDY 0x40 +#define IDE_DF 0x20 +#define IDE_ERR 0x01 + +static int diskno = 1; + +static int +ide_wait_ready(bool check_error) +{ + int r; + + while (((r = inb(0x1F7)) & (IDE_BSY|IDE_DRDY)) != IDE_DRDY) + /* do nothing */; + + if (check_error && (r & (IDE_DF|IDE_ERR)) != 0) + return -1; + return 0; +} + +bool +ide_probe_disk1(void) +{ + int r, x; + + // wait for Device 0 to be ready + ide_wait_ready(0); + + // switch to Device 1 + outb(0x1F6, 0xE0 | (1<<4)); + + // check for Device 1 to be ready for a while + for (x = 0; + x < 1000 && ((r = inb(0x1F7)) & (IDE_BSY|IDE_DF|IDE_ERR)) != 0; + x++) + /* do nothing */; + + // switch back to Device 0 + outb(0x1F6, 0xE0 | (0<<4)); + + cprintf("Device 1 presence: %d\n", (x < 1000)); + return (x < 1000); +} + +void +ide_set_disk(int d) +{ + if (d != 0 && d != 1) + panic("bad disk number"); + diskno = d; +} + + +int +ide_read(uint32_t secno, void *dst, size_t nsecs) +{ + int r; + + assert(nsecs <= 256); + + ide_wait_ready(0); + + outb(0x1F2, nsecs); + outb(0x1F3, secno & 0xFF); + outb(0x1F4, (secno >> 8) & 0xFF); + outb(0x1F5, (secno >> 16) & 0xFF); + outb(0x1F6, 0xE0 | ((diskno&1)<<4) | ((secno>>24)&0x0F)); + outb(0x1F7, 0x20); // CMD 0x20 means read sector + + for (; nsecs > 0; nsecs--, dst += SECTSIZE) { + if ((r = ide_wait_ready(1)) < 0) + return r; + insl(0x1F0, dst, SECTSIZE/4); + } + + return 0; +} + +int +ide_write(uint32_t secno, const void *src, size_t nsecs) +{ + int r; + + assert(nsecs <= 256); + + ide_wait_ready(0); + + outb(0x1F2, nsecs); + outb(0x1F3, secno & 0xFF); + outb(0x1F4, (secno >> 8) & 0xFF); + outb(0x1F5, (secno >> 16) & 0xFF); + outb(0x1F6, 0xE0 | ((diskno&1)<<4) | ((secno>>24)&0x0F)); + outb(0x1F7, 0x30); // CMD 0x30 means write sector + + for (; nsecs > 0; nsecs--, src += SECTSIZE) { + if ((r = ide_wait_ready(1)) < 0) + return r; + outsl(0x1F0, src, SECTSIZE/4); + } + + return 0; +} + diff --git a/fs/lorem b/fs/lorem new file mode 100644 index 0000000..c7c76e5 --- /dev/null +++ b/fs/lorem @@ -0,0 +1,12 @@ +Lorem ipsum dolor sit amet, consectetur +adipisicing elit, sed do eiusmod tempor +incididunt ut labore et dolore magna +aliqua. Ut enim ad minim veniam, quis +nostrud exercitation ullamco laboris +nisi ut aliquip ex ea commodo consequat. +Duis aute irure dolor in reprehenderit +in voluptate velit esse cillum dolore eu +fugiat nulla pariatur. Excepteur sint +occaecat cupidatat non proident, sunt in +culpa qui officia deserunt mollit anim +id est laborum. diff --git a/fs/motd b/fs/motd new file mode 100644 index 0000000..3643fef --- /dev/null +++ b/fs/motd @@ -0,0 +1,4 @@ +This is /motd, the message of the day. + +Welcome to the JOS kernel, now with a file system! + diff --git a/fs/newmotd b/fs/newmotd new file mode 100644 index 0000000..58c9cf2 --- /dev/null +++ b/fs/newmotd @@ -0,0 +1,2 @@ +This is the NEW message of the day! + diff --git a/fs/script b/fs/script new file mode 100644 index 0000000..e4291d4 --- /dev/null +++ b/fs/script @@ -0,0 +1,5 @@ +echo This is from the script. +cat lorem | num | cat +echo These are my file descriptors. +lsfd -1 +echo This is the end of the script. diff --git a/fs/serv.c b/fs/serv.c new file mode 100644 index 0000000..76c1d99 --- /dev/null +++ b/fs/serv.c @@ -0,0 +1,345 @@ +/* + * File system server main loop - + * serves IPC requests from other environments. + */ + +#include +#include + +#include "fs.h" + + +#define debug 0 + +// The file system server maintains three structures +// for each open file. +// +// 1. The on-disk 'struct File' is mapped into the part of memory +// that maps the disk. This memory is kept private to the file +// server. +// 2. Each open file has a 'struct Fd' as well, which sort of +// corresponds to a Unix file descriptor. This 'struct Fd' is kept +// on *its own page* in memory, and it is shared with any +// environments that have the file open. +// 3. 'struct OpenFile' links these other two structures, and is kept +// private to the file server. The server maintains an array of +// all open files, indexed by "file ID". (There can be at most +// MAXOPEN files open concurrently.) The client uses file IDs to +// communicate with the server. File IDs are a lot like +// environment IDs in the kernel. Use openfile_lookup to translate +// file IDs to struct OpenFile. + +struct OpenFile { + uint32_t o_fileid; // file id + struct File *o_file; // mapped descriptor for open file + int o_mode; // open mode + struct Fd *o_fd; // Fd page +}; + +// Max number of open files in the file system at once +#define MAXOPEN 1024 +#define FILEVA 0xD0000000 + +// initialize to force into data section +struct OpenFile opentab[MAXOPEN] = { + { 0, 0, 1, 0 } +}; + +// Virtual address at which to receive page mappings containing client requests. +union Fsipc *fsreq = (union Fsipc *)0x0ffff000; + +void +serve_init(void) +{ + int i; + uintptr_t va = FILEVA; + for (i = 0; i < MAXOPEN; i++) { + opentab[i].o_fileid = i; + opentab[i].o_fd = (struct Fd*) va; + va += PGSIZE; + } +} + +// Allocate an open file. +int +openfile_alloc(struct OpenFile **o) +{ + int i, r; + + // Find an available open-file table entry + for (i = 0; i < MAXOPEN; i++) { + switch (pageref(opentab[i].o_fd)) { + case 0: + if ((r = sys_page_alloc(0, opentab[i].o_fd, PTE_P|PTE_U|PTE_W)) < 0) + return r; + /* fall through */ + case 1: + opentab[i].o_fileid += MAXOPEN; + *o = &opentab[i]; + memset(opentab[i].o_fd, 0, PGSIZE); + return (*o)->o_fileid; + } + } + return -E_MAX_OPEN; +} + +// Look up an open file for envid. +int +openfile_lookup(envid_t envid, uint32_t fileid, struct OpenFile **po) +{ + struct OpenFile *o; + + o = &opentab[fileid % MAXOPEN]; + if (pageref(o->o_fd) <= 1 || o->o_fileid != fileid) + return -E_INVAL; + *po = o; + return 0; +} + +// Open req->req_path in mode req->req_omode, storing the Fd page and +// permissions to return to the calling environment in *pg_store and +// *perm_store respectively. +int +serve_open(envid_t envid, struct Fsreq_open *req, + void **pg_store, int *perm_store) +{ + char path[MAXPATHLEN]; + struct File *f; + int fileid; + int r; + struct OpenFile *o; + + if (debug) + cprintf("serve_open %08x %s 0x%x\n", envid, req->req_path, req->req_omode); + + // Copy in the path, making sure it's null-terminated + memmove(path, req->req_path, MAXPATHLEN); + path[MAXPATHLEN-1] = 0; + + // Find an open file ID + if ((r = openfile_alloc(&o)) < 0) { + if (debug) + cprintf("openfile_alloc failed: %e", r); + return r; + } + fileid = r; + + // Open the file + if (req->req_omode & O_CREAT) { + if ((r = file_create(path, &f)) < 0) { + if (!(req->req_omode & O_EXCL) && r == -E_FILE_EXISTS) + goto try_open; + if (debug) + cprintf("file_create failed: %e", r); + return r; + } + } else { +try_open: + if ((r = file_open(path, &f)) < 0) { + if (debug) + cprintf("file_open failed: %e", r); + return r; + } + } + + // Truncate + if (req->req_omode & O_TRUNC) { + if ((r = file_set_size(f, 0)) < 0) { + if (debug) + cprintf("file_set_size failed: %e", r); + return r; + } + } + if ((r = file_open(path, &f)) < 0) { + if (debug) + cprintf("file_open failed: %e", r); + return r; + } + + // Save the file pointer + o->o_file = f; + + // Fill out the Fd structure + o->o_fd->fd_file.id = o->o_fileid; + o->o_fd->fd_omode = req->req_omode & O_ACCMODE; + o->o_fd->fd_dev_id = devfile.dev_id; + o->o_mode = req->req_omode; + + if (debug) + cprintf("sending success, page %08x\n", (uintptr_t) o->o_fd); + + // Share the FD page with the caller by setting *pg_store, + // store its permission in *perm_store + *pg_store = o->o_fd; + *perm_store = PTE_P|PTE_U|PTE_W|PTE_SHARE; + + return 0; +} + +// Set the size of req->req_fileid to req->req_size bytes, truncating +// or extending the file as necessary. +int +serve_set_size(envid_t envid, struct Fsreq_set_size *req) +{ + struct OpenFile *o; + int r; + + if (debug) + cprintf("serve_set_size %08x %08x %08x\n", envid, req->req_fileid, req->req_size); + + // Every file system IPC call has the same general structure. + // Here's how it goes. + + // First, use openfile_lookup to find the relevant open file. + // On failure, return the error code to the client with ipc_send. + if ((r = openfile_lookup(envid, req->req_fileid, &o)) < 0) + return r; + + // Second, call the relevant file system function (from fs/fs.c). + // On failure, return the error code to the client. + return file_set_size(o->o_file, req->req_size); +} + +// Read at most ipc->read.req_n bytes from the current seek position +// in ipc->read.req_fileid. Return the bytes read from the file to +// the caller in ipc->readRet, then update the seek position. Returns +// the number of bytes successfully read, or < 0 on error. +int +serve_read(envid_t envid, union Fsipc *ipc) +{ + struct Fsreq_read *req = &ipc->read; + struct Fsret_read *ret = &ipc->readRet; + + if (debug) + cprintf("serve_read %08x %08x %08x\n", envid, req->req_fileid, req->req_n); + + // Lab 5: Your code here: + return 0; +} + + +// Write req->req_n bytes from req->req_buf to req_fileid, starting at +// the current seek position, and update the seek position +// accordingly. Extend the file if necessary. Returns the number of +// bytes written, or < 0 on error. +int +serve_write(envid_t envid, struct Fsreq_write *req) +{ + if (debug) + cprintf("serve_write %08x %08x %08x\n", envid, req->req_fileid, req->req_n); + + // LAB 5: Your code here. + panic("serve_write not implemented"); +} + +// Stat ipc->stat.req_fileid. Return the file's struct Stat to the +// caller in ipc->statRet. +int +serve_stat(envid_t envid, union Fsipc *ipc) +{ + struct Fsreq_stat *req = &ipc->stat; + struct Fsret_stat *ret = &ipc->statRet; + struct OpenFile *o; + int r; + + if (debug) + cprintf("serve_stat %08x %08x\n", envid, req->req_fileid); + + if ((r = openfile_lookup(envid, req->req_fileid, &o)) < 0) + return r; + + strcpy(ret->ret_name, o->o_file->f_name); + ret->ret_size = o->o_file->f_size; + ret->ret_isdir = (o->o_file->f_type == FTYPE_DIR); + return 0; +} + +// Flush all data and metadata of req->req_fileid to disk. +int +serve_flush(envid_t envid, struct Fsreq_flush *req) +{ + struct OpenFile *o; + int r; + + if (debug) + cprintf("serve_flush %08x %08x\n", envid, req->req_fileid); + + if ((r = openfile_lookup(envid, req->req_fileid, &o)) < 0) + return r; + file_flush(o->o_file); + return 0; +} + + +int +serve_sync(envid_t envid, union Fsipc *req) +{ + fs_sync(); + return 0; +} + +typedef int (*fshandler)(envid_t envid, union Fsipc *req); + +fshandler handlers[] = { + // Open is handled specially because it passes pages + /* [FSREQ_OPEN] = (fshandler)serve_open, */ + [FSREQ_READ] = serve_read, + [FSREQ_STAT] = serve_stat, + [FSREQ_FLUSH] = (fshandler)serve_flush, + [FSREQ_WRITE] = (fshandler)serve_write, + [FSREQ_SET_SIZE] = (fshandler)serve_set_size, + [FSREQ_SYNC] = serve_sync +}; + +void +serve(void) +{ + uint32_t req, whom; + int perm, r; + void *pg; + + while (1) { + perm = 0; + req = ipc_recv((int32_t *) &whom, fsreq, &perm); + if (debug) + cprintf("fs req %d from %08x [page %08x: %s]\n", + req, whom, uvpt[PGNUM(fsreq)], fsreq); + + // All requests must contain an argument page + if (!(perm & PTE_P)) { + cprintf("Invalid request from %08x: no argument page\n", + whom); + continue; // just leave it hanging... + } + + pg = NULL; + if (req == FSREQ_OPEN) { + r = serve_open(whom, (struct Fsreq_open*)fsreq, &pg, &perm); + } else if (req < ARRAY_SIZE(handlers) && handlers[req]) { + r = handlers[req](whom, fsreq); + } else { + cprintf("Invalid request code %d from %08x\n", req, whom); + r = -E_INVAL; + } + ipc_send(whom, r, pg, perm); + sys_page_unmap(0, fsreq); + } +} + +void +umain(int argc, char **argv) +{ + static_assert(sizeof(struct File) == 256); + binaryname = "fs"; + cprintf("FS is running\n"); + + // Check that we are able to do I/O + outw(0x8A00, 0x8A00); + cprintf("FS can do I/O\n"); + + serve_init(); + fs_init(); + fs_test(); + serve(); +} + diff --git a/fs/testshell.sh b/fs/testshell.sh new file mode 100644 index 0000000..c4f5984 --- /dev/null +++ b/fs/testshell.sh @@ -0,0 +1,7 @@ +echo hello world | cat +cat lorem +cat lorem |num +cat lorem |num |num |num |num |num +lsfd -1 +cat script +sh