Implement a proper block layer with partition support.
[pintos-anon] / src / filesys / inode.c
index be1df576dd1a52343a103ee8303c8ac36964c1f8..34635637114212a54a5fb1313a6f4a1484b57ff2 100644 (file)
@@ -1,45 +1,62 @@
 #include "filesys/inode.h"
 #include "filesys/inode.h"
-#include <bitmap.h>
 #include <list.h>
 #include <debug.h>
 #include <round.h>
 #include <list.h>
 #include <debug.h>
 #include <round.h>
-#include <stdio.h>
+#include <string.h>
 #include "filesys/filesys.h"
 #include "filesys/filesys.h"
+#include "filesys/free-map.h"
 #include "threads/malloc.h"
 
 #include "threads/malloc.h"
 
+/* Identifies an inode. */
+#define INODE_MAGIC 0x494e4f44
+
 /* On-disk inode.
 /* On-disk inode.
-   Must be exactly DISK_SECTOR_SIZE bytes long. */
+   Must be exactly BLOCK_SECTOR_SIZE bytes long. */
 struct inode_disk
   {
 struct inode_disk
   {
+    block_sector_t start;               /* First data sector. */
     off_t length;                       /* File size in bytes. */
     off_t length;                       /* File size in bytes. */
-    disk_sector_t start;                /* 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 a file SIZE
+/* Returns the number of sectors to allocate for an inode SIZE
    bytes long. */
 static inline size_t
 bytes_to_sectors (off_t size)
 {
    bytes long. */
 static inline size_t
 bytes_to_sectors (off_t size)
 {
-  return DIV_ROUND_UP (size, DISK_SECTOR_SIZE);
+  return DIV_ROUND_UP (size, BLOCK_SECTOR_SIZE);
 }
 
 /* In-memory inode. */
 struct inode 
   {
 }
 
 /* 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. */
     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'. */
 static struct list open_inodes;
 
 /* List of open inodes, so that opening a single inode twice
    returns the same `struct inode'. */
 static struct list open_inodes;
 
-static void deallocate_inode (const struct inode *);
-
 /* Initializes the inode module. */
 void
 inode_init (void) 
 /* Initializes the inode module. */
 void
 inode_init (void) 
@@ -47,164 +64,282 @@ inode_init (void)
   list_init (&open_inodes);
 }
 
   list_init (&open_inodes);
 }
 
-/* Initializes an inode for a file LENGTH bytes in size and
+/* Initializes an inode with LENGTH bytes of data and
    writes the new inode to sector SECTOR on the file system
    writes the new inode to sector SECTOR on the file system
-   disk.  Allocates sectors for the file from FREE_MAP.
+   device.
    Returns true if successful.
    Returns true if successful.
-   Returns false if memory or disk allocation fails.  FREE_MAP
-   may be modified regardless. */
+   Returns false if memory or disk allocation fails. */
 bool
 bool
-inode_create (struct bitmap *free_map, disk_sector_t sector, off_t length) 
+inode_create (block_sector_t sector, off_t length)
 {
 {
-  static const char zero_sector[DISK_SECTOR_SIZE];
-  struct inode_disk *idx;
-  size_t start;
-  size_t i;
+  struct inode_disk *disk_inode = NULL;
+  bool success = false;
 
 
-  ASSERT (free_map != NULL);
   ASSERT (length >= 0);
 
   ASSERT (length >= 0);
 
-  /* Allocate sectors. */
-  start = bitmap_scan_and_flip (free_map, 0, bytes_to_sectors (length), false);
-  if (start == BITMAP_ERROR)
-    return false;
-
-  /* Create inode. */
-  idx = calloc (sizeof *idx, 1);
-  if (idx == NULL)
-    return false;
-  idx->length = length;
-  idx->start = start;
-
-  /* Commit to disk. */
-  disk_write (filesys_disk, sector, idx);
-  for (i = 0; i < bytes_to_sectors (length); i++)
-    disk_write (filesys_disk, idx->start + i, zero_sector);
-
-  free (idx);
-  return true;
+  /* 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 *
 }
 
 /* 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)) 
     {
   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 memory. */
         }
     }
 
   /* Allocate memory. */
-  idx = calloc (1, sizeof *idx);
-  if (idx == NULL)
+  inode = malloc (sizeof *inode);
+  if (inode == NULL)
     return NULL;
 
   /* Initialize. */
     return NULL;
 
   /* Initialize. */
-  list_push_front (&open_inodes, &idx->elem);
-  idx->sector = sector;
-  idx->open_cnt = 1;
-  idx->removed = false;
+  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;
+}
 
 
-  /* Read from disk. */
-  ASSERT (sizeof idx->data == DISK_SECTOR_SIZE);
-  disk_read (filesys_disk, sector, &idx->data);
+/* Reopens and returns INODE. */
+struct inode *
+inode_reopen (struct inode *inode)
+{
+  if (inode != NULL)
+    inode->open_cnt++;
+  return inode;
+}
 
 
-  return idx;
+/* Returns INODE's inode number. */
+block_sector_t
+inode_get_inumber (const struct inode *inode)
+{
+  return inode->sector;
 }
 
 }
 
