mirror of
				https://git.proxmox.com/git/grub2
				synced 2025-10-27 18:54:09 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			426 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			426 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| /* 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 (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
 | ||
| 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 curently 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 (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."));
 | ||
| }
 | ||
| #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);
 | ||
| }
 | 
