msdos.c 12.1 KB
Newer Older
1
/* pc.c - Read PC style partition tables.  */
okuji's avatar
okuji committed
2
/*
3
 *  GRUB  --  GRand Unified Bootloader
4
 *  Copyright (C) 2002,2004,2005,2006,2007,2008,2009  Free Software Foundation, Inc.
okuji's avatar
okuji committed
5
 *
6
 *  GRUB is free software: you can redistribute it and/or modify
okuji's avatar
okuji committed
7
 *  it under the terms of the GNU General Public License as published by
8
 *  the Free Software Foundation, either version 3 of the License, or
okuji's avatar
okuji committed
9 10
 *  (at your option) any later version.
 *
11
 *  GRUB is distributed in the hope that it will be useful,
okuji's avatar
okuji committed
12 13 14 15 16
 *  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
17
 *  along with GRUB.  If not, see <http://www.gnu.org/licenses/>.
okuji's avatar
okuji committed
18 19
 */

20
#include <grub/partition.h>
21
#include <grub/msdos_partition.h>
22 23 24
#include <grub/disk.h>
#include <grub/mm.h>
#include <grub/misc.h>
25
#include <grub/dl.h>
26
#include <grub/i18n.h>
27

28 29
GRUB_MOD_LICENSE ("GPLv3+");

30
static struct grub_partition_map grub_msdos_partition_map;
31

okuji's avatar
okuji committed
32

33 34 35 36 37 38 39 40
#ifdef GRUB_UTIL
#include <grub/emu/misc.h>

struct embed_signature
{
  const char *name;
  const char *signature;
  int signature_len;
41
  enum { TYPE_SOFTWARE, TYPE_RAID } type;
42 43
};

44
const char message_warn[][200] = {
45 46
  /* 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
47 48 49 50
     "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.  */
51 52
  [TYPE_RAID] = N_("Sector %llu is already in use by raid controller `%s';"
		   " avoiding it.  "
53
		   "Please ask the manufacturer not to store data in MBR gap"),
54 55
  [TYPE_SOFTWARE] = N_("Sector %llu is already in use by the program `%s';"
		       " avoiding it.  "
56 57 58
		       "This software may cause boot or other problems in "
		       "future.  Please ask its authors not to store data "
		       "in the boot track") 
59 60 61
};


62 63 64 65 66 67 68
/* Signatures of other software that may be using sectors in the embedding
   area.  */
struct embed_signature embed_signatures[] =
  {
    {
      .name = "ZISD",
      .signature = "ZISD",
69 70
      .signature_len = 4,
      .type = TYPE_SOFTWARE
71 72 73 74
    },
    {
      .name = "FlexNet",
      .signature = "\xd4\x41\xa0\xf5\x03\x00\x03\x00",
75 76
      .signature_len = 8,
      .type = TYPE_SOFTWARE
77 78 79 80
    },
    {
      .name = "FlexNet",
      .signature = "\xd8\x41\xa0\xf5\x02\x00\x02\x00",
81 82
      .signature_len = 8,
      .type = TYPE_SOFTWARE
83 84 85 86 87 88
    },
    {
      /* 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",
89 90 91 92 93 94 95 96
      .signature_len = 16,
      .type = TYPE_SOFTWARE
    },
    {
      .name = "HighPoint RAID controller",
      .signature = "ycgl",
      .signature_len = 4,
      .type = TYPE_RAID
97 98 99 100 101 102 103
    },
    {
      /* https://bugs.launchpad.net/bugs/987022 */
      .name = "Acer registration utility (?)",
      .signature = "GREGRegDone.Tag\x00",
      .signature_len = 16,
      .type = TYPE_SOFTWARE
104 105 106 107
    }
  };
#endif

108 109
grub_err_t
grub_partition_msdos_iterate (grub_disk_t disk,
110 111
			      grub_partition_iterate_hook_t hook,
			      void *hook_data)
okuji's avatar
okuji committed
112
{
113
  struct grub_partition p;
114
  struct grub_msdos_partition_mbr mbr;
115 116
  int labeln = 0;
  grub_disk_addr_t lastaddr;
117
  grub_disk_addr_t ext_offset;
118 119 120 121 122
  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)
