Implement a proper block layer with partition support.
[pintos-anon] / src / filesys / inode.c
index 16d1455925b0f85baccb0cd0d5ce5099c6c0ac35..34635637114212a54a5fb1313a6f4a1484b57ff2 100644 (file)
@@ -1,36 +1,61 @@
 #include "filesys/inode.h"
-#include <bitmap.h>
 #include <list.h>
 #include <debug.h>
 #include <round.h>
-#include <stdio.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 DISK_SECTOR_SIZE bytes long. */
+   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. */
-    disk_sector_t first_sector;         /* Starting sector. */
-    uint32_t unused[126];               /* Unused padding. */
+    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 
   {
-    list_elem elem;                     /* Element in inode list. */
-    disk_sector_t sector;               /* Sector number of disk location. */
+    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. */
-    struct inode_disk data;             /* On-disk data. */
+    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'. */
-struct list open_inodes;
-
-static struct inode *alloc_inode (disk_sector_t);
+static struct list open_inodes;
 
 /* Initializes the inode module. */
 void
@@ -39,195 +64,282 @@ inode_init (void)
   list_init (&open_inodes);
 }
 
-/* Allocates sectors from bitmap B for the content of a file
-   whose size is LENGTH bytes, and returns a new `struct inode'
-   properly initialized for the file.
-   It is the caller's responsible to write the inode to disk with
-   inode_commit(), as well as the bitmap.
-   If memory or disk allocation fails, returns a null pointer,
-   leaving bitmap B is unchanged. */
-struct inode *
-inode_create (struct bitmap *b, disk_sector_t sector, off_t length) 
+/* 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 *idx;
-  size_t sector_cnt;
+  struct inode_disk *disk_inode = NULL;
+  bool success = false;
 
-  ASSERT (b != NULL);
   ASSERT (length >= 0);
 
-  /* Allocate inode. */
-  idx = alloc_inode (sector);
-  if (idx == NULL)
-    return NULL;
-
-  /* Allocate disk sectors. */
-  sector_cnt = DIV_ROUND_UP (length, DISK_SECTOR_SIZE);
-  idx->data.length = length;
-  idx->data.first_sector = bitmap_scan_and_flip (b, 0, sector_cnt, false);
-  if (idx->data.first_sector == BITMAP_ERROR)
-    return NULL;
+  /* 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);
 
-  /* Zero out the file contents. */
-  if (sector_cnt > 0) 
+  disk_inode = calloc (1, sizeof *disk_inode);
+  if (disk_inode != NULL)
     {
-      static const char zero_sector[DISK_SECTOR_SIZE];
-      disk_sector_t i;
-      
-      for (i = 0; i < sector_cnt; i++)
-        disk_write (filesys_disk, idx->data.first_sector + i, zero_sector);
+      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 idx;
-
- error:
-  inode_remove (idx);
-  inode_close (idx);
-  return NULL;
+  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 (disk_sector_t sector) 
+inode_open (block_sector_t sector)
 {
-  list_elem *e;
-  struct inode *idx;
+  struct list_elem *e;
+  struct inode *inode;
 
-  /* Check whether this inode is already open.
-     (A hash table would be better, but the Pintos base code
-     avoids using the hash table so that users are free to modify
-     it at will.) */
+  /* Check whether this inode is already open. */
   for (e = list_begin (&open_inodes); e != list_end (&open_inodes);
        e = list_next (e)) 
     {
-      idx = list_entry (e, struct inode, elem);
-      if (idx->sector == sector) 
+      inode = list_entry (e, struct inode, elem);
+      if (inode->sector == sector) 
         {
-          idx->open_cnt++;
-          return idx;
+          inode_reopen (inode);
+          return inode; 
         }
     }
 
-  /* Allocate inode. */
-  idx = alloc_inode (sector);
-  if (idx == NULL)
+  /* Allocate memory. */
+  inode = malloc (sizeof *inode);
+  if (inode == NULL)
     return NULL;
 
-  /* Read from disk. */
-  ASSERT (sizeof idx->data == DISK_SECTOR_SIZE);
-  disk_read (filesys_disk, sector, &idx->data);
+  /* 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;
+}
 
-  return idx;
+/* Reopens and returns INODE. */
+struct inode *
+inode_reopen (struct inode *inode)
+{
+  if (inode != NULL)
+    inode->open_cnt++;
+  return inode;
 }
 
-/* Closes inode IDX and writes it to disk.
-   If this was the last reference to IDX, frees its memory.
-   If IDX was also a removed inode, frees its blocks. */
+/* 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 *idx
+inode_close (struct inode *inode
 {
-  if (idx == NULL)
+  /* Ignore null pointer. */
+  if (inode == NULL)
     return;
 
-  if (--idx->open_cnt == 0)
+  /* Release resources if this was the last opener. */
+  if (--inode->open_cnt == 0)
     {
-      if (idx->removed) 
+      /* Remove from inode list and release lock. */
+      list_remove (&inode->elem);
+      /* Deallocate blocks if removed. */
+      if (inode->removed) 
         {
-          struct bitmap *free_map;
-
-          free_map = bitmap_create (disk_size (filesys_disk));
-          if (free_map != NULL)
-            {
-              disk_sector_t start, end;
-              
-              bitmap_read (free_map, free_map_file);
-
-              /* Reset inode sector bit. */
-              bitmap_reset (free_map, idx->sector);
-
-              /* Reset inode data sector bits. */
-              start = idx->data.first_sector;
-              end = start + DIV_ROUND_UP (idx->data.length, DISK_SECTOR_SIZE);
-              bitmap_set_multiple (free_map, start, end, false);
-
-              bitmap_write (free_map, free_map_file);
-              bitmap_destroy (free_map);
-            }
-          else
-            printf ("inode_close(): can't free blocks");
+          free_map_release (inode->sector, 1);
+          free_map_release (inode->data.start,
+                            bytes_to_sectors (inode->data.length)); 
         }
-      list_remove (&idx->elem);
-      free (idx); 
-    }
-}
 
-/* Writes IDX to disk. */
-void
-inode_commit (const struct inode *idx) 
-{
-  ASSERT (idx != NULL);
-  ASSERT (sizeof idx->data == DISK_SECTOR_SIZE);
-  disk_write (filesys_disk, idx->sector, &idx->data);
+      free (inode); 
+    }
 }
 
