2 Copyright © 1995-2012, 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, 0x000000000000ULL
);
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, 0xF502CA018E5EULL
);
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 #undef WritePartitionDataQ
174 #define WritePartitionDataQ(root, table, tablesize, blk) TDERR_WriteProt
175 #define PartitionWriteBlock(base, root, blk, mem) TDERR_WriteProt
178 #undef WritePartitionDataQ
179 #define WritePartitionDataQ(root, table, tablesize, blk) 0
180 #define PartitionWriteBlock(base, root, blk, mem) 0
183 static void GPT_PatchDosEnvec(struct DosEnvec
*de
, struct GPTPartition
*p
)
188 if (is_aros_uuid_le(&p
->TypeID
))
190 type
= AROS_LE2LONG(p
->TypeID
.time_low
);
191 /* This casting is needed for proper sign expansion */
192 bootpri
= (BYTE
)(AROS_LE2LONG(p
->Flags1
) & GPT_PF1_AROS_BOOTPRI
);
196 const struct TypeMapping
*m
;
198 for (m
= PartTypes
; m
->DOSType
; m
++)
200 if (m
->uuid
&& uuid_cmp_le(&p
->TypeID
, m
->uuid
))
208 setDosType(de
, type
);
209 de
->de_BootPri
= bootpri
;
212 static LONG
GPTCheckHeader(struct Library
*PartitionBase
, struct PartitionHandle
*root
, struct GPTHeader
*hdr
, UQUAD block
)
214 /* Load the GPT header */
215 if (!readBlock(PartitionBase
, root
, block
, hdr
))
217 ULONG hdrSize
= AROS_LE2LONG(hdr
->HeaderSize
);
218 UQUAD currentblk
= AROS_LE2QUAD(hdr
->CurrentBlock
);
220 D(bug("[GPT] Header size: specified %u, expected %u\n", hdrSize
, GPT_MIN_HEADER_SIZE
));
221 DREAD(KPrintF("[GPT] Read: Header block %llu, backup block %llu\n", currentblk
, AROS_LE2QUAD(hdr
->BackupBlock
)));
223 /* Check signature, header size, and current block number */
224 if ((!memcmp(hdr
->Signature
, GPT_SIGNATURE
, sizeof(hdr
->Signature
))) &&
225 (hdrSize
>= GPT_MIN_HEADER_SIZE
) && (currentblk
== block
))
228 * Use zlib routine for CRC32.
229 * CHECKME: is it correct on bigendian machines? It should, however who knows...
231 ULONG orig_crc
= AROS_LE2LONG(hdr
->HeaderCRC32
);
234 hdr
->HeaderCRC32
= 0;
235 crc
= Crc32_ComputeBuf(0, hdr
, hdrSize
);
237 D(bug("[GPT] Header CRC: calculated 0x%08X, expected 0x%08X\n", crc
, orig_crc
));
239 return (crc
== orig_crc
) ? 1 : 2;
245 static LONG
PartitionGPTCheckPartitionTable(struct Library
*PartitionBase
, struct PartitionHandle
*root
)
250 /* GPT can be placed only in the root of the disk */
254 blk
= AllocMem(root
->de
.de_SizeBlock
<< 2, MEMF_ANY
);
259 /* First of all, we must have valid MBR stub */
260 if (MBRCheckPartitionTable(PartitionBase
, root
, blk
))
262 struct PCPartitionTable
*pcpt
= ((struct MBR
*)blk
)->pcpt
;
264 D(bug("[GPT] MBR check passed, first partition type 0x%02X, start block %u\n", pcpt
[0].type
, pcpt
[0].first_sector
));
266 /* We must have partition 0 of type GPT starting at block 1 */
267 if ((pcpt
[0].type
== MBRT_GPT
) && (AROS_LE2LONG(pcpt
[0].first_sector
) == 1))
269 res
= GPTCheckHeader(PartitionBase
, root
, blk
, 1);
271 /* 2 is a special return code for "bad CRC" */
272 if (res
== ERROR_BAD_CRC
)
274 /* Try to read backup header */
275 UQUAD block
= AROS_LE2QUAD(((struct GPTHeader
*)blk
)->BackupBlock
);
277 res
= GPTCheckHeader(PartitionBase
, root
, blk
, block
);
279 /* There's no third backup :( */
280 if (res
== ERROR_BAD_CRC
)
286 FreeMem(blk
, root
->de
.de_SizeBlock
<< 2);
290 static LONG
GPTReadPartitionTable(struct Library
*PartitionBase
, struct PartitionHandle
*root
, struct GPTHeader
*hdr
, UQUAD block
)
293 LONG err
= ERROR_NOT_A_DOS_DISK
;
295 DREAD(KPrintF("[GPT] Read: header block %llu\n", block
));
296 res
= GPTCheckHeader(PartitionBase
, root
, hdr
, block
);
298 return ERROR_BAD_CRC
;
302 struct GPTPartition
*table
;
303 ULONG cnt
= AROS_LE2LONG(hdr
->NumEntries
);
304 ULONG entrysize
= AROS_LE2LONG(hdr
->EntrySize
);
305 ULONG tablesize
= AROS_ROUNDUP2(entrysize
* cnt
, root
->de
.de_SizeBlock
<< 2);
306 UQUAD startblk
, endblk
;
308 DREAD(bug("[GPT] Read: %u entries per %u bytes, %u bytes total\n", cnt
, entrysize
, tablesize
));
310 table
= AllocMem(tablesize
, MEMF_ANY
);
312 return ERROR_NO_FREE_STORE
;
314 startblk
= AROS_LE2QUAD(hdr
->StartBlock
);
316 DREAD(KPrintF("[GPT] Read: start block %llu\n", startblk
));
317 res
= ReadPartitionDataQ(root
, table
, tablesize
, startblk
);
320 ULONG orig_crc
= AROS_LE2LONG(hdr
->PartCRC32
);
321 ULONG crc
= Crc32_ComputeBuf(0, table
, entrysize
* cnt
);
323 D(bug("[GPT] Data CRC: calculated 0x%08X, expected 0x%08X\n", crc
, orig_crc
));
327 struct GPTPartition
*p
= table
;
330 DREAD(bug("[GPT] Adding partitions...\n"));
333 for (i
= 0; i
< cnt
; i
++)
335 struct GPTPartitionHandle
*gph
;
337 startblk
= AROS_LE2QUAD(p
->StartBlock
);
338 endblk
= AROS_LE2QUAD(p
->EndBlock
);
341 * Skip unused entries. NumEntries in the header holds total number of preallocated entries,
342 * not the number of used ones.
343 * Normally GPT table has 128 preallocated entries, but only first of them are used.
344 * Just in case, we allow gaps between used entries. However (tested with MacOS X Disk Utility)
345 * partition editors seem to squeeze the table and do not leave empty entries when deleting
346 * partitions in the middle of the disk.
348 if (!memcmp(&p
->TypeID
, &GPT_Type_Unused
, sizeof(uuid_t
)))
351 DREAD(PRINT_LE_UUID("Type ", &p
->TypeID
));
352 DREAD(PRINT_LE_UUID("Partition", &p
->PartitionID
));
353 DREAD(KPrintF("[GPT] Blocks %llu - %llu\n", startblk
, endblk
));
354 DREAD(KPrintF("[GPT] Flags 0x%08lX 0x%08lX\n", AROS_LE2LONG(p
->Flags0
), AROS_LE2LONG(p
->Flags1
)));
355 DREAD(KPrintF("[GPT] Offset 0x%p\n", (APTR
)p
- (APTR
)table
));
357 gph
= AllocVec(sizeof(struct GPTPartitionHandle
) + entrysize
, MEMF_CLEAR
);
360 initPartitionHandle(root
, &gph
->ph
, startblk
, endblk
- startblk
+ 1);
362 /* Map UUID to a DOSType */
363 GPT_PatchDosEnvec(&gph
->ph
.de
, p
);
365 /* Store the whole entry and convert name into ASCII form */
366 CopyMem(p
, &gph
[1], entrysize
);
367 FromUTF16(gph
->name
, p
->Name
, 36);
369 gph
->ph
.ln
.ln_Name
= gph
->name
;
370 gph
->entrySize
= entrysize
;
372 ADDTAIL(&root
->table
->list
, gph
);
373 DREAD(bug("[GPT] Added partition %u (%s), handle 0x%p\n", i
, gph
->name
, gph
));
377 err
= ERROR_NO_FREE_STORE
;
381 /* Jump to next entry, skip 'entrysize' bytes */
382 p
= (APTR
)p
+ entrysize
;
389 FreeMem(table
, tablesize
);
395 static void PartitionGPTClosePartitionTable(struct Library
*PartitionBase
, struct PartitionHandle
*root
)
397 struct PartitionHandle
*ph
, *ph2
;
399 /* Free all partition entries */
400 ForeachNodeSafe(&root
->table
->list
, ph
, ph2
)
403 FreeMem(root
->table
->data
, root
->de
.de_SizeBlock
<<2);
406 static LONG
PartitionGPTOpenPartitionTable(struct Library
*PartitionBase
, struct PartitionHandle
*root
)
411 * The header is attached to partition table handle.
412 * This allows us to write back the complete header and keep
413 * data we don't know about in future GPT revisions.
415 root
->table
->data
= AllocMem(root
->de
.de_SizeBlock
<< 2, MEMF_ANY
);
416 if (!root
->table
->data
)
417 return ERROR_NO_FREE_STORE
;
419 /* Read primary GPT table */
420 res
= GPTReadPartitionTable(PartitionBase
, root
, root
->table
->data
, 1);
422 if (res
== ERROR_BAD_CRC
)
424 /* If CRC failed, read backup table */
425 struct GPTHeader
*hdr
= root
->table
->data
;
426 UQUAD block
= AROS_LE2QUAD(hdr
->BackupBlock
);
428 res
= GPTReadPartitionTable(PartitionBase
, root
, hdr
, block
);
430 /* There's no third backup... */
431 if (res
== ERROR_BAD_CRC
)
432 res
= ERROR_NOT_A_DOS_DISK
;
435 /* Cleanup if reading failed */
437 PartitionGPTClosePartitionTable(PartitionBase
, root
);
442 static LONG
GPTWriteTable(struct Library
*PartitionBase
, struct PartitionHandle
*root
, struct GPTHeader
*hdr
, struct GPTPartition
*table
,
443 UQUAD headerblk
, UQUAD backupblk
, UQUAD startblk
, ULONG tablesize
)
448 hdr
->CurrentBlock
= AROS_QUAD2LE(headerblk
);
449 hdr
->BackupBlock
= AROS_QUAD2LE(backupblk
);
450 hdr
->StartBlock
= AROS_QUAD2LE(startblk
);
451 hdr
->HeaderCRC32
= 0;
453 /* We modify the header, so we have to recalculate its CRC */
454 crc
= Crc32_ComputeBuf(0, hdr
, AROS_LE2LONG(hdr
->HeaderSize
));
455 hdr
->HeaderCRC32
= AROS_LONG2LE(crc
);
456 DWRITE(bug("[GPT] New header CRC 0x%08X\n", crc
));
458 DWRITE(KPrintF("[GPT] Write data: start block %llu\n", startblk
));
459 res
= WritePartitionDataQ(root
, table
, tablesize
, startblk
);
461 DWRITE(bug("[GPT] Write result: %u\n", res
));
464 DWRITE(KPrintF("[GPT] Write header: start block %llu\n", headerblk
));
465 res
= PartitionWriteBlock(PartitionBase
, root
, headerblk
, hdr
);
467 DWRITE(bug("[GPT] Write result: %u\n", res
));
470 return deviceError(res
);
473 static LONG
PartitionGPTWritePartitionTable(struct Library
*PartitionBase
, struct PartitionHandle
*root
)
475 struct GPTHeader
*hdr
= root
->table
->data
;
476 ULONG cnt
= AROS_LE2LONG(hdr
->NumEntries
);
477 ULONG entrysize
= AROS_LE2LONG(hdr
->EntrySize
);
478 ULONG tablesize
= AROS_ROUNDUP2(entrysize
* cnt
, root
->de
.de_SizeBlock
<< 2);
479 struct GPTPartition
*table
;
482 * TODO: Update legacy MBR data here when adding/moving is implemented. IntelMacs have
483 * legacy MBR filled in with copies of four first entries in GPT table if at least one
484 * FAT partition is defined on the drive (to support Windows XP).
485 * CHS data for these entries is always set to (c=1023, h=254, s=63), however start and end
486 * block numbers reflect the real position. Apple's disk utility always keeps these entries
487 * in sync with their respective GPT entries. We need to do the same. Also remember to keep
488 * boot code in sector 0.
491 DWRITE(bug("[GPT] Write: %u entries per %u bytes, %u bytes total\n", cnt
, entrysize
, tablesize
));
493 /* Allocate buffer for the whole table */
494 table
= AllocMem(tablesize
, MEMF_CLEAR
);
498 struct GPTPartition
*p
= table
;
499 struct GPTPartitionHandle
*gph
;
504 * Collect our entries and build up the whole table.
505 * At this point we are guaranteed to have no more entries than
506 * can fit into reserved space. It's AddPartition()'s job to ensure this.
508 ForeachNode(&root
->table
->list
, gph
)
510 DWRITE(bug("[GPT] Writing partition %s, handle 0x%p\n", gph
->name
, gph
));
513 * Put our entry into the buffer.
514 * Use entry's own length, because if this entry is created by AddPartition(),
515 * it can be shorter than on-disk one (if someone uses extended length we don't know about).
517 CopyMem(&gph
[1], p
, gph
->entrySize
);
519 DWRITE(PRINT_LE_UUID("Type ", &p
->TypeID
));
520 DWRITE(PRINT_LE_UUID("Partition", &p
->PartitionID
));
521 DWRITE(KPrintF("[GPT] Blocks %llu - %llu\n", AROS_LE2QUAD(p
->StartBlock
), AROS_LE2QUAD(p
->EndBlock
)));
522 DWRITE(KPrintF("[GPT] Flags 0x%08lX 0x%08lX\n", AROS_LE2LONG(p
->Flags0
), AROS_LE2LONG(p
->Flags1
)));
523 DWRITE(KPrintF("[GPT] Offset 0x%p\n", (APTR
)p
- (APTR
)table
));
525 /* Jump to next entry */
526 p
= (APTR
)p
+ entrysize
;
529 crc
= Crc32_ComputeBuf(0, table
, entrysize
* cnt
);
530 hdr
->PartCRC32
= AROS_LONG2LE(crc
);
531 DWRITE(bug("[GPT] New data CRC 0x%08X\n", crc
));
533 /* First we attempt to write a backup table. It's placed in the end. */
534 backup
= root
->dg
.dg_TotalSectors
- 1;
535 res
= GPTWriteTable(PartitionBase
, root
, hdr
, table
, backup
, 1, backup
- tablesize
, tablesize
);
540 * And only if succeeded, write a primary one.
541 * This gives us a chance to discard writing if something goes wrong with disk/device/whatever.
543 res
= GPTWriteTable(PartitionBase
, root
, hdr
, table
, 1, backup
, 2, tablesize
);
546 FreeMem(table
, tablesize
);
551 return ERROR_NO_FREE_STORE
;
554 static LONG
PartitionGPTGetPartitionAttr(struct Library
*PartitionBase
, struct PartitionHandle
*ph
, struct TagItem
*tag
)
556 struct GPTPartition
*part
= (APTR
)ph
+ sizeof(struct GPTPartitionHandle
);
561 uuid_from_le((uuid_t
*)tag
->ti_Data
, &part
->TypeID
);
562 PTYPE(tag
->ti_Data
)->id_len
= sizeof(uuid_t
);
566 /* This extra flag is valid only for AROS partitions */
567 if (is_aros_uuid_le(&part
->TypeID
))
568 *((ULONG
*)tag
->ti_Data
) = (AROS_LE2LONG(part
->Flags1
) & GPT_PF1_AROS_BOOTABLE
) ? TRUE
: FALSE
;
570 *((ULONG
*)tag
->ti_Data
) = FALSE
;
574 *((ULONG
*)tag
->ti_Data
) = (AROS_LE2LONG(part
->Flags1
) & GPT_PF1_NOMOUNT
) ? FALSE
: TRUE
;
578 *((ULONG
*)tag
->ti_Data
) = AROS_LE2LONG(part
->StartBlock
);
582 *((ULONG
*)tag
->ti_Data
) = AROS_LE2LONG(part
->EndBlock
);
589 static LONG
PartitionGPTSetPartitionAttrs(struct Library
*PartitionBase
, struct PartitionHandle
*ph
, const struct TagItem
*taglist
)
591 struct GPTPartition
*part
= (APTR
)ph
+ sizeof(struct GPTPartitionHandle
);
593 struct TagItem
*bootable
= NULL
;
595 while ((tag
= NextTagItem((struct TagItem
**)&taglist
)))
600 strncpy(GPTH(ph
)->name
, (char *)tag
->ti_Data
, 36);
601 ToUTF16(part
->Name
, GPTH(ph
)->name
, 36);
605 /* Foolproof check */
606 if (PTYPE(tag
->ti_Data
)->id_len
== sizeof(uuid_t
))
608 uuid_to_le(&part
->TypeID
, (uuid_t
*)tag
->ti_Data
);
609 /* Update DOSType according to a new type ID */
610 GPT_PatchDosEnvec(&ph
->de
, part
);
619 D(bug("[GPT] Setting automount flag to %ld\n", tag
->ti_Data
));
620 D(bug("[GPT] Partition handle 0x%p, flags 0x%08X\n", ph
, part
->Flags1
));
623 part
->Flags1
&= ~AROS_LONG2LE(GPT_PF1_NOMOUNT
);
625 part
->Flags1
|= AROS_LONG2LE(GPT_PF1_NOMOUNT
);
627 D(bug("[GPT] New flags: 0x%08X\n", part
->Flags1
));
631 /* TODO: implement the rest (geometry, dosenvec, start/end block) */
636 * Now check bootable attribute.
637 * It is applicable only to AROS partitions, so we check it here,
638 * after possible type change.
640 if (bootable
&& is_aros_uuid_le(&part
->TypeID
))
642 if (bootable
->ti_Data
)
643 part
->Flags1
|= AROS_LONG2LE(GPT_PF1_AROS_BOOTABLE
);
645 part
->Flags1
&= ~AROS_LONG2LE(GPT_PF1_AROS_BOOTABLE
);
651 static ULONG
PartitionGPTDestroyPartitionTable(struct Library
*PartitionBase
,
652 struct PartitionHandle
*root
)
654 return PartitionMBRDestroyPartitionTable(PartitionBase
, root
);
657 static const struct PartitionAttribute PartitionGPTPartitionTableAttrs
[]=
659 {PTT_TYPE
, PLAM_READ
},
663 static const struct PartitionAttribute PartitionGPTPartitionAttrs
[]=
665 {PT_GEOMETRY
, PLAM_READ
},
666 {PT_TYPE
, PLAM_READ
|PLAM_WRITE
},
667 {PT_POSITION
, PLAM_READ
},
668 {PT_NAME
, PLAM_READ
|PLAM_WRITE
},
669 {PT_BOOTABLE
, PLAM_READ
|PLAM_WRITE
},
670 {PT_AUTOMOUNT
, PLAM_READ
|PLAM_WRITE
},
671 {PT_STARTBLOCK
, PLAM_READ
},
672 {PT_ENDBLOCK
, PLAM_READ
},
676 const struct PTFunctionTable PartitionGPT
=
680 PartitionGPTCheckPartitionTable
,
681 PartitionGPTOpenPartitionTable
,
682 PartitionGPTClosePartitionTable
,
683 PartitionGPTWritePartitionTable
,
689 PartitionGPTGetPartitionAttr
,
690 PartitionGPTSetPartitionAttrs
,
691 PartitionGPTPartitionTableAttrs
,
692 PartitionGPTPartitionAttrs
,
693 PartitionGPTDestroyPartitionTable
,