Implement a proper block layer with partition support.
[pintos-anon] / src / devices / partition.c
diff --git a/src/devices/partition.c b/src/devices/partition.c
new file mode 100644 (file)
index 0000000..7e97332
--- /dev/null
@@ -0,0 +1,324 @@
+#include "devices/partition.h"
+#include <packed.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include "devices/block.h"
+#include "threads/malloc.h"
+
+/* A partition of a block device. */
+struct partition
+  {
+    struct block *block;                /* Underlying block device. */
+    block_sector_t start;               /* First sector within device. */
+  };
+
+static struct block_operations partition_operations;
+
+static void read_partition_table (struct block *, block_sector_t sector,
+                                  block_sector_t primary_extended_sector,
+                                  int *part_nr);
+static void found_partition (struct block *, uint8_t type,
+                             block_sector_t start, block_sector_t size,
+                             int part_nr);
+static const char *partition_type_name (uint8_t);
+
+/* Scans BLOCK for partitions of interest to Pintos. */
+void
+partition_scan (struct block *block)
+{
+  int part_nr = 0;
+  read_partition_table (block, 0, 0, &part_nr);
+  if (part_nr == 0)
+    printf ("%s: Device contains no partitions\n", block_name (block));
+}
+
+/* Reads the partition table in the given SECTOR of BLOCK and
+   scans it for partitions of interest to Pintos.
+
+   If SECTOR is 0, so that this is the top-level partition table
+   on BLOCK, then PRIMARY_EXTENDED_SECTOR is not meaningful;
+   otherwise, it should designate the sector of the top-level
+   extended partition table that was traversed to arrive at
+   SECTOR, for use in finding logical partitions (see the large
+   comment below).
+
+   PART_NR points to the number of non-empty primary or logical
+   partitions already encountered on BLOCK.  It is incremented as
+   partitions are found. */
+static void
+read_partition_table (struct block *block, block_sector_t sector,
+                      block_sector_t primary_extended_sector,
+                      int *part_nr)
+{
+  /* Format of a partition table entry.  See [Partitions]. */
+  struct partition_table_entry
+    {
+      uint8_t bootable;         /* 0x00=not bootable, 0x80=bootable. */
+      uint8_t start_chs[3];     /* Encoded starting cylinder, head, sector. */
+      uint8_t type;             /* Partition type (see partition_type_name). */
+      uint8_t end_chs[3];       /* Encoded ending cylinder, head, sector. */
+      uint32_t offset;          /* Start sector offset from partition table. */
+      uint32_t size;            /* Number of sectors. */
+    }
+  PACKED;
+
+  /* Partition table sector. */
+  struct partition_table
+    {
+      uint8_t loader[446];      /* Loader, in top-level partition table. */
+      struct partition_table_entry partitions[4];       /* Table entries. */
+      uint16_t signature;       /* Should be 0xaa55. */
+    }
+  PACKED;
+
+  struct partition_table *pt;
+  size_t i;
+
+  /* Check SECTOR validity. */
+  if (sector >= block_size (block))
+    {
+      printf ("%s: Partition table at sector %"PRDSNu" past end of device.\n",
+              block_name (block), sector);
+      return;
+    }
+
+  /* Read sector. */
+  ASSERT (sizeof *pt == BLOCK_SECTOR_SIZE);
+  pt = malloc (sizeof *pt);
+  if (pt == NULL)
+    PANIC ("Failed to allocate memory for partition table.");
+  block_read (block, 0, pt);
+
+  /* Check signature. */
+  if (pt->signature != 0xaa55)
+    {
+      if (primary_extended_sector == 0)
+        printf ("%s: Invalid partition table signature\n", block_name (block));
+      else
+        printf ("%s: Invalid extended partition table in sector %"PRDSNu"\n",
+                block_name (block), sector);
+      free (pt);
+      return;
+    }
+
+  /* Parse partitions. */
+  for (i = 0; i < sizeof pt->partitions / sizeof *pt->partitions; i++)
+    {
+      struct partition_table_entry *e = &pt->partitions[i];
+
+      if (e->size == 0 || e->type == 0)
+        {
+          /* Ignore empty partition. */
+        }
+      else if (e->type == 0x05       /* Extended partition. */
+               || e->type == 0x0f    /* Windows 98 extended partition. */
+               || e->type == 0x85    /* Linux extended partition. */
+               || e->type == 0xc5)   /* DR-DOS extended partition. */
+        {
+          printf ("%s: Extended partition in sector %"PRDSNu"\n",
+                  block_name (block), sector);
+
+          /* The interpretation of the offset field for extended
+             partitions is bizarre.  When the extended partition
+             table entry is in the master boot record, that is,
+             the device's primary partition table in sector 0, then
+             the offset is an absolute sector number.  Otherwise,
+             no matter how deep the partition table we're reading
+             is nested, the offset is relative to the start of
+             the extended partition that the MBR points to. */
+          if (sector == 0)
+            read_partition_table (block, e->offset, e->offset, part_nr);
+          else
+            read_partition_table (block, e->offset + primary_extended_sector,
+                                  primary_extended_sector, part_nr);
+        }
+      else
+        {
+          ++*part_nr;
+
+          found_partition (block, e->type, e->offset + sector,
+                           e->size, *part_nr);
+        }
+    }
+
+  free (pt);
+}
+
+/* We have found a primary or logical partition of the given TYPE
+   on BLOCK, starting at sector START and continuing for SIZE
+   sectors, which we are giving the partition number PART_NR.
+   Check whether this is a partition of interest to Pintos, and
+   if so then add it to the proper element of partitions[]. */
+static void
+found_partition (struct block *block, uint8_t part_type,
+                 block_sector_t start, block_sector_t size,
+                 int part_nr)
+{
+  if (start >= block_size (block))
+    printf ("%s%d: Partition starts past end of device (sector %"PRDSNu")\n",
+            block_name (block), part_nr, start);
+  else if (start + size < start || start + size > block_size (block))
+    printf ("%s%d: Partition end (%"PRDSNu") past end of device (%"PRDSNu")\n",
+            block_name (block), part_nr, start + size, block_size (block));
+  else
+    {
+      enum block_type type = (part_type == 0x20 ? BLOCK_KERNEL
+                              : part_type == 0x21 ? BLOCK_FILESYS
+                              : part_type == 0x22 ? BLOCK_SCRATCH
+                              : part_type == 0x23 ? BLOCK_SWAP
+                              : BLOCK_FOREIGN);
+      struct partition *p;
+      char extra_info[128];
+      char name[16];
+
+      p = malloc (sizeof *p);
+      if (p == NULL)
+        PANIC ("Failed to allocate memory for partition descriptor");
+      p->block = block;
+      p->start = start;
+
+      snprintf (name, sizeof name, "%s%d", block_name (block), part_nr);
+      snprintf (extra_info, sizeof extra_info, "%s (%02x)",
+                partition_type_name (part_type), part_type);
+      block_register (name, type, extra_info, size, &partition_operations, p);
+    }
+}
+
+/* Returns a human-readable name for the given partition TYPE. */
+static const char *
+partition_type_name (uint8_t type)
+{
+  /* Name of each known type of partition.
+     From util-linux-2.12r/fdisk/i386_sys_types.c.
+     This initializer makes use of a C99 feature that allows
+     array elements to be initialized by index. */
+  static const char *type_names[256] =
+    {
+      [0x00] = "Empty",
+      [0x01] = "FAT12",
+      [0x02] = "XENIX root",
+      [0x03] = "XENIX usr",
+      [0x04] = "FAT16 <32M",
+      [0x05] = "Extended",
+      [0x06] = "FAT16",
+      [0x07] = "HPFS/NTFS",
+      [0x08] = "AIX",
+      [0x09] = "AIX bootable",
+      [0x0a] = "OS/2 Boot Manager",
+      [0x0b] = "W95 FAT32",
+      [0x0c] = "W95 FAT32 (LBA)",
+      [0x0e] = "W95 FAT16 (LBA)",
+      [0x0f] = "W95 Ext'd (LBA)",
+      [0x10] = "OPUS",
+      [0x11] = "Hidden FAT12",
+      [0x12] = "Compaq diagnostics",
+      [0x14] = "Hidden FAT16 <32M",
+      [0x16] = "Hidden FAT16",
+      [0x17] = "Hidden HPFS/NTFS",
+      [0x18] = "AST SmartSleep",
+      [0x1b] = "Hidden W95 FAT32",
+      [0x1c] = "Hidden W95 FAT32 (LBA)",
+      [0x1e] = "Hidden W95 FAT16 (LBA)",
+      [0x20] = "Pintos OS kernel",
+      [0x21] = "Pintos file system",
+      [0x22] = "Pintos scratch",
+      [0x23] = "Pintos swap",
+      [0x24] = "NEC DOS",
+      [0x39] = "Plan 9",
+      [0x3c] = "PartitionMagic recovery",
+      [0x40] = "Venix 80286",
+      [0x41] = "PPC PReP Boot",
+      [0x42] = "SFS",
+      [0x4d] = "QNX4.x",
+      [0x4e] = "QNX4.x 2nd part",
+      [0x4f] = "QNX4.x 3rd part",
+      [0x50] = "OnTrack DM",
+      [0x51] = "OnTrack DM6 Aux1",
+      [0x52] = "CP/M",
+      [0x53] = "OnTrack DM6 Aux3",
+      [0x54] = "OnTrackDM6",
+      [0x55] = "EZ-Drive",
+      [0x56] = "Golden Bow",
+      [0x5c] = "Priam Edisk",
+      [0x61] = "SpeedStor",
+      [0x63] = "GNU HURD or SysV",
+      [0x64] = "Novell Netware 286",
+      [0x65] = "Novell Netware 386",
+      [0x70] = "DiskSecure Multi-Boot",
+      [0x75] = "PC/IX",
+      [0x80] = "Old Minix",
+      [0x81] = "Minix / old Linux",
+      [0x82] = "Linux swap / Solaris",
+      [0x83] = "Linux",
+      [0x84] = "OS/2 hidden C: drive",
+      [0x85] = "Linux extended",
+      [0x86] = "NTFS volume set",
+      [0x87] = "NTFS volume set",
+      [0x88] = "Linux plaintext",
+      [0x8e] = "Linux LVM",
+      [0x93] = "Amoeba",
+      [0x94] = "Amoeba BBT",
+      [0x9f] = "BSD/OS",
+      [0xa0] = "IBM Thinkpad hibernation",
+      [0xa5] = "FreeBSD",
+      [0xa6] = "OpenBSD",
+      [0xa7] = "NeXTSTEP",
+      [0xa8] = "Darwin UFS",
+      [0xa9] = "NetBSD",
+      [0xab] = "Darwin boot",
+      [0xb7] = "BSDI fs",
+      [0xb8] = "BSDI swap",
+      [0xbb] = "Boot Wizard hidden",
+      [0xbe] = "Solaris boot",
+      [0xbf] = "Solaris",
+      [0xc1] = "DRDOS/sec (FAT-12)",
+      [0xc4] = "DRDOS/sec (FAT-16 < 32M)",
+      [0xc6] = "DRDOS/sec (FAT-16)",
+      [0xc7] = "Syrinx",
+      [0xda] = "Non-FS data",
+      [0xdb] = "CP/M / CTOS / ...",
+      [0xde] = "Dell Utility",
+      [0xdf] = "BootIt",
+      [0xe1] = "DOS access",
+      [0xe3] = "DOS R/O",
+      [0xe4] = "SpeedStor",
+      [0xeb] = "BeOS fs",
+      [0xee] = "EFI GPT",
+      [0xef] = "EFI (FAT-12/16/32)",
+      [0xf0] = "Linux/PA-RISC boot",
+      [0xf1] = "SpeedStor",
+      [0xf4] = "SpeedStor",
+      [0xf2] = "DOS secondary",
+      [0xfd] = "Linux raid autodetect",
+      [0xfe] = "LANstep",
+      [0xff] = "BBT",
+    };
+
+  return type_names[type] != NULL ? type_names[type] : "Unknown";
+}
+
+/* Reads sector SECTOR from partition P into BUFFER, which must
+   have room for BLOCK_SECTOR_SIZE bytes. */
+static void
+partition_read (void *p_, block_sector_t sector, void *buffer)
+{
+  struct partition *p = p_;
+  block_read (p->block, p->start + sector, buffer);
+}
+
+/* Write sector SECTOR to partition P from BUFFER, which must
+   contain BLOCK_SECTOR_SIZE bytes.  Returns after the block has
+   acknowledged receiving the data. */
+static void
+partition_write (void *p_, block_sector_t sector, const void *buffer)
+{
+  struct partition *p = p_;
+  block_write (p->block, p->start + sector, buffer);
+}
+
+static struct block_operations partition_operations =
+  {
+    partition_read,
+    partition_write
+  };