1 /* $NetBSD: subr_disk_mbr.c,v 1.31 2008/01/02 11:48:53 ad Exp $ */ 2 3 /* 4 * Copyright (c) 1982, 1986, 1988 Regents of the University of California. 5 * All rights reserved. 6 * 7 * Redistribution and use in source and binary forms, with or without 8 * modification, are permitted provided that the following conditions 9 * are met: 10 * 1. Redistributions of source code must retain the above copyright 11 * notice, this list of conditions and the following disclaimer. 12 * 2. Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in the 14 * documentation and/or other materials provided with the distribution. 15 * 3. Neither the name of the University nor the names of its contributors 16 * may be used to endorse or promote products derived from this software 17 * without specific prior written permission. 18 * 19 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 20 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 22 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 23 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 25 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 26 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 27 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 28 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 29 * SUCH DAMAGE. 30 * 31 * @(#)ufs_disksubr.c 7.16 (Berkeley) 5/4/91 32 */ 33 34 /* 35 * Code to find a NetBSD label on a disk that contains an i386 style MBR. 36 * The first NetBSD label found in the 2nd sector of a NetBSD partition 37 * is used. 38 * If we don't find a label searching the MBR, we look at the start of the 39 * disk, if that fails then a label is faked up from the MBR. 40 * 41 * If there isn't a disklabel or anything in the MBR then partition a 42 * is set to cover the whole disk. 43 * Useful for files that contain single filesystems (etc). 44 * 45 * This code will read host endian netbsd labels from little endian MBR. 46 * 47 * Based on the i386 disksubr.c 48 * 49 * Since the mbr only has 32bit fields for sector addresses, we do the same. 50 * 51 * XXX There are potential problems writing labels to disks where there 52 * is only space for 8 netbsd partitions but this code has been compiled 53 * with MAXPARTITIONS=16. 54 */ 55 56 #include <sys/cdefs.h> 57 __KERNEL_RCSID(0, "$NetBSD: subr_disk_mbr.c,v 1.31 2008/01/02 11:48:53 ad Exp $"); 58 59 #include <sys/param.h> 60 #include <sys/systm.h> 61 #include <sys/buf.h> 62 #include <sys/bootblock.h> 63 #include <sys/disklabel.h> 64 #include <sys/disk.h> 65 #include <sys/syslog.h> 66 67 #include "opt_mbr.h" 68 69 typedef struct mbr_partition mbr_partition_t; 70 71 /* 72 * We allocate a buffer 2 sectors large, and look in both.... 73 * That means we find labels written by other ports with different offsets. 74 * LABELSECTOR and LABELOFFSET are only used if the disk doesn't have a label. 75 */ 76 #if LABELSECTOR > 1 || LABELOFFSET > 512 77 #error Invalid LABELSECTOR or LABELOFFSET 78 #endif 79 80 #define MBR_LABELSECTOR 1 81 82 #define SCAN_CONTINUE 0 83 #define SCAN_FOUND 1 84 #define SCAN_ERROR 2 85 86 typedef struct mbr_args { 87 struct disklabel *lp; 88 void (*strat)(struct buf *); 89 struct buf *bp; 90 const char *msg; 91 int error; 92 int written; /* number of times we wrote label */ 93 int found_mbr; /* set if disk has a valid mbr */ 94 uint label_sector; /* where we found the label */ 95 int action; 96 uint32_t secperunit; 97 #define READ_LABEL 1 98 #define UPDATE_LABEL 2 99 #define WRITE_LABEL 3 100 } mbr_args_t; 101 102 static int validate_label(mbr_args_t *, uint); 103 static int look_netbsd_part(mbr_args_t *, mbr_partition_t *, int, uint); 104 static int write_netbsd_label(mbr_args_t *, mbr_partition_t *, int, uint); 105 106 static int 107 read_sector(mbr_args_t *a, uint sector, int count) 108 { 109 int error; 110 111 error = disk_read_sectors(a->strat, a->lp, a->bp, sector, count); 112 if (error != 0) 113 a->error = error; 114 return error; 115 } 116 117 /* 118 * Scan MBR for partitions, call 'action' routine for each. 119 */ 120 121 static int 122 scan_mbr(mbr_args_t *a, int (*actn)(mbr_args_t *, mbr_partition_t *, int, uint)) 123 { 124 mbr_partition_t ptns[MBR_PART_COUNT]; 125 mbr_partition_t *dp; 126 struct mbr_sector *mbr; 127 uint ext_base, this_ext, next_ext; 128 int rval; 129 int i; 130 int j; 131 #ifdef COMPAT_386BSD_MBRPART 132 int dp_386bsd = -1; 133 int ap_386bsd = -1; 134 #endif 135 136 ext_base = 0; 137 this_ext = 0; 138 for (;;) { 139 if (read_sector(a, this_ext, 1)) { 140 a->msg = "dos partition I/O error"; 141 return SCAN_ERROR; 142 } 143 144 /* Note: Magic number is little-endian. */ 145 mbr = (void *)a->bp->b_data; 146 if (mbr->mbr_magic != htole16(MBR_MAGIC)) 147 return SCAN_CONTINUE; 148 149 /* Copy data out of buffer so action can use bp */ 150 memcpy(ptns, &mbr->mbr_parts, sizeof ptns); 151 152 /* Look for drivers and skip them */ 153 if (ext_base == 0 && ptns[0].mbrp_type == MBR_PTYPE_DM6_DDO) { 154 /* We've found a DM6 DDO partition type (used by 155 * the Ontrack Disk Manager drivers). 156 * 157 * Ensure that there are no other partitions in the 158 * MBR and jump to the real partition table (stored 159 * in the first sector of the second track). */ 160 bool ok = true; 161 162 for (i = 1; i < MBR_PART_COUNT; i++) 163 if (ptns[i].mbrp_type != MBR_PTYPE_UNUSED) 164 ok = false; 165 166 if (ok) { 167 this_ext = le32toh(a->lp->d_secpercyl / 168 a->lp->d_ntracks); 169 continue; 170 } 171 } 172 173 /* look for NetBSD partition */ 174 next_ext = 0; 175 dp = ptns; 176 j = 0; 177 for (i = 0; i < MBR_PART_COUNT; i++, dp++) { 178 if (dp->mbrp_type == MBR_PTYPE_UNUSED) 179 continue; 180 /* Check end of partition is inside disk limits */ 181 if ((uint64_t)ext_base + le32toh(dp->mbrp_start) + 182 le32toh(dp->mbrp_size) > a->lp->d_secperunit) { 183 /* This mbr doesn't look good.... */ 184 a->msg = "mbr partition exceeds disk size"; 185 /* ...but don't report this as an error (yet) */ 186 return SCAN_CONTINUE; 187 } 188 a->found_mbr = 1; 189 if (MBR_IS_EXTENDED(dp->mbrp_type)) { 190 next_ext = le32toh(dp->mbrp_start); 191 continue; 192 } 193 #ifdef COMPAT_386BSD_MBRPART 194 if (dp->mbrp_type == MBR_PTYPE_386BSD) { 195 /* 196 * If more than one matches, take last, 197 * as NetBSD install tool does. 198 */ 199 if (this_ext == 0) { 200 dp_386bsd = i; 201 ap_386bsd = j; 202 } 203 continue; 204 } 205 #endif 206 rval = (*actn)(a, dp, j, this_ext); 207 if (rval != SCAN_CONTINUE) 208 return rval; 209 j++; 210 } 211 if (next_ext == 0) 212 break; 213 if (ext_base == 0) { 214 ext_base = next_ext; 215 next_ext = 0; 216 } 217 next_ext += ext_base; 218 if (next_ext <= this_ext) 219 break; 220 this_ext = next_ext; 221 } 222 #ifdef COMPAT_386BSD_MBRPART 223 if (this_ext == 0 && dp_386bsd != -1) 224 return (*actn)(a, &ptns[dp_386bsd], ap_386bsd, 0); 225 #endif 226 return SCAN_CONTINUE; 227 } 228 229 /* 230 * Attempt to read a disk label from a device 231 * using the indicated strategy routine. 232 * The label must be partly set up before this: 233 * secpercyl, secsize and anything required for a block i/o read 234 * operation in the driver's strategy/start routines 235 * must be filled in before calling us. 236 * 237 * If dos partition table requested, attempt to load it and 238 * find disklabel inside a DOS partition. Also, if bad block 239 * table needed, attempt to extract it as well. Return buffer 240 * for use in signalling errors if requested. 241 * 242 * Returns null on success and an error string on failure. 243 */ 244 const char * 245 readdisklabel(dev_t dev, void (*strat)(struct buf *), struct disklabel *lp, 246 struct cpu_disklabel *osdep) 247 { 248 struct dkbad *bdp; 249 int rval; 250 int i; 251 mbr_args_t a; 252 253 memset(&a, 0, sizeof a); 254 a.lp = lp; 255 a.strat = strat; 256 a.action = READ_LABEL; 257 258 /* minimal requirements for architypal disk label */ 259 if (lp->d_secsize == 0) 260 lp->d_secsize = DEV_BSIZE; 261 if (lp->d_secperunit == 0) 262 lp->d_secperunit = 0x1fffffff; 263 a.secperunit = lp->d_secperunit; 264 lp->d_npartitions = RAW_PART + 1; 265 for (i = 0; i < RAW_PART; i++) { 266 lp->d_partitions[i].p_size = 0; 267 lp->d_partitions[i].p_offset = 0; 268 } 269 if (lp->d_partitions[RAW_PART].p_size == 0) 270 lp->d_partitions[RAW_PART].p_size = lp->d_secperunit; 271 lp->d_partitions[RAW_PART].p_offset = 0; 272 273 /* 274 * Set partition 'a' to be the whole disk. 275 * Cleared if we find an mbr or a netbsd label. 276 */ 277 lp->d_partitions[0].p_size = lp->d_partitions[RAW_PART].p_size; 278 lp->d_partitions[0].p_fstype = FS_BSDFFS; 279 280 /* get a buffer and initialize it */ 281 a.bp = geteblk(2 * (int)lp->d_secsize); 282 a.bp->b_dev = dev; 283 284 if (osdep) 285 /* 286 * Scan mbr searching for netbsd partition and saving 287 * bios partition information to use if the netbsd one 288 * is absent. 289 */ 290 rval = scan_mbr(&a, look_netbsd_part); 291 else 292 rval = SCAN_CONTINUE; 293 294 if (rval == SCAN_CONTINUE) { 295 /* Look at start of disk */ 296 rval = validate_label(&a, 0); 297 } 298 299 #if 0 300 /* 301 * Save sector where we found the label for the 'don't overwrite 302 * the label' check in bounds_check_with_label. 303 */ 304 if (rval == SCAN_FOUND) 305 xxx->label_sector = a.label_sector; 306 #endif 307 308 /* Obtain bad sector table if requested and present */ 309 if (rval == SCAN_FOUND && osdep && (lp->d_flags & D_BADSECT)) { 310 struct dkbad *db; 311 int blkno; 312 313 bdp = &osdep->bad; 314 i = 0; 315 rval = SCAN_ERROR; 316 do { 317 /* read a bad sector table */ 318 blkno = lp->d_secperunit - lp->d_nsectors + i; 319 if (lp->d_secsize > DEV_BSIZE) 320 blkno *= lp->d_secsize / DEV_BSIZE; 321 else 322 blkno /= DEV_BSIZE / lp->d_secsize; 323 /* if successful, validate, otherwise try another */ 324 if (read_sector(&a, blkno, 1)) { 325 a.msg = "bad sector table I/O error"; 326 continue; 327 } 328 db = (struct dkbad *)(a.bp->b_data); 329 #define DKBAD_MAGIC 0x4321 330 if (db->bt_mbz != 0 || db->bt_flag != DKBAD_MAGIC) { 331 a.msg = "bad sector table corrupted"; 332 continue; 333 } 334 rval = SCAN_FOUND; 335 *bdp = *db; 336 break; 337 } while (a.bp->b_error && (i += 2) < 10 && 338 i < lp->d_nsectors); 339 } 340 341 brelse(a.bp, 0); 342 if (rval == SCAN_ERROR || rval == SCAN_CONTINUE) 343 return a.msg; 344 return NULL; 345 } 346 347 static int 348 look_netbsd_part(mbr_args_t *a, mbr_partition_t *dp, int slot, uint ext_base) 349 { 350 struct partition *pp; 351 int ptn_base = ext_base + le32toh(dp->mbrp_start); 352 int rval; 353 354 if ( 355 #ifdef COMPAT_386BSD_MBRPART 356 dp->mbrp_type == MBR_PTYPE_386BSD || 357 #endif 358 dp->mbrp_type == MBR_PTYPE_NETBSD) { 359 rval = validate_label(a, ptn_base); 360 361 #if RAW_PART == 3 362 /* Put actual location where we found the label into ptn 2 */ 363 if (rval == SCAN_FOUND || a->lp->d_partitions[2].p_size == 0) { 364 a->lp->d_partitions[2].p_size = le32toh(dp->mbrp_size); 365 a->lp->d_partitions[2].p_offset = ptn_base; 366 } 367 #endif 368 369 /* If we got a netbsd label look no further */ 370 if (rval == SCAN_FOUND) 371 return rval; 372 } 373 374 /* Install main partitions into e..h and extended into i+ */ 375 if (ext_base == 0) 376 slot += 4; 377 else { 378 slot = 4 + MBR_PART_COUNT; 379 pp = &a->lp->d_partitions[slot]; 380 for (; slot < MAXPARTITIONS; pp++, slot++) { 381 /* This gets called twice - avoid duplicates */ 382 if (pp->p_offset == ptn_base && 383 pp->p_size == le32toh(dp->mbrp_size)) 384 break; 385 if (pp->p_size == 0) 386 break; 387 } 388 } 389 390 if (slot < MAXPARTITIONS) { 391 /* Stop 'a' being the entire disk */ 392 a->lp->d_partitions[0].p_size = 0; 393 a->lp->d_partitions[0].p_fstype = 0; 394 395 /* save partition info */ 396 pp = &a->lp->d_partitions[slot]; 397 pp->p_offset = ptn_base; 398 pp->p_size = le32toh(dp->mbrp_size); 399 pp->p_fstype = xlat_mbr_fstype(dp->mbrp_type); 400 401 if (slot >= a->lp->d_npartitions) 402 a->lp->d_npartitions = slot + 1; 403 } 404 405 return SCAN_CONTINUE; 406 } 407 408 409 static int 410 validate_label(mbr_args_t *a, uint label_sector) 411 { 412 struct disklabel *dlp; 413 char *dlp_lim, *dlp_byte; 414 int error; 415 416 /* Next, dig out disk label */ 417 if (read_sector(a, label_sector, 2)) { 418 a->msg = "disk label read failed"; 419 return SCAN_ERROR; 420 } 421 422 /* Locate disk label within block and validate */ 423 /* 424 * XXX (dsl) This search may be a waste of time, a lot of other i386 425 * code assumes the label is at offset LABELOFFSET (=0) in the sector. 426 * 427 * If we want to support disks from other netbsd ports, then the 428 * code should also allow for a shorter label nearer the end of 429 * the disk sector, and (IIRC) labels within 8k of the disk start. 430 */ 431 dlp = (void *)a->bp->b_data; 432 dlp_lim = (char *)a->bp->b_data + a->bp->b_bcount - sizeof *dlp; 433 for (;; dlp = (void *)((char *)dlp + sizeof(long))) { 434 if ((char *)dlp > dlp_lim) { 435 if (a->action != WRITE_LABEL) 436 return SCAN_CONTINUE; 437 /* Write at arch. dependant default location */ 438 dlp_byte = (char *)a->bp->b_data + LABELOFFSET; 439 if (label_sector) 440 dlp_byte += MBR_LABELSECTOR * a->lp->d_secsize; 441 else 442 dlp_byte += LABELSECTOR * a->lp->d_secsize; 443 dlp = (void *)dlp_byte; 444 break; 445 } 446 if (dlp->d_magic != DISKMAGIC || dlp->d_magic2 != DISKMAGIC) 447 continue; 448 if (dlp->d_npartitions > MAXPARTITIONS || dkcksum(dlp) != 0) { 449 a->msg = "disk label corrupted"; 450 continue; 451 } 452 break; 453 } 454 455 switch (a->action) { 456 case READ_LABEL: 457 *a->lp = *dlp; 458 if ((a->msg = convertdisklabel(a->lp, a->strat, a->bp, 459 a->secperunit)) != NULL) 460 return SCAN_ERROR; 461 a->label_sector = label_sector; 462 return SCAN_FOUND; 463 case UPDATE_LABEL: 464 case WRITE_LABEL: 465 *dlp = *a->lp; 466 a->bp->b_oflags &= ~BO_DONE; 467 a->bp->b_flags &= ~B_READ; 468 a->bp->b_flags |= B_WRITE; 469 (*a->strat)(a->bp); 470 error = biowait(a->bp); 471 if (error != 0) { 472 a->error = error; 473 a->msg = "disk label write failed"; 474 return SCAN_ERROR; 475 } 476 a->written++; 477 /* Write label to all mbr partitions */ 478 return SCAN_CONTINUE; 479 default: 480 return SCAN_ERROR; 481 } 482 } 483 484 /* 485 * Check new disk label for sensibility 486 * before setting it. 487 */ 488 int 489 setdisklabel(struct disklabel *olp, struct disklabel *nlp, u_long openmask, 490 struct cpu_disklabel *osdep) 491 { 492 int i; 493 struct partition *opp, *npp; 494 495 /* sanity clause */ 496 if (nlp->d_secpercyl == 0 || nlp->d_secsize == 0 497 || (nlp->d_secsize % DEV_BSIZE) != 0) 498 return (EINVAL); 499 500 /* special case to allow disklabel to be invalidated */ 501 if (nlp->d_magic == 0xffffffff) { 502 *olp = *nlp; 503 return (0); 504 } 505 506 if (nlp->d_magic != DISKMAGIC || nlp->d_magic2 != DISKMAGIC || 507 dkcksum(nlp) != 0) 508 return (EINVAL); 509 510 /* XXX missing check if other dos partitions will be overwritten */ 511 512 while (openmask != 0) { 513 i = ffs(openmask) - 1; 514 openmask &= ~(1 << i); 515 if (i > nlp->d_npartitions) 516 return (EBUSY); 517 opp = &olp->d_partitions[i]; 518 npp = &nlp->d_partitions[i]; 519 /* 520 * Copy internally-set partition information 521 * if new label doesn't include it. XXX 522 */ 523 if (npp->p_fstype == FS_UNUSED && opp->p_fstype != FS_UNUSED) { 524 *npp = *opp; 525 continue; 526 } 527 if (npp->p_offset != opp->p_offset || npp->p_size < opp->p_size) 528 return (EBUSY); 529 } 530 nlp->d_checksum = 0; 531 nlp->d_checksum = dkcksum(nlp); 532 *olp = *nlp; 533 return (0); 534 } 535 536 537 /* 538 * Write disk label back to device after modification. 539 */ 540 int 541 writedisklabel(dev_t dev, void (*strat)(struct buf *), struct disklabel *lp, 542 struct cpu_disklabel *osdep) 543 { 544 mbr_args_t a; 545 546 memset(&a, 0, sizeof a); 547 a.lp = lp; 548 a.strat = strat; 549 550 /* get a buffer and initialize it */ 551 a.bp = geteblk(2 * (int)lp->d_secsize); 552 a.bp->b_dev = dev; 553 554 /* osdep => we expect an mbr with label in netbsd ptn */ 555 a.action = osdep != NULL ? WRITE_LABEL : UPDATE_LABEL; 556 557 /* Write/update the label to every netbsd mbr partition */ 558 scan_mbr(&a, write_netbsd_label); 559 560 /* Old write the label at the start of the volume on disks that 561 * don't have a valid mbr (always update an existing one) */ 562 a.action = a.found_mbr ? UPDATE_LABEL : WRITE_LABEL; 563 validate_label(&a, 0); 564 565 if (a.written == 0 && a.error == 0) 566 a.error = ESRCH; 567 568 brelse(a.bp, 0); 569 return a.error; 570 } 571 572 static int 573 write_netbsd_label(mbr_args_t *a, mbr_partition_t *dp, int slot, uint ext_base) 574 { 575 int ptn_base = ext_base + le32toh(dp->mbrp_start); 576 577 if (dp->mbrp_type != MBR_PTYPE_NETBSD) 578 return SCAN_CONTINUE; 579 580 return validate_label(a, ptn_base); 581 } 582