// Copyright 2016 The Fuchsia Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include #include #include // for zx_cprng_draw #include #include #include #include #include #include #include #include #include #include #include "gpt/gpt.h" namespace libgpt { namespace { #define G_PRINTF(f, ...) \ if (debug_out) \ printf((f), ##__VA_ARGS__); bool debug_out = false; struct mbr_partition_t { uint8_t status; uint8_t chs_first[3]; uint8_t type; uint8_t chs_last[3]; uint32_t lba; uint32_t sectors; }; // Since the human-readable representation of a GUID is the following format, // ordered little-endian, it is useful to group a GUID into these // appropriately-sized groups. struct guid_t { uint32_t data1; uint16_t data2; uint16_t data3; uint8_t data4[8]; }; static_assert(sizeof(gpt_header_t) == GPT_HEADER_SIZE, "unexpected gpt header size"); static_assert(sizeof(gpt_partition_t) == GPT_ENTRY_SIZE, "unexpected gpt entry size"); struct gpt_priv_t { // device to use int fd; // block size in bytes uint64_t blocksize; // number of blocks uint64_t blocks; // true if valid mbr exists on disk bool mbr; // header buffer, should be primary copy gpt_header_t header; // partition table buffer gpt_partition_t ptable[PARTITIONS_COUNT]; // copy of buffer from when last init'd or sync'd. gpt_partition_t backup[PARTITIONS_COUNT]; gpt_device_t device; }; #define get_priv(dev) ((gpt_priv_t*)((uintptr_t)(dev)-offsetof(gpt_priv_t, device))) void print_array(gpt_partition_t* const a[PARTITIONS_COUNT], int c) { char GUID[GPT_GUID_STRLEN]; char name[GPT_NAME_LEN / 2 + 1]; for (int i = 0; i < c; ++i) { uint8_to_guid_string(GUID, a[i]->type); memset(name, 0, GPT_NAME_LEN / 2 + 1); utf16_to_cstring(name, (const uint16_t*)a[i]->name, GPT_NAME_LEN / 2); printf("Name: %s \n Start: %lu -- End: %lu \nType: %s\n", name, a[i]->first, a[i]->last, GUID); } } void partition_init(gpt_partition_t* part, const char* name, const uint8_t* type, const uint8_t* guid, uint64_t first, uint64_t last, uint64_t flags) { memcpy(part->type, type, sizeof(part->type)); memcpy(part->guid, guid, sizeof(part->guid)); part->first = first; part->last = last; part->flags = flags; cstring_to_utf16((uint16_t*)part->name, name, sizeof(part->name) / sizeof(uint16_t)); } int gpt_sync_current(int fd, uint64_t blocksize, gpt_header_t* header, gpt_partition_t* ptable) { // write partition table first off_t rc = lseek(fd, header->entries * blocksize, SEEK_SET); if (rc < 0) { return -1; } size_t ptable_size = header->entries_count * header->entries_size; rc = write(fd, ptable, ptable_size); if (rc < 0 || (size_t)rc != ptable_size) { return -1; } // then write the header rc = lseek(fd, header->current * blocksize, SEEK_SET); if (rc < 0) { return -1; } uint8_t block[blocksize]; memset(block, 0, sizeof(blocksize)); memcpy(block, header, sizeof(*header)); rc = write(fd, block, blocksize); if (rc != (ssize_t)blocksize) { return -1; } return 0; } int gpt_device_finalize_and_sync(gpt_device_t* dev, bool persist) { gpt_priv_t* priv = get_priv(dev); // write fake mbr if needed uint8_t mbr[priv->blocksize]; off_t rc; if (!priv->mbr) { memset(mbr, 0, priv->blocksize); mbr[0x1fe] = 0x55; mbr[0x1ff] = 0xaa; mbr_partition_t* mpart = (mbr_partition_t*)(mbr + 0x1be); mpart->chs_first[1] = 0x1; mpart->type = 0xee; // gpt protective mbr mpart->chs_last[0] = 0xfe; mpart->chs_last[1] = 0xff; mpart->chs_last[2] = 0xff; mpart->lba = 1; mpart->sectors = priv->blocks & 0xffffffff; rc = lseek(priv->fd, 0, SEEK_SET); if (rc < 0) { return -1; } rc = write(priv->fd, mbr, priv->blocksize); if (rc < 0 || (size_t)rc != priv->blocksize) { return -1; } priv->mbr = true; } // fill in the new header fields gpt_header_t header; memset(&header, 0, sizeof(header)); header.magic = GPT_MAGIC; header.revision = 0x00010000; // gpt version 1.0 header.size = GPT_HEADER_SIZE; if (dev->valid) { header.current = priv->header.current; header.backup = priv->header.backup; memcpy(header.guid, priv->header.guid, 16); } else { header.current = 1; // backup gpt is in the last block header.backup = priv->blocks - 1; // generate a guid zx_cprng_draw(header.guid, GPT_GUID_LEN); } // always write 128 entries in partition table size_t ptable_size = PARTITIONS_COUNT * sizeof(gpt_partition_t); gpt_partition_t* buf = static_cast(malloc(ptable_size)); if (!buf) { return -1; } memset(buf, 0, ptable_size); // generate partition table uint8_t* ptr = reinterpret_cast(buf); int i; gpt_partition_t** p; for (i = 0, p = dev->partitions; i < PARTITIONS_COUNT && *p; i++, p++) { memcpy(ptr, *p, GPT_ENTRY_SIZE); ptr += GPT_ENTRY_SIZE; } // fill in partition table fields in header header.entries = dev->valid ? priv->header.entries : 2; header.entries_count = PARTITIONS_COUNT; header.entries_size = GPT_ENTRY_SIZE; header.entries_crc = crc32(0, reinterpret_cast(buf), ptable_size); uint64_t ptable_blocks = ptable_size / priv->blocksize; header.first = header.entries + ptable_blocks; header.last = header.backup - ptable_blocks - 1; // calculate header checksum header.crc32 = crc32(0, (const unsigned char*)&header, GPT_HEADER_SIZE); // the copy cached in priv is the primary copy memcpy(&priv->header, &header, sizeof(header)); // the header copy on stack is now the backup copy... header.current = priv->header.backup; header.backup = priv->header.current; header.entries = priv->header.last + 1; header.crc32 = 0; header.crc32 = crc32(0, (const unsigned char*)&header, GPT_HEADER_SIZE); if (persist) { // write backup to disk rc = gpt_sync_current(priv->fd, priv->blocksize, &header, buf); if (rc < 0) { goto fail; } // write primary copy to disk rc = gpt_sync_current(priv->fd, priv->blocksize, &priv->header, buf); if (rc < 0) { goto fail; } } // align backup with new on-disk state memcpy(priv->backup, priv->ptable, sizeof(priv->ptable)); dev->valid = true; free(buf); return 0; fail: free(buf); return -1; } int compare(const void* ls, const void* rs) { const auto* l = *static_cast(ls); const auto* r = *static_cast(rs); if (l == NULL && r == NULL) { return 0; } if (l == NULL) { return 1; } if (r == NULL) { return -1; } if (l->first == r->first) { return 0; } return (l->first < r->first) ? -1 : 1; } } // namespace __BEGIN_CDECLS void gpt_set_debug_output_enabled(bool enabled) { debug_out = enabled; } void cstring_to_utf16(uint16_t* dst, const char* src, size_t maxlen) { size_t len = strlen(src); if (len > maxlen) len = maxlen; for (size_t i = 0; i < len; i++) { *dst++ = (uint16_t)(*src++ & 0x7f); } } char* utf16_to_cstring(char* dst, const uint16_t* src, size_t len) { size_t i = 0; char* ptr = dst; while (i < len) { char c = src[i++] & 0x7f; if (!c) continue; *ptr++ = c; } return dst; } void print_table(const gpt_device_t* device) { int count = 0; for (; device->partitions[count] != NULL; ++count) ; print_array(device->partitions, count); } bool gpt_is_sys_guid(uint8_t* guid, ssize_t len) { static const uint8_t sys_guid[GPT_GUID_LEN] = GUID_SYSTEM_VALUE; return len == GPT_GUID_LEN && !memcmp(guid, sys_guid, GPT_GUID_LEN); } bool gpt_is_data_guid(uint8_t* guid, ssize_t len) { static const uint8_t data_guid[GPT_GUID_LEN] = GUID_DATA_VALUE; return len == GPT_GUID_LEN && !memcmp(guid, data_guid, GPT_GUID_LEN); } bool gpt_is_install_guid(uint8_t* guid, ssize_t len) { static const uint8_t install_guid[GPT_GUID_LEN] = GUID_INSTALL_VALUE; return len == GPT_GUID_LEN && !memcmp(guid, install_guid, GPT_GUID_LEN); } bool gpt_is_efi_guid(uint8_t* guid, ssize_t len) { static const uint8_t efi_guid[GPT_GUID_LEN] = GUID_EFI_VALUE; return len == GPT_GUID_LEN && !memcmp(guid, efi_guid, GPT_GUID_LEN); } int gpt_get_diffs(const gpt_device_t* dev, int idx, unsigned* diffs) { gpt_priv_t* priv = get_priv(dev); *diffs = 0; if (idx >= PARTITIONS_COUNT) { return -1; } if (dev->partitions[idx] == NULL) { return -1; } gpt_partition_t* a = dev->partitions[idx]; gpt_partition_t* b = priv->backup + idx; if (memcmp(a->type, b->type, sizeof(a->type))) { *diffs |= GPT_DIFF_TYPE; } if (memcmp(a->guid, b->guid, sizeof(a->guid))) { *diffs |= GPT_DIFF_GUID; } if (a->first != b->first) { *diffs |= GPT_DIFF_FIRST; } if (a->last != b->last) { *diffs |= GPT_DIFF_LAST; } if (a->flags != b->flags) { *diffs |= GPT_DIFF_FLAGS; } if (memcmp(a->name, b->name, sizeof(a->name))) { *diffs |= GPT_DIFF_NAME; } return 0; } int gpt_device_init(int fd, uint32_t blocksize, uint64_t blocks, gpt_device_t** out_dev) { ssize_t ptable_size; uint32_t saved_crc, crc; gpt_partition_t* ptable; gpt_header_t* header; off_t rc; gpt_priv_t* priv = static_cast(calloc(1, sizeof(gpt_priv_t))); if (!priv) return -1; priv->fd = fd; priv->blocksize = blocksize; priv->blocks = blocks; uint8_t block[blocksize]; if (priv->blocksize < 512) { G_PRINTF("blocksize < 512 not supported\n"); goto fail; } // Read protective MBR. rc = lseek(fd, 0, SEEK_SET); if (rc < 0) { goto fail; } rc = read(fd, block, blocksize); if (rc < 0 || (uint64_t)rc != blocksize) { goto fail; } priv->mbr = block[0x1fe] == 0x55 && block[0x1ff] == 0xaa; // read the gpt header (lba 1) rc = read(fd, block, blocksize); if (rc < 0 || (uint64_t)rc != blocksize) { goto fail; } header = &priv->header; memcpy(header, block, sizeof(*header)); // is this a valid gpt header? if (header->magic != GPT_MAGIC) { G_PRINTF("invalid header magic!\n"); goto out; // ok to have an invalid header } // header checksum saved_crc = header->crc32; header->crc32 = 0; crc = crc32(0, (const unsigned char*)header, header->size); if (crc != saved_crc) { G_PRINTF("header crc check failed\n"); goto out; } if (header->entries_count > PARTITIONS_COUNT) { G_PRINTF("too many partitions!\n"); goto out; } priv->device.valid = true; if (header->entries_count == 0) { goto out; } if (header->entries_count > PARTITIONS_COUNT) { G_PRINTF("too many partitions\n"); goto out; } ptable = priv->ptable; // read the partition table rc = lseek(fd, header->entries * blocksize, SEEK_SET); if (rc < 0) { goto fail; } ptable_size = header->entries_size * header->entries_count; if ((size_t)ptable_size > SIZE_MAX) { G_PRINTF("partition table too big\n"); goto out; } rc = read(fd, ptable, ptable_size); if (rc != ptable_size) { goto fail; } // partition table checksum crc = crc32(0, (const unsigned char*)ptable, ptable_size); if (crc != header->entries_crc) { G_PRINTF("table crc check failed\n"); goto out; } // save original state so we can know what we changed memcpy(priv->backup, priv->ptable, sizeof(priv->ptable)); // fill the table of valid partitions for (unsigned i = 0; i < header->entries_count; i++) { if (ptable[i].first == 0 && ptable[i].last == 0) continue; priv->device.partitions[i] = &ptable[i]; } out: *out_dev = &priv->device; return 0; fail: free(priv); return -1; } void gpt_device_release(gpt_device_t* dev) { gpt_priv_t* priv = get_priv(dev); free(priv); } int gpt_device_finalize(gpt_device_t* dev) { return gpt_device_finalize_and_sync(dev, false); } int gpt_device_sync(gpt_device_t* dev) { return gpt_device_finalize_and_sync(dev, true); } int gpt_device_range(const gpt_device_t* dev, uint64_t* block_start, uint64_t* block_end) { gpt_priv_t* priv = get_priv(dev); if (!dev->valid) { G_PRINTF("partition header invalid\n"); return -1; } // check range *block_start = priv->header.first; *block_end = priv->header.last; return 0; } int gpt_partition_add(gpt_device_t* dev, const char* name, const uint8_t* type, const uint8_t* guid, uint64_t offset, uint64_t blocks, uint64_t flags) { gpt_priv_t* priv = get_priv(dev); if (!dev->valid) { G_PRINTF("partition header invalid, sync to generate a default header\n"); return -1; } if (blocks == 0) { G_PRINTF("partition must be at least 1 block\n"); return -1; } uint64_t first = offset; uint64_t last = first + blocks - 1; // check range if (last < first || first < priv->header.first || last > priv->header.last) { G_PRINTF("partition must be in range of usable blocks[%" PRIu64 ", %" PRIu64 "]\n", priv->header.first, priv->header.last); return -1; } // check for overlap int i; int tail = -1; for (i = 0; i < PARTITIONS_COUNT; i++) { if (!dev->partitions[i]) { tail = i; break; } if (first <= dev->partitions[i]->last && last >= dev->partitions[i]->first) { G_PRINTF("partition range overlaps\n"); return -1; } } if (tail == -1) { G_PRINTF("too many partitions\n"); return -1; } // find a free slot gpt_partition_t* part = NULL; for (i = 0; i < PARTITIONS_COUNT; i++) { if (priv->ptable[i].first == 0 && priv->ptable[i].last == 0) { part = &priv->ptable[i]; break; } } assert(part); // insert the new element into the list partition_init(part, name, type, guid, first, last, flags); dev->partitions[tail] = part; return 0; } int gpt_partition_clear(gpt_device_t* dev, uint64_t offset, uint64_t blocks) { gpt_priv_t* priv = get_priv(dev); if (!dev->valid) { G_PRINTF("partition header invalid, sync to generate a default header\n"); return -1; } if (blocks == 0) { G_PRINTF("must clear at least 1 block\n"); return -1; } uint64_t first = offset; uint64_t last = offset + blocks - 1; if (last < first || first < priv->header.first || last > priv->header.last) { G_PRINTF("must clear in the range of usable blocks[%" PRIu64 ", %" PRIu64 "]\n", priv->header.first, priv->header.last); return -1; } char zero[priv->blocksize]; memset(zero, 0, sizeof(zero)); for (size_t i = first; i <= last; i++) { if (pwrite(priv->fd, zero, sizeof(zero), priv->blocksize * i) != (ssize_t)sizeof(zero)) { G_PRINTF("Failed to write to block %zu; errno: %d\n", i, errno); return -1; } } return 0; } int gpt_partition_remove(gpt_device_t* dev, const uint8_t* guid) { // look for the entry in the partition list int i; for (i = 0; i < PARTITIONS_COUNT; i++) { if (!memcmp(dev->partitions[i]->guid, guid, sizeof(dev->partitions[i]->guid))) { break; } } if (i == PARTITIONS_COUNT) { G_PRINTF("partition not found\n"); return -1; } // clear the entry memset(dev->partitions[i], 0, GPT_ENTRY_SIZE); // pack the partition list for (i = i + 1; i < PARTITIONS_COUNT; i++) { if (dev->partitions[i] == NULL) { dev->partitions[i - 1] = NULL; } else { dev->partitions[i - 1] = dev->partitions[i]; } } return 0; } int gpt_partition_remove_all(gpt_device_t* dev) { memset(dev->partitions, 0, sizeof(dev->partitions)); return 0; } void uint8_to_guid_string(char* dst, const uint8_t* src) { guid_t* guid = (guid_t*)src; sprintf(dst, "%08X-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X", guid->data1, guid->data2, guid->data3, guid->data4[0], guid->data4[1], guid->data4[2], guid->data4[3], guid->data4[4], guid->data4[5], guid->data4[6], guid->data4[7]); } void gpt_device_get_header_guid(const gpt_device_t* dev, uint8_t (*disk_guid_out)[GPT_GUID_LEN]) { gpt_header_t* header = &get_priv(dev)->header; memcpy(disk_guid_out, header->guid, GPT_GUID_LEN); } int gpt_device_read_gpt(int fd, gpt_device_t** gpt_out) { block_info_t info; ssize_t rc = ioctl_block_get_info(fd, &info); if (rc < 0) { return static_cast(rc); } if (info.block_size < 1) { return -1; } int result = gpt_device_init(fd, info.block_size, info.block_count, gpt_out); if (result < 0) { return result; } else if (!(*gpt_out)->valid) { gpt_device_release(*gpt_out); *gpt_out = NULL; return -1; } return 0; } void gpt_sort_partitions(gpt_partition_t** base, size_t count) { qsort(base, count, sizeof(gpt_partition_t*), compare); } const char* gpt_guid_to_type(const char* guid) { if (!strcmp("FE3A2A5D-4F32-41A7-B725-ACCC3285A309", guid)) { return "cros kernel"; } else if (!strcmp("3CB8E202-3B7E-47DD-8A3C-7FF2A13CFCEC", guid)) { return "cros rootfs"; } else if (!strcmp("2E0A753D-9E48-43B0-8337-B15192CB1B5E", guid)) { return "cros reserved"; } else if (!strcmp("CAB6E88E-ABF3-4102-A07A-D4BB9BE3C1D3", guid)) { return "cros firmware"; } else if (!strcmp("C12A7328-F81F-11D2-BA4B-00A0C93EC93B", guid)) { return "efi system"; } else if (!strcmp("EBD0A0A2-B9E5-4433-87C0-68B6B72699C7", guid)) { return "data"; } else if (!strcmp("21686148-6449-6E6F-744E-656564454649", guid)) { return "bios"; } else if (!strcmp(GUID_SYSTEM_STRING, guid)) { return "fuchsia-system"; } else if (!strcmp(GUID_DATA_STRING, guid)) { return "fuchsia-data"; } else if (!strcmp(GUID_INSTALL_STRING, guid)) { return "fuchsia-install"; } else if (!strcmp(GUID_BLOB_STRING, guid)) { return "fuchsia-blob"; } else if (!strcmp(GUID_FVM_STRING, guid)) { return "fuchsia-fvm"; } else if (!strcmp(GUID_ZIRCON_A_STRING, guid)) { return "zircon-a"; } else if (!strcmp(GUID_ZIRCON_B_STRING, guid)) { return "zircon-b"; } else if (!strcmp(GUID_ZIRCON_R_STRING, guid)) { return "zircon-r"; } else if (!strcmp(GUID_SYS_CONFIG_STRING, guid)) { return "sys-config"; } else if (!strcmp(GUID_FACTORY_CONFIG_STRING, guid)) { return "factory"; } else if (!strcmp(GUID_BOOTLOADER_STRING, guid)) { return "bootloader"; } else if (!strcmp(GUID_VBMETA_A_STRING, guid)) { return "vbmeta_a"; } else if (!strcmp(GUID_VBMETA_B_STRING, guid)) { return "vbmeta_b"; } else { return "unknown"; } } __END_CDECLS } // namespace libgpt