Implement a proper block layer with partition support.
[pintos-anon] / src / devices / partition.c
1 #include "devices/partition.h"
2 #include <packed.h>
3 #include <stdlib.h>
4 #include <string.h>
5 #include <stdio.h>
6 #include "devices/block.h"
7 #include "threads/malloc.h"
8
9 /* A partition of a block device. */
10 struct partition
11   {
12     struct block *block;                /* Underlying block device. */
13     block_sector_t start;               /* First sector within device. */
14   };
15
16 static struct block_operations partition_operations;
17
18 static void read_partition_table (struct block *, block_sector_t sector,
19                                   block_sector_t primary_extended_sector,
20                                   int *part_nr);
21 static void found_partition (struct block *, uint8_t type,
22                              block_sector_t start, block_sector_t size,
23                              int part_nr);
24 static const char *partition_type_name (uint8_t);
25
26 /* Scans BLOCK for partitions of interest to Pintos. */
27 void
28 partition_scan (struct block *block)
29 {
30   int part_nr = 0;
31   read_partition_table (block, 0, 0, &part_nr);
32   if (part_nr == 0)
33     printf ("%s: Device contains no partitions\n", block_name (block));
34 }
35
36 /* Reads the partition table in the given SECTOR of BLOCK and
37    scans it for partitions of interest to Pintos.
38
39    If SECTOR is 0, so that this is the top-level partition table
40    on BLOCK, then PRIMARY_EXTENDED_SECTOR is not meaningful;
41    otherwise, it should designate the sector of the top-level
42    extended partition table that was traversed to arrive at
43    SECTOR, for use in finding logical partitions (see the large
44    comment below).
45
46    PART_NR points to the number of non-empty primary or logical
47    partitions already encountered on BLOCK.  It is incremented as
48    partitions are found. */
49 static void
50 read_partition_table (struct block *block, block_sector_t sector,
51                       block_sector_t primary_extended_sector,
52                       int *part_nr)
53 {
54   /* Format of a partition table entry.  See [Partitions]. */
55   struct partition_table_entry
56     {
57       uint8_t bootable;         /* 0x00=not bootable, 0x80=bootable. */
58       uint8_t start_chs[3];     /* Encoded starting cylinder, head, sector. */
59       uint8_t type;             /* Partition type (see partition_type_name). */
60       uint8_t end_chs[3];       /* Encoded ending cylinder, head, sector. */
61       uint32_t offset;          /* Start sector offset from partition table. */
62       uint32_t size;            /* Number of sectors. */
63     }
64   PACKED;
65
66   /* Partition table sector. */
67   struct partition_table
68     {
69       uint8_t loader[446];      /* Loader, in top-level partition table. */
70       struct partition_table_entry partitions[4];       /* Table entries. */
71       uint16_t signature;       /* Should be 0xaa55. */
72     }
73   PACKED;
74
75   struct partition_table *pt;
76   size_t i;
77
78   /* Check SECTOR validity. */
79   if (sector >= block_size (block))
80     {
81       printf ("%s: Partition table at sector %"PRDSNu" past end of device.\n",
82               block_name (block), sector);
83       return;
84     }
85
86   /* Read sector. */
87   ASSERT (sizeof *pt == BLOCK_SECTOR_SIZE);
88   pt = malloc (sizeof *pt);
89   if (pt == NULL)
90     PANIC ("Failed to allocate memory for partition table.");
91   block_read (block, 0, pt);
92
93   /* Check signature. */
94   if (pt->signature != 0xaa55)
95     {
96       if (primary_extended_sector == 0)
97         printf ("%s: Invalid partition table signature\n", block_name (block));
98       else
99         printf ("%s: Invalid extended partition table in sector %"PRDSNu"\n",
100                 block_name (block), sector);
101       free (pt);
102       return;
103     }
104
105   /* Parse partitions. */
106   for (i = 0; i < sizeof pt->partitions / sizeof *pt->partitions; i++)
107     {
108       struct partition_table_entry *e = &pt->partitions[i];
109
110       if (e->size == 0 || e->type == 0)
111         {
112           /* Ignore empty partition. */
113         }
114       else if (e->type == 0x05       /* Extended partition. */
115                || e->type == 0x0f    /* Windows 98 extended partition. */
116                || e->type == 0x85    /* Linux extended partition. */
117                || e->type == 0xc5)   /* DR-DOS extended partition. */
118         {
119           printf ("%s: Extended partition in sector %"PRDSNu"\n",
120                   block_name (block), sector);
121
122           /* The interpretation of the offset field for extended
123              partitions is bizarre.  When the extended partition
124              table entry is in the master boot record, that is,
125              the device's primary partition table in sector 0, then
126              the offset is an absolute sector number.  Otherwise,
127              no matter how deep the partition table we're reading
128              is nested, the offset is relative to the start of
129              the extended partition that the MBR points to. */
130           if (sector == 0)
131             read_partition_table (block, e->offset, e->offset, part_nr);
132           else
133             read_partition_table (block, e->offset + primary_extended_sector,
134                                   primary_extended_sector, part_nr);
135         }
136       else
137         {
138           ++*part_nr;
139
140           found_partition (block, e->type, e->offset + sector,
141                            e->size, *part_nr);
142         }
143     }
144
145   free (pt);
146 }
147
148 /* We have found a primary or logical partition of the given TYPE
149    on BLOCK, starting at sector START and continuing for SIZE
150    sectors, which we are giving the partition number PART_NR.
151    Check whether this is a partition of interest to Pintos, and
152    if so then add it to the proper element of partitions[]. */
153 static void
154 found_partition (struct block *block, uint8_t part_type,
155                  block_sector_t start, block_sector_t size,
156                  int part_nr)
157 {
158   if (start >= block_size (block))
159     printf ("%s%d: Partition starts past end of device (sector %"PRDSNu")\n",
160             block_name (block), part_nr, start);
161   else if (start + size < start || start + size > block_size (block))
162     printf ("%s%d: Partition end (%"PRDSNu") past end of device (%"PRDSNu")\n",
163             block_name (block), part_nr, start + size, block_size (block));
164   else
165     {
166       enum block_type type = (part_type == 0x20 ? BLOCK_KERNEL
167                               : part_type == 0x21 ? BLOCK_FILESYS
168                               : part_type == 0x22 ? BLOCK_SCRATCH
169                               : part_type == 0x23 ? BLOCK_SWAP
170                               : BLOCK_FOREIGN);
171       struct partition *p;
172       char extra_info[128];
173       char name[16];
174
175       p = malloc (sizeof *p);
176       if (p == NULL)
177         PANIC ("Failed to allocate memory for partition descriptor");
178       p->block = block;
179       p->start = start;
180
181       snprintf (name, sizeof name, "%s%d", block_name (block), part_nr);
182       snprintf (extra_info, sizeof extra_info, "%s (%02x)",
183                 partition_type_name (part_type), part_type);
184       block_register (name, type, extra_info, size, &partition_operations, p);
185     }
186 }
187
188 /* Returns a human-readable name for the given partition TYPE. */
189 static const char *
190 partition_type_name (uint8_t type)
191 {
192   /* Name of each known type of partition.
193      From util-linux-2.12r/fdisk/i386_sys_types.c.
194      This initializer makes use of a C99 feature that allows
195      array elements to be initialized by index. */
196   static const char *type_names[256] =
197     {
198       [0x00] = "Empty",
199       [0x01] = "FAT12",
200       [0x02] = "XENIX root",
201       [0x03] = "XENIX usr",
202       [0x04] = "FAT16 <32M",
203       [0x05] = "Extended",
204       [0x06] = "FAT16",
205       [0x07] = "HPFS/NTFS",
206       [0x08] = "AIX",
207       [0x09] = "AIX bootable",
208       [0x0a] = "OS/2 Boot Manager",
209       [0x0b] = "W95 FAT32",
210       [0x0c] = "W95 FAT32 (LBA)",
211       [0x0e] = "W95 FAT16 (LBA)",
212       [0x0f] = "W95 Ext'd (LBA)",
213       [0x10] = "OPUS",
214       [0x11] = "Hidden FAT12",
215       [0x12] = "Compaq diagnostics",
216       [0x14] = "Hidden FAT16 <32M",
217       [0x16] = "Hidden FAT16",
218       [0x17] = "Hidden HPFS/NTFS",
219       [0x18] = "AST SmartSleep",
220       [0x1b] = "Hidden W95 FAT32",
221       [0x1c] = "Hidden W95 FAT32 (LBA)",
222       [0x1e] = "Hidden W95 FAT16 (LBA)",
223       [0x20] = "Pintos OS kernel",
224       [0x21] = "Pintos file system",
225       [0x22] = "Pintos scratch",
226       [0x23] = "Pintos swap",
227       [0x24] = "NEC DOS",
228       [0x39] = "Plan 9",
229       [0x3c] = "PartitionMagic recovery",
230       [0x40] = "Venix 80286",
231       [0x41] = "PPC PReP Boot",
232       [0x42] = "SFS",
233       [0x4d] = "QNX4.x",
234       [0x4e] = "QNX4.x 2nd part",
235       [0x4f] = "QNX4.x 3rd part",
236       [0x50] = "OnTrack DM",
237       [0x51] = "OnTrack DM6 Aux1",
238       [0x52] = "CP/M",
239       [0x53] = "OnTrack DM6 Aux3",
240       [0x54] = "OnTrackDM6",
241       [0x55] = "EZ-Drive",
242       [0x56] = "Golden Bow",
243       [0x5c] = "Priam Edisk",
244       [0x61] = "SpeedStor",
245       [0x63] = "GNU HURD or SysV",
246       [0x64] = "Novell Netware 286",
247       [0x65] = "Novell Netware 386",
248       [0x70] = "DiskSecure Multi-Boot",
249       [0x75] = "PC/IX",
250       [0x80] = "Old Minix",
251       [0x81] = "Minix / old Linux",
252       [0x82] = "Linux swap / Solaris",
253       [0x83] = "Linux",
254       [0x84] = "OS/2 hidden C: drive",
255       [0x85] = "Linux extended",
256       [0x86] = "NTFS volume set",
257       [0x87] = "NTFS volume set",
258       [0x88] = "Linux plaintext",
259       [0x8e] = "Linux LVM",
260       [0x93] = "Amoeba",
261       [0x94] = "Amoeba BBT",
262       [0x9f] = "BSD/OS",
263       [0xa0] = "IBM Thinkpad hibernation",
264       [0xa5] = "FreeBSD",
265       [0xa6] = "OpenBSD",
266       [0xa7] = "NeXTSTEP",
267       [0xa8] = "Darwin UFS",
268       [0xa9] = "NetBSD",
269       [0xab] = "Darwin boot",
270       [0xb7] = "BSDI fs",
271       [0xb8] = "BSDI swap",
272       [0xbb] = "Boot Wizard hidden",
273       [0xbe] = "Solaris boot",
274       [0xbf] = "Solaris",
275       [0xc1] = "DRDOS/sec (FAT-12)",
276       [0xc4] = "DRDOS/sec (FAT-16 < 32M)",
277       [0xc6] = "DRDOS/sec (FAT-16)",
278       [0xc7] = "Syrinx",
279       [0xda] = "Non-FS data",
280       [0xdb] = "CP/M / CTOS / ...",
281       [0xde] = "Dell Utility",
282       [0xdf] = "BootIt",
283       [0xe1] = "DOS access",
284       [0xe3] = "DOS R/O",
285       [0xe4] = "SpeedStor",
286       [0xeb] = "BeOS fs",
287       [0xee] = "EFI GPT",
288       [0xef] = "EFI (FAT-12/16/32)",
289       [0xf0] = "Linux/PA-RISC boot",
290       [0xf1] = "SpeedStor",
291       [0xf4] = "SpeedStor",
292       [0xf2] = "DOS secondary",
293       [0xfd] = "Linux raid autodetect",
294       [0xfe] = "LANstep",
295       [0xff] = "BBT",
296     };
297
298   return type_names[type] != NULL ? type_names[type] : "Unknown";
299 }
300
301 /* Reads sector SECTOR from partition P into BUFFER, which must
302    have room for BLOCK_SECTOR_SIZE bytes. */
303 static void
304 partition_read (void *p_, block_sector_t sector, void *buffer)
305 {
306   struct partition *p = p_;
307   block_read (p->block, p->start + sector, buffer);
308 }
309
310 /* Write sector SECTOR to partition P from BUFFER, which must
311    contain BLOCK_SECTOR_SIZE bytes.  Returns after the block has
312    acknowledged receiving the data. */
313 static void
314 partition_write (void *p_, block_sector_t sector, const void *buffer)
315 {
316   struct partition *p = p_;
317   block_write (p->block, p->start + sector, buffer);
318 }
319
320 static struct block_operations partition_operations =
321   {
322     partition_read,
323     partition_write
324   };