123
	delta = disk->partition->start;
124 125 126
      else
	return grub_error (GRUB_ERR_BAD_PART_TABLE, "no embedding supported");
    }
127

okuji's avatar
okuji committed
128
  p.offset = 0;
129 130
  ext_offset = 0;
  p.number = -1;
131
  p.partmap = &grub_msdos_partition_map;
132

133 134 135 136
  /* Any value different than `p.offset' will satisfy the check during
     first loop.  */
  lastaddr = !p.offset;

okuji's avatar
okuji committed
137 138 139
  while (1)
    {
      int i;
140
      struct grub_msdos_partition_entry *e;
141

okuji's avatar
okuji committed
142
      /* Read the MBR.  */
143
      if (grub_disk_read (disk, p.offset, 0, sizeof (mbr), &mbr))
okuji's avatar
okuji committed
144 145
	goto finish;

146 147 148 149 150 151
      /* 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");

152 153 154 155 156 157 158 159 160 161 162 163
      /* 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;

okuji's avatar
okuji committed
164
      /* Check if it is valid.  */
165
      if (mbr.signature != grub_cpu_to_le16_compile_time (GRUB_PC_PARTITION_SIGNATURE))
166
	return grub_error (GRUB_ERR_BAD_PART_TABLE, "no signature");
okuji's avatar
okuji committed
167

168 169 170 171
      for (i = 0; i < 4; i++)
	if (mbr.entries[i].flag & 0x7f)
	  return grub_error (GRUB_ERR_BAD_PART_TABLE, "bad boot flag");

okuji's avatar
okuji committed
172 173 174 175
      /* Analyze DOS partitions.  */
      for (p.index = 0; p.index < 4; p.index++)
	{
	  e = mbr.entries + p.index;
176

177 178 179 180 181
	  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);
182
	  p.msdostype = e->type;
okuji's avatar
okuji committed
183

184
	  grub_dprintf ("partition",
185
			"partition %d: flag 0x%x, type 0x%x, start 0x%llx, len 0x%llx\n",
186
			p.index, e->flag, e->type,
187 188
			(unsigned long long) p.start,
			(unsigned long long) p.len);
189

okuji's avatar
okuji committed
190
	  /* If this partition is a normal one, call the hook.  */
191 192
	  if (! grub_msdos_partition_is_empty (e->type)
	      && ! grub_msdos_partition_is_extended (e->type))
okuji's avatar
okuji committed
193
	    {
194
	      p.number++;
195

196
	      if (hook (disk, &p, hook_data))
197
		return grub_errno;
okuji's avatar
okuji committed
198
	    }
199
	  else if (p.number < 3)
okuji's avatar
okuji committed
200 201
	    /* If this partition is a logical one, shouldn't increase the
	       partition number.  */
202
	    p.number++;
okuji's avatar
okuji committed
203 204 205 206 207 208
	}

      /* Find an extended partition.  */
      for (i = 0; i < 4; i++)
	{
	  e = mbr.entries + i;
209

210
	  if (grub_msdos_partition_is_extended (e->type))
okuji's avatar
okuji committed
211
	    {
212 213 214
	      p.offset = ext_offset
		+ (grub_le_to_cpu32 (e->start)
		   << (disk->log_sector_size - GRUB_DISK_SECTOR_BITS));
215 216
	      if (! ext_offset)
		ext_offset = p.offset;
okuji's avatar
okuji committed
217 218 219 220 221 222 223 224 225 226 227

	      break;
	    }
	}

      /* If no extended partition, the end.  */
      if (i == 4)
	break;
    }

 finish:
228
  return grub_errno;
okuji's avatar
okuji committed
229 230
}

231
#ifdef GRUB_UTIL
232 233 234

#pragma GCC diagnostic ignored "-Wformat-nonliteral"

