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