diff --git a/src/filesys/inode.c b/src/filesys/inode.c new file mode 100644 index 0000000000000000000000000000000000000000..38827393fc8777495f8c8ce1a5cdf0f6210f736e --- /dev/null +++ b/src/filesys/inode.c @@ -0,0 +1,345 @@ +#include "filesys/inode.h" +#include <list.h> +#include <debug.h> +#include <round.h> +#include <string.h> +#include "filesys/filesys.h" +#include "filesys/free-map.h" +#include "threads/malloc.h" + +/* Identifies an inode. */ +#define INODE_MAGIC 0x494e4f44 + +/* On-disk inode. + Must be exactly BLOCK_SECTOR_SIZE bytes long. */ +struct inode_disk + { + block_sector_t start; /* First data sector. */ + off_t length; /* File size in bytes. */ + unsigned magic; /* Magic number. */ + uint32_t unused[125]; /* Not used. */ + }; + +/* Returns the number of sectors to allocate for an inode SIZE + bytes long. */ +static inline size_t +bytes_to_sectors (off_t size) +{ + return DIV_ROUND_UP (size, BLOCK_SECTOR_SIZE); +} + +/* In-memory inode. */ +struct inode + { + struct list_elem elem; /* Element in inode list. */ + block_sector_t sector; /* Sector number of disk location. */ + int open_cnt; /* Number of openers. */ + bool removed; /* True if deleted, false otherwise. */ + int deny_write_cnt; /* 0: writes ok, >0: deny writes. */ + struct inode_disk data; /* Inode content. */ + }; + +/* Returns the block device sector that contains byte offset POS + within INODE. + Returns -1 if INODE does not contain data for a byte at offset + POS. */ +static block_sector_t +byte_to_sector (const struct inode *inode, off_t pos) +{ + ASSERT (inode != NULL); + if (pos < inode->data.length) + return inode->data.start + pos / BLOCK_SECTOR_SIZE; + else + return -1; +} + +/* List of open inodes, so that opening a single inode twice + returns the same `struct inode'. */ +static struct list open_inodes; + +/* Initializes the inode module. */ +void +inode_init (void) +{ + list_init (&open_inodes); +} + +/* Initializes an inode with LENGTH bytes of data and + writes the new inode to sector SECTOR on the file system + device. + Returns true if successful. + Returns false if memory or disk allocation fails. */ +bool +inode_create (block_sector_t sector, off_t length) +{ + struct inode_disk *disk_inode = NULL; + bool success = false; + + ASSERT (length >= 0); + + /* If this assertion fails, the inode structure is not exactly + one sector in size, and you should fix that. */ + ASSERT (sizeof *disk_inode == BLOCK_SECTOR_SIZE); + + disk_inode = calloc (1, sizeof *disk_inode); + if (disk_inode != NULL) + { + size_t sectors = bytes_to_sectors (length); + disk_inode->length = length; + disk_inode->magic = INODE_MAGIC; + if (free_map_allocate (sectors, &disk_inode->start)) + { + block_write (fs_device, sector, disk_inode); + if (sectors > 0) + { + static char zeros[BLOCK_SECTOR_SIZE]; + size_t i; + + for (i = 0; i < sectors; i++) + block_write (fs_device, disk_inode->start + i, zeros); + } + success = true; + } + free (disk_inode); + } + return success; +} + +/* Reads an inode from SECTOR + and returns a `struct inode' that contains it. + Returns a null pointer if memory allocation fails. */ +struct inode * +inode_open (block_sector_t sector) +{ + struct list_elem *e; + struct inode *inode; + + /* Check whether this inode is already open. */ + for (e = list_begin (&open_inodes); e != list_end (&open_inodes); + e = list_next (e)) + { + inode = list_entry (e, struct inode, elem); + if (inode->sector == sector) + { + inode_reopen (inode); + return inode; + } + } + + /* Allocate memory. */ + inode = malloc (sizeof *inode); + if (inode == NULL) + return NULL; + + /* Initialize. */ + list_push_front (&open_inodes, &inode->elem); + inode->sector = sector; + inode->open_cnt = 1; + inode->deny_write_cnt = 0; + inode->removed = false; + block_read (fs_device, inode->sector, &inode->data); + return inode; +} + +/* Reopens and returns INODE. */ +struct inode * +inode_reopen (struct inode *inode) +{ + if (inode != NULL) + inode->open_cnt++; + return inode; +} + +/* Returns INODE's inode number. */ +block_sector_t +inode_get_inumber (const struct inode *inode) +{ + return inode->sector; +} + +/* Closes INODE and writes it to disk. + If this was the last reference to INODE, frees its memory. + If INODE was also a removed inode, frees its blocks. */ +void +inode_close (struct inode *inode) +{ + /* Ignore null pointer. */ + if (inode == NULL) + return; + + /* Release resources if this was the last opener. */ + if (--inode->open_cnt == 0) + { + /* Remove from inode list and release lock. */ + list_remove (&inode->elem); + + /* Deallocate blocks if removed. */ + if (inode->removed) + { + free_map_release (inode->sector, 1); + free_map_release (inode->data.start, + bytes_to_sectors (inode->data.length)); + } + + free (inode); + } +} + +/* Marks INODE to be deleted when it is closed by the last caller who + has it open. */ +void +inode_remove (struct inode *inode) +{ + ASSERT (inode != NULL); + inode->removed = true; +} + +/* Reads SIZE bytes from INODE into BUFFER, starting at position OFFSET. + Returns the number of bytes actually read, which may be less + than SIZE if an error occurs or end of file is reached. */ +off_t +inode_read_at (struct inode *inode, void *buffer_, off_t size, off_t offset) +{ + uint8_t *buffer = buffer_; + off_t bytes_read = 0; + uint8_t *bounce = NULL; + + while (size > 0) + { + /* Disk sector to read, starting byte offset within sector. */ + block_sector_t sector_idx = byte_to_sector (inode, offset); + int sector_ofs = offset % BLOCK_SECTOR_SIZE; + + /* Bytes left in inode, bytes left in sector, lesser of the two. */ + off_t inode_left = inode_length (inode) - offset; + int sector_left = BLOCK_SECTOR_SIZE - sector_ofs; + int min_left = inode_left < sector_left ? inode_left : sector_left; + + /* Number of bytes to actually copy out of this sector. */ + int chunk_size = size < min_left ? size : min_left; + if (chunk_size <= 0) + break; + + if (sector_ofs == 0 && chunk_size == BLOCK_SECTOR_SIZE) + { + /* Read full sector directly into caller's buffer. */ + block_read (fs_device, sector_idx, buffer + bytes_read); + } + else + { + /* Read sector into bounce buffer, then partially copy + into caller's buffer. */ + if (bounce == NULL) + { + bounce = malloc (BLOCK_SECTOR_SIZE); + if (bounce == NULL) + break; + } + block_read (fs_device, sector_idx, bounce); + memcpy (buffer + bytes_read, bounce + sector_ofs, chunk_size); + } + + /* Advance. */ + size -= chunk_size; + offset += chunk_size; + bytes_read += chunk_size; + } + free (bounce); + + return bytes_read; +} + +/* Writes SIZE bytes from BUFFER into INODE, starting at OFFSET. + Returns the number of bytes actually written, which may be + less than SIZE if end of file is reached or an error occurs. + (Normally a write at end of file would extend the inode, but + growth is not yet implemented.) */ +off_t +inode_write_at (struct inode *inode, const void *buffer_, off_t size, + off_t offset) +{ + const uint8_t *buffer = buffer_; + off_t bytes_written = 0; + uint8_t *bounce = NULL; + + if (inode->deny_write_cnt) + return 0; + + while (size > 0) + { + /* Sector to write, starting byte offset within sector. */ + block_sector_t sector_idx = byte_to_sector (inode, offset); + int sector_ofs = offset % BLOCK_SECTOR_SIZE; + + /* Bytes left in inode, bytes left in sector, lesser of the two. */ + off_t inode_left = inode_length (inode) - offset; + int sector_left = BLOCK_SECTOR_SIZE - sector_ofs; + int min_left = inode_left < sector_left ? inode_left : sector_left; + + /* Number of bytes to actually write into this sector. */ + int chunk_size = size < min_left ? size : min_left; + if (chunk_size <= 0) + break; + + if (sector_ofs == 0 && chunk_size == BLOCK_SECTOR_SIZE) + { + /* Write full sector directly to disk. */ + block_write (fs_device, sector_idx, buffer + bytes_written); + } + else + { + /* We need a bounce buffer. */ + if (bounce == NULL) + { + bounce = malloc (BLOCK_SECTOR_SIZE); + if (bounce == NULL) + break; + } + + /* If the sector contains data before or after the chunk + we're writing, then we need to read in the sector + first. Otherwise we start with a sector of all zeros. */ + if (sector_ofs > 0 || chunk_size < sector_left) + block_read (fs_device, sector_idx, bounce); + else + memset (bounce, 0, BLOCK_SECTOR_SIZE); + memcpy (bounce + sector_ofs, buffer + bytes_written, chunk_size); + block_write (fs_device, sector_idx, bounce); + } + + /* Advance. */ + size -= chunk_size; + offset += chunk_size; + bytes_written += chunk_size; + } + free (bounce); + + return bytes_written; +} + +/* Disables writes to INODE. + May be called at most once per inode opener. */ +void +inode_deny_write (struct inode *inode) +{ + inode->deny_write_cnt++; + ASSERT (inode->deny_write_cnt <= inode->open_cnt); +} + +/* Re-enables writes to INODE. + Must be called once by each inode opener who has called + inode_deny_write() on the inode, before closing the inode. */ +void +inode_allow_write (struct inode *inode) +{ + ASSERT (inode->deny_write_cnt > 0); + ASSERT (inode->deny_write_cnt <= inode->open_cnt); + inode->deny_write_cnt--; +} + +/* Returns the length, in bytes, of INODE's data. */ +off_t +inode_length (const struct inode *inode) +{ + return inode->data.length; +}