Start work on partition support.
authorBen Pfaff <blp@cs.stanford.edu>
Sat, 29 Jan 2005 18:23:34 +0000 (18:23 +0000)
committerBen Pfaff <blp@cs.stanford.edu>
Sat, 29 Jan 2005 18:23:34 +0000 (18:23 +0000)
(Doesn't build.)

13 files changed:
src/Makefile.build
src/devices/disk.c
src/devices/disk.h
src/devices/partition.c [new file with mode: 0644]
src/devices/partition.h [new file with mode: 0644]
src/filesys/file.c
src/filesys/filesys.c
src/filesys/filesys.h
src/filesys/fsutil.c
src/filesys/inode.c
src/threads/init.c
src/threads/loader.S
src/utils/pintos

index d4c6d659d0afc5af2da03dbe67751d10beff0324..15641989f55e560b44caba1c1f83d9f06a329798 100644 (file)
@@ -28,6 +28,7 @@ devices_SRC += devices/kbd.c          # Keyboard device.
 devices_SRC += devices/vga.c           # Video device.
 devices_SRC += devices/serial.c                # Serial port device.
 devices_SRC += devices/disk.c          # IDE disk device.
+devices_SRC += devices/partition.c     # Disk partitions.
 devices_SRC += devices/intq.c          # Interrupt queue.
 
 # Library code shared between kernel and user programs.
index 971f3af537288f38523d92e0807e2489870e33f6..5320d2e48f602c8eb1b5a36f9899b789263fb362 100644 (file)
@@ -204,6 +204,15 @@ disk_size (struct disk *d)
   return d->capacity;
 }
 
+/* Returns a human-readable name for disk D. */
+const char *
+disk_name (struct disk *d) 
+{
+  ASSERT (d != NULL);
+
+  return d->name;
+}
+
 /* Reads sector SEC_NO from disk D into BUFFER, which must have
    room for DISK_SECTOR_SIZE bytes. */
 void
@@ -373,15 +382,7 @@ identify_ata_device (struct disk *d)
 
   /* Print identification message. */
   printf ("%s: detected %'"PRDSNu" sector (", d->name, d->capacity);
-  if (d->capacity > 1024 / DISK_SECTOR_SIZE * 1024 * 1024)
-    printf ("%"PRDSNu" GB",
-            d->capacity / (1024 / DISK_SECTOR_SIZE * 1024 * 1024));
-  else if (d->capacity > 1024 / DISK_SECTOR_SIZE * 1024)
-    printf ("%"PRDSNu" MB", d->capacity / (1024 / DISK_SECTOR_SIZE * 1024));
-  else if (d->capacity > 1024 / DISK_SECTOR_SIZE)
-    printf ("%"PRDSNu" kB", d->capacity / (1024 / DISK_SECTOR_SIZE));
-  else
-    printf ("%"PRDSNu" byte", d->capacity * DISK_SECTOR_SIZE);
+  print_human_readable_size ((uint64_t) d->capacity * DISK_SECTOR_SIZE);
   printf (") disk, model \"");
   print_ata_string ((char *) &id[27], 40);
   printf ("\", serial \"");
index 3bcbb9a10612d223434bae8b81bd75b72de3d4d4..affbe323c5361f79572f8b499762e576ce8944df 100644 (file)
@@ -20,6 +20,7 @@ void disk_print_stats (void);
 
 struct disk *disk_get (int chan_no, int dev_no);
 disk_sector_t disk_size (struct disk *);
+const char *disk_name (struct disk *);
 void disk_read (struct disk *, disk_sector_t, void *);
 void disk_write (struct disk *, disk_sector_t, const void *);
 
diff --git a/src/devices/partition.c b/src/devices/partition.c
new file mode 100644 (file)
index 0000000..796e816
--- /dev/null
@@ -0,0 +1,166 @@
+#include "devices/partition.h"
+#include <stdio.h>
+#include "threads/malloc.h"
+
+struct partition
+  {
+    struct disk *disk;
+    disk_sector_t first_sector;
+    disk_sector_t sector_cnt;
+  };
+
+struct partition partitions[PARTITION_CNT];
+
+static void scan_partition_table (struct disk *, disk_sector_t);
+
+void
+partition_init (void)
+{
+  int chan, dev;
+
+  for (chan = 0; chan < 2; chan++)
+    for (dev = 0; dev < 2; dev++)
+      {
+        struct disk *disk = disk_get (chan, dev);
+        if (disk != NULL)
+          scan_partition_table (disk, 0);
+      }
+
+}
+
+struct partition *
+partition_get (enum partition_type type)
+{
+  ASSERT (type < PARTITION_CNT);
+
+  return &partitions[type];
+}
+
+disk_sector_t
+partition_size (struct partition *p)
+{
+  ASSERT (p != NULL);
+
+  return p->sector_cnt;
+}
+
+void
+partition_read (struct partition *p, disk_sector_t sector, void *buffer)
+{
+  ASSERT (p != NULL);
+  ASSERT (sector < p->sector_cnt);
+  ASSERT (buffer != NULL);
+
+  disk_read (p->disk, p->first_sector + sector, buffer);
+}
+
+void
+partition_write (struct partition *p, disk_sector_t sector, const void *buffer)
+{
+  ASSERT (p != NULL);
+  ASSERT (sector < p->sector_cnt);
+  ASSERT (buffer != NULL);
+
+  disk_write (p->disk, p->first_sector + sector, buffer);
+}
+\f
+#define PARTITION_CNT 4
+#define PARTITION_TABLE_SIGNATURE 0xaa55
+
+struct partition_table_entry
+  {
+    uint8_t bootable;
+    uint8_t start_chs[3];
+    uint8_t type;
+    uint8_t end_chs[3];
+    uint32_t first_sector;
+    uint32_t sector_cnt;
+  };
+
+struct partition_table
+  {
+    uint8_t boot_loader[446];
+    struct partition_table_entry partitions[PARTITION_CNT];
+    uint16_t signature;
+  };
+
+static void register_partition (struct disk *,
+                                const struct partition_table_entry *,
+                                enum partition_type);
+
+static void
+scan_partition_table (struct disk *disk, disk_sector_t sector)
+{
+  struct partition_table *pt;
+
+  ASSERT (sizeof *pt == DISK_SECTOR_SIZE);
+  pt = xmalloc (sizeof *pt);
+  disk_read (disk, sector, pt);
+
+  if (pt->signature == PARTITION_TABLE_SIGNATURE)
+    {
+      size_t i;
+
+      for (i = 0; i < PARTITION_CNT; i++)
+        {
+          struct partition_table_entry *pte = &pt->partitions[i];
+
+          switch (pte->type)
+            {
+            case 0x05: /* DOS extended partition. */
+            case 0x0f: /* Windows 95 extented partition. */
+            case 0x85: /* Linux extended partition. */
+            case 0xc5: /* DRDOS "secured" extended partition. */
+              scan_partition_table (disk, pte->first_sector);
+              break;
+
+            case 0x20: /* Pintos boot partition. */
+              register_partition (disk, pte, PARTITION_BOOT);
+              break;
+
+            case 0x21: /* Pintos file system partition. */
+              register_partition (disk, pte, PARTITION_FILESYS);
+              break;
+
+            case 0x22: /* Pintos scratch partition. */
+              register_partition (disk, pte, PARTITION_SCRATCH);
+              break;
+
+            case 0x23: /* Pintos swap partition. */
+              register_partition (disk, pte, PARTITION_SWAP);
+              break;
+
+            default:
+              /* We don't know anything about this kind of
+                 partition.  Ignore it. */
+              break;
+            }
+
+        }
+    }
+
+  free (pt);
+}
+
+static void
+register_partition (struct disk *disk, const struct partition_table_entry *pte,
+                    enum partition_type type)
+{
+  static const char *partition_names[] =
+    {"boot", "file system", "scratch", "swap"};
+  struct partition *p;
+
+  ASSERT (type < PARTITION_CNT);
+
+  printf ("%s: Found %"PRDSNu" sector (", disk_name (disk), pte->sector_cnt);
+  print_human_readable_size ((uint64_t) pte->sector_cnt * DISK_SECTOR_SIZE);
+  printf (") %s partition starting at sector %"PRDSNu"\n",
+          partition_names[pte->type], pte->first_sector);
+
+  p = &partitions[type];
+  if (p->disk != NULL)
+    PANIC ("Can't handle multiple %s partitions", partition_names[pte->type]);
+  p->disk = disk;
+  p->first_sector = pte->first_sector;
+  p->sector_cnt = pte->sector_cnt;
+}
diff --git a/src/devices/partition.h b/src/devices/partition.h
new file mode 100644 (file)
index 0000000..e86da89
--- /dev/null
@@ -0,0 +1,23 @@
+#ifndef DEVICES_PARTITION_H
+#define DEVICES_PARTITION_H
+
+#include "devices/disk.h"
+
+/* A particular partition. */
+enum partition_type
+  {
+    PARTITION_BOOT,     /* The Pintos boot image. */
+    PARTITION_FILESYS,  /* File system partition. */
+    PARTITION_SCRATCH,  /* Scratch partition. */
+    PARTITION_SWAP,     /* Swap partition. */
+
+    PARTITION_CNT       /* Number of partitions. */
+  };
+
+void partition_init (void);
+struct partition *partition_get (enum partition_type);
+disk_sector_t partition_size (struct partition *);
+void partition_read (struct partition *, disk_sector_t, void *);
+void partition_write (struct partition *, disk_sector_t, const void *);
+
+#endif /* devices/partition.h */
index 27e907feabe36b689d40bf85ae0d865cdc5deef9..17001eab2855682907908a97ea64ce171246f717 100644 (file)
@@ -4,6 +4,7 @@
 #include "filesys/directory.h"
 #include "filesys/inode.h"
 #include "filesys/filesys.h"
