2 Copyright © 1995-2011, The AROS Development Team. All rights reserved.
5 Desc: GPT partition table handler
18 * Note that we use KPrintF() for debugging in some places.
19 * KPrintF() uses RawDoFmt() for formatting, which (if patched by locale.library)
20 * correctly supports %llu, unlike kprintf().
21 * This will change when i386 port gets kernel.resource. After this kprintf()
22 * will be moved to libdebug.a and rewritten to simply call KrnBug().
25 #include <exec/memory.h>
26 #include <libraries/partition.h>
27 #include <proto/debug.h>
28 #include <proto/exec.h>
29 #include <proto/partition.h>
30 #include <proto/utility.h>
32 #include "partition_support.h"
33 #include "partition_types.h"
34 #include "partitiongpt.h"
35 #include "partitionmbr.h"
39 /* Some error code that collides with neither trackdisk.device not dos.library error codes */
40 #define ERROR_BAD_CRC 255
42 struct GPTPartitionHandle
44 struct PartitionHandle ph
; /* Public part */
45 ULONG entrySize
; /* Size of table entry */
46 char name
[36]; /* Name in ASCII */
47 /* Actual table entry follows */
50 #define GPTH(ph) ((struct GPTPartitionHandle *)ph)
52 static const uuid_t GPT_Type_Unused
= MAKE_UUID(0x00000000, 0x0000, 0x0000, 0x0000, 0x000000000000);
54 * This is a bit special.
55 * The first four bytes (time_low) hold DOS Type ID (for simple mapping),
56 * so we set them to zero here. We ignore it during comparison.
57 * I hope this won't create any significant problems. Even if some ID ever collides, it will
58 * unlikely collide with existing DOSTypes being used, so it can be blacklisted then.
60 static const uuid_t GPT_Type_AROS
= MAKE_UUID(0x00000000, 0xBB67, 0x46C5, 0xAA4A, 0xF502CA018E5E);
64 * UTF16-LE conversion.
65 * Currently these are very basic routines which handle only Latin-1 character set.
66 * If needed, conversion can be performed using codesets.library (but don't forget
67 * that you can run early during system bootup and codesets.library won't be
68 * available by that time).
71 static void FromUTF16(char *to
, char *from
, ULONG len
)
75 for (i
= 0; i
< len
; i
++)
77 /* Currently we know only 7-bit ASCII characters */
87 static void ToUTF16(char *to
, char *from
, ULONG len
)
91 for (i
= 0; i
< len
; i
++)
93 /* Currently we know only 7-bit ASCII characters */
103 * Little-endian UUID conversion and comparison.
104 * We can't use uuid.library here because it's not available during
105 * system bootup. However, we are going to use it for generation.
107 static inline void uuid_from_le(uuid_t
*to
, uuid_t
*id
)
109 to
->time_low
= AROS_LE2LONG(id
->time_low
);
110 to
->time_mid
= AROS_LE2WORD(id
->time_mid
);
111 to
->time_hi_and_version
= AROS_LE2WORD(id
->time_hi_and_version
);
113 /* Do not replace it with CopyMem(), gcc optimizes this nicely */
114 memcpy(&to
->clock_seq_hi_and_reserved
, &id
->clock_seq_hi_and_reserved
, 8);
117 static inline void uuid_to_le(uuid_t
*to
, uuid_t
*id
)
119 to
->time_low
= AROS_LONG2LE(id
->time_low
);
120 to
->time_mid
= AROS_WORD2LE(id
->time_mid
);
121 to
->time_hi_and_version
= AROS_WORD2LE(id
->time_hi_and_version
);
123 /* Do not replace it with CopyMem(), gcc optimizes this nicely */
124 memcpy(&to
->clock_seq_hi_and_reserved
, &id
->clock_seq_hi_and_reserved
, 8);
127 static inline BOOL
uuid_cmp_le(uuid_t
*leid
, const uuid_t
*id
)
129 if (AROS_LE2LONG(leid
->time_low
) != id
->time_low
)
131 if (AROS_LE2WORD(leid
->time_mid
) != id
->time_mid
)
133 if (AROS_LE2WORD(leid
->time_hi_and_version
) != id
->time_hi_and_version
)
136 return !memcmp(&leid
->clock_seq_hi_and_reserved
, &id
->clock_seq_hi_and_reserved
, 8);
139 /* For AROS we put DOS Type ID into first four bytes of UUID (time_low), so we ignore them. */
140 static inline BOOL
is_aros_uuid_le(uuid_t
*leid
)
142 if (AROS_LE2WORD(leid
->time_mid
) != GPT_Type_AROS
.time_mid
)
144 if (AROS_LE2WORD(leid
->time_hi_and_version
) != GPT_Type_AROS
.time_hi_and_version
)
147 return !memcmp(&leid
->clock_seq_hi_and_reserved
, &GPT_Type_AROS
.clock_seq_hi_and_reserved
, 8);
152 static void PRINT_LE_UUID(char *s
, uuid_t
*id
)
156 bug("[GPT] %s UUID: 0x%08X-%04X-%04X-%02X%02X-", s
,
157 AROS_LE2LONG(id
->time_low
), AROS_LE2WORD(id
->time_mid
), AROS_LE2WORD(id
->time_hi_and_version
),
158 id
->clock_seq_hi_and_reserved
, id
->clock_seq_low
);
160 for (i
= 0; i
< sizeof(id
->node
); i
++)
161 bug("%02X", id
->node
[i
]);
168 #define PRINT_LE_UUID(s, id)
173 #define writeDataFromBlock(root, blk, tablesize, table) TDERR_WriteProt
174 #define PartitionWriteBlock(base, root, blk, mem) TDERR_WriteProt
177 #define writeDataFromBlock(root, blk, tablesize, table) 0
178 #define PartitionWriteBlock(base, root, blk, mem) 0
181 static void GPT_PatchDosEnvec(struct DosEnvec
*de
, struct GPTPartition
*p
)
186 if (is_aros_uuid_le(&p
->TypeID
))
188 type
= AROS_LE2LONG(p
->TypeID
.time_low
);
189 /* This casting is needed for proper sign expansion */
190 bootpri
= (BYTE
)(AROS_LE2LONG(p
->Flags1
) & GPT_PF1_AROS_BOOTPRI
);
194 const struct TypeMapping
*m
;
196 for (m
= PartTypes
; m
->DOSType
; m
++)
198 if (m
->uuid
&& uuid_cmp_le(&p
->TypeID
, m
->uuid
))
206 setDosType(de
, type
);
207 de
->de_BootPri
= bootpri
;
210 static LONG
GPTCheckHeader(struct Library
*PartitionBase
, struct PartitionHandle
*root
, struct GPTHeader
*hdr
, UQUAD block
)
212 /* Load the GPT header */
213 if (!readBlock(PartitionBase
, root
, block
, hdr
))
215 ULONG hdrSize
= AROS_LE2LONG(hdr
->HeaderSize
);
216 UQUAD currentblk
= AROS_LE2QUAD(hdr
->CurrentBlock
);
218 D(bug("[GPT] Header size: specified %u, expected %u\n", hdrSize
, GPT_MIN_HEADER_SIZE
));
219 DREAD(KPrintF("[GPT] Read: Header block %llu, backup block %llu\n", currentblk
, AROS_LE2QUAD(hdr
->BackupBlock
)));
221 /* Check signature, header size, and current block number */
222 if ((!memcmp(hdr
->Signature
, GPT_SIGNATURE
, sizeof(hdr
->Signature
))) &&
223 (hdrSize
>= GPT_MIN_HEADER_SIZE
) && (currentblk
== block
))
226 * Use zlib routine for CRC32.
227 * CHECKME: is it correct on bigendian machines? It should, however who knows...
229 ULONG orig_crc
= AROS_LE2LONG(hdr
->HeaderCRC32
);
232 hdr
->HeaderCRC32
= 0;
233 crc
= Crc32_ComputeBuf(0, hdr
, hdrSize
);
235 D(bug("[GPT] Header CRC: calculated 0x%08X, expected 0x%08X\n", crc
, orig_crc
));
237 return (crc
== orig_crc
) ? 1 : 2;
243 static LONG
PartitionGPTCheckPartitionTable(struct Library
*PartitionBase
, struct PartitionHandle
*root
)
248 /* GPT can be placed only in the root of the disk */
252 blk
= AllocMem(root
->de
.de_SizeBlock
<< 2, MEMF_ANY
);
257 /* First of all, we must have valid MBR stub */
258 if (MBRCheckPartitionTable(PartitionBase
, root
, blk
))
260 struct PCPartitionTable
*pcpt
= ((struct MBR
*)blk
)->pcpt
;
262 D(bug("[GPT] MBR check passed, first partition type 0x%02X, start block %u\n", pcpt
[0].type
, pcpt
[0].first_sector
));
264 /* We must have partition 0 of type GPT starting at block 1 */
265 if ((pcpt
[0].type
== MBRT_GPT
) && (AROS_LE2LONG(pcpt
[0].first_sector
) == 1))
267 res
= GPTCheckHeader(PartitionBase
, root
, blk
, 1);
269 /* 2 is a special return code for "bad CRC" */
270 if (res
== ERROR_BAD_CRC
)
272 /* Try to read backup header */
273 UQUAD block
= AROS_LE2QUAD(((struct GPTHeader
*)blk
)->BackupBlock
);
275 res
= GPTCheckHeader(PartitionBase
, root
, blk
, block
);
277 /* There's no third backup :( */
278 if (res
== ERROR_BAD_CRC
)
284 FreeMem(blk
, root
->de
.de_SizeBlock
<< 2);
288 static LONG
GPTReadPartitionTable(struct Library
*PartitionBase
, struct PartitionHandle
*root
, struct GPTHeader
*hdr
, UQUAD block
)
291 LONG err
= ERROR_NOT_A_DOS_DISK
;
293 DREAD(KPrintF("[GPT] Read: header block %llu\n", block
));
294 res
= GPTCheckHeader(PartitionBase
, root
, hdr
, block
);
296 return ERROR_BAD_CRC
;
300 struct GPTPartition
*table
;
301 ULONG cnt
= AROS_LE2LONG(hdr
->NumEntries
);
302 ULONG entrysize
= AROS_LE2LONG(hdr
->EntrySize
);
303 ULONG tablesize
= AROS_ROUNDUP2(entrysize
* cnt
, root
->de
.de_SizeBlock
<< 2);
304 UQUAD startblk
, endblk
;
306 DREAD(bug("[GPT] Read: %u entries per %u bytes, %u bytes total\n", cnt
, entrysize
, tablesize
));
308 table
= AllocMem(tablesize
, MEMF_ANY
);
310 return ERROR_NO_FREE_STORE
;
312 startblk
= AROS_LE2QUAD(hdr
->StartBlock
);
314 DREAD(KPrintF("[GPT] Read: start block %llu\n", startblk
));
315 res
= readDataFromBlock(root
, startblk
, tablesize
, table
);
318 ULONG orig_crc
= AROS_LE2LONG(hdr
->PartCRC32
);
319 ULONG crc
= Crc32_ComputeBuf(0, table
, entrysize
* cnt
);
321 D(bug("[GPT] Data CRC: calculated 0x%08X, expected 0x%08X\n", crc
, orig_crc
));
325 struct GPTPartition
*p
= table
;
328 DREAD(bug("[GPT] Adding partitions...\n"));
331 for (i
= 0; i
< cnt
; i
++)
333 struct GPTPartitionHandle
*gph
;
335 startblk
= AROS_LE2QUAD(p
->StartBlock
);
336 endblk
= AROS_LE2QUAD(p
->EndBlock
);
339 * Skip unused entries. NumEntries in the header holds total number of preallocated entries,
340 * not the number of used ones.
341 * Normally GPT table has 128 preallocated entries, but only first of them are used.
342 * Just in case, we allow gaps between used entries. However (tested with MacOS X Disk Utility)
343 * partition editors seem to squeeze the table and do not leave empty entries when deleting
344 * partitions in the middle of the disk.
346 if (!memcmp(&p
->TypeID
, &GPT_Type_Unused
, sizeof(uuid_t
)))
349 DREAD(PRINT_LE_UUID("Type ", &p
->TypeID
));
350 DREAD(PRINT_LE_UUID("Partition", &p
->PartitionID
));
351 DREAD(KPrintF("[GPT] Blocks %llu - %llu\n", startblk
, endblk
));
352 DREAD(KPrintF("[GPT] Flags 0x%08lX 0x%08lX\n", AROS_LE2LONG(p
->Flags0
), AROS_LE2LONG(p
->Flags1
)));
353 DREAD(KPrintF("[GPT] Offset 0x%p\n", (APTR
)p
- (APTR
)table
));
355 gph
= AllocVec(sizeof(struct GPTPartitionHandle
) + entrysize
, MEMF_CLEAR
);
358 initPartitionHandle(root
, &gph
->ph
, startblk
, endblk
- startblk
+ 1);
360 /* Map UUID to a DOSType */
361 GPT_PatchDosEnvec(&gph
->ph
.de
, p
);
363 /* Store the whole entry and convert name into ASCII form */
364 CopyMem(p
, &gph
[1], entrysize
);
365 FromUTF16(gph
->name
, p
->Name
, 36);
367 gph
->ph
.ln
.ln_Name
= gph
->name
;
368 gph
->entrySize
= entrysize
;
370 ADDTAIL(&root
->table
->list
, gph
);
371 DREAD(bug("[GPT] Added partition %u (%s), handle 0x%p\n", i
, gph
->name
, gph
));
375 err
= ERROR_NO_FREE_STORE
;
379 /* Jump to next entry, skip 'entrysize' bytes */
380 p
= (APTR
)p
+ entrysize
;
387 FreeMem(table
, tablesize
);
393 static void PartitionGPTClosePartitionTable(struct Library
*PartitionBase
, struct PartitionHandle
*root
)
395 struct PartitionHandle
*ph
, *ph2
;
397 /* Free all partition entries */
398 ForeachNodeSafe(&root
->table
->list
, ph
, ph2
)
401 FreeMem(root
->table
->data
, root
->de
.de_SizeBlock
<<2);
404 static LONG
PartitionGPTOpenPartitionTable(struct Library
*PartitionBase
, struct PartitionHandle
*root
)
409 * The header is attached to partition table handle.
410 * This allows us to write back the complete header and keep
411 * data we don't know about in future GPT revisions.
413 root
->table
->data
= AllocMem(root
->de
.de_SizeBlock
<< 2, MEMF_ANY
);
414 if (!root
->table
->data
)
415 return ERROR_NO_FREE_STORE
;
417 /* Read primary GPT table */
418 res
= GPTReadPartitionTable(PartitionBase
, root
, root
->table
->data
, 1);
420 if (res
== ERROR_BAD_CRC
)
422 /* If CRC failed, read backup table */
423 struct GPTHeader
*hdr
= root
->table
->data
;
424 UQUAD block
= AROS_LE2QUAD(hdr
->BackupBlock
);
426 res
= GPTReadPartitionTable(PartitionBase
, root
, hdr
, block
);
428 /* There's no third backup... */
429 if (res
== ERROR_BAD_CRC
)
430 res
= ERROR_NOT_A_DOS_DISK
;
433 /* Cleanup if reading failed */
435 PartitionGPTClosePartitionTable(PartitionBase
, root
);
440 static LONG
GPTWriteTable(struct Library
*PartitionBase
, struct PartitionHandle
*root
, struct GPTHeader
*hdr
, struct GPTPartition
*table
,
441 UQUAD headerblk
, UQUAD backupblk
, UQUAD startblk
, ULONG tablesize
)
446 hdr
->CurrentBlock
= AROS_QUAD2LE(headerblk
);
447 hdr
->BackupBlock
= AROS_QUAD2LE(backupblk
);
448 hdr
->StartBlock
= AROS_QUAD2LE(startblk
);
449 hdr
->HeaderCRC32
= 0;
451 /* We modify the header, so we have to recalculate its CRC */
452 crc
= Crc32_ComputeBuf(0, hdr
, AROS_LE2LONG(hdr
->HeaderSize
));
453 hdr
->HeaderCRC32
= AROS_LONG2LE(crc
);
454 DWRITE(bug("[GPT] New header CRC 0x%08X\n", crc
));
456 DWRITE(KPrintF("[GPT] Write data: start block %llu\n", startblk
));
457 res
= writeDataFromBlock(root
, startblk
, tablesize
, table
);
459 DWRITE(bug("[GPT] Write result: %u\n", res
));
462 DWRITE(KPrintF("[GPT] Write header: start block %llu\n", headerblk
));
463 res
= PartitionWriteBlock(PartitionBase
, root
, headerblk
, hdr
);
465 DWRITE(bug("[GPT] Write result: %u\n", res
));
468 return deviceError(res
);
471 static LONG
PartitionGPTWritePartitionTable(struct Library
*PartitionBase
, struct PartitionHandle
*root
)
473 struct GPTHeader
*hdr
= root
->table
->data
;
474 ULONG cnt
= AROS_LE2LONG(hdr
->NumEntries
);
475 ULONG entrysize
= AROS_LE2LONG(hdr
->EntrySize
);
476 ULONG tablesize
= AROS_ROUNDUP2(entrysize
* cnt
, root
->de
.de_SizeBlock
<< 2);
477 struct GPTPartition
*table
;
480 * TODO: Update legacy MBR data here when adding/moving is implemented. IntelMacs have
481 * legacy MBR filled in with copies of four first entries in GPT table if at least one
482 * FAT partition is defined on the drive (to support Windows XP).
483 * CHS data for these entries is always set to (c=1023, h=254, s=63), however start and end
484 * block numbers reflect the real position. Apple's disk utility always keeps these entries
485 * in sync with their respective GPT entries. We need to do the same. Also remember to keep
486 * boot code in sector 0.
489 DWRITE(bug("[GPT] Write: %u entries per %u bytes, %u bytes total\n", cnt
, entrysize
, tablesize
));
491 /* Allocate buffer for the whole table */
492 table
= AllocMem(tablesize
, MEMF_CLEAR
);
496 struct GPTPartition
*p
= table
;
497 struct GPTPartitionHandle
*gph
;
502 * Collect our entries and build up the whole table.
503 * At this point we are guaranteed to have no more entries than
504 * can fit into reserved space. It's AddPartition()'s job to ensure this.
506 ForeachNode(&root
->table
->list
, gph
)
508 DWRITE(bug("[GPT] Writing partition %s, handle 0x%p\n", gph
->name
, gph
));
511 * Put our entry into the buffer.
512 * Use entry's own length, because if this entry is created by AddPartition(),
513 * it can be shorter than on-disk one (if someone uses extended length we don't know about).
515 CopyMem(&gph
[1], p
, gph
->entrySize
);
517 DWRITE(PRINT_LE_UUID("Type ", &p
->TypeID
));
518 DWRITE(PRINT_LE_UUID("Partition", &p
->PartitionID
));
519 DWRITE(KPrintF("[GPT] Blocks %llu - %llu\n", AROS_LE2QUAD(p
->StartBlock
), AROS_LE2QUAD(p
->EndBlock
)));
520 DWRITE(KPrintF("[GPT] Flags 0x%08lX 0x%08lX\n", AROS_LE2LONG(p
->Flags0
), AROS_LE2LONG(p
->Flags1
)));
521 DWRITE(KPrintF("[GPT] Offset 0x%p\n", (APTR
)p
- (APTR
)table
));
523 /* Jump to next entry */
524 p
= (APTR
)p
+ entrysize
;
527 crc
= Crc32_ComputeBuf(0, table
, entrysize
* cnt
);
528 hdr
->PartCRC32
= AROS_LONG2LE(crc
);
529 DWRITE(bug("[GPT] New data CRC 0x%08X\n", crc
));
531 /* First we attempt to write a backup table. It's placed in the end. */
532 backup
= root
->dg
.dg_TotalSectors
- 1;
533 res
= GPTWriteTable(PartitionBase
, root
, hdr
, table
, backup
, 1, backup
- tablesize
, tablesize
);
538 * And only if succeeded, write a primary one.
539 * This gives us a chance to discard writing if something goes wrong with disk/device/whatever.
541 res
= GPTWriteTable(PartitionBase
, root
, hdr
, table
, 1, backup
, 2, tablesize
);
544 FreeMem(table
, tablesize
);
549 return ERROR_NO_FREE_STORE
;
552 static LONG
PartitionGPTGetPartitionAttr(struct Library
*PartitionBase
, struct PartitionHandle
*ph
, struct TagItem
*tag
)
554 struct GPTPartition
*part
= (APTR
)ph
+ sizeof(struct GPTPartitionHandle
);
559 uuid_from_le((uuid_t
*)tag
->ti_Data
, &part
->TypeID
);
560 PTYPE(tag
->ti_Data
)->id_len
= sizeof(uuid_t
);
564 /* This extra flag is valid only for AROS partitions */
565 if (is_aros_uuid_le(&part
->TypeID
))
566 *((ULONG
*)tag
->ti_Data
) = (AROS_LE2LONG(part
->Flags1
) & GPT_PF1_AROS_BOOTABLE
) ? TRUE
: FALSE
;
568 *((ULONG
*)tag
->ti_Data
) = FALSE
;
572 *((ULONG
*)tag
->ti_Data
) = (AROS_LE2LONG(part
->Flags1
) & GPT_PF1_NOMOUNT
) ? FALSE
: TRUE
;
576 *((ULONG
*)tag
->ti_Data
) = AROS_LE2LONG(part
->StartBlock
);
580 *((ULONG
*)tag
->ti_Data
) = AROS_LE2LONG(part
->EndBlock
);
587 static LONG
PartitionGPTSetPartitionAttrs(struct Library
*PartitionBase
, struct PartitionHandle
*ph
, const struct TagItem
*taglist
)
589 struct GPTPartition
*part
= (APTR
)ph
+ sizeof(struct GPTPartitionHandle
);
591 struct TagItem
*bootable
= NULL
;
593 while ((tag
= NextTagItem(&taglist
)))
598 strncpy(GPTH(ph
)->name
, (char *)tag
->ti_Data
, 36);
599 ToUTF16(part
->Name
, GPTH(ph
)->name
, 36);
603 /* Foolproof check */
604 if (PTYPE(tag
->ti_Data
)->id_len
== sizeof(uuid_t
))
606 uuid_to_le(&part
->TypeID
, (uuid_t
*)tag
->ti_Data
);
607 /* Update DOSType according to a new type ID */
608 GPT_PatchDosEnvec(&ph
->de
, part
);
617 D(bug("[GPT] Setting automount flag to %ld\n", tag
->ti_Data
));
618 D(bug("[GPT] Partition handle 0x%p, flags 0x%08X\n", ph
, part
->Flags1
));
621 part
->Flags1
&= ~AROS_LONG2LE(GPT_PF1_NOMOUNT
);
623 part
->Flags1
|= AROS_LONG2LE(GPT_PF1_NOMOUNT
);
625 D(bug("[GPT] New flags: 0x%08X\n", part
->Flags1
));
629 /* TODO: implement the rest (geometry, dosenvec, start/end block) */
634 * Now check bootable attribute.
635 * It is applicable only to AROS partitions, so we check it here,
636 * after possible type change.
638 if (bootable
&& is_aros_uuid_le(&part
->TypeID
))
640 if (bootable
->ti_Data
)
641 part
->Flags1
|= AROS_LONG2LE(GPT_PF1_AROS_BOOTABLE
);
643 part
->Flags1
&= ~AROS_LONG2LE(GPT_PF1_AROS_BOOTABLE
);
649 static const struct PartitionAttribute PartitionGPTPartitionTableAttrs
[]=
651 {PTT_TYPE
, PLAM_READ
},
655 static const struct PartitionAttribute PartitionGPTPartitionAttrs
[]=
657 {PT_GEOMETRY
, PLAM_READ
},
658 {PT_TYPE
, PLAM_READ
|PLAM_WRITE
},
659 {PT_POSITION
, PLAM_READ
},
660 {PT_NAME
, PLAM_READ
|PLAM_WRITE
},
661 {PT_BOOTABLE
, PLAM_READ
|PLAM_WRITE
},
662 {PT_AUTOMOUNT
, PLAM_READ
|PLAM_WRITE
},
663 {PT_STARTBLOCK
, PLAM_READ
},
664 {PT_ENDBLOCK
, PLAM_READ
},
668 const struct PTFunctionTable PartitionGPT
=
672 PartitionGPTCheckPartitionTable
,
673 PartitionGPTOpenPartitionTable
,
674 PartitionGPTClosePartitionTable
,
675 PartitionGPTWritePartitionTable
,
681 PartitionGPTGetPartitionAttr
,
682 PartitionGPTSetPartitionAttrs
,
683 PartitionGPTPartitionTableAttrs
,
684 PartitionGPTPartitionAttrs
,