| /* pc.c - Read PC style partition tables. */ |
| /* |
| * GRUB -- GRand Unified Bootloader |
| * Copyright (C) 2002,2004,2005,2006,2007,2008,2009 Free Software Foundation, Inc. |
| * |
| * GRUB is free software: you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License as published by |
| * the Free Software Foundation, either version 3 of the License, or |
| * (at your option) any later version. |
| * |
| * GRUB is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| * |
| * You should have received a copy of the GNU General Public License |
| * along with GRUB. If not, see <http://www.gnu.org/licenses/>. |
| */ |
| |
| #include <grub/partition.h> |
| #include <grub/msdos_partition.h> |
| #include <grub/disk.h> |
| #include <grub/mm.h> |
| #include <grub/misc.h> |
| #include <grub/dl.h> |
| #include <grub/i18n.h> |
| |
| GRUB_MOD_LICENSE ("GPLv3+"); |
| |
| static struct grub_partition_map grub_msdos_partition_map; |
| |
| |
| #ifdef GRUB_UTIL |
| #include <grub/emu/misc.h> |
| |
| struct embed_signature |
| { |
| const char *name; |
| const char *signature; |
| int signature_len; |
| enum { TYPE_SOFTWARE, TYPE_RAID } type; |
| }; |
| |
| const char message_warn[][200] = { |
| /* TRANSLATORS: MBR gap and boot track is the same thing and is the space |
| between MBR and first partitition. If your language translates well only |
| "boot track", you can just use it everywhere. Next two messages are about |
| RAID controllers/software bugs which GRUB has to live with. Please spread |
| the message that these are bugs in other software and not merely |
| suboptimal behaviour. */ |
| [TYPE_RAID] = N_("Sector %llu is already in use by raid controller `%s';" |
| " avoiding it. " |
| "Please ask the manufacturer not to store data in MBR gap"), |
| [TYPE_SOFTWARE] = N_("Sector %llu is already in use by the program `%s';" |
| " avoiding it. " |
| "This software may cause boot or other problems in " |
| "future. Please ask its authors not to store data " |
| "in the boot track") |
| }; |
| |
| |
| /* Signatures of other software that may be using sectors in the embedding |
| area. */ |
| struct embed_signature embed_signatures[] = |
| { |
| { |
| .name = "ZISD", |
| .signature = "ZISD", |
| .signature_len = 4, |
| .type = TYPE_SOFTWARE |
| }, |
| { |
| .name = "FlexNet", |
| .signature = "\xd4\x41\xa0\xf5\x03\x00\x03\x00", |
| .signature_len = 8, |
| .type = TYPE_SOFTWARE |
| }, |
| { |
| .name = "FlexNet", |
| .signature = "\xd8\x41\xa0\xf5\x02\x00\x02\x00", |
| .signature_len = 8, |
| .type = TYPE_SOFTWARE |
| }, |
| { |
| /* from Ryan Perkins */ |
| .name = "HP Backup and Recovery Manager (?)", |
| .signature = "\x70\x8a\x5d\x46\x35\xc5\x1b\x93" |
| "\xae\x3d\x86\xfd\xb1\x55\x3e\xe0", |
| .signature_len = 16, |
| .type = TYPE_SOFTWARE |
| }, |
| { |
| .name = "HighPoint RAID controller", |
| .signature = "ycgl", |
| .signature_len = 4, |
| .type = TYPE_RAID |
| }, |
| { |
| /* https://bugs.launchpad.net/bugs/987022 */ |
| .name = "Acer registration utility (?)", |
| .signature = "GREGRegDone.Tag\x00", |
| .signature_len = 16, |
| .type = TYPE_SOFTWARE |
| } |
| }; |
| #endif |
| |
| grub_err_t |
| grub_partition_msdos_iterate (grub_disk_t disk, |
| grub_partition_iterate_hook_t hook, |
| void *hook_data) |
| { |
| struct grub_partition p; |
| struct grub_msdos_partition_mbr mbr; |
| int labeln = 0; |
| grub_disk_addr_t lastaddr; |
| grub_disk_addr_t ext_offset; |
| grub_disk_addr_t delta = 0; |
| |
| if (disk->partition && disk->partition->partmap == &grub_msdos_partition_map) |
| { |
| if (disk->partition->msdostype == GRUB_PC_PARTITION_TYPE_LINUX_MINIX) |
| delta = disk->partition->start; |
| else |
| return grub_error (GRUB_ERR_BAD_PART_TABLE, "no embedding supported"); |
| } |
| |
| p.offset = 0; |
| ext_offset = 0; |
| p.number = -1; |
| p.partmap = &grub_msdos_partition_map; |
| |
| /* Any value different than `p.offset' will satisfy the check during |
| first loop. */ |
| lastaddr = !p.offset; |
| |
| while (1) |
| { |
| int i; |
| struct grub_msdos_partition_entry *e; |
| |
| /* Read the MBR. */ |
| if (grub_disk_read (disk, p.offset, 0, sizeof (mbr), &mbr)) |
| goto finish; |
| |
| /* If this is a GPT partition, this MBR is just a dummy. */ |
| if (p.offset == 0) |
| for (i = 0; i < 4; i++) |
| if (mbr.entries[i].type == GRUB_PC_PARTITION_TYPE_GPT_DISK) |
| return grub_error (GRUB_ERR_BAD_PART_TABLE, "dummy mbr"); |
| |
| /* This is our loop-detection algorithm. It works the following way: |
| It saves last position which was a power of two. Then it compares the |
| saved value with a current one. This way it's guaranteed that the loop |
| will be broken by at most third walk. |
| */ |
| if (labeln && lastaddr == p.offset) |
| return grub_error (GRUB_ERR_BAD_PART_TABLE, "loop detected"); |
| |
| labeln++; |
| if ((labeln & (labeln - 1)) == 0) |
| lastaddr = p.offset; |
| |
| /* Check if it is valid. */ |
| if (mbr.signature != grub_cpu_to_le16_compile_time (GRUB_PC_PARTITION_SIGNATURE)) |
| return grub_error (GRUB_ERR_BAD_PART_TABLE, "no signature"); |
| |
| for (i = 0; i < 4; i++) |
| if (mbr.entries[i].flag & 0x7f) |
| return grub_error (GRUB_ERR_BAD_PART_TABLE, "bad boot flag"); |
| |
| /* Analyze DOS partitions. */ |
| for (p.index = 0; p.index < 4; p.index++) |
| { |
| e = mbr.entries + p.index; |
| |
| p.start = p.offset |
| + (grub_le_to_cpu32 (e->start) |
| << (disk->log_sector_size - GRUB_DISK_SECTOR_BITS)) - delta; |
| p.len = grub_le_to_cpu32 (e->length) |
| << (disk->log_sector_size - GRUB_DISK_SECTOR_BITS); |
| p.msdostype = e->type; |
| |
| grub_dprintf ("partition", |
| "partition %d: flag 0x%x, type 0x%x, start 0x%llx, len 0x%llx\n", |
| p.index, e->flag, e->type, |
| (unsigned long long) p.start, |
| (unsigned long long) p.len); |
| |
| /* If this partition is a normal one, call the hook. */ |
| if (! grub_msdos_partition_is_empty (e->type) |
| && ! grub_msdos_partition_is_extended (e->type)) |
| { |
| p.number++; |
| |
| if (hook (disk, &p, hook_data)) |
| return grub_errno; |
| } |
| else if (p.number < 3) |
| /* If this partition is a logical one, shouldn't increase the |
| partition number. */ |
| p.number++; |
| } |
| |
| /* Find an extended partition. */ |
| for (i = 0; i < 4; i++) |
| { |
| e = mbr.entries + i; |
| |
| if (grub_msdos_partition_is_extended (e->type)) |
| { |
| p.offset = ext_offset |
| + (grub_le_to_cpu32 (e->start) |
| << (disk->log_sector_size - GRUB_DISK_SECTOR_BITS)); |
| if (! ext_offset) |
| ext_offset = p.offset; |
| |
| break; |
| } |
| } |
| |
| /* If no extended partition, the end. */ |
| if (i == 4) |
| break; |
| } |
| |
| finish: |
| return grub_errno; |
| } |
| |
| #ifdef GRUB_UTIL |
| |
| #pragma GCC diagnostic ignored "-Wformat-nonliteral" |
| |
| static grub_err_t |
| pc_partition_map_embed (struct grub_disk *disk, unsigned int *nsectors, |
| unsigned int max_nsectors, |
| grub_embed_type_t embed_type, |
| grub_disk_addr_t **sectors) |
| { |
| grub_disk_addr_t end = ~0ULL; |
| struct grub_msdos_partition_mbr mbr; |
| int labeln = 0; |
| /* Any value different than `p.offset' will satisfy the check during |
| first loop. */ |
| grub_disk_addr_t lastaddr = 1; |
| grub_disk_addr_t ext_offset = 0; |
| grub_disk_addr_t offset = 0; |
| |
| if (embed_type != GRUB_EMBED_PCBIOS) |
| return grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET, |
| "PC-style partitions currently support " |
| "only PC-BIOS embedding"); |
| |
| if (disk->partition) |
| return grub_error (GRUB_ERR_OUT_OF_RANGE, |
| "Embedding on MSDOS subpartition isn't supported"); |
| |
| while (1) |
| { |
| int i; |
| struct grub_msdos_partition_entry *e; |
| grub_err_t err; |
| |
| /* Read the MBR. */ |
| err = grub_disk_read (disk, offset, 0, sizeof (mbr), &mbr); |
| if (err) |
| return err; |
| |
| /* This is our loop-detection algorithm. It works the following way: |
| It saves last position which was a power of two. Then it compares the |
| saved value with a current one. This way it's guaranteed that the loop |
| will be broken by at most third walk. |
| */ |
| if (labeln && lastaddr == offset) |
| return grub_error (GRUB_ERR_BAD_PART_TABLE, "loop detected"); |
| |
| labeln++; |
| if ((labeln & (labeln - 1)) == 0) |
| lastaddr = offset; |
| |
| /* Check if it is valid. */ |
| if (mbr.signature != grub_cpu_to_le16_compile_time (GRUB_PC_PARTITION_SIGNATURE)) |
| return grub_error (GRUB_ERR_BAD_PART_TABLE, "no signature"); |
| |
| for (i = 0; i < 4; i++) |
| if (mbr.entries[i].flag & 0x7f) |
| return grub_error (GRUB_ERR_BAD_PART_TABLE, "bad boot flag"); |
| |
| /* Analyze DOS partitions. */ |
| for (i = 0; i < 4; i++) |
| { |
| e = mbr.entries + i; |
| |
| if (!grub_msdos_partition_is_empty (e->type) |
| && end > offset |
| + (grub_le_to_cpu32 (e->start) |
| << (disk->log_sector_size - GRUB_DISK_SECTOR_BITS))) |
| end = offset + (grub_le_to_cpu32 (e->start) |
| << (disk->log_sector_size - GRUB_DISK_SECTOR_BITS)); |
| |
| /* If this is a GPT partition, this MBR is just a dummy. */ |
| if (e->type == GRUB_PC_PARTITION_TYPE_GPT_DISK && i == 0) |
| return grub_error (GRUB_ERR_BAD_PART_TABLE, "dummy mbr"); |
| } |
| |
| /* Find an extended partition. */ |
| for (i = 0; i < 4; i++) |
| { |
| e = mbr.entries + i; |
| |
| if (grub_msdos_partition_is_extended (e->type)) |
| { |
| offset = ext_offset |
| + (grub_le_to_cpu32 (e->start) |
| << (disk->log_sector_size - GRUB_DISK_SECTOR_BITS)); |
| if (! ext_offset) |
| ext_offset = offset; |
| |
| break; |
| } |
| } |
| |
| /* If no extended partition, the end. */ |
| if (i == 4) |
| break; |
| } |
| |
| if (end >= *nsectors + 1) |
| { |
| unsigned i, j; |
| char *embed_signature_check; |
| unsigned int orig_nsectors, avail_nsectors; |
| |
| orig_nsectors = *nsectors; |
| *nsectors = end - 1; |
| avail_nsectors = *nsectors; |
| if (*nsectors > max_nsectors) |
| *nsectors = max_nsectors; |
| *sectors = grub_malloc (*nsectors * sizeof (**sectors)); |
| if (!*sectors) |
| return grub_errno; |
| for (i = 0; i < *nsectors; i++) |
| (*sectors)[i] = 1 + i; |
| |
| /* Check for software that is already using parts of the embedding |
| * area. |
| */ |
| embed_signature_check = grub_malloc (GRUB_DISK_SECTOR_SIZE); |
| for (i = 0; i < *nsectors; i++) |
| { |
| if (grub_disk_read (disk, (*sectors)[i], 0, GRUB_DISK_SECTOR_SIZE, |
| embed_signature_check)) |
| continue; |
| |
| for (j = 0; j < ARRAY_SIZE (embed_signatures); j++) |
| if (! grub_memcmp (embed_signatures[j].signature, |
| embed_signature_check, |
| embed_signatures[j].signature_len)) |
| break; |
| if (j == ARRAY_SIZE (embed_signatures)) |
| continue; |
| grub_util_warn (_(message_warn[embed_signatures[j].type]), |
| (*sectors)[i], embed_signatures[j].name); |
| avail_nsectors--; |
| if (avail_nsectors < *nsectors) |
| *nsectors = avail_nsectors; |
| |
| /* Avoid this sector. */ |
| for (j = i; j < *nsectors; j++) |
| (*sectors)[j]++; |
| |
| /* Have we run out of space? */ |
| if (avail_nsectors < orig_nsectors) |
| break; |
| |
| /* Make sure to check the next sector. */ |
| i--; |
| } |
| grub_free (embed_signature_check); |
| |
| if (*nsectors < orig_nsectors) |
| return grub_error (GRUB_ERR_OUT_OF_RANGE, |
| N_("other software is using the embedding area, and " |
| "there is not enough room for core.img. Such " |
| "software is often trying to store data in a way " |
| "that avoids detection. We recommend you " |
| "investigate")); |
| |
| return GRUB_ERR_NONE; |
| } |
| |
| if (end <= 1) |
| return grub_error (GRUB_ERR_FILE_NOT_FOUND, |
| N_("this msdos-style partition label has no " |
| "post-MBR gap; embedding won't be possible")); |
| |
| if (*nsectors > 62) |
| return grub_error (GRUB_ERR_OUT_OF_RANGE, |
| N_("your core.img is unusually large. " |
| "It won't fit in the embedding area")); |
| |
| return grub_error (GRUB_ERR_OUT_OF_RANGE, |
| N_("your embedding area is unusually small. " |
| "core.img won't fit in it.")); |
| } |
| |
| #pragma GCC diagnostic error "-Wformat-nonliteral" |
| |
| #endif |
| |
| |
| /* Partition map type. */ |
| static struct grub_partition_map grub_msdos_partition_map = |
| { |
| .name = "msdos", |
| .iterate = grub_partition_msdos_iterate, |
| #ifdef GRUB_UTIL |
| .embed = pc_partition_map_embed |
| #endif |
| }; |
| |
| GRUB_MOD_INIT(part_msdos) |
| { |
| grub_partition_map_register (&grub_msdos_partition_map); |
| } |
| |
| GRUB_MOD_FINI(part_msdos) |
| { |
| grub_partition_map_unregister (&grub_msdos_partition_map); |
| } |