+#include "devices/partition.h"
 #include "threads/malloc.h"
 
 /* An open file. */
@@ -91,7 +92,7 @@ file_read_at (struct file *file, void *buffer_, off_t size,
       /* Read sector into bounce buffer, then copy into caller's
          buffer. */
       sector_idx = inode_byte_to_sector (file->inode, file_ofs);
-      disk_read (filesys_disk, sector_idx, file->bounce);
+      partition_read (filesys_partition, sector_idx, file->bounce);
       memcpy (buffer + bytes_read, file->bounce + sector_ofs, chunk_size);
 
       /* Advance. */
@@ -153,11 +154,11 @@ file_write_at (struct file *file, const void *buffer_, off_t size,
          first.  Otherwise we start with a sector of all zeros. */
       sector_idx = inode_byte_to_sector (file->inode, file_ofs);
       if (sector_ofs > 0 || chunk_size < sector_left)
-        disk_read (filesys_disk, sector_idx, file->bounce);
+        partition_read (filesys_partition, sector_idx, file->bounce);
       else
         memset (file->bounce, 0, DISK_SECTOR_SIZE);
       memcpy (file->bounce + sector_ofs, buffer + bytes_written, chunk_size);
-      disk_write (filesys_disk, sector_idx, file->bounce);
+      partition_write (filesys_partition, sector_idx, file->bounce);
 
       /* Advance. */
       size -= chunk_size;
index 798989065f11110135fb1200e9aa50cbd7f39477..847bfb85c93b81b8846fd2908e110532db1b0672 100644 (file)
@@ -35,6 +35,7 @@
 #include "filesys/inode.h"
 #include "filesys/directory.h"
 #include "devices/disk.h"
+#include "devices/partition.h"
 
 /* Filesystem.
 
@@ -42,9 +43,9 @@
    assignments (projects 2 and 3), please treat all the code in
    the filesys directory as a black box.  No changes should be
    needed.  For those projects, a single lock external to the
-   filesystem code suffices.
+   file system code suffices.
 
-   The filesystem consists of a set of files.  Each file has a
+   The file system consists of a set of files.  Each file has a
    header called an `index node' or `inode', represented by
    struct inode, that is stored by itself in a single sector (see
    inode.h).  The header contains the file's length in bytes and
@@ -65,7 +66,7 @@
    directory.h), each of which, if it is in use, associates a
    filename with the sector of the file's inode.
 
-   The filesystem implemented here has the following limitations:
+   The file system implemented here has the following limitations:
 
      - No synchronization.  Concurrent accesses will interfere
        with one another, so external synchronization is needed.
 /* Root directory. */
 #define NUM_DIR_ENTRIES 10      /* Maximum number of directory entries. */
 
-/* The disk that contains the filesystem. */
-struct disk *filesys_disk;
+/* The partition that contains the file system. */
+struct partition *filesys_partition;
 
 /* The free map and root directory files.
    These files are opened by filesys_init() and never closed. */
@@ -109,16 +110,16 @@ struct file *free_map_file, *root_dir_file;
 
 static void do_format (void);
 
-/* Initializes the filesystem module.
-   If FORMAT is true, reformats the filesystem. */
+/* Initializes the file system module.
+   If FORMAT is true, reformats the file system. */
 void
 filesys_init (bool format) 
 {
   inode_init ();
 
-  filesys_disk = disk_get (0, 1);
-  if (filesys_disk == NULL)
-    PANIC ("hd0:1 (hdb) not present, filesystem initialization failed");
+  filesys_partition = partition_get (PARTITION_FILESYS);
+  if (filesys_partition == NULL)
+    PANIC ("missing file system partition");
 
   if (format) 
     do_format ();
@@ -131,7 +132,7 @@ filesys_init (bool format)
     PANIC ("can't open root dir file");
 }
 
-/* Shuts down the filesystem module, writing any unwritten data
+/* Shuts down the file system module, writing any unwritten data
    to disk.
    Currently there's nothing to do.  You'll need to add code here
    when you implement write-behind caching. */
@@ -161,7 +162,7 @@ filesys_create (const char *name, off_t initial_size)
     goto done;
 
   /* Allocate a block for the inode. */
-  free_map = bitmap_create (disk_size (filesys_disk));
+  free_map = bitmap_create (partition_size (filesys_partition));
   if (free_map == NULL)
     goto done;
   bitmap_read (free_map, free_map_file);
@@ -259,7 +260,7 @@ filesys_remove (const char *name)
   return success;
 }
 
-/* Prints a list of files in the filesystem to the system
+/* Prints a list of files in the file system to the system
    console.
    Returns true if successful, false on failure,
    which occurs only if an internal memory allocation fails. */
@@ -276,7 +277,7 @@ filesys_list (void)
   return true;
 }
 
-/* Dumps the filesystem state to the system console,
+/* Dumps the file system state to the system console,
    including the free map, the list of files, and file contents.
    Returns true if successful, false on failure,
    which occurs only if an internal memory allocation fails. */
@@ -287,7 +288,7 @@ filesys_dump (void)
   struct dir *dir;  
 
   printf ("Free map:\n");
-  free_map = bitmap_create (disk_size (filesys_disk));
+  free_map = bitmap_create (partition_size (filesys_partition));
   if (free_map == NULL)
     return false;
   bitmap_read (free_map, free_map_file);
@@ -308,8 +309,8 @@ filesys_dump (void)
 static void must_succeed_function (int, bool) NO_INLINE;
 #define MUST_SUCCEED(EXPR) must_succeed_function (__LINE__, EXPR)
 