-/* Marks IDX to be deleted when it is closed by the last caller who
+/* Marks INODE to be deleted when it is closed by the last caller who
    has it open. */
 void
-inode_remove (struct inode *idx
+inode_remove (struct inode *inode
 {
-  ASSERT (idx != NULL);
-  idx->removed = true;
+  ASSERT (inode != NULL);
+  inode->removed = true;
 }
 
-/* Returns the disk sector that contains byte offset POS within
-   the file with inode IDX.
-   Returns -1 if IDX does not contain data for a byte at offset
-   POS. */
-disk_sector_t
-inode_byte_to_sector (const struct inode *idx, off_t pos) 
+/* 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) 
 {
-  ASSERT (idx != NULL);
+  uint8_t *buffer = buffer_;
+  off_t bytes_read = 0;
+  uint8_t *bounce = NULL;
 
-  if (pos < idx->data.length)
-    return idx->data.first_sector + pos / DISK_SECTOR_SIZE;
-  else
-    return (disk_sector_t) -1;
+  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;
 }
 
-/* Returns the length, in bytes, of the file with inode IDX. */
+/* 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_length (const struct inode *idx)
+inode_write_at (struct inode *inode, const void *buffer_, off_t size,
+                off_t offset) 
 {
-  ASSERT (idx != NULL);
-  return idx->data.length;
+  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;
 }
 
-/* Prints a representation of IDX to the system console. */
+/* Disables writes to INODE.
+   May be called at most once per inode opener. */
 void
-inode_print (const struct inode *idx
+inode_deny_write (struct inode *inode
 {
-  printf ("Inode %"PRDSNu": %"PRDSNu" bytes, "
-          "%zu sectors starting at %"PRDSNu"\n",
-          idx->sector, idx->data.length,
-          (size_t) DIV_ROUND_UP (idx->data.length, DISK_SECTOR_SIZE),
-          idx->data.first_sector);
+  inode->deny_write_cnt++;
+  ASSERT (inode->deny_write_cnt <= inode->open_cnt);
 }
-\f
-/* Returns a newly allocated and initialized inode. */
-static struct inode *
-alloc_inode (disk_sector_t sector) 
-{
-  /* Allocate memory. */
-  struct inode *idx = calloc (1, sizeof *idx);
-  if (idx == NULL)
-    return NULL;
 
-  /* Initialize. */
-  list_push_front (&open_inodes, &idx->elem);
-  idx->sector = sector;
-  idx->open_cnt = 1;
-  idx->removed = false;
+/* 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--;
+}
 
-  return idx;
+/* Returns the length, in bytes, of INODE's data. */
+off_t
+inode_length (const struct inode *inode)
+{
+  return inode->data.length;
 }