1 /* $NetBSD: disksubr.c,v 1.40 2009/03/18 16:00:10 cegger Exp $ */ 2 3 /* 4 * Copyright (c) 1995 Leo Weppelman. 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 Leo Weppelman. 18 * 4. The name of the author may not be used to endorse or promote products 19 * derived from this software without specific prior written permission 20 * 21 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 22 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 23 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 24 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 25 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 26 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 27 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 28 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 29 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 30 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 */ 32 33 #include <sys/cdefs.h> 34 __KERNEL_RCSID(0, "$NetBSD: disksubr.c,v 1.40 2009/03/18 16:00:10 cegger Exp $"); 35 36 #ifndef DISKLABEL_NBDA 37 #define DISKLABEL_NBDA /* required */ 38 #endif 39 40 #include "opt_compat_netbsd.h" 41 42 #include <sys/param.h> 43 #include <sys/systm.h> 44 #include <sys/buf.h> 45 #include <ufs/ufs/dinode.h> 46 #include <ufs/ffs/fs.h> 47 #include <sys/disk.h> 48 #include <sys/disklabel.h> 49 #include <machine/ahdilabel.h> 50 51 /* 52 * BBSIZE in <ufs/ffs/fs.h> must be greater than 53 * or equal to BBMINSIZE in <machine/disklabel.h> 54 */ 55 #if BBSIZE < BBMINSIZE 56 #error BBSIZE smaller than BBMINSIZE 57 #endif 58 59 static void ck_label(struct disklabel *, struct cpu_disklabel *); 60 static int bsd_label(dev_t, void (*)(struct buf *), 61 struct disklabel *, u_int, u_int *); 62 static int ahdi_label(dev_t, void (*)(struct buf *), 63 struct disklabel *, struct cpu_disklabel *); 64 static void ahdi_to_bsd(struct disklabel *, struct ahdi_ptbl *); 65 static u_int ahdi_getparts(dev_t, void (*)(struct buf *), u_int, 66 u_int, u_int, struct ahdi_ptbl *); 67 68 /* 69 * Attempt to read a disk label from a device using the 70 * indicated strategy routine. The label must be partly 71 * set up before this: 72 * secpercyl and anything required in the strategy routine 73 * (e.g. sector size) must be filled in before calling us. 74 * Returns NULL on success and an error string on failure. 75 */ 76 const char * 77 readdisklabel(dev_t dev, void (*strat)(struct buf *), struct disklabel *lp, struct cpu_disklabel *clp) 78 { 79 int e; 80 81 if (clp != NULL) 82 memset(clp, 0, sizeof *clp); 83 else printf("Warning: clp == NULL\n"); 84 85 /* 86 * Give some guaranteed validity to the disk label. 87 */ 88 if (lp->d_secsize == 0) 89 lp->d_secsize = DEV_BSIZE; 90 if (lp->d_secperunit == 0) 91 lp->d_secperunit = 0x1fffffff; 92 if (lp->d_secpercyl == 0) 93 return("Zero secpercyl"); 94 95 /* 96 * Some parts of the kernel (see scsipi/cd.c for an example) 97 * assume that stuff they already had setup in d_partitions 98 * is still there after reading the disklabel. Hence the 99 * 'if 0' 100 */ 101 #if 0 102 memset(lp->d_partitions, 0, sizeof lp->d_partitions); 103 #endif 104 105 lp->d_partitions[RAW_PART].p_size = lp->d_secperunit; 106 lp->d_npartitions = RAW_PART + 1; 107 lp->d_bbsize = BBSIZE; 108 lp->d_sbsize = SBLOCKSIZE; 109 110 #ifdef DISKLABEL_NBDA 111 /* Try the native NetBSD/Atari format first. */ 112 e = bsd_label(dev, strat, lp, 0, clp != NULL ? &clp->cd_label : NULL); 113 #endif 114 #if 0 115 /* Other label formats go here. */ 116 if (e > 0) 117 e = foo_label(dev, strat, lp, ...); 118 #endif 119 #ifdef DISKLABEL_AHDI 120 /* The unprotected AHDI format comes last. */ 121 if (e > 0 && (clp != NULL)) 122 e = ahdi_label(dev, strat, lp, clp); 123 #endif 124 if (e < 0) 125 return("I/O error"); 126 127 /* Unknown format or uninitialized volume? */ 128 if (e > 0) 129 uprintf("Warning: unknown disklabel format" 130 "- assuming empty disk\n"); 131 132 /* Calulate new checksum. */ 133 lp->d_magic = lp->d_magic2 = DISKMAGIC; 134 lp->d_checksum = 0; 135 lp->d_checksum = dkcksum(lp); 136 137 return(NULL); 138 } 139 140 /* 141 * Check new disk label for sensibility before setting it. 142 */ 143 int 144 setdisklabel(struct disklabel *olp, struct disklabel *nlp, u_long openmask, struct cpu_disklabel *clp) 145 { 146 /* special case to allow disklabel to be invalidated */ 147 if (nlp->d_magic == 0xffffffff) { 148 *olp = *nlp; 149 return(0); 150 } 151 152 /* sanity clause */ 153 if (nlp->d_secpercyl == 0 || nlp->d_npartitions > MAXPARTITIONS 154 || nlp->d_secsize == 0 || (nlp->d_secsize % DEV_BSIZE) != 0 155 || nlp->d_magic != DISKMAGIC || nlp->d_magic2 != DISKMAGIC 156 || dkcksum(nlp) != 0) 157 return(EINVAL); 158 159 #ifdef DISKLABEL_AHDI 160 if (clp && clp->cd_bblock) 161 ck_label(nlp, clp); 162 #endif 163 while (openmask) { 164 struct partition *op, *np; 165 int i = ffs(openmask) - 1; 166 openmask &= ~(1 << i); 167 if (i >= nlp->d_npartitions) 168 return(EBUSY); 169 op = &olp->d_partitions[i]; 170 np = &nlp->d_partitions[i]; 171 if (np->p_offset != op->p_offset || np->p_size < op->p_size) 172 return(EBUSY); 173 /* 174 * Copy internally-set partition information 175 * if new label doesn't include it. XXX 176 */ 177 if (np->p_fstype == FS_UNUSED && op->p_fstype != FS_UNUSED) { 178 np->p_fstype = op->p_fstype; 179 np->p_fsize = op->p_fsize; 180 np->p_frag = op->p_frag; 181 np->p_cpg = op->p_cpg; 182 } 183 } 184 nlp->d_checksum = 0; 185 nlp->d_checksum = dkcksum(nlp); 186 *olp = *nlp; 187 return(0); 188 } 189 190 /* 191 * Write disk label back to device after modification. 192 */ 193 int 194 writedisklabel(dev_t dev, void (*strat)(struct buf *), struct disklabel *lp, struct cpu_disklabel *clp) 195 { 196 struct buf *bp; 197 u_int blk; 198 int rv; 199 200 blk = clp->cd_bblock; 201 if (blk == NO_BOOT_BLOCK) 202 return(ENXIO); 203 204 bp = geteblk(BBMINSIZE); 205 bp->b_dev = MAKEDISKDEV(major(dev), DISKUNIT(dev), RAW_PART); 206 bp->b_flags |= B_READ; 207 bp->b_bcount = BBMINSIZE; 208 bp->b_blkno = blk; 209 bp->b_cylinder = blk / lp->d_secpercyl; 210 (*strat)(bp); 211 rv = biowait(bp); 212 if (!rv) { 213 struct bootblock *bb = (struct bootblock *)bp->b_data; 214 /* 215 * Allthough the disk pack label may appear anywhere 216 * in the boot block while reading, it is always 217 * written at a fixed location. 218 */ 219 if (clp->cd_label != LABELOFFSET) { 220 clp->cd_label = LABELOFFSET; 221 memset(bb, 0, sizeof(*bb)); 222 } 223 bb->bb_magic = (blk == 0) ? NBDAMAGIC : AHDIMAGIC; 224 BBSETLABEL(bb, lp); 225 226 bp->b_oflags &= ~(BO_DONE); 227 bp->b_flags &= ~(B_READ); 228 bp->b_flags |= B_WRITE; 229 bp->b_bcount = BBMINSIZE; 230 bp->b_blkno = blk; 231 bp->b_cylinder = blk / lp->d_secpercyl; 232 (*strat)(bp); 233 rv = biowait(bp); 234 } 235 brelse(bp, 0); 236 return(rv); 237 } 238 239 /* 240 * Read bootblock at block `blkno' and check 241 * if it contains a valid NetBSD disk label. 242 * 243 * Returns: 0 if successful, 244 * -1 if an I/O error occurred, 245 * +1 if no valid label was found. 246 */ 247 static int 248 bsd_label(dev, strat, label, blkno, offsetp) 249 dev_t dev; 250 void (*strat)(struct buf *); 251 struct disklabel *label; 252 u_int blkno, 253 *offsetp; 254 { 255 struct buf *bp; 256 int rv; 257 258 bp = geteblk(BBMINSIZE); 259 bp->b_dev = MAKEDISKDEV(major(dev), DISKUNIT(dev), RAW_PART); 260 bp->b_flags |= B_READ; 261 bp->b_bcount = BBMINSIZE; 262 bp->b_blkno = blkno; 263 bp->b_cylinder = blkno / label->d_secpercyl; 264 (*strat)(bp); 265 266 rv = -1; 267 if (!biowait(bp)) { 268 struct bootblock *bb; 269 u_int32_t *p, *end; 270 271 rv = 1; 272 bb = (struct bootblock *)bp->b_data; 273 end = (u_int32_t *)((char *)&bb[1] - sizeof(struct disklabel)); 274 for (p = (u_int32_t *)bb; p < end; ++p) { 275 struct disklabel *dl = (struct disklabel *)&p[1]; 276 /* 277 * Compatibility kludge: the boot block magic number is 278 * new in 1.1A, in previous versions the disklabel was 279 * stored at the end of the boot block (offset 7168). 280 */ 281 if ( ( (p[0] == NBDAMAGIC && blkno == 0) 282 || (p[0] == AHDIMAGIC && blkno != 0) 283 #ifdef COMPAT_11 284 || (char *)dl - (char *)bb == 7168 285 #endif 286 ) 287 && dl->d_npartitions <= MAXPARTITIONS 288 && dl->d_magic2 == DISKMAGIC 289 && dl->d_magic == DISKMAGIC 290 && dkcksum(dl) == 0 291 ) { 292 if (offsetp != NULL) 293 *offsetp = (char *)dl - (char *)bb; 294 *label = *dl; 295 rv = 0; 296 break; 297 } 298 } 299 } 300 brelse(bp, 0); 301 return(rv); 302 } 303 304 #ifdef DISKLABEL_AHDI 305 /* 306 * Check for consistency between the NetBSD partition table 307 * and the AHDI auxiliary root sectors. There's no good reason 308 * to force such consistency, but issuing a warning may help 309 * an inexperienced sysadmin to prevent corruption of AHDI 310 * partitions. 311 */ 312 static void 313 ck_label(struct disklabel *dl, struct cpu_disklabel *cdl) 314 { 315 u_int *rp, i; 316 317 for (i = 0; i < dl->d_npartitions; ++i) { 318 struct partition *p = &dl->d_partitions[i]; 319 if (i == RAW_PART || p->p_size == 0) 320 continue; 321 if ( (p->p_offset >= cdl->cd_bslst 322 && p->p_offset <= cdl->cd_bslend) 323 || (cdl->cd_bslst >= p->p_offset 324 && cdl->cd_bslst < p->p_offset + p->p_size)) { 325 uprintf("Warning: NetBSD partition %c includes" 326 " AHDI bad sector list\n", 'a'+i); 327 } 328 for (rp = &cdl->cd_roots[0]; *rp; ++rp) { 329 if (*rp >= p->p_offset 330 && *rp < p->p_offset + p->p_size) { 331 uprintf("Warning: NetBSD partition %c" 332 " includes AHDI auxiliary root\n", 'a'+i); 333 } 334 } 335 } 336 } 337 338 /* 339 * Check volume for the existence of an AHDI label. Fetch 340 * NetBSD label from NBD or RAW partition, or otherwise 341 * create a fake NetBSD label based on the AHDI label. 342 * 343 * Returns: 0 if successful, 344 * -1 if an I/O error occurred, 345 * +1 if no valid AHDI label was found. 346 */ 347 int 348 ahdi_label(dev_t dev, void (*strat)(struct buf *), struct disklabel *dl, struct cpu_disklabel *cdl) 349 { 350 struct ahdi_ptbl apt; 351 u_int i; 352 int j; 353 354 /* 355 * The AHDI format requires a specific block size. 356 */ 357 if (dl->d_secsize != AHDI_BSIZE) 358 return(1); 359 360 /* 361 * Fetch the AHDI partition descriptors. 362 */ 363 apt.at_cdl = cdl; 364 apt.at_nroots = apt.at_nparts = 0; 365 i = ahdi_getparts(dev, strat, dl->d_secpercyl, 366 AHDI_BBLOCK, AHDI_BBLOCK, &apt); 367 if (i) { 368 if (i < dl->d_secperunit) 369 return(-1); /* disk read error */ 370 else return(1); /* reading past end of medium */ 371 } 372 373 /* 374 * Perform sanity checks. 375 */ 376 if (apt.at_bslst == 0 || apt.at_bslend == 0) { 377 /* 378 * Illegal according to Atari, however some hd-utils 379 * use it - notably ICD *sigh* 380 * Work around it..... 381 */ 382 apt.at_bslst = apt.at_bslend = 0; 383 uprintf("Warning: Illegal 'bad sector list' format" 384 "- assuming non exists\n"); 385 } 386 if (apt.at_hdsize == 0 || apt.at_nparts == 0) /* unlikely */ 387 return(1); 388 if (apt.at_nparts > AHDI_MAXPARTS) /* XXX kludge */ 389 return(-1); 390 for (i = 0; i < apt.at_nparts; ++i) { 391 struct ahdi_part *p1 = &apt.at_parts[i]; 392 393 for (j = 0; j < apt.at_nroots; ++j) { 394 u_int aux = apt.at_roots[j]; 395 if (aux >= p1->ap_st && aux <= p1->ap_end) 396 return(1); 397 } 398 for (j = i + 1; j < apt.at_nparts; ++j) { 399 struct ahdi_part *p2 = &apt.at_parts[j]; 400 if (p1->ap_st >= p2->ap_st && p1->ap_st <= p2->ap_end) 401 return(1); 402 if (p2->ap_st >= p1->ap_st && p2->ap_st <= p1->ap_end) 403 return(1); 404 } 405 if (p1->ap_st >= apt.at_bslst && p1->ap_st <= apt.at_bslend) 406 return(1); 407 if (apt.at_bslst >= p1->ap_st && apt.at_bslst <= p1->ap_end) 408 return(1); 409 } 410 411 /* 412 * Search for a NetBSD disk label 413 */ 414 apt.at_bblock = NO_BOOT_BLOCK; 415 for (i = 0; i < apt.at_nparts; ++i) { 416 struct ahdi_part *pd = &apt.at_parts[i]; 417 u_int id = *((u_int32_t *)&pd->ap_flg); 418 if (id == AHDI_PID_NBD || id == AHDI_PID_RAW) { 419 u_int blkno = pd->ap_st; 420 j = bsd_label(dev, strat, dl, blkno, &apt.at_label); 421 if (j < 0) { 422 return(j); /* I/O error */ 423 } 424 if (!j) { 425 apt.at_bblock = blkno; /* got it */ 426 ck_label(dl, cdl); 427 return(0); 428 } 429 /* 430 * Not yet, but if this is the first NBD partition 431 * on this volume, we'll mark it anyway as a possible 432 * destination for future writedisklabel() calls, just 433 * in case there is no valid disk label on any of the 434 * other AHDI partitions. 435 */ 436 if (id == AHDI_PID_NBD 437 && apt.at_bblock == NO_BOOT_BLOCK) 438 apt.at_bblock = blkno; 439 } 440 } 441 442 /* 443 * No NetBSD disk label on this volume, use the AHDI 444 * label to create a fake BSD label. If there is no 445 * NBD partition on this volume either, subsequent 446 * writedisklabel() calls will fail. 447 */ 448 ahdi_to_bsd(dl, &apt); 449 return(0); 450 } 451 452 /* 453 * Map the AHDI partition table to the NetBSD table. 454 * 455 * This means: 456 * Part 0 : Root 457 * Part 1 : Swap 458 * Part 2 : Whole disk 459 * Part 3.. : User partitions 460 * 461 * When more than one root partition is found, only the first one will 462 * be recognized as such. The others are mapped as user partitions. 463 */ 464 static void 465 ahdi_to_bsd(struct disklabel *dl, struct ahdi_ptbl *apt) 466 { 467 int i, have_root, user_part; 468 469 user_part = RAW_PART; 470 have_root = (apt->at_bblock != NO_BOOT_BLOCK); 471 472 for (i = 0; i < apt->at_nparts; ++i) { 473 struct ahdi_part *pd = &apt->at_parts[i]; 474 int fst, pno = -1; 475 476 switch (*((u_int32_t *)&pd->ap_flg)) { 477 case AHDI_PID_NBD: 478 /* 479 * If this partition has been marked as the 480 * first NBD partition, it will be the root 481 * partition. 482 */ 483 if (pd->ap_st == apt->at_bblock) 484 pno = 0; 485 /* FALL THROUGH */ 486 case AHDI_PID_NBR: 487 /* 488 * If there is no NBD partition and this is 489 * the first NBR partition, it will be the 490 * root partition. 491 */ 492 if (!have_root) { 493 have_root = 1; 494 pno = 0; 495 } 496 /* FALL THROUGH */ 497 case AHDI_PID_NBU: 498 fst = FS_BSDFFS; 499 break; 500 case AHDI_PID_NBS: 501 case AHDI_PID_SWP: 502 if (dl->d_partitions[1].p_size == 0) 503 pno = 1; 504 fst = FS_SWAP; 505 break; 506 case AHDI_PID_BGM: 507 case AHDI_PID_GEM: 508 fst = FS_MSDOS; 509 break; 510 default: 511 fst = FS_OTHER; 512 break; 513 } 514 if (pno < 0) { 515 if((pno = user_part + 1) >= MAXPARTITIONS) 516 continue; 517 user_part = pno; 518 } 519 dl->d_partitions[pno].p_size = pd->ap_end - pd->ap_st + 1; 520 dl->d_partitions[pno].p_offset = pd->ap_st; 521 dl->d_partitions[pno].p_fstype = fst; 522 } 523 dl->d_npartitions = user_part + 1; 524 } 525 526 /* 527 * Fetch the AHDI partitions and auxiliary roots. 528 * 529 * Returns: 0 if successful, 530 * otherwise an I/O error occurred, and the 531 * number of the offending block is returned. 532 */ 533 static u_int 534 ahdi_getparts(dev, strat, secpercyl, rsec, esec, apt) 535 dev_t dev; 536 void (*strat)(struct buf *); 537 u_int secpercyl, 538 rsec, esec; 539 struct ahdi_ptbl *apt; 540 { 541 struct ahdi_part *part, *end; 542 struct ahdi_root *root; 543 struct buf *bp; 544 u_int rv; 545 546 bp = geteblk(AHDI_BSIZE); 547 bp->b_dev = MAKEDISKDEV(major(dev), DISKUNIT(dev), RAW_PART); 548 bp->b_flags |= B_READ; 549 bp->b_bcount = AHDI_BSIZE; 550 bp->b_blkno = rsec; 551 bp->b_cylinder = rsec / secpercyl; 552 (*strat)(bp); 553 if (biowait(bp)) { 554 rv = rsec + (rsec == 0); 555 goto done; 556 } 557 root = (struct ahdi_root *)bp->b_data; 558 559 if (rsec == AHDI_BBLOCK) 560 end = &root->ar_parts[AHDI_MAXRPD]; 561 else end = &root->ar_parts[AHDI_MAXARPD]; 562 for (part = root->ar_parts; part < end; ++part) { 563 u_int id = *((u_int32_t *)&part->ap_flg); 564 if (!(id & 0x01000000)) 565 continue; 566 if ((id &= 0x00ffffff) == AHDI_PID_XGM) { 567 u_int offs = part->ap_st + esec; 568 if (apt->at_nroots < AHDI_MAXROOTS) 569 apt->at_roots[apt->at_nroots] = offs; 570 apt->at_nroots += 1; 571 rv = ahdi_getparts(dev, strat, secpercyl, offs, 572 (esec == AHDI_BBLOCK) ? offs : esec, apt); 573 if (rv) 574 goto done; 575 continue; 576 } 577 else if (apt->at_nparts < AHDI_MAXPARTS) { 578 struct ahdi_part *p = &apt->at_parts[apt->at_nparts]; 579 *((u_int32_t *)&p->ap_flg) = id; 580 p->ap_st = part->ap_st + rsec; 581 p->ap_end = p->ap_st + part->ap_size - 1; 582 } 583 apt->at_nparts += 1; 584 } 585 apt->at_hdsize = root->ar_hdsize; 586 apt->at_bslst = root->ar_bslst; 587 apt->at_bslend = root->ar_bslst + root->ar_bslsize - 1; 588 rv = 0; 589 done: 590 brelse(bp, 0); 591 return(rv); 592 } 593 #endif /* DISKLABEL_AHDI */ 594