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