-/* 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. */
+/* 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
 void
-inode_close (struct inode *idx
+inode_close (struct inode *inode
 {
   /* Ignore null pointer. */
 {
   /* Ignore null pointer. */
-  if (idx == NULL)
+  if (inode == NULL)
     return;
 
   /* Release resources if this was the last opener. */
     return;
 
   /* Release resources if this was the last opener. */
-  if (--idx->open_cnt == 0)
+  if (--inode->open_cnt == 0)
     {
     {
+      /* Remove from inode list and release lock. */
+      list_remove (&inode->elem);
       /* Deallocate blocks if removed. */
       /* Deallocate blocks if removed. */
-      if (idx->removed)
-        deallocate_inode (idx);
+      if (inode->removed) 
+        {
+          free_map_release (inode->sector, 1);
+          free_map_release (inode->data.start,
+                            bytes_to_sectors (inode->data.length)); 
+        }
 
 
-      /* Remove from inode list and free memory. */
-      list_remove (&idx->elem);
-      free (idx); 
+      free (inode); 
     }
 }
 
     }
 }
 
-/* Deallocates the blocks allocated for IDX. */
-static void
-deallocate_inode (const struct inode *idx)
+/* 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) 
 {
 {
-  struct bitmap *free_map = bitmap_create (disk_size (filesys_disk));
-  if (free_map != NULL) 
+  uint8_t *buffer = buffer_;
+  off_t bytes_read = 0;
+  uint8_t *bounce = NULL;
+
+  while (size > 0) 
     {
     {
-      bitmap_read (free_map, free_map_file);
-      bitmap_reset (free_map, idx->sector);
-      bitmap_set_multiple (free_map, idx->data.start,
-                           bytes_to_sectors (idx->data.length), false);
-      bitmap_write (free_map, free_map_file);
-      bitmap_destroy (free_map);
+      /* 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;
     }
     }
-  else
-    printf ("inode_close(): can't free blocks");
+  free (bounce);
+
+  return bytes_read;
 }
 
 }
 
-/* Marks IDX to be deleted when it is closed by the last caller who
-   has it open. */
-void
-inode_remove (struct 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_write_at (struct inode *inode, const void *buffer_, off_t size,
+                off_t offset) 
 {
 {
-  ASSERT (idx != NULL);
-  idx->removed = true;
+  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;
 }
 
 }
 
-/* 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) 
+/* Disables writes to INODE.
+   May be called at most once per inode opener. */
+void
+inode_deny_write (struct inode *inode) 
 {
 {
-  ASSERT (idx != NULL);
-  ASSERT (pos < idx->data.length);
-  return idx->data.start + pos / DISK_SECTOR_SIZE;
+  inode->deny_write_cnt++;
+  ASSERT (inode->deny_write_cnt <= inode->open_cnt);
 }
 
 }
 
-/* Returns the length, in bytes, of the file with inode IDX. */
-off_t
-inode_length (const struct inode *idx)
+/* 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 (idx != NULL);
-  return idx->data.length;
+  ASSERT (inode->deny_write_cnt > 0);
+  ASSERT (inode->deny_write_cnt <= inode->open_cnt);
+  inode->deny_write_cnt--;
 }
 
 }
 
-/* Prints a representation of IDX to the system console. */
-void
-inode_print (const struct inode *idx) 
+/* Returns the length, in bytes, of INODE's data. */
+off_t
+inode_length (const struct inode *inode)
 {
 {
-  ASSERT (idx != NULL);
-  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.start);
+  return inode->data.length;
 }
 }