235
static grub_err_t
236
pc_partition_map_embed (struct grub_disk *disk, unsigned int *nsectors,
237
			unsigned int max_nsectors,
238
			grub_embed_type_t embed_type,
239
			grub_disk_addr_t **sectors)
240 241 242 243 244 245 246 247 248 249 250 251
{
  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,
252
		       "PC-style partitions currently support "
253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282
		       "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.  */
283
      if (mbr.signature != grub_cpu_to_le16_compile_time (GRUB_PC_PARTITION_SIGNATURE))
284 285 286 287 288 289 290 291 292 293 294 295
	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)
296 297 298 299 300
	      && 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));
301 302 303 304 305 306 307 308 309 310 311 312 313

	  /* 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))
	    {
314 315 316
	      offset = ext_offset 
		+ (grub_le_to_cpu32 (e->start) 
		   << (disk->log_sector_size - GRUB_DISK_SECTOR_BITS));
317 318 319 320 321 322 323 324 325 326 327 328
	      if (! ext_offset)
		ext_offset = offset;

	      break;
	    }
	}

      /* If no extended partition, the end.  */
      if (i == 4)
	break;
    }

329
  if (end >= *nsectors + 1)
330
    {
Colin Watson's avatar
Colin Watson committed
331
      unsigned i, j;
332
      char *embed_signature_check;
333
      unsigned int orig_nsectors, avail_nsectors;
334 335

      orig_nsectors = *nsectors;
336
      *nsectors = end - 1;
337 338 339
      avail_nsectors = *nsectors;
      if (*nsectors > max_nsectors)
	*nsectors = max_nsectors;
340 341 342 343 344
      *sectors = grub_malloc (*nsectors * sizeof (**sectors));
      if (!*sectors)
	return grub_errno;
      for (i = 0; i < *nsectors; i++)
	(*sectors)[i] = 1 + i;
345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362

      /* 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;
363
	  grub_util_warn (_(message_warn[embed_signatures[j].type]),
364
			  (*sectors)[i], embed_signatures[j].name);
365 366 367
	  avail_nsectors--;
	  if (avail_nsectors < *nsectors)
	    *nsectors = avail_nsectors;
368 369 370 371

	  /* Avoid this sector.  */
	  for (j = i; j < *nsectors; j++)
	    (*sectors)[j]++;
372 373 374 375 376 377 378

	  /* Have we run out of space?  */
	  if (avail_nsectors < orig_nsectors)
	    break;

	  /* Make sure to check the next sector.  */
	  i--;
379 380 381
	}
      grub_free (embed_signature_check);

382
      if (*nsectors < orig_nsectors)
383
	return grub_error (GRUB_ERR_OUT_OF_RANGE,
384 385 386 387 388
			   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"));
389

390 391 392 393 394
      return GRUB_ERR_NONE;
    }

  if (end <= 1)
    return grub_error (GRUB_ERR_FILE_NOT_FOUND,
395 396
		       N_("this msdos-style partition label has no "
			  "post-MBR gap; embedding won't be possible"));
397

398
  if (*nsectors > 62)
399
    return grub_error (GRUB_ERR_OUT_OF_RANGE,
400 401
		       N_("your core.img is unusually large.  "
			  "It won't fit in the embedding area"));
402 403

  return grub_error (GRUB_ERR_OUT_OF_RANGE,
404 405
		     N_("your embedding area is unusually small.  "
			"core.img won't fit in it."));
406
}
407 408 409

#pragma GCC diagnostic error "-Wformat-nonliteral"

410 411
#endif

412 413

/* Partition map type.  */
414
static struct grub_partition_map grub_msdos_partition_map =
415
  {
416
    .name = "msdos",
417
    .iterate = grub_partition_msdos_iterate,
418 419 420
#ifdef GRUB_UTIL
    .embed = pc_partition_map_embed
#endif
421 422
  };

423
GRUB_MOD_INIT(part_msdos)
424
{
425
  grub_partition_map_register (&grub_msdos_partition_map);
426 427
}

428
GRUB_MOD_FINI(part_msdos)
429
{
430
  grub_partition_map_unregister (&grub_msdos_partition_map);
431
}