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