1 /* $NetBSD: subr_disk_mbr.c,v 1.18 2006/06/11 23:25:23 christos 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.18 2006/06/11 23:25:23 christos Exp $"); 58 59 #include <sys/param.h> 60 #include <sys/systm.h> 61 #include <sys/buf.h> 62 #include <sys/disklabel.h> 63 #include <sys/disk.h> 64 #include <sys/syslog.h> 65 66 #include "opt_mbr.h" 67 68 typedef struct mbr_partition mbr_partition_t; 69 70 /* 71 * We allocate a buffer 2 sectors large, and look in both.... 72 * That means we find labels written by other ports with different offsets. 73 * LABELSECTOR and LABELOFFSET are only used if the disk doesn't have a label. 74 */ 75 #if LABELSECTOR > 1 || LABELOFFSET > 512 76 #error Invalid LABELSECTOR or LABELOFFSET 77 #endif 78 79 #define MBR_LABELSECTOR 1 80 81 #define SCAN_CONTINUE 0 82 #define SCAN_FOUND 1 83 #define SCAN_ERROR 2 84 85 typedef struct mbr_args { 86 struct disklabel *lp; 87 void (*strat)(struct buf *); 88 struct buf *bp; 89 const char *msg; 90 int error; 91 int written; /* number of times we wrote label */ 92 int found_mbr; /* set if disk has a valid mbr */ 93 uint label_sector; /* where we found the label */ 94 int action; 95 #define READ_LABEL 1 96 #define UPDATE_LABEL 2 97 #define WRITE_LABEL 3 98 } mbr_args_t; 99 100 static int validate_label(mbr_args_t *, uint); 101 static int look_netbsd_part(mbr_args_t *, mbr_partition_t *, int, uint); 102 static int write_netbsd_label(mbr_args_t *, mbr_partition_t *, int, uint); 103 104 static int 105 read_sector(mbr_args_t *a, uint sector, int count) 106 { 107 struct buf *bp = a->bp; 108 int error; 109 110 bp->b_blkno = sector; 111 bp->b_bcount = count * a->lp->d_secsize; 112 bp->b_flags = (bp->b_flags & ~(B_WRITE | B_DONE)) | B_READ; 113 bp->b_cylinder = sector / a->lp->d_secpercyl; 114 (*a->strat)(bp); 115 error = biowait(bp); 116 if (error != 0) 117 a->error = error; 118 return error; 119 } 120 121 /* 122 * Scan MBR for partitions, call 'action' routine for each. 123 */ 124 125 static int 126 scan_mbr(mbr_args_t *a, int (*actn)(mbr_args_t *, mbr_partition_t *, int, uint)) 127 { 128 mbr_partition_t ptns[MBR_PART_COUNT]; 129 mbr_partition_t *dp; 130 struct mbr_sector *mbr; 131 uint ext_base, this_ext, next_ext; 132 int rval; 133 int i; 134 int j; 135 #ifdef COMPAT_386BSD_MBRPART 136 int dp_386bsd = -1; 137 int ap_386bsd = -1; 138 #endif 139 140 ext_base = 0; 141 this_ext = 0; 142 for (;;) { 143 if (read_sector(a, this_ext, 1)) { 144 a->msg = "dos partition I/O error"; 145 return SCAN_ERROR; 146 } 147 148 /* Note: Magic number is little-endian. */ 149 mbr = (void *)a->bp->b_data; 150 if (mbr->mbr_magic != htole16(MBR_MAGIC)) 151 return SCAN_CONTINUE; 152 153 /* Copy data out of buffer so action can use bp */ 154 memcpy(ptns, &mbr->mbr_parts, sizeof ptns); 155 156 /* Look for drivers and skip them */ 157 if (ext_base == 0 && ptns[0].mbrp_type == MBR_PTYPE_DM6_DDO) { 158 /* We've found a DM6 DDO partition type (used by 159 * the Ontrack Disk Manager drivers). 160 * 161 * Ensure that there are no other partitions in the 162 * MBR and jump to the real partition table (stored 163 * in the first sector of the second track). */ 164 boolean_t ok = TRUE; 165 166 for (i = 1; i < MBR_PART_COUNT; i++) 167 if (ptns[i].mbrp_type != MBR_PTYPE_UNUSED) 168 ok = FALSE; 169 170 if (ok) { 171 this_ext = le32toh(a->lp->d_secpercyl / 172 a->lp->d_ntracks); 173 continue; 174 } 175 } 176 177 /* look for NetBSD partition */ 178 next_ext = 0; 179 dp = ptns; 180 j = 0; 181 for (i = 0; i < MBR_PART_COUNT; i++, dp++) { 182 if (dp->mbrp_type == MBR_PTYPE_UNUSED) 183 continue; 184 /* Check end of partition is inside disk limits */ 185 if ((uint64_t)ext_base + le32toh(dp->mbrp_start) + 186 le32toh(dp->mbrp_size) > a->lp->d_secperunit) { 187 /* This mbr doesn't look good.... */ 188 a->msg = "mbr partition exceeds disk size"; 189 /* ...but don't report this as an error (yet) */ 190 return SCAN_CONTINUE; 191 } 192 a->found_mbr = 1; 193 if (MBR_IS_EXTENDED(dp->mbrp_type)) { 194 next_ext = le32toh(dp->mbrp_start); 195 continue; 196 } 197 #ifdef COMPAT_386BSD_MBRPART 198 if (dp->mbrp_type == MBR_PTYPE_386BSD) { 199 /* 200 * If more than one matches, take last, 201 * as NetBSD install tool does. 202 */ 203 if (this_ext == 0) { 204 dp_386bsd = i; 205 ap_386bsd = j; 206 } 207 continue; 208 } 209 #endif 210 rval = (*actn)(a, dp, j, this_ext); 211 if (rval != SCAN_CONTINUE) 212 return rval; 213 j++; 214 } 215 if (next_ext == 0) 216 break; 217 if (ext_base == 0) { 218 ext_base = next_ext; 219 next_ext = 0; 220 } 221 next_ext += ext_base; 222 if (next_ext <= this_ext) 223 break; 224 this_ext = next_ext; 225 } 226 #ifdef COMPAT_386BSD_MBRPART 227 if (this_ext == 0 && dp_386bsd != -1) 228 return (*actn)(a, &ptns[dp_386bsd], ap_386bsd, 0); 229 #endif 230 return SCAN_CONTINUE; 231 } 232 233 /* 234 * Attempt to read a disk label from a device 235 * using the indicated strategy routine. 236 * The label must be partly set up before this: 237 * secpercyl, secsize and anything required for a block i/o read 238 * operation in the driver's strategy/start routines 239 * must be filled in before calling us. 240 * 241 * If dos partition table requested, attempt to load it and 242 * find disklabel inside a DOS partition. Also, if bad block 243 * table needed, attempt to extract it as well. Return buffer 244 * for use in signalling errors if requested. 245 * 246 * Returns null on success and an error string on failure. 247 */ 248 const char * 249 readdisklabel(dev_t dev, void (*strat)(struct buf *), struct disklabel *lp, 250 struct cpu_disklabel *osdep) 251 { 252 struct dkbad *bdp; 253 int rval; 254 int i; 255 mbr_args_t a; 256 257 memset(&a, 0, sizeof a); 258 a.lp = lp; 259 a.strat = strat; 260 a.action = READ_LABEL; 261 262 /* minimal requirements for architypal disk label */ 263 if (lp->d_secsize == 0) 264 lp->d_secsize = DEV_BSIZE; 265 if (lp->d_secperunit == 0) 266 lp->d_secperunit = 0x1fffffff; 267 lp->d_npartitions = RAW_PART + 1; 268 for (i = 0; i < RAW_PART; i++) { 269 lp->d_partitions[i].p_size = 0; 270 lp->d_partitions[i].p_offset = 0; 271 } 272 if (lp->d_partitions[RAW_PART].p_size == 0) 273 lp->d_partitions[RAW_PART].p_size = lp->d_secperunit; 274 lp->d_partitions[RAW_PART].p_offset = 0; 275 276 /* 277 * Set partition 'a' to be the whole disk. 278 * Cleared if we find an mbr or a netbsd label. 279 */ 280 lp->d_partitions[0].p_size = lp->d_partitions[RAW_PART].p_size; 281 lp->d_partitions[0].p_fstype = FS_BSDFFS; 282 283 /* get a buffer and initialize it */ 284 a.bp = geteblk(2 * (int)lp->d_secsize); 285 a.bp->b_dev = dev; 286 287 if (osdep) 288 /* 289 * Scan mbr searching for netbsd partition and saving 290 * bios partition information to use if the netbsd one 291 * is absent. 292 */ 293 rval = scan_mbr(&a, look_netbsd_part); 294 else 295 rval = SCAN_CONTINUE; 296 297 if (rval == SCAN_CONTINUE) { 298 /* Look at start of disk */ 299 rval = validate_label(&a, 0); 300 } 301 302 #if 0 303 /* 304 * Save sector where we found the label for the 'don't overwrite 305 * the label' check in bounds_check_with_label. 306 */ 307 if (rval == SCAN_FOUND) 308 xxx->label_sector = a.label_sector; 309 #endif 310 311 /* Obtain bad sector table if requested and present */ 312 if (rval == SCAN_FOUND && osdep && (lp->d_flags & D_BADSECT)) { 313 struct dkbad *db; 314 int blkno; 315 316 bdp = &osdep->bad; 317 i = 0; 318 rval = SCAN_ERROR; 319 do { 320 /* read a bad sector table */ 321 blkno = lp->d_secperunit - lp->d_nsectors + i; 322 if (lp->d_secsize > DEV_BSIZE) 323 blkno *= lp->d_secsize / DEV_BSIZE; 324 else 325 blkno /= DEV_BSIZE / lp->d_secsize; 326 /* if successful, validate, otherwise try another */ 327 if (read_sector(&a, blkno, 1)) { 328 a.msg = "bad sector table I/O error"; 329 continue; 330 } 331 db = (struct dkbad *)(a.bp->b_data); 332 #define DKBAD_MAGIC 0x4321 333 if (db->bt_mbz != 0 || db->bt_flag != DKBAD_MAGIC) { 334 a.msg = "bad sector table corrupted"; 335 continue; 336 } 337 rval = SCAN_FOUND; 338 *bdp = *db; 339 break; 340 } while ((a.bp->b_flags & B_ERROR) && (i += 2) < 10 && 341 i < lp->d_nsectors); 342 } 343 344 brelse(a.bp); 345 if (rval == SCAN_ERROR || rval == SCAN_CONTINUE) 346 return a.msg; 347 return NULL; 348 } 349 350 static int 351 look_netbsd_part(mbr_args_t *a, mbr_partition_t *dp, int slot, uint ext_base) 352 { 353 struct partition *pp; 354 int ptn_base = ext_base + le32toh(dp->mbrp_start); 355 int rval; 356 357 if ( 358 #ifdef COMPAT_386BSD_MBRPART 359 dp->mbrp_type == MBR_PTYPE_386BSD || 360 #endif 361 dp->mbrp_type == MBR_PTYPE_NETBSD) { 362 rval = validate_label(a, ptn_base); 363 364 #if RAW_PART == 3 365 /* Put actual location where we found the label into ptn 2 */ 366 if (rval == SCAN_FOUND || a->lp->d_partitions[2].p_size == 0) { 367 a->lp->d_partitions[2].p_size = le32toh(dp->mbrp_size); 368 a->lp->d_partitions[2].p_offset = ptn_base; 369 } 370 #endif 371 372 /* If we got a netbsd label look no further */ 373 if (rval == SCAN_FOUND) 374 return rval; 375 } 376 377 /* Install main partitions into e..h and extended into i+ */ 378 if (ext_base == 0) 379 slot += 4; 380 else { 381 slot = 4 + MBR_PART_COUNT; 382 pp = &a->lp->d_partitions[slot]; 383 for (; slot < MAXPARTITIONS; pp++, slot++) { 384 /* This gets called twice - avoid duplicates */ 385 if (pp->p_offset == ptn_base && 386 pp->p_size == le32toh(dp->mbrp_size)) 387 break; 388 if (pp->p_size == 0) 389 break; 390 } 391 } 392 393 if (slot < MAXPARTITIONS) { 394 /* Stop 'a' being the entire disk */ 395 a->lp->d_partitions[0].p_size = 0; 396 a->lp->d_partitions[0].p_fstype = 0; 397 398 /* save partition info */ 399 pp = &a->lp->d_partitions[slot]; 400 pp->p_offset = ptn_base; 401 pp->p_size = le32toh(dp->mbrp_size); 402 pp->p_fstype = xlat_mbr_fstype(dp->mbrp_type); 403 404 if (slot >= a->lp->d_npartitions) 405 a->lp->d_npartitions = slot + 1; 406 } 407 408 return SCAN_CONTINUE; 409 } 410 411 412 static int 413 validate_label(mbr_args_t *a, uint label_sector) 414 { 415 struct disklabel *dlp; 416 char *dlp_lim, *dlp_byte; 417 int error; 418 419 /* Next, dig out disk label */ 420 if (read_sector(a, label_sector, 2)) { 421 a->msg = "disk label read failed"; 422 return SCAN_ERROR; 423 } 424 425 /* Locate disk label within block and validate */ 426 /* 427 * XXX (dsl) This search may be a waste of time, a lot of other i386 428 * code assumes the label is at offset LABELOFFSET (=0) in the sector. 429 * 430 * If we want to support disks from other netbsd ports, then the 431 * code should also allow for a shorter label nearer the end of 432 * the disk sector, and (IIRC) labels within 8k of the disk start. 433 */ 434 dlp = (void *)a->bp->b_data; 435 dlp_lim = a->bp->b_data + a->bp->b_bcount - sizeof *dlp; 436 for (;; dlp = (void *)((char *)dlp + sizeof(long))) { 437 if ((char *)dlp > dlp_lim) { 438 if (a->action != WRITE_LABEL) 439 return SCAN_CONTINUE; 440 /* Write at arch. dependant default location */ 441 dlp_byte = a->bp->b_data + LABELOFFSET; 442 if (label_sector) 443 dlp_byte += MBR_LABELSECTOR * a->lp->d_secsize; 444 else 445 dlp_byte += LABELSECTOR * a->lp->d_secsize; 446 dlp = (void *)dlp_byte; 447 break; 448 } 449 if (dlp->d_magic != DISKMAGIC || dlp->d_magic2 != DISKMAGIC) 450 continue; 451 if (dlp->d_npartitions > MAXPARTITIONS || dkcksum(dlp) != 0) { 452 a->msg = "disk label corrupted"; 453 continue; 454 } 455 break; 456 } 457 458 switch (a->action) { 459 case READ_LABEL: 460 *a->lp = *dlp; 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_flags &= ~(B_READ|B_DONE); 467 a->bp->b_flags |= B_WRITE; 468 (*a->strat)(a->bp); 469 error = biowait(a->bp); 470 if (error != 0) { 471 a->error = error; 472 a->msg = "disk label write failed"; 473 return SCAN_ERROR; 474 } 475 a->written++; 476 /* Write label to all mbr partitions */ 477 return SCAN_CONTINUE; 478 default: 479 return SCAN_ERROR; 480 } 481 } 482 483 /* 484 * Check new disk label for sensibility 485 * before setting it. 486 */ 487 int 488 setdisklabel(struct disklabel *olp, struct disklabel *nlp, u_long openmask, 489 struct cpu_disklabel *osdep) 490 { 491 int i; 492 struct partition *opp, *npp; 493 494 /* sanity clause */ 495 if (nlp->d_secpercyl == 0 || nlp->d_secsize == 0 496 || (nlp->d_secsize % DEV_BSIZE) != 0) 497 return (EINVAL); 498 499 /* special case to allow disklabel to be invalidated */ 500 if (nlp->d_magic == 0xffffffff) { 501 *olp = *nlp; 502 return (0); 503 } 504 505 if (nlp->d_magic != DISKMAGIC || nlp->d_magic2 != DISKMAGIC || 506 dkcksum(nlp) != 0) 507 return (EINVAL); 508 509 /* XXX missing check if other dos partitions will be overwritten */ 510 511 while (openmask != 0) { 512 i = ffs(openmask) - 1; 513 openmask &= ~(1 << i); 514 if (i > nlp->d_npartitions) 515 return (EBUSY); 516 opp = &olp->d_partitions[i]; 517 npp = &nlp->d_partitions[i]; 518 /* 519 * Copy internally-set partition information 520 * if new label doesn't include it. XXX 521 */ 522 if (npp->p_fstype == FS_UNUSED && opp->p_fstype != FS_UNUSED) { 523 *npp = *opp; 524 continue; 525 } 526 if (npp->p_offset != opp->p_offset || npp->p_size < opp->p_size) 527 return (EBUSY); 528 } 529 nlp->d_checksum = 0; 530 nlp->d_checksum = dkcksum(nlp); 531 *olp = *nlp; 532 return (0); 533 } 534 535 536 /* 537 * Write disk label back to device after modification. 538 */ 539 int 540 writedisklabel(dev_t dev, void (*strat)(struct buf *), struct disklabel *lp, 541 struct cpu_disklabel *osdep) 542 { 543 mbr_args_t a; 544 545 memset(&a, 0, sizeof a); 546 a.lp = lp; 547 a.strat = strat; 548 549 /* get a buffer and initialize it */ 550 a.bp = geteblk(2 * (int)lp->d_secsize); 551 a.bp->b_dev = dev; 552 553 /* osdep => we expect an mbr with label in netbsd ptn */ 554 a.action = osdep != NULL ? WRITE_LABEL : UPDATE_LABEL; 555 556 /* Write/update the label to every netbsd mbr partition */ 557 scan_mbr(&a, write_netbsd_label); 558 559 /* Old write the label at the start of the volume on disks that 560 * don't have a valid mbr (always update an existing one) */ 561 a.action = a.found_mbr ? UPDATE_LABEL : WRITE_LABEL; 562 validate_label(&a, 0); 563 564 if (a.written == 0 && a.error == 0) 565 a.error = ESRCH; 566 567 brelse(a.bp); 568 return a.error; 569 } 570 571 static int 572 write_netbsd_label(mbr_args_t *a, mbr_partition_t *dp, int slot, uint ext_base) 573 { 574 int ptn_base = ext_base + le32toh(dp->mbrp_start); 575 576 if (dp->mbrp_type != MBR_PTYPE_NETBSD) 577 return SCAN_CONTINUE; 578 579 return validate_label(a, ptn_base); 580 } 581 582 583 /* 584 * Determine the size of the transfer, and make sure it is 585 * within the boundaries of the partition. Adjust transfer 586 * if needed, and signal errors or early completion. 587 */ 588 int 589 bounds_check_with_label(struct disk *dk, struct buf *bp, int wlabel) 590 { 591 struct disklabel *lp = dk->dk_label; 592 struct partition *p = lp->d_partitions + DISKPART(bp->b_dev); 593 int labelsector = LABELSECTOR; 594 int64_t sz; 595 596 #if RAW_PART == 3 597 labelsector += lp->d_partitions[2].p_offset; 598 #endif 599 600 sz = howmany(bp->b_bcount, lp->d_secsize); 601 602 if (bp->b_blkno + sz > p->p_size) { 603 sz = p->p_size - bp->b_blkno; 604 if (sz == 0) { 605 /* If exactly at end of disk, return EOF. */ 606 bp->b_resid = bp->b_bcount; 607 return (0); 608 } 609 if (sz < 0) { 610 /* If past end of disk, return EINVAL. */ 611 bp->b_error = EINVAL; 612 goto bad; 613 } 614 /* Otherwise, truncate request. */ 615 bp->b_bcount = sz << DEV_BSHIFT; 616 } 617 618 /* Overwriting disk label? */ 619 if (bp->b_blkno + p->p_offset <= labelsector && 620 bp->b_blkno + p->p_offset + sz > labelsector && 621 (bp->b_flags & B_READ) == 0 && !wlabel) { 622 bp->b_error = EROFS; 623 goto bad; 624 } 625 626 /* calculate cylinder for disksort to order transfers with */ 627 bp->b_cylinder = (bp->b_blkno + p->p_offset) / 628 (lp->d_secsize / DEV_BSIZE) / lp->d_secpercyl; 629 return (1); 630 631 bad: 632 bp->b_flags |= B_ERROR; 633 return (-1); 634 } 635