| /**************************************************************************** |
| * apps/fsutils/mkgpt/mkgpt.c |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| * |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. The |
| * ASF licenses this file to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance with the |
| * License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
| * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
| * License for the specific language governing permissions and limitations |
| * under the License. |
| * |
| ****************************************************************************/ |
| |
| /**************************************************************************** |
| * Included Files |
| ****************************************************************************/ |
| |
| #include <fcntl.h> |
| #include <inttypes.h> |
| #include <stdbool.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <stdint.h> |
| #include <unistd.h> |
| #include <string.h> |
| #include <debug.h> |
| |
| #include <sys/stat.h> |
| #include <sys/ioctl.h> |
| |
| #include <nuttx/crc32.h> |
| |
| /**************************************************************************** |
| * Pre-processor Definitions |
| ****************************************************************************/ |
| |
| #ifndef FAR |
| #define FAR |
| #endif |
| |
| #define EFI_VERSION 0x00010000 |
| #define EFI_MAGIC "EFI PART" |
| #define EFI_ENTRIES 128 |
| #define EFI_NAMELEN 36 |
| |
| /**************************************************************************** |
| * Private Types |
| ****************************************************************************/ |
| |
| struct efi_header_s |
| { |
| uint8_t magic[8]; |
| uint32_t version; |
| uint32_t header_sz; |
| uint32_t crc32; |
| uint32_t reserved; |
| uint64_t header_lba; |
| uint64_t backup_lba; |
| uint64_t first_lba; |
| uint64_t last_lba; |
| uint8_t volume_uuid[16]; |
| uint64_t entries_lba; |
| uint32_t entries_count; |
| uint32_t entries_size; |
| uint32_t entries_crc32; |
| } |
| __attribute__ ((packed)); |
| |
| struct efi_entry_s |
| { |
| uint8_t type_uuid[16]; |
| uint8_t uniq_uuid[16]; |
| uint64_t first_lba; |
| uint64_t last_lba; |
| uint64_t attr; |
| uint16_t name[EFI_NAMELEN]; |
| }; |
| |
| struct efi_ptable_s |
| { |
| uint8_t mbr[512]; |
| union |
| { |
| struct efi_header_s header; |
| uint8_t block[512]; |
| }; |
| |
| struct efi_entry_s entry[EFI_ENTRIES]; |
| }; |
| |
| /**************************************************************************** |
| * Private Data |
| ****************************************************************************/ |
| |
| static const uint8_t g_partition_type_uuid[16] = |
| { |
| 0xa2, 0xa0, 0xd0, 0xeb, 0xe5, 0xb9, 0x33, 0x44, |
| 0x87, 0xc0, 0x68, 0xb6, 0xb7, 0x26, 0x99, 0xc7, |
| }; |
| |
| static const uint8_t g_partition_type_efi[16] = |
| { |
| 0x28, 0x73, 0x2a, 0xc1, 0x1f, 0xf8, 0xd2, 0x11, |
| 0xba, 0x4b, 0x00, 0xa0, 0xc9, 0x3e, 0xc9, 0x3b, |
| }; |
| |
| static const uint8_t g_partition_type_linux[16] = |
| { |
| 0xa2, 0xa0, 0xd0, 0xeb, 0xe5, 0xb9, 0x33, 0x44, |
| 0x87, 0xc0, 0x68, 0xb6, 0xb7, 0x26, 0x99, 0xc7, |
| }; |
| |
| static const uint8_t g_partition_type_swap[16] = |
| { |
| 0x6d, 0xfd, 0x57, 0x06, 0xab, 0xa4, 0xc4, 0x43, |
| 0x84, 0xe5, 0x09, 0x33, 0xc8, 0x4b, 0x4f, 0x4f, |
| }; |
| |
| /**************************************************************************** |
| * Private Functions |
| ****************************************************************************/ |
| |
| static void get_uuid(FAR uint8_t *uuid) |
| { |
| arc4random_buf(uuid, 16); |
| } |
| |
| static void init_mbr(FAR uint8_t *mbr, uint32_t blocks) |
| { |
| /* nonbootable */ |
| |
| mbr[0x1be] = 0x00; |
| |
| /* bogus CHS */ |
| |
| mbr[0x1bf] = 0x00; |
| mbr[0x1c0] = 0x01; |
| mbr[0x1c1] = 0x00; |
| |
| /* GPT partition */ |
| |
| mbr[0x1c2] = 0xee; |
| |
| /* bogus CHS */ |
| |
| mbr[0x1c3] = 0xfe; |
| mbr[0x1c4] = 0xff; |
| mbr[0x1c5] = 0xff; |
| |
| /* start */ |
| |
| mbr[0x1c6] = 0x01; |
| mbr[0x1c7] = 0x00; |
| mbr[0x1c8] = 0x00; |
| mbr[0x1c9] = 0x00; |
| |
| memcpy(mbr + 0x1ca, &blocks, sizeof(uint32_t)); |
| |
| /* signature */ |
| |
| mbr[0x1fe] = 0x55; |
| mbr[0x1ff] = 0xaa; |
| } |
| |
| int add_ptn(FAR struct efi_ptable_s *ptbl, uint64_t first, uint64_t last, |
| FAR const char *name, FAR const uint8_t *type) |
| { |
| FAR struct efi_header_s *hdr = &ptbl->header; |
| FAR struct efi_entry_s *entry = ptbl->entry; |
| int n; |
| |
| if (first < 34) |
| { |
| fprintf(stderr, "partition '%s' overlaps partition table\n", name); |
| return -1; |
| } |
| |
| if (last > hdr->last_lba) |
| { |
| fprintf(stderr, "partition '%s' does not fit on disk\n", name); |
| return -1; |
| } |
| |
| if (hdr->entries_count > EFI_ENTRIES) |
| { |
| fprintf(stderr, "out of partition table entries\n"); |
| return -1; |
| } |
| |
| memcpy(entry[hdr->entries_count].type_uuid, type, 16); |
| get_uuid(entry[hdr->entries_count].uniq_uuid); |
| entry[hdr->entries_count].first_lba = first; |
| entry[hdr->entries_count].last_lba = last; |
| entry[hdr->entries_count].attr = 1ull << 63; |
| for (n = 0; n < EFI_NAMELEN && *name; n++) |
| { |
| entry[hdr->entries_count].name[n] = *name++; |
| } |
| |
| hdr->entries_count++; |
| return 0; |
| } |
| |
| static int usage(void) |
| { |
| fprintf(stderr, |
| "usage: mkgpt write <disk> [ <partition> ]*\n" |
| " mkgpt read <disk>\n" |
| "\n" |
| "partition: [<name>]:<size>[kmg][=linux|=swap|=efi]\n" |
| " or: @<file-of-partitions>\n" |
| ); |
| return 0; |
| } |
| |
| static void show(FAR struct efi_ptable_s *ptbl) |
| { |
| FAR struct efi_entry_s *entry = ptbl->entry; |
| char name[EFI_NAMELEN + 1]; |
| unsigned n; |
| unsigned m; |
| |
| fprintf(stderr, "ptn start block end block name\n"); |
| fprintf(stderr, "---- ------------- ------------- --------------------\n"); |
| |
| for (n = 0; n < ptbl->header.entries_count; n++) |
| { |
| for (m = 0; m < EFI_NAMELEN; m++) |
| { |
| name[m] = entry[n].name[m] & 127; |
| } |
| |
| name[m] = 0; |
| fprintf(stderr, "#%03d %13" PRIu64 " %13" PRIu64" %s\n", |
| n + 1, entry[n].first_lba, entry[n].last_lba, name); |
| } |
| } |
| |
| static uint64_t parse_size(FAR const char *sz) |
| { |
| size_t l = strlen(sz); |
| uint64_t n = strtoull(sz, 0, 0); |
| |
| if (l) |
| { |
| switch (sz[l - 1]) |
| { |
| case 'k': |
| case 'K': |
| n *= 1024; |
| break; |
| case 'm': |
| case 'M': |
| n *= 1024 * 1024; |
| break; |
| case 'g': |
| case 'G': |
| n *= 1024 * 1024 * 1024; |
| break; |
| } |
| } |
| |
| return n; |
| } |
| |
| static int parse_ptn(FAR struct efi_ptable_s *ptbl, FAR char *x, |
| FAR uint64_t *lba) |
| { |
| FAR const uint8_t *type = g_partition_type_uuid; |
| FAR char *y = strchr(x, ':'); |
| FAR char *z; |
| uint64_t sz; |
| |
| if (!y) |
| { |
| fprintf(stderr, "invalid partition entry: %s\n", x); |
| return -1; |
| } |
| |
| *y++ = 0; |
| z = strchr(y, '='); |
| if (z) |
| { |
| *z++ = 0; |
| if (!strcmp(z, "efi")) |
| { |
| type = g_partition_type_efi; |
| } |
| else if (!strcmp(z, "linux")) |
| { |
| type = g_partition_type_linux; |
| } |
| else if (!strcmp(z, "swap")) |
| { |
| type = g_partition_type_swap; |
| } |
| } |
| |
| if (*y == '0') |
| { |
| sz = ptbl->header.last_lba - *lba; |
| } |
| else |
| { |
| sz = parse_size(y); |
| if (sz & 511) |
| { |
| fprintf(stderr, "partition size must be multiple of 512\n"); |
| return -1; |
| } |
| |
| sz /= 512; |
| } |
| |
| if (sz == 0) |
| { |
| fprintf(stderr, "zero size partitions not allowed\n"); |
| return -1; |
| } |
| |
| if (x[0] && add_ptn(ptbl, *lba, *lba + sz - 1, x, type)) |
| { |
| return -1; |
| } |
| |
| *lba += sz; |
| return 0; |
| } |
| |
| static void update_crc32(FAR struct efi_ptable_s *ptbl) |
| { |
| uint32_t n; |
| |
| n = crc32part((FAR const uint8_t *)ptbl->entry, |
| sizeof(ptbl->entry[0]) * |
| ptbl->header.entries_count, ~0l) ^ ~0l; |
| ptbl->header.entries_crc32 = n; |
| |
| ptbl->header.crc32 = 0; |
| n = crc32part((FAR const uint8_t *)&ptbl->header, |
| sizeof(ptbl->header), ~0l) ^ ~0l; |
| ptbl->header.crc32 = n; |
| } |
| |
| static int verify_gpt_pratition(FAR struct efi_ptable_s *ptbl) |
| { |
| FAR struct efi_header_s *hdr = &ptbl->header; |
| uint32_t orgcrc; |
| |
| if ((ptbl->mbr[0x1c2] != 0xee && ptbl->mbr[0x1c2] != 0x00) || |
| ptbl->mbr[0x1fe] != 0x55 || ptbl->mbr[0x1ff] != 0xaa) |
| { |
| fprintf(stderr, "Invalid Protective or Hybrid MBR(%x)\n", |
| ptbl->mbr[0x1c2]); |
| return -1; |
| } |
| |
| if (memcmp(hdr->magic, EFI_MAGIC, sizeof(hdr->magic))) |
| { |
| fprintf(stderr, "GPT: Header signature is wrong\n"); |
| return -1; |
| } |
| |
| orgcrc = hdr->crc32; |
| hdr->crc32 = 0; |
| if (orgcrc != (crc32part((FAR const uint8_t *)hdr, |
| sizeof(*hdr), ~0l) ^ ~0l)) |
| { |
| fprintf(stderr, "GPT: Header CRC is wrong\n"); |
| return -1; |
| } |
| |
| hdr->crc32 = orgcrc; |
| if (hdr->header_lba != 1) |
| { |
| fprintf(stderr, "GPT: Header starting lba incorrect\n"); |
| return -1; |
| } |
| |
| if (hdr->entries_crc32 != (crc32part((FAR const uint8_t *)ptbl->entry, |
| sizeof(ptbl->entry[0]) * ptbl->header.entries_count, ~0l) ^ ~0l)) |
| { |
| fprintf(stderr, "GPT: Header entry signature is wrong\n"); |
| return -1; |
| } |
| |
| show(ptbl); |
| return 0; |
| } |
| |
| /**************************************************************************** |
| * Public Functions |
| ****************************************************************************/ |
| |
| int main(int argc, FAR char **argv) |
| { |
| FAR struct efi_header_s *hdr; |
| FAR struct efi_ptable_s *ptbl; |
| FAR const char *device; |
| struct stat s; |
| uint64_t sz; |
| uint64_t lba; |
| uint64_t off; |
| int ret = -1; |
| int fd; |
| bool verify = false; |
| |
| if (argc < 2) |
| { |
| return usage(); |
| } |
| |
| device = argv[2]; |
| if (!strcmp(argv[1], "write") && argc >= 3) |
| { |
| argc -= 2; |
| argv += 2; |
| } |
| else if (!strcmp(argv[1], "read")) |
| { |
| verify = true; |
| } |
| else |
| { |
| return usage(); |
| } |
| |
| fd = open(device, O_RDWR); |
| if (fd < 0) |
| { |
| fprintf(stderr, "error: cannot open '%s'\n", device); |
| return ret; |
| } |
| |
| if (fstat(fd, &s)) |
| { |
| fprintf(stderr, "error: cannot stat '%s'\n", device); |
| goto err; |
| } |
| |
| sz = s.st_size; |
| if (sz & 511) |
| { |
| fprintf(stderr, "error: file size not multiple of 512\n"); |
| goto err; |
| } |
| |
| sz /= 512; |
| ptbl = calloc(1, sizeof(*ptbl)); |
| if (!ptbl) |
| { |
| goto err; |
| } |
| |
| if (verify) |
| { |
| if (read(fd, ptbl, sizeof(*ptbl)) != sizeof(*ptbl)) |
| { |
| fprintf(stderr, "error read primary partition table\n"); |
| goto out; |
| } |
| |
| ret = verify_gpt_pratition(ptbl); |
| if (ret < 0) |
| { |
| lib_dumpbuffer("Dump GPT partition:", (FAR uint8_t *)ptbl, |
| sizeof(*ptbl)); |
| } |
| |
| goto out; |
| } |
| |
| init_mbr(ptbl->mbr, sz - 1); |
| hdr = &ptbl->header; |
| memcpy(hdr->magic, EFI_MAGIC, sizeof(hdr->magic)); |
| hdr->version = EFI_VERSION; |
| hdr->header_sz = sizeof(struct efi_header_s); |
| hdr->header_lba = 1; |
| hdr->backup_lba = sz - 1; |
| hdr->first_lba = 34; |
| hdr->last_lba = sz - 33; |
| get_uuid(hdr->volume_uuid); |
| hdr->entries_lba = 2; |
| hdr->entries_count = 0; |
| hdr->entries_size = sizeof(struct efi_entry_s); |
| lba = hdr->first_lba; |
| |
| while (argc > 1) |
| { |
| if (argv[1][0] == '@') |
| { |
| FAR char *p; |
| FAR FILE *f; |
| char line[256]; |
| |
| f = fopen(argv[1] + 1, "r"); |
| if (!f) |
| { |
| fprintf(stderr, "cannot read partitions from '%s\n", argv[1]); |
| goto out; |
| } |
| |
| while (fgets(line, sizeof(line), f)) |
| { |
| p = line + strlen(line); |
| while (p > line) |
| { |
| if (*--p > ' ') |
| { |
| break; |
| } |
| |
| *p = 0; |
| } |
| |
| p = line; |
| while (*p && (*p <= ' ')) |
| { |
| p++; |
| } |
| |
| if (*p == '#') |
| { |
| continue; |
| } |
| |
| if (*p == 0) |
| { |
| continue; |
| } |
| |
| if (parse_ptn(ptbl, p, &lba)) |
| { |
| fclose(f); |
| goto out; |
| } |
| } |
| |
| fclose(f); |
| } |
| else |
| { |
| if (parse_ptn(ptbl, argv[1], &lba)) |
| { |
| goto out; |
| } |
| } |
| |
| argc--; |
| argv++; |
| } |
| |
| update_crc32(ptbl); |
| show(ptbl); |
| off = (sz - 33) * 512; |
| if (write(fd, ptbl, sizeof(*ptbl)) != sizeof(*ptbl)) |
| { |
| fprintf(stderr, "error writing primary partition table\n"); |
| goto out; |
| } |
| |
| if (lseek(fd, off, SEEK_SET) != off) |
| { |
| fprintf(stderr, "error seeking to secondary partition table\n"); |
| goto out; |
| } |
| |
| /* reverse these for the secondary copy */ |
| |
| hdr->header_lba = sz -1; |
| hdr->backup_lba = 1; |
| update_crc32(ptbl); |
| |
| if (write(fd, &ptbl->entry, sizeof(ptbl->entry)) != |
| sizeof(ptbl->entry)) |
| { |
| fprintf(stderr, "error writing secondary partition table\n"); |
| goto out; |
| } |
| |
| if (write(fd, &ptbl->header, 512) != 512) |
| { |
| fprintf(stderr, "error writing secondary partition header\n"); |
| goto out; |
| } |
| |
| fsync(fd); |
| |
| out: |
| free(ptbl); |
| err: |
| close(fd); |
| return ret; |
| } |