From fba4443410241dd95c25a0fd7c5f8c0d8ff30ada Mon Sep 17 00:00:00 2001 From: Ben Pfaff Date: Sat, 29 Jan 2005 18:23:34 +0000 Subject: [PATCH] Start work on partition support. (Doesn't build.) --- src/Makefile.build | 1 + src/devices/disk.c | 19 +- src/devices/disk.h | 1 + src/devices/partition.c | 166 +++++++++++++++++ src/devices/partition.h | 23 +++ src/filesys/file.c | 7 +- src/filesys/filesys.c | 45 ++--- src/filesys/filesys.h | 2 +- src/filesys/fsutil.c | 40 +++-- src/filesys/inode.c | 9 +- src/threads/init.c | 14 +- src/threads/loader.S | 381 ++++++++++++++-------------------------- src/utils/pintos | 373 ++++++++++++++++++++++++++++++--------- 13 files changed, 689 insertions(+), 392 deletions(-) create mode 100644 src/devices/partition.c create mode 100644 src/devices/partition.h diff --git a/src/Makefile.build b/src/Makefile.build index d4c6d65..1564198 100644 --- a/src/Makefile.build +++ b/src/Makefile.build @@ -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. diff --git a/src/devices/disk.c b/src/devices/disk.c index 971f3af..5320d2e 100644 --- a/src/devices/disk.c +++ b/src/devices/disk.c @@ -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 \""); diff --git a/src/devices/disk.h b/src/devices/disk.h index 3bcbb9a..affbe32 100644 --- a/src/devices/disk.h +++ b/src/devices/disk.h @@ -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 index 0000000..796e816 --- /dev/null +++ b/src/devices/partition.c @@ -0,0 +1,166 @@ +#include "devices/partition.h" +#include +#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); +} + +#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 index 0000000..e86da89 --- /dev/null +++ b/src/devices/partition.h @@ -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 */ diff --git a/src/filesys/file.c b/src/filesys/file.c index 27e907f..17001ea 100644 --- a/src/filesys/file.c +++ b/src/filesys/file.c @@ -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; diff --git a/src/filesys/filesys.c b/src/filesys/filesys.c index 7989890..847bfb8 100644 --- a/src/filesys/filesys.c +++ b/src/filesys/filesys.c @@ -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. @@ -100,8 +101,8 @@ /* 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"); } -/* 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"); diff --git a/src/filesys/filesys.h b/src/filesys/filesys.h index 9563fbb..fb969f1 100644 --- a/src/filesys/filesys.h +++ b/src/filesys/filesys.h @@ -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. */ diff --git a/src/filesys/fsutil.c b/src/filesys/fsutil.c index 7756946..0482f2d 100644 --- a/src/filesys/fsutil.c +++ b/src/filesys/fsutil.c @@ -6,6 +6,7 @@ #include #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) diff --git a/src/filesys/inode.c b/src/filesys/inode.c index be1df57..9f7dfe8 100644 --- a/src/filesys/inode.c +++ b/src/filesys/inode.c @@ -5,6 +5,7 @@ #include #include #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); diff --git a/src/threads/init.c b/src/threads/init.c index c17316a..73a2e98 100644 --- a/src/threads/init.c +++ b/src/threads/init.c @@ -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" diff --git a/src/threads/loader.S b/src/threads/loader.S index 11ab7c9..58fb837 100644 --- a/src/threads/loader.S +++ b/src/threads/loader.S @@ -64,271 +64,154 @@ 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 diff --git a/src/utils/pintos b/src/utils/pintos index a86e8de..47daaad 100755 --- a/src/utils/pintos +++ b/src/utils/pintos @@ -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); } + +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); +} -- 2.30.2