1 /* $NetBSD: disksubr.c,v 1.43 2010/04/13 11:22:22 tsutsui 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.43 2010/04/13 11:22:22 tsutsui 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, 73 struct cpu_disklabel *clp) 74 { 75 int e; 76 77 if (clp != NULL) 78 memset(clp, 0, sizeof *clp); 79 else 80 printf("Warning: clp == NULL\n"); 81 82 /* 83 * Give some guaranteed validity to the disk label. 84 */ 85 if (lp->d_secsize == 0) 86 lp->d_secsize = DEV_BSIZE; 87 if (lp->d_secperunit == 0) 88 lp->d_secperunit = 0x1fffffff; 89 if (lp->d_secpercyl == 0) 90 return "Zero secpercyl"; 91 92 /* 93 * Some parts of the kernel (see scsipi/cd.c for an example) 94 * assume that stuff they already had setup in d_partitions 95 * is still there after reading the disklabel. Hence the 96 * 'if 0' 97 */ 98 #if 0 99 memset(lp->d_partitions, 0, sizeof lp->d_partitions); 100 #endif 101 102 lp->d_partitions[RAW_PART].p_size = lp->d_secperunit; 103 lp->d_npartitions = RAW_PART + 1; 104 lp->d_bbsize = BBSIZE; 105 lp->d_sbsize = SBLOCKSIZE; 106 107 #ifdef DISKLABEL_NBDA 108 /* Try the native NetBSD/Atari format first. */ 109 e = bsd_label(dev, strat, lp, 0, clp != NULL ? &clp->cd_label : NULL); 110 #endif 111 #if 0 112 /* Other label formats go here. */ 113 if (e > 0) 114 e = foo_label(dev, strat, lp, ...); 115 #endif 116 #ifdef DISKLABEL_AHDI 117 /* The unprotected AHDI format comes last. */ 118 if (e > 0 && (clp != NULL)) 119 e = ahdi_label(dev, strat, lp, clp); 120 #endif 121 if (e < 0) 122 return "I/O error"; 123 124 /* Unknown format or uninitialized volume? */ 125 if (e > 0) 126 uprintf("Warning: unknown disklabel format" 127 "- assuming empty disk\n"); 128 129 /* Calulate new checksum. */ 130 lp->d_magic = lp->d_magic2 = DISKMAGIC; 131 lp->d_checksum = 0; 132 lp->d_checksum = dkcksum(lp); 133 134 return NULL; 135 } 136 137 /* 138 * Check new disk label for sensibility before setting it. 139 */ 140 int 141 setdisklabel(struct disklabel *olp, struct disklabel *nlp, u_long openmask, 142 struct cpu_disklabel *clp) 143 { 144 145 /* special case to allow disklabel to be invalidated */ 146 if (nlp->d_magic == 0xffffffff) { 147 *olp = *nlp; 148 return 0; 149 } 150 151 /* sanity clause */ 152 if (nlp->d_secpercyl == 0 || nlp->d_npartitions > MAXPARTITIONS || 153 nlp->d_secsize == 0 || (nlp->d_secsize % DEV_BSIZE) != 0 || 154 nlp->d_magic != DISKMAGIC || nlp->d_magic2 != DISKMAGIC || 155 dkcksum(nlp) != 0) 156 return EINVAL; 157 158 #ifdef DISKLABEL_AHDI 159 if (clp && clp->cd_bblock) 160 ck_label(nlp, clp); 161 #endif 162 while (openmask) { 163 struct partition *op, *np; 164 int i = ffs(openmask) - 1; 165 openmask &= ~(1 << i); 166 if (i >= nlp->d_npartitions) 167 return EBUSY; 168 op = &olp->d_partitions[i]; 169 np = &nlp->d_partitions[i]; 170 if (np->p_offset != op->p_offset || np->p_size < op->p_size) 171 return EBUSY; 172 /* 173 * Copy internally-set partition information 174 * if new label doesn't include it. XXX 175 */ 176 if (np->p_fstype == FS_UNUSED && op->p_fstype != FS_UNUSED) { 177 np->p_fstype = op->p_fstype; 178 np->p_fsize = op->p_fsize; 179 np->p_frag = op->p_frag; 180 np->p_cpg = op->p_cpg; 181 } 182 } 183 nlp->d_checksum = 0; 184 nlp->d_checksum = dkcksum(nlp); 185 *olp = *nlp; 186 return 0; 187 } 188 189 /* 190 * Write disk label back to device after modification. 191 */ 192 int 193 writedisklabel(dev_t dev, void (*strat)(struct buf *), struct disklabel *lp, 194 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 == 0) { 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_t dev, void (*strat)(struct buf *), struct disklabel *label, 249 u_int blkno, u_int *offsetp) 250 { 251 struct buf *bp; 252 int rv; 253 254 bp = geteblk(BBMINSIZE); 255 bp->b_dev = MAKEDISKDEV(major(dev), DISKUNIT(dev), RAW_PART); 256 bp->b_flags |= B_READ; 257 bp->b_bcount = BBMINSIZE; 258 bp->b_blkno = blkno; 259 bp->b_cylinder = blkno / label->d_secpercyl; 260 (*strat)(bp); 261 262 rv = -1; 263 if (!biowait(bp)) { 264 struct bootblock *bb; 265 uint32_t *p, *end; 266 267 rv = 1; 268 bb = (struct bootblock *)bp->b_data; 269 end = (uint32_t *)((char *)&bb[1] - sizeof(struct disklabel)); 270 for (p = (uint32_t *)bb; p < end; ++p) { 271 struct disklabel *dl = (struct disklabel *)&p[1]; 272 /* 273 * Compatibility kludge: the boot block magic number is 274 * new in 1.1A, in previous versions the disklabel was 275 * stored at the end of the boot block (offset 7168). 276 */ 277 if (((p[0] == NBDAMAGIC && blkno == 0) || 278 (p[0] == AHDIMAGIC && blkno != 0) 279 #ifdef COMPAT_11 280 || (char *)dl - (char *)bb == 7168 281 #endif 282 ) && 283 dl->d_npartitions <= MAXPARTITIONS && 284 dl->d_magic2 == DISKMAGIC && 285 dl->d_magic == DISKMAGIC && 286 dkcksum(dl) == 0) { 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, 344 struct cpu_disklabel *cdl) 345 { 346 struct ahdi_ptbl apt; 347 u_int i; 348 int j; 349 350 /* 351 * The AHDI format requires a specific block size. 352 */ 353 if (dl->d_secsize != AHDI_BSIZE) 354 return 1; 355 356 /* 357 * Fetch the AHDI partition descriptors. 358 */ 359 apt.at_cdl = cdl; 360 apt.at_nroots = apt.at_nparts = 0; 361 i = ahdi_getparts(dev, strat, dl->d_secpercyl, 362 AHDI_BBLOCK, AHDI_BBLOCK, &apt); 363 if (i) { 364 if (i < dl->d_secperunit) 365 return -1; /* disk read error */ 366 else 367 return 1; /* reading past end of medium */ 368 } 369 370 /* 371 * Perform sanity checks. 372 */ 373 if (apt.at_bslst == 0 || apt.at_bslend == 0) { 374 /* 375 * Illegal according to Atari, however some hd-utils 376 * use it - notably ICD *sigh* 377 * Work around it..... 378 */ 379 apt.at_bslst = apt.at_bslend = 0; 380 uprintf("Warning: Illegal 'bad sector list' format" 381 "- assuming non exists\n"); 382 } 383 if (apt.at_hdsize == 0 || apt.at_nparts == 0) /* unlikely */ 384 return 1; 385 if (apt.at_nparts > AHDI_MAXPARTS) /* XXX kludge */ 386 return -1; 387 for (i = 0; i < apt.at_nparts; ++i) { 388 struct ahdi_part *p1 = &apt.at_parts[i]; 389 390 for (j = 0; j < apt.at_nroots; ++j) { 391 u_int aux = apt.at_roots[j]; 392 if (aux >= p1->ap_st && aux <= p1->ap_end) 393 return 1; 394 } 395 for (j = i + 1; j < apt.at_nparts; ++j) { 396 struct ahdi_part *p2 = &apt.at_parts[j]; 397 if (p1->ap_st >= p2->ap_st && p1->ap_st <= p2->ap_end) 398 return 1; 399 if (p2->ap_st >= p1->ap_st && p2->ap_st <= p1->ap_end) 400 return 1; 401 } 402 if (p1->ap_st >= apt.at_bslst && p1->ap_st <= apt.at_bslend) 403 return 1; 404 if (apt.at_bslst >= p1->ap_st && apt.at_bslst <= p1->ap_end) 405 return 1; 406 } 407 408 /* 409 * Search for a NetBSD disk label 410 */ 411 apt.at_bblock = NO_BOOT_BLOCK; 412 for (i = 0; i < apt.at_nparts; ++i) { 413 struct ahdi_part *pd = &apt.at_parts[i]; 414 u_int id = *((uint32_t *)&pd->ap_flg); 415 if (id == AHDI_PID_NBD || id == AHDI_PID_RAW) { 416 u_int blkno = pd->ap_st; 417 j = bsd_label(dev, strat, dl, blkno, &apt.at_label); 418 if (j < 0) { 419 return j; /* I/O error */ 420 } 421 if (j == 0) { 422 apt.at_bblock = blkno; /* got it */ 423 ck_label(dl, cdl); 424 return 0; 425 } 426 /* 427 * Not yet, but if this is the first NBD partition 428 * on this volume, we'll mark it anyway as a possible 429 * destination for future writedisklabel() calls, just 430 * in case there is no valid disk label on any of the 431 * other AHDI partitions. 432 */ 433 if (id == AHDI_PID_NBD && 434 apt.at_bblock == NO_BOOT_BLOCK) 435 apt.at_bblock = blkno; 436 } 437 } 438 439 /* 440 * No NetBSD disk label on this volume, use the AHDI 441 * label to create a fake BSD label. If there is no 442 * NBD partition on this volume either, subsequent 443 * writedisklabel() calls will fail. 444 */ 445 ahdi_to_bsd(dl, &apt); 446 return 0; 447 } 448 449 /* 450 * Map the AHDI partition table to the NetBSD table. 451 * 452 * This means: 453 * Part 0 : Root 454 * Part 1 : Swap 455 * Part 2 : Whole disk 456 * Part 3.. : User partitions 457 * 458 * When more than one root partition is found, only the first one will 459 * be recognized as such. The others are mapped as user partitions. 460 */ 461 static void 462 ahdi_to_bsd(struct disklabel *dl, struct ahdi_ptbl *apt) 463 { 464 int i, have_root, user_part; 465 466 user_part = RAW_PART; 467 have_root = (apt->at_bblock != NO_BOOT_BLOCK); 468 469 for (i = 0; i < apt->at_nparts; ++i) { 470 struct ahdi_part *pd = &apt->at_parts[i]; 471 int fst, pno = -1; 472 473 switch (*((uint32_t *)&pd->ap_flg)) { 474 case AHDI_PID_NBD: 475 /* 476 * If this partition has been marked as the 477 * first NBD partition, it will be the root 478 * partition. 479 */ 480 if (pd->ap_st == apt->at_bblock) 481 pno = 0; 482 /* FALL THROUGH */ 483 case AHDI_PID_NBR: 484 /* 485 * If there is no NBD partition and this is 486 * the first NBR partition, it will be the 487 * root partition. 488 */ 489 if (!have_root) { 490 have_root = 1; 491 pno = 0; 492 } 493 /* FALL THROUGH */ 494 case AHDI_PID_NBU: 495 fst = FS_BSDFFS; 496 break; 497 case AHDI_PID_NBS: 498 case AHDI_PID_SWP: 499 if (dl->d_partitions[1].p_size == 0) 500 pno = 1; 501 fst = FS_SWAP; 502 break; 503 case AHDI_PID_BGM: 504 case AHDI_PID_GEM: 505 fst = FS_MSDOS; 506 break; 507 default: 508 fst = FS_OTHER; 509 break; 510 } 511 if (pno < 0) { 512 if ((pno = user_part + 1) >= MAXPARTITIONS) 513 continue; 514 user_part = pno; 515 } 516 dl->d_partitions[pno].p_size = pd->ap_end - pd->ap_st + 1; 517 dl->d_partitions[pno].p_offset = pd->ap_st; 518 dl->d_partitions[pno].p_fstype = fst; 519 } 520 dl->d_npartitions = user_part + 1; 521 } 522 523 /* 524 * Fetch the AHDI partitions and auxiliary roots. 525 * 526 * Returns: 0 if successful, 527 * otherwise an I/O error occurred, and the 528 * number of the offending block is returned. 529 */ 530 static u_int 531 ahdi_getparts(dev_t dev, void (*strat)(struct buf *), u_int secpercyl, 532 u_int rsec, u_int esec, struct ahdi_ptbl *apt) 533 { 534 struct ahdi_part *part, *end; 535 struct ahdi_root *root; 536 struct buf *bp; 537 u_int rv; 538 539 bp = geteblk(AHDI_BSIZE); 540 bp->b_dev = MAKEDISKDEV(major(dev), DISKUNIT(dev), RAW_PART); 541 bp->b_flags |= B_READ; 542 bp->b_bcount = AHDI_BSIZE; 543 bp->b_blkno = rsec; 544 bp->b_cylinder = rsec / secpercyl; 545 (*strat)(bp); 546 if (biowait(bp)) { 547 rv = rsec + (rsec == 0); 548 goto done; 549 } 550 root = (struct ahdi_root *)bp->b_data; 551 552 if (rsec == AHDI_BBLOCK) 553 end = &root->ar_parts[AHDI_MAXRPD]; 554 else 555 end = &root->ar_parts[AHDI_MAXARPD]; 556 for (part = root->ar_parts; part < end; ++part) { 557 u_int id = *((uint32_t *)&part->ap_flg); 558 if (!(id & 0x01000000)) 559 continue; 560 if ((id &= 0x00ffffff) == AHDI_PID_XGM) { 561 u_int offs = part->ap_st + esec; 562 if (apt->at_nroots < AHDI_MAXROOTS) 563 apt->at_roots[apt->at_nroots] = offs; 564 apt->at_nroots += 1; 565 rv = ahdi_getparts(dev, strat, secpercyl, offs, 566 (esec == AHDI_BBLOCK) ? offs : esec, apt); 567 if (rv != 0) 568 goto done; 569 continue; 570 } 571 else if (apt->at_nparts < AHDI_MAXPARTS) { 572 struct ahdi_part *p = &apt->at_parts[apt->at_nparts]; 573 *((uint32_t *)&p->ap_flg) = id; 574 p->ap_st = part->ap_st + rsec; 575 p->ap_end = p->ap_st + part->ap_size - 1; 576 } 577 apt->at_nparts += 1; 578 } 579 apt->at_hdsize = root->ar_hdsize; 580 apt->at_bslst = root->ar_bslst; 581 apt->at_bslend = root->ar_bslst + root->ar_bslsize - 1; 582 rv = 0; 583 done: 584 brelse(bp, 0); 585 return rv; 586 } 587 #endif /* DISKLABEL_AHDI */ 588