-/* Performs basic sanity checks on the filesystem.
-   The filesystem should not contain a file named `foo' when
+/* Performs basic sanity checks on the file system.
+   The file system should not contain a file named `foo' when
    called. */
 void
 filesys_self_test (void)
@@ -355,26 +356,26 @@ filesys_self_test (void)
   printf ("filesys: self test ok\n");
 }
 \f
-/* Formats the filesystem. */
+/* Formats the file system. */
 static void
 do_format (void)
 {
   struct bitmap *free_map;
   struct dir *dir;
 
-  printf ("Formatting filesystem...");
+  printf ("Formatting file system...");
 
   /* Create the initial bitmap and reserve sectors for the
      free map and root directory inodes. */
-  free_map = bitmap_create (disk_size (filesys_disk));
+  free_map = bitmap_create (partition_size (filesys_partition));
   if (free_map == NULL)
-    PANIC ("bitmap creation failed--disk is too large");
+    PANIC ("bitmap creation failed--file system partition is too large");
   bitmap_mark (free_map, FREE_MAP_SECTOR);
   bitmap_mark (free_map, ROOT_DIR_SECTOR);
 
   /* Allocate free map and root dir files. */
   if (!inode_create (free_map, FREE_MAP_SECTOR, bitmap_file_size (free_map)))
-    PANIC ("free map creation failed--disk is too large");
+    PANIC ("free map creation failed--file system partition is too large");
   if (!inode_create (free_map, ROOT_DIR_SECTOR, dir_size (NUM_DIR_ENTRIES)))
     PANIC ("root directory creation failed");
 
index 9563fbb37e2926a255d4256db8b268666e0a3870..fb969f1db7509d61894b630a1b3f4915a383842d 100644 (file)
@@ -5,7 +5,7 @@
 #include "filesys/off_t.h"
 
 /* Disk used for filesystem. */
-extern struct disk *filesys_disk;
+extern struct partition *filesys_partition;
 
 /* The free map file, opened by filesys_init() and never
    closed. */
index 7756946587e26873a6cb5aa9f723eb91e39adfd4..0482f2da1dc58c99f8fa9ccaafa11829ec2e0272 100644 (file)
@@ -6,6 +6,7 @@
 #include <string.h>
 #include "filesys/file.h"
 #include "filesys/filesys.h"
+#include "devices/partition.h"
 #include "threads/mmu.h"
 #include "threads/palloc.h"
 
@@ -22,29 +23,29 @@ char *fsutil_print_file;
 /* Name of a file to delete. */
 char *fsutil_remove_file;
 
-/* List all files in the filesystem to the system console? */
+/* List all files in the file system to the system console? */
 bool fsutil_list_files;
 
-/* Dump full contents of filesystem to the system console? */
+/* Dump full contents of file system to the system console? */
 bool fsutil_dump_filesys;
 
 /* Copies from the "scratch" disk, hdc or hd1:0,
-   to a file named FILENAME in the filesystem.
+   to a file named FILENAME in the file system.
    The file will be SIZE bytes in length. */
 static void
 copy_in (const char *filename, off_t size) 
 {
-  struct disk *src;
+  struct partition *src;
   struct file *dst;
   disk_sector_t sector;
   void *buffer;
 
-  /* Open source disk. */
-  src = disk_get (1, 0);
+  /* Open scratch partition. */
+  src = partition_get (PARTITION_SCRATCH);
   if (src == NULL)
-    PANIC ("couldn't open source disk (hdc or hd1:0)");
-  if (size > (off_t) disk_size (src) * DISK_SECTOR_SIZE)
-    PANIC ("source disk is too small for %lld-byte file",
+    PANIC ("couldn't open scratch partition");
+  if (size > (off_t) partition_size (src) * DISK_SECTOR_SIZE)
+    PANIC ("scratch partition is too small for %lld-byte file",
            (unsigned long long) size);
   
   /* Create destination file. */
@@ -60,7 +61,7 @@ copy_in (const char *filename, off_t size)
   while (size > 0)
     {
       int chunk_size = size > DISK_SECTOR_SIZE ? DISK_SECTOR_SIZE : size;
-      disk_read (src, sector++, buffer);
+      partition_read (src, sector++, buffer);
       if (file_write (dst, buffer, chunk_size) != chunk_size)
         PANIC ("%s: write failed with %lld bytes unwritten",
                filename, (unsigned long long) size);
@@ -80,7 +81,7 @@ copy_out (const char *filename)
 {
   void *buffer;
   struct file *src;
-  struct disk *dst;
+  struct partition *dst;
   off_t size;
   disk_sector_t sector;
 
@@ -92,17 +93,18 @@ copy_out (const char *filename)
     PANIC ("%s: open failed", filename);
   size = file_length (src);
 
-  /* Open target disk. */
-  dst = disk_get (1, 0);
+  /* Open target partition. */
+  dst = partition_get (PARTITION_SCRATCH);
   if (dst == NULL)
-    PANIC ("couldn't open target disk (hdc or hd1:0)");
-  if (size + DISK_SECTOR_SIZE > (off_t) disk_size (dst) * DISK_SECTOR_SIZE)
-    PANIC ("target disk is too small for %lld-byte file",
+    PANIC ("couldn't open scratch partition");
+  if (size + DISK_SECTOR_SIZE
+      > (off_t) partition_size (dst) * DISK_SECTOR_SIZE)
+    PANIC ("scratch partition is too small for %lld-byte file",
            (unsigned long long) size);
   
   /* Write size to sector 0. */
   *(uint32_t *) buffer = size;
-  disk_write (dst, 0, buffer);
+  partition_write (dst, 0, buffer);
   
   /* Do copy. */
   sector = 1;
@@ -112,7 +114,7 @@ copy_out (const char *filename)
       if (file_read (src, buffer, chunk_size) != chunk_size)
         PANIC ("%s: read failed with %lld bytes unread",
                filename, (unsigned long long) size);
-      disk_write (dst, sector++, buffer);
+      partition_write (dst, sector++, buffer);
       size -= chunk_size;
     }
   palloc_free_page (buffer);
@@ -120,7 +122,7 @@ copy_out (const char *filename)
   file_close (src);
 }
 
-/* Executes the filesystem operations described by the variables
+/* Executes the file system operations described by the variables
    declared in fsutil.h. */
 void
 fsutil_run (void) 
index be1df576dd1a52343a103ee8303c8ac36964c1f8..9f7dfe80217ecb9376dec55a54542de8c19e49ec 100644 (file)
@@ -5,6 +5,7 @@
 #include <round.h>
 #include <stdio.h>
 #include "filesys/filesys.h"
+#include "devices/partition.h"
 #include "threads/malloc.h"
 
 /* On-disk inode.
@@ -77,9 +78,9 @@ inode_create (struct bitmap *free_map, disk_sector_t sector, off_t length)
   idx->start = start;
 
   /* Commit to disk. */
-  disk_write (filesys_disk, sector, idx);
+  partition_write (filesys_partition, sector, idx);
   for (i = 0; i < bytes_to_sectors (length); i++)
-    disk_write (filesys_disk, idx->start + i, zero_sector);
+    partition_write (filesys_partition, idx->start + i, zero_sector);
 
   free (idx);
   return true;
@@ -122,7 +123,7 @@ inode_open (disk_sector_t sector)
 
   /* Read from disk. */
   ASSERT (sizeof idx->data == DISK_SECTOR_SIZE);
-  disk_read (filesys_disk, sector, &idx->data);
+  partition_read (filesys_partition, sector, &idx->data);
 
   return idx;
 }
@@ -154,7 +155,7 @@ inode_close (struct inode *idx)
 static void
 deallocate_inode (const struct inode *idx)
 {
-  struct bitmap *free_map = bitmap_create (disk_size (filesys_disk));
+  struct bitmap *free_map = bitmap_create (partition_size (filesys_partition));
   if (free_map != NULL) 
     {
       bitmap_read (free_map, free_map_file);
index c17316aa59efec1951ad48eed5c8e4522a3e0381..73a2e98305ffed99b683a9a350624e44c3f78717 100644 (file)
@@ -29,6 +29,7 @@
 #endif
 #ifdef FILESYS
 #include "devices/disk.h"
+#include "devices/partition.h"
 #include "filesys/filesys.h"
 #include "filesys/fsutil.h"
 #endif
@@ -40,7 +41,7 @@ size_t ram_pages;
 uint32_t *base_page_dir;
 
 #ifdef FILESYS
-/* -f: Format the filesystem? */
+/* -f: Format the file system? */
 static bool format_filesys;
 #endif
 
@@ -108,8 +109,9 @@ main (void)
   timer_calibrate ();
 
 #ifdef FILESYS
-  /* Initialize filesystem. */
+  /* Initialize file system. */
   disk_init ();
+  partition_init ();
   filesys_init (format_filesys);
   fsutil_run ();
 #endif
@@ -267,15 +269,15 @@ argv_init (void)
           " -ul USER_MAX        Limit user memory to USER_MAX pages.\n"
 #endif
 #ifdef FILESYS
-          " -f                  Format the filesystem disk (hdb or hd0:1).\n"
+          " -f                  Format the file system disk (hdb or hd0:1).\n"
           " -ci FILENAME SIZE   Copy SIZE bytes from the scratch disk (hdc\n"
-          "                     or hd1:0) into the filesystem as FILENAME\n"
+          "                     or hd1:0) into the file system as FILENAME\n"
           " -co FILENAME        Copy FILENAME to the scratch disk, with\n"
           "                     size at start of sector 0 and data afterward\n"
           " -p FILENAME         Print the contents of FILENAME\n"
           " -r FILENAME         Delete FILENAME\n"
-          " -ls                 List the files in the filesystem\n"
-          " -D                  Dump complete filesystem contents\n"
+          " -ls                 List the files in the file system\n"
+          " -D                  Dump complete file system contents\n"
 #endif
           " -q                  Power off after doing requested actions.\n"
           " -u                  Print this help message and power off.\n"
index 11ab7c9e4d1e5f2bb6ddc1753532528ae1a65681..58fb837aec5471c90f57a78354189b3c841bec55 100644 (file)
 start:
        .code16
 
-# Disable interrupts.
-# String instructions go upward.
-
-       cli
-       cld
-
-# Set up data segments.
+# Set up segment registers.
+# Stack grows downward starting from us.
 
        sub ax, ax
-       mov es, ax
        mov ds, ax
-
-# Set up stack segment.
-# Stack grows downward starting from us.
-# We don't ever use the stack so this is strictly speaking
-# unnecessary.
-
        mov ss, ax
        mov sp, 0x7c00
        
-#### Enable A20.  Address line 20 is tied to low when the machine
-#### boots, which prevents addressing memory about 1 MB.  This code
-#### fixes it.
-       
-# Poll status register while busy.
-
-1:     in al, 0x64
-       test al, 0x2
-       jnz 1b
+# Scan floppy disks.
 
-# Send command for writing output port.
-
-       mov al, 0xd1
-       outb 0x64, al
-
-# Poll status register while busy.
-
-1:     in al, 0x64
-       test al, 0x2
-       jnz 1b
-
-# Enable A20 line.
-
-       mov al, 0xdf
-       out 0x60, al
-
-#### Get memory size, via interrupt 15h function 88h, which returns CF
-#### clear if successful, with AX = (kB of physical memory) - 1024.
-#### This only works for memory sizes <= 65 MB, which should be fine
-#### for our purposes.  We cap memory at 64 MB because that's all we
-#### prepare page tables for, below.
-       
-       mov ah, 0x88
-       int 0x15
-       jc panic
-       cli                     # BIOS might have enabled interrupts
-       add eax, 1024           # Total kB memory
-       cmp eax, 0x10000        # Cap at 64 MB
-       jbe 1f
-       mov eax, 0x10000
-1:     shr eax, 2              # Total 4 kB pages
-       mov ram_pages, eax 
+       sub dl, dl
+       sub ebx, ebx
+       mov eax, 0x7e00
+       mov es, ax
+1:     call scan_partitions
+       inc dl
+       jnc 1b
+
+# Scan hard disks.
+       mov dl, 0x80
+1:     call scan_partitions
+       inc dl
+       jnc 1b
+
+       call panic
+       .asciz "No boot partition"
+
+scan_partitions:
+       # EBX = sector number of partition table
+       # DL = drive number
+       # ES:0000 -> buffer for partition table
+       # Returns CF set if drive exists, CF clear otherwise.
+       call read_sector
+       jnc no_such_drive
+2:     cmp word ptr [es:510], 0xaa55
+       jnz no_boot_partition
        
-#### Create temporary page directory and page table and set page
-#### directory base register.
-
-# Create page directory at 64 kB and fill with zeroes.
-       mov ax, 0x1000
+       mov si, 446
+1:     mov al, [es:si+4]
+       cmp al, 0x20
+       jz found_boot_partition
+       cmp al, 0x05
+       jz found_extended_partition
+       cmp al, 0x0f
+       jz found_extended_partition
+       cmp al, 0x85
+       jz found_extended_partition
+       cmp al, 0xc5
+       jz found_extended_partition
+next_parttbl_entry:    
+       add si, 16
+       cmp si, 510
+       jb 1b
+
+no_boot_partition:     
+       clc
+       ret
+no_such_drive:
+       stc
+       ret
+
+found_extended_partition:
+       # DL = drive number.
+       # ES:SI -> partition table entry for extended partition.
+       # Recursively examine it.
+       pusha
+       mov ebx, es:[si+8]
+       mov ax, es
+       add ax, 0x20
        mov es, ax
-       sub eax, eax
-       sub edi, edi
-       mov ecx, 0x400
-       rep stosd
-
-# Add PDEs to point to PTEs for the first 64 MB of RAM.
-# Also add identical PDEs starting at LOADER_PHYS_BASE.
-# See [IA32-v3] section 3.7.6 for a description of the bits in eax.
-
-# A bug in some versions of GAS prevents us from using the straightforward
-#      mov es:[di + LOADER_PHYS_BASE / 1024 / 1024], eax
-# so we calculate the displacement in bx instead.
-
-       mov eax, 0x11007
-       mov ecx, 0x11
-       sub di, di
-       mov ebx, LOADER_PHYS_BASE
-       shr ebx, 20
-1:     mov es:[di], eax
-       mov es:[bx + di], eax 
-       add di, 4
-       add eax, 0x1000
-       loop 1b
-
-# Set up one-to-map linear to physical map for the first 64 MB of RAM.
-# See [IA32-v3] section 3.7.6 for a description of the bits in eax.
-
-       mov ax, 0x1100
+       call scan_partitions
+       popa
+       jmp next_parttbl_entry
+
+found_boot_partition:
+       mov ebx, [es:si+8]              # EBX = first sector
+       mov cx, [es:si+12]              # CX = number of sectors
+       mov ax, 0x1000                  # ES:0000 -> load address
+       mov es, ax
+1:     call read_sector
+       add ax, 0x20
        mov es, ax
-       mov eax, 0x7
-       mov cx, 0x4000
-       sub di, di
-1:     mov es:[di], eax 
-       add di, 4 
-       add eax, 0x1000
        loop 1b
 
-# Set page directory base register.
-
-       mov eax, 0x10000
-       mov cr3, eax
-       
-#### Switch to protected mode.
-
-# Then we point the GDTR to our GDT.  Protected mode requires a GDT.
-# We need a data32 prefix to ensure that all 32 bits of the GDT
-# descriptor are loaded (default is to load only 24 bits).
-
-       data32 lgdt gdtdesc
-
-# Then we turn on the following bits in CR0:
-#    PE (Protect Enable): this turns on protected mode.
-#    PG (Paging): turns on paging.
-#    WP (Write Protect): if unset, ring 0 code ignores
-#       write-protect bits in page tables (!).
-#    EM (Emulation): forces floating-point instructions to trap.
-#       We don't support floating point. 
-       
-       mov eax, cr0
-       or eax, CR0_PE + CR0_PG + CR0_WP + CR0_EM
-       mov cr0, eax
-       
-# We're now in protected mode in a 16-bit segment.  The CPU still has
-# the real-mode code segment cached in cs's segment descriptor.  We
-# need to reload cs, and the easiest way is to use a far jump.
-# Because we're not in a 32-bit segment the data32 prefix is needed to
-# jump to a 32-bit offset.
-
-       data32 ljmp SEL_KCSEG, 1f + LOADER_PHYS_BASE
-
-# We're now in protected mode in a 32-bit segment.
+       ljmp 0x1000, 0
 
-       .code32
-
-# Reload all the other segment registers and the stack pointer to
-# point into our new GDT.
-
-1:     mov ax, SEL_KDSEG
-       mov ds, ax              
-       mov es, ax              
-       mov fs, ax              
-       mov gs, ax              
-       mov ss, ax
-       mov esp, LOADER_PHYS_BASE + 0x30000
-
-#### Load kernel starting at physical address LOADER_KERN_BASE by
-#### frobbing the IDE controller directly.
-
-       mov ebx, 1
-       mov edi, LOADER_KERN_BASE + LOADER_PHYS_BASE
+       # ebx: sector number
+       # dl: drive number
+       # es:0000: destination buffer
+       # returns error flag in CF
 read_sector:
+       pusha
+       or dl, dl                       # Floppy drives: DL < 0
+       js read_floppy_sector
 
-# Poll status register while controller busy.
-
-       mov edx, 0x1f7
-1:     in al, dx
-       test al, 0x80
-       jnz 1b
-
-# Read a single sector.
-
-       mov edx, 0x1f2
-       mov al, 1 
-       out dx, al
-
-# Sector number to write in low 28 bits.
-# LBA mode, device 0 in top 4 bits.
-
-       mov eax, ebx
-       and eax, 0x0fffffff
-       or eax, 0xe0000000
-
-# Dump eax to ports 0x1f3...0x1f6.
-
-       mov ecx, 4
-1:     inc dx
-       out dx, al
-       shr eax, 8
-       loop 1b
-
-# READ command to command register.
-
-       inc dx
-       mov al, 0x20
-       out dx, al
-
-# Poll status register while controller busy.
-
-1:     in al, dx
-       test al, 0x80
-       jnz 1b
-
-# Poll status register until data ready.
-
-1:     in al, dx
-       test al, 0x08
-       jz 1b
-
-# Transfer sector.
-
-       mov ecx, 256
-       mov edx, 0x1f0
-       rep insw
-
-# Next sector.
-
-       inc ebx
-       cmp ebx, KERNEL_LOAD_PAGES*8 + 1
-       jnz read_sector
-
-#### Jump to kernel entry point.
-
-       mov eax, LOADER_PHYS_BASE + LOADER_KERN_BASE
-       call eax
-       jmp panic
-
-#### GDT
-
-gdt:
-       .quad 0x0000000000000000        # null seg
-       .quad 0x00cf9a000000ffff        # code seg
-       .quad 0x00cf92000000ffff        # data seg
+read_harddrv_sector:
+       sub eax, eax
+       push eax                        # LBA sector number [32:63]
+       push ebx                        # LBA sector number [0:31]
+       mov ax, es
+       shl eax, 4
+       push eax                        # Buffer linear address
+       push 1                          # Transfer one sector
+       push 16                         # Packet size
+       mov ah, 0x42                    # Extended read
+       mov si, sp                      # DS:SI -> packet
+       int 0x13                        # Error code in CF
+success:       
+       popa
+       ret                             # Error code in CF
+
+read_floppy_sector:
+       #define HEADS 2
+       #define SECTORS 36
+
+       # In: BX = LBA sector number, DL = drive.
+       # Out: BL = drive, DX = cylinder, AL = head, AH = sector.
+       sub ax, ax
+       xchg dx, bx                     # DX = LBA sector number, BL = drive
+       mov cx, HEADS * SECTORS
+       div cx                          # AX = cyl, DX = hd + (sec-1) * SECTORS
+       xchg ax, dx                     # DX = cyl, AX = hd + (sec-1) * SECTORS
+       mov cl, SECTORS
+       div cl                          # AL = head, AH = sector - 1
+       inc ah
+
+       # Read sector.
+       mov ch, dl                      # CH = cylinder
+       mov cl, ah                      # CL = sector
+       mov dh, al                      # DH = head
+       mov dl, bl                      # DL = drive
+       mov ax, 0x0201                  # AH = function, AL = sectors to read
+       sub bx, bx                      # ES:BX -> buffer
+       pusha
+       int 0x13
+       popa
+       jnc success
+
+       # Reset floppy drive motor, try again.
+       sub ah, ah
+       int 0x13
+       popa
+       jmp read_sector
        
-gdtdesc:
-       .word   0x17                    # sizeof (gdt) - 1
-       .long   gdt + LOADER_PHYS_BASE  # address gdt
-
-#### Fatal error.
-#### Print panicmsg (with help from the BIOS) and spin.
-
-panic:  .code16                        # We only panic in real mode.
-       mov si, offset panicmsg
-       mov ah, 0xe
-       sub bh, bh
-1:     lodsb
-       test al, al
-2:     jz 2b                   # Spin.
-       int 0x10
-       jmp 1b
-
-panicmsg:
-       .ascii "Panic!"
-       .byte 0
-
-#### Memory size in 4 kB pages.
-       .org LOADER_RAM_PAGES - LOADER_BASE
-ram_pages:
-       .long 0
-
-#### Command-line arguments inserted by another utility.
-#### The loader doesn't use these, but we note their
-#### location here for easy reference.
-       .org LOADER_CMD_LINE - LOADER_BASE
-cmd_line:
-       .fill 0x80, 1, 0
+#### The partition table goes here.
+       .org 446
+part_tbl:      
 
 #### Boot-sector signature for BIOS inspection.
-       .org LOADER_BIOS_SIG - LOADER_BASE
+       .org 510
        .word 0xaa55
index a86e8de282973d2a6f9f5d209b2e52a34618d045..47daaad505b58fe60792de0c56c44c26d4ec308f 100755 (executable)
@@ -5,15 +5,18 @@ use POSIX;
 
 our ($mem) = 4;
 our ($serial_out) = 1;
-our (@disks) = ("os.dsk", "fs.dsk", "scratch.dsk", "swap.dsk");
+our ($disk) = "pintos.dsk";
+our (@part_files) = ("boot.part", "filesys.part", "scratch.part", "swap.part");
 our ($sim);
 our ($debug);
 our ($vga);
 our ($jitter, $realtime);
 
+our (%role2type) = (0 => 0x20, 1 => 0x21, 2 => 0x22, 3 => 0x23);
+
 use Getopt::Long qw(:config require_order bundling);
 unshift (@ARGV, split (' ', $ENV{PINTOSOPTS}))
-    if defined $ENV{PINTOSOPTS};
+  if defined $ENV{PINTOSOPTS};
 GetOptions ("sim=s" => sub { set_sim (@_) },
            "bochs" => sub { set_sim ("bochs") },
            "qemu" => sub { set_sim ("qemu") },
@@ -23,24 +26,27 @@ GetOptions ("sim=s" => sub { set_sim (@_) },
            "no-debug" => sub { set_debug ("no-debug") },
            "monitor" => sub { set_debug ("monitor") },
            "gdb" => sub { set_debug ("gdb") },
-           
-           "run|get|put|make-disk" => \&cmd_option,
-           
+
+           "run|get|put|assemble|disassemble" => \&cmd_option,
+
            "m|memory=i" => \$mem,
            "j|jitter=i" => sub { set_jitter (@_) },
            "r|realtime" => sub { set_realtime () },
-           
+
            "v|no-vga" => sub { set_vga ('none'); },
            "s|no-serial" => sub { $serial_out = 0; },
            "t|terminal" => sub { set_vga ('terminal'); },
-           
+
            "h|help" => sub { usage (0); },
 
-           "0|os-disk|disk-0|hda=s" => \$disks[0],
-           "1|fs-disk|disk-1|hdb=s" => \$disks[1],
-           "2|scratch-disk|disk-2|hdc=s" => \$disks[2],
-           "3|swap-disk|disk-3|hdd=s" => \$disks[3])
-    or exit 1;
+           "disk=s" => \$disk,
+
+           "boot-partition=s" => \$part_files[0],
+           "fs-partition|filesys-partition=s" => \$part_files[1],
+           "scratch-partition=s" => \$part_files[2],
+           "swap-partition=s" => \$part_files[3]
+          )
+  or exit 1;
 
 $sim = "bochs" if !defined $sim;
 $debug = "no-debug" if !defined $debug;
@@ -49,14 +55,14 @@ $vga = "window" if !defined $vga;
 sub set_sim {
     my ($new_sim) = @_;
     die "--$new_sim conflicts with --$sim\n"
-       if defined ($sim) && $sim ne $new_sim;
+      if defined ($sim) && $sim ne $new_sim;
     $sim = $new_sim;
 }
 
 sub set_debug {
     my ($new_debug) = @_;
     die "--$new_debug conflicts with --$debug\n"
-       if defined ($debug) && $debug ne $new_debug;
+      if defined ($debug) && $debug ne $new_debug;
     $debug = $new_debug;
 }
 
@@ -72,7 +78,7 @@ sub set_jitter {
     my ($new_jitter) = @_;
     die "--realtime conflicts with --jitter\n" if defined $realtime;
     die "different --jitter already defined\n"
-       if defined $jitter && $jitter != $new_jitter;
+      if defined $jitter && $jitter != $new_jitter;
     $jitter = $new_jitter;
 }
 
@@ -87,17 +93,10 @@ sub cmd_option {
 }
 
 die "no command specified; use --help for help\n"
-    if @ARGV < 1;
+  if @ARGV < 1;
 my ($cmd) = shift @ARGV;
 if ($cmd eq 'run') {
     run_vm (@ARGV);
-} elsif ($cmd eq 'make-disk') {
-    usage () if @ARGV != 2;
-    my ($file, $mb) = @ARGV;
-    usage () if $mb !~ /^\d+(\.\d+)?|\.\d+$/;
-    die "$file: already exists\n" if -e $file;
-
-    create_disk ($file, int ($mb * 1008));
 } elsif ($cmd eq 'put') {
     # Take a -f option to combine formatting with putting.
     my ($format) = 0;
@@ -110,16 +109,32 @@ if ($cmd eq 'run') {
     my ($hostfn, $guestfn) = @ARGV;
     $guestfn = $hostfn if !defined $guestfn;
 
+    # Disassemble.
+    @part_files = ("part0.tmp", "part1.tmp", "part2.tmp", "part3.tmp");
+    disassemble ($part_files[0], $part_files[1], undef, $part_files[3]);
+    die "missing file system partition\n" if ! -e $part_files[1];
+
     # Create scratch disk from file.
-    die "$hostfn: $!\n" if ! -e $hostfn;
-    my ($size) = -s _;
-    if ($size) { 
-       copy_pad ($hostfn, "scratch.dsk", 512);
-    } else {
-       open (SCRATCH, ">scratch.dsk") or die "scratch.dsk: create: $!\n";
-       syswrite (SCRATCH, "\0" x 512);
-       close (SCRATCH);
+    open (FILE, "<$hostfn") or die "$hostfn: open: $!\n";
+    my ($scratchfn) = $part_files[1];
+    open (SCRATCH, ">$scratchfn") or die "$scratchfn: create: $!\n";
+    my ($size) = 0;
+    for (;;) {
+       my ($buf);
+       my ($amt) = sysread (FILE, $buf, 65536);
+       die "$hostfn: read: $!\n" if $amt < 0;
+       last if $amt == 0;
+       syswrite (FILE, $buf, $amt) == $amt or die "$scratchfn: write: $!\n";
+       $size += $amt;
     }
+    my ($zeros) = 512 - $size % 512;
+    syswrite (FILE, "\0" x $zeros) == $zeros or die "$scratchfn: write: $!\n";
+    close (SCRATCH);
+    close (FILE);
+
+    # Reassemble.
+    assemble ();
+    unlink (@part_files);
 
     # Do copy.
     my (@cmd) = ("-ci", $guestfn, $size, "-q");
@@ -131,31 +146,45 @@ if ($cmd eq 'run') {
     $hostfn = $guestfn if !defined $hostfn;
     die "$hostfn: already exists\n" if -e $hostfn;
 
-    # Create scratch disk big enough for any file in the filesystem
+    # Disassemble.
+    @part_files = ("part0.tmp", "part1.tmp", undef, "part3.tmp");
+    disassemble (@part_files);
+
+    # Create scratch disk big enough for any file in the file system
     # (modulo sparse files).
-    die "$disks[1]: $!\n" if ! -e $disks[1];
+    die "missing file system partition\n" if ! -e $part_files[1];
     my ($fs_size) = -s _;
-    my ($scratch_size) = -s $disks[2];
-    $scratch_size = 0 if !defined $scratch_size;
-    create_disk ($disks[2], $fs_size / 1024 + 16)
-       if $scratch_size < $fs_size + 16384;
+    my ($approx_mb) = (16 * 63 * 512) * 2;
+    $part_files[2] = sprintf ("%d",
+                             int (($fs_size + $approx_mb - 1) / $approx_mb));
+    assemble (@part_files);
 
     # Do copy.
     run_vm ("-co", $guestfn, "-q");
 
+    # FIXME: we could just read the parttbl, then copy directly.
+    # Disassemble.
+    my ($scratchfn) = "part2.tmp";
+    disassemble (undef, undef, $scratchfn, undef);
+
     # Read out scratch disk.
-    print "copying $guestfn from $disks[2] to $hostfn...\n";
-    open (SRC, "<$disks[2]") or die "$disks[2]: open: $!\n";
+    print "copying $guestfn from $scratchfn to $hostfn...\n";
+    open (SRC, "<$scratchfn") or die "$scratchfn: open: $!\n";
     open (DST, ">$hostfn") or die "$hostfn: create: $!\n";
     my ($input);
-    read (SRC, $input, 512) == 512 or die "$disks[2]: read error\n";
+    read (SRC, $input, 512) == 512 or die "$scratchfn: read error\n";
     my ($size) = unpack ("V", $input);
-    $size != 0xffffffff or die "$guestfn: too big for $disks[2]?";
+    $size != 0xffffffff or die "$guestfn: too big for $scratchfn?";
     my ($src);
-    read (SRC, $src, $size) == $size or die "$disks[2]: read error\n";
+    read (SRC, $src, $size) == $size or die "$scratchfn: read error\n";
     print DST $src or die "$hostfn: write error\n";
     close (DST);
     close (SRC);
+} elsif ($cmd eq 'assemble') {
+    die "$part_files[0] not found ($!), but a boot partition is required\n"
+      if ! -e $part_files[0];
+    do { $_ = undef if ! -e $_ } foreach @part_files[1...3];
+    assemble ();
 } elsif ($cmd eq 'help') {
     usage (0);
 } else {
@@ -170,9 +199,10 @@ sub usage {
     print "Usage: pintos [OPTION...] COMMAND [ARG...]\n";
     print "where COMMAND is one of the following:\n";
     print "  run [CMDLINE...]        run a VM in the simulator\n";
-    print "  make-disk FILE.DSK SIZE create FILE.DSK as empty SIZE MB disk\n";
     print "  put HOSTFN [GUESTFN]    copy HOSTFN into VM (as GUESTFN)\n";
     print "  get GUESTFN [HOSTFN]    copy GUESTFN out of VM (to HOSTFN)\n";
+    print "  assemble                assemble a VM disk from partitions\n";
+    print "  disassemble             disassemble a VM disk into partitions\n";
     print "  help                    print this help message and exit\n";
     print "Simulator options:\n";
     print "  --bochs          (default) Use Bochs as simulator\n";
@@ -187,14 +217,16 @@ sub usage {
     print "  -s, --no-serial  No serial output\n";
     print "  -t, --terminal   Display VGA in terminal (Bochs only)\n";
     print "VM options:\n";
+    print "  -d, --disk=DISK  File holding VM's disk (default: pintos.dsk)\n";
     print "  -j SEED          Randomize timer interrupts (Bochs only)\n";
     print "  -r, --realtime   Use realistic, but not reproducible, timings\n";
     print "  -m, --mem=MB     Run VM with MB megabytes of physical memory\n";
-    print "Disk options:\n";
-    print "  --os-disk=DISK    Set OS disk file (default: os.dsk)\n";
-    print "  --fs-disk=DISK    Set FS disk file (default: fs.dsk)\n";
-    print "  --scratch-disk=DISK   Set scratch disk (default: scratch.dsk)\n";
-    print "  --swap-disk=DISK  Set swap disk file (default: swap.dsk)\n";
+    print "Assemble/disassemble partitions (default names in parentheses):\n";
+    print "(SOURCE is a file or a size in MB, for a blank partition.)\n";
+    print "  --boot=FILE      Boot partition (boot.part)\n";
+    print "  --filesys=SOURCE File system partition (filesys.part)\n";
+    print "  --scratch=SOURCE Scratch partition (scratch.part)\n";
+    print "  --swap=SOURCE    Swap partition (swap.part)\n";
     exit $exitcode;
 }
 
@@ -211,24 +243,21 @@ sub create_disk {
 sub run_vm {
     my (@args) = @_;
 
-    our (@disks);
-
-    die "$disks[0]: can't find OS disk\n" if ! -e $disks[0];
-    for my $i (1...3) {
-       undef $disks[$i] if ! -e $disks[$i];
-    }
-
-    if (my ($project) = `pwd` =~ /\b(threads|userprog|vm|filesys)\b/) {
-       if ((grep ($project eq $_, qw (userprog vm filesys)))
-           && !defined ($disks[1])) {
-           print STDERR "warning: it looks like you're running the $project ";
-           print STDERR "project, but no file system disk is present\n";
-       }
-       if ($project eq 'vm' && !defined $disks[3]) {
-           print STDERR "warning: it looks like you're running the $project ";
-           print STDERR "project, but no swap disk is present\n";
-       }
-    }
+    our ($disk);
+    die "$disk: can't find OS disk\n" if ! -e $disk;
+
+    # FIXME
+    #     if (my ($project) = `pwd` =~ /\b(threads|userprog|vm|filesys)\b/) {
+    #  if ((grep ($project eq $_, qw (userprog vm filesys)))
+    #      && !defined ($disks[1])) {
+    #      print STDERR "warning: it looks like you're running the $project ";
+    #      print STDERR "project, but no file system disk is present\n";
+    #  }
+    #  if ($project eq 'vm' && !defined $disks[3]) {
+    #      print STDERR "warning: it looks like you're running the $project ";
+    #      print STDERR "project, but no swap disk is present\n";
+    #  }
+    #     }
 
     write_cmd_line ($disks[0], @args);
 
@@ -247,12 +276,12 @@ sub run_vm {
 
        open (BOCHSRC, ">bochsrc.txt") or die "bochsrc.txt: create: $!\n";
        print BOCHSRC "romimage: file=$bochsshare/BIOS-bochs-latest, "
-           . "address=0xf0000\n";
+         . "address=0xf0000\n";
        print BOCHSRC "vgaromimage: $bochsshare/VGABIOS-lgpl-latest\n";
        print BOCHSRC bochs_disk_line ("ata0-master", $disks[0]);
        print BOCHSRC bochs_disk_line ("ata0-slave", $disks[1]);
        print BOCHSRC "ata1: enabled=1, ioaddr1=0x170, ioaddr2=0x370, irq=15\n"
-           if defined ($disks[2]) || defined ($disks[3]);
+         if defined ($disks[2]) || defined ($disks[3]);
        print BOCHSRC bochs_disk_line ("ata1-master", $disks[2]);
        print BOCHSRC bochs_disk_line ("ata1-slave", $disks[3]);
        print BOCHSRC "boot: c\n";
@@ -266,9 +295,9 @@ sub run_vm {
        print BOCHSRC "log: bochsout.txt\n";
        if ($vga ne 'terminal') {
            print BOCHSRC "com1: enabled=1, dev=/dev/stdout\n"
-               if $serial_out;
+             if $serial_out;
            print BOCHSRC "display_library: nogui\n"
-               if $vga eq 'none';
+             if $vga eq 'none';
        } else {
            print BOCHSRC "display_library: term\n";
        }
@@ -289,9 +318,9 @@ sub run_vm {
        }
     } elsif ($sim eq 'qemu') {
        print "warning: qemu doesn't support --terminal\n"
-           if $vga eq 'terminal';
+         if $vga eq 'terminal';
        print "warning: qemu doesn't support jitter\n"
-           if defined $jitter;
+         if defined $jitter;
        my (@cmd) = ('qemu');
        push (@cmd, '-hda', $disks[0]) if defined $disks[0];
        push (@cmd, '-hdb', $disks[1]) if defined $disks[1];
@@ -305,13 +334,13 @@ sub run_vm {
        run_command (@cmd);
     } elsif ($sim eq 'gsx') {
        print "warning: VMware GSX Server doesn't support --$debug\n"
-           if $debug ne 'no-debug';
+         if $debug ne 'no-debug';
        print "warning: VMware GSX Server doesn't support --no-vga\n"
-           if $vga eq 'none';
+         if $vga eq 'none';
        print "warning: VMware GSX Server doesn't support --terminal\n"
-           if $vga eq 'terminal';
+         if $vga eq 'terminal';
        print "warning: VMware GSX Server doesn't support jitter\n"
-           if defined $jitter;
+         if defined $jitter;
 
        open (VMX, ">pintos.vmx") or die "pintos.vmx: create: $!\n";
        chmod 0777 & ~umask, "pintos.vmx";
@@ -422,9 +451,9 @@ sub bochs_disk_line {
     my ($device, $file) = @_;
     return "" if !defined $file;
     my (%geom) = disk_geometry ($file);
-    return "$device: type=disk, path=$file, mode=flat, "
-       . "cylinders=$geom{C}, heads=$geom{H}, spt=$geom{S}, "
-       . "translation=none\n";
+    return ("$device: type=disk, path=$file, mode=flat, "
+           . "cylinders=$geom{C}, heads=$geom{H}, spt=$geom{S}, "
+           . "translation=none\n");
 }
 
 sub disk_geometry {
@@ -440,3 +469,189 @@ sub disk_geometry {
            H => 16,
            S => 63);
 }
+\f
+sub assemble {
+    my ($files) = @_;
+
+    my (@parts);
+
+    my (@part_names) = ("boot", "file system", "scratch", "swap");
+    my ($next_start) = 1;
+    for my $i (0..3) {
+       my (%part);
+
+       my ($name) = $part_names[$i];
+       my ($file) = $files[$i];
+       my ($size);
+       if (-e $file) {
+           $size = -s _;
+       } else {
+           if (($mb) = $file =~ /^\d+(\.\d+)?|\.\d+$/) {
+               $size = $mb * 63 * 16 * 512;
+               undef $file;
+           } else {
+               die ("$file: stat: $!\n");
+           }
+       }
+
+       die "$name: not a multiple of 512 bytes in size\n"
+         if $size % 512;
+       my ($sector_cnt) = $size / 512;
+       my ($start) = $next_start;
+       $next_start += $sector_cnt;
+
+       push (@parts,
+             {ROLE => $i,
+              FILE => $file,
+              START = $start,
+              SECTORS => $sector_cnt});
+    }
+    die "Sorry, disk size (", ($sector_cnt * 512) / 1024 / 1024, " MB) "
+      . "exceeds limit (approx. 503 MB)\n"
+       if $sector_cnt > 1023 * 63 * 16;
+
+    my ($part_tbl) = "\0" x 446;
+    for my $p (@parts) {
+       my ($bootable) = $p->{ROLE} == 0 ? 0x80 : 0x00;
+       my (@start_chs) = linear_to_chs ($p->{START});
+       my ($type) = $role2type{$p->{ROLE}};
+       my (@end_chs) = linear_to_chs ($p->{START} + $p->{SECTORS} - 1);
+
+       my ($part_tbl_entry) = pack ("C CCC C CCC V V",
+                                    $bootable,
+                                    pack_chs (@start_chs),
+                                    $type,
+                                    pack_chs (@end_chs),
+                                    $p->{START}, $p->{SECTORS});
+       length ($part_tbl_entry) == 16 or die;
+       $part_tbl .= $part_tbl_entry;
+    }
+    $part_tbl .= "\0" x 16 while length ($part_tbl) < 510;
+    $part_tbl .= pack ("v", 0xaa55);
+    length ($part_tbl) == 512 or die;
+
+    our ($disk);
+    open (DISK, ">$disk") or die "$disk: create: $!\n";
+    syswrite (DISK, $part_tbl) == 512 or die "$disk: write: $!\n";
+
+    for my $p (@parts) {
+       $from_file = defined ($p->{FILE});
+       open (PART, "<$p->{FILE}") or die "$p->{FILE}: open: $!\n"
+         if $from_file;
+
+       my ($buf);
+       for (my ($ofs) = 0; $ofs < $p->{SECTORS}; $ofs += length ($buf)) {
+           my ($bytes_left) = ($p->{SECTORS} - $ofs) * 512;
+           my ($read_bytes) = $bytes_left > 16384 ? 16384 : $bytes_left;
+
+           if ($from_file) {
+               my ($ret) = sysread (PART, $buf, $read_bytes);
+               die "$p->{FILE}: read: $!\n" if $ret < 0;
+               die "$p->{FILE}: unexpected end of file\n"
+                 if $ret != $read_bytes;
+           } else {
+               $buf = "\0" x $read_bytes;
+           }
+
+           syswrite (DISK, $buf) == length ($buf)
+             or die "$p->{FILE}: write: $!\n"
+       }
+
+       close (PART) if $from_file;
+    }
+
+    close (DISK) or die "$disk: close: $!\n";
+}
+
+sub linear_to_chs {
+    my ($linear) = @_;
+
+    # We maintain these as constants.
+    my ($heads) = 16;
+    my ($sectors) = 63;
+    my ($sectors_per_cylinder) = $heads * sectors;
+
+    # Calculate C, H, S.
+    my ($c) = int ($linear / $sectors_per_cylinder);
+    my ($cylinder_ofs) = $linear % $sectors_per_cylinder;
+    my ($h) = int ($cylinder_ofs / $sectors);
+    my ($s) = $cylinder_ofs % $sectors;
+
+    die if $c > 1023 || $h > 15 || $s > 63;
+
+    return ($c, $h, $s);
+}
+
+sub pack_chs {
+    my ($c, $h, $s) = @_;
+    die if $c > 1023 || $h > 15 || $s > 63;
+
+    my ($pc, $ph, $ps) = ($h, $s | (($c & 0x300) >> 2), $c & 0xff);
+    die if $pc > 255 || $ph > 255 || ps > 255;
+
+    return ($pc, $ph, $ps);
+}
+
+sub read_part_tbl {
+    my ($part_tbl);
+    open (DISK, "<$disk") or die "$disk: open: $!\n";
+    sysread (DISK, $part_tbl, 512) == 512 or die "$disk: read: $!\n";
+    close (DISK);
+
+    my ($loader, @partitions, $signature);
+    ($loader, @partitions[0..3], $signature)
+      = unpack ("a446 (a16)4 v", $part_tbl);
+
+    die "$disk: invalid partition table signature\n" if $signature != 0xaa55;
+
+    my (@parts);
+    for my $partition (@partitions) {
+       my ($bootable, @start_chs_packed, $type, @end_chs_packed,
+           $start, $sector_cnt);
+       ($bootable, $start_chs_packed[0...2], $type, @end_chs_packed[0...2],
+        $start, $sector_cnt)
+         = unpack ("C CCC C CCC V V", $partition) or die;
+
+       my ($role) = (reverse (%role2type{$type})){$type};
+       next if !defined ($role);
+
+       push (@parts,
+             {ROLE => $1,
+              START => $start,
+              SECTORS => $sector_cnt});
+    }
+
+    return @parts;
+}
+
+sub disassemble {
+    my ($files) = @_;
+
+    open (DISK, "<$disk") or die "$disk: open: $!\n";
+    for my $p (read_part_tbl ()) {
+       use Fcntl 'SEEK_CUR';
+
+       my ($file) = $files[$p->{ROLE}];
+       next if !defined $file;
+
+       open (PART, ">$file") or die "$file: create: $!\n";
+       sysseek (DISK, $p->{START} * 512, SEEK_CUR) or die "$disk: seek: $!\n";
+
+       my ($buf);
+       for (my ($ofs) = 0; $ofs < $p->{SECTORS}; $ofs += length ($buf)) {
+           my ($bytes_left) = ($p->{SECTORS} - $ofs) * 512;
+           my ($read_bytes) = $bytes_left > 16384 ? 16384 : $bytes_left;
+
+           my ($ret) = sysread (DISK, $buf, $read_bytes);
+           die "$p->{FILE}: read: $!\n" if $ret < 0;
+           die "$p->{FILE}: unexpected end of file\n"
+             if $ret != $read_bytes;
+
+           syswrite (PART, $buf) == length ($buf)
+             or die "$p->{FILE}: write: $!\n";
+       }
+
+       close (PART) or die "$file: close: $!\n";
+    }
+    close (DISK);
+}