1 /* 2 * CDDL HEADER START 3 * 4 * The contents of this file are subject to the terms of the 5 * Common Development and Distribution License (the "License"). 6 * You may not use this file except in compliance with the License. 7 * 8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 9 * or http://www.opensolaris.org/os/licensing. 10 * See the License for the specific language governing permissions 11 * and limitations under the License. 12 * 13 * When distributing Covered Code, include this CDDL HEADER in each 14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE. 15 * If applicable, add the following below this CDDL HEADER, with the 16 * fields enclosed by brackets "[]" replaced with your own identifying 17 * information: Portions Copyright [yyyy] [name of copyright owner] 18 * 19 * CDDL HEADER END 20 */ 21 /* 22 * Copyright (c) 2011 Gary Mills 23 * 24 * Copyright 2008 Sun Microsystems, Inc. All rights reserved. 25 * Use is subject to license terms. 26 */ 27 28 /* 29 * fsck_pcfs -- common.c 30 * All the routines in this file are being swiped directly from 31 * mkfs_pcfs. Eventually this file should only exist in one place 32 * and be part of a library that both mkfs and fsck link against. 33 */ 34 #include <stdio.h> 35 #include <string.h> 36 #include <unistd.h> 37 #include <stdlib.h> 38 #include <libintl.h> 39 #include <sys/isa_defs.h> 40 #include <sys/types.h> 41 #include <sys/stat.h> 42 #include <sys/fcntl.h> 43 #include <sys/dktp/fdisk.h> 44 #include <sys/fs/pc_fs.h> 45 #include <sys/fs/pc_dir.h> 46 #include <sys/fs/pc_label.h> 47 #include "fsck_pcfs.h" 48 #include "pcfs_common.h" 49 #include "pcfs_bpb.h" 50 51 /* 52 * The assumption here is that _BIG_ENDIAN implies sparc, and 53 * so in addition to swapping bytes we also have to construct 54 * packed structures by hand to avoid bus errors due to improperly 55 * aligned pointers. 56 */ 57 #ifdef _BIG_ENDIAN 58 void swap_pack_grab32bpb(bpb_t *wbpb, struct _boot_sector *bsp); 59 void swap_pack_grabbpb(bpb_t *wbpb, struct _boot_sector *bsp); 60 #endif /* _BIG_ENDIAN */ 61 62 /* 63 * Global variables related to input questions 64 */ 65 extern int AlwaysYes; 66 extern int AlwaysNo; 67 68 /* 69 * store_16_bits 70 * Save the lower 16 bits of a 32 bit value (v) into the provided 71 * buffer (pointed at by *bp), and increment the buffer pointer 72 * as well. This way the routine can be called multiple times in 73 * succession to fill buffers. The value is stored in little-endian 74 * order. 75 */ 76 void 77 store_16_bits(uchar_t **bp, uint32_t v) 78 { 79 uchar_t *l = *bp; 80 81 *l++ = v & 0xff; 82 *l = (v >> 8) & 0xff; 83 *bp += 2; 84 } 85 86 void 87 read_16_bits(uchar_t *bp, uint32_t *value) 88 { 89 *value = *bp++; 90 *value += *bp << 8; 91 } 92 93 /* 94 * store_32_bits 95 * Save the 32 bit value (v) into the provided buffer (pointed 96 * at by *bp), and increment the buffer pointer as well. This way 97 * the routine can be called multiple times in succession to fill 98 * buffers. The value is stored in little-endian order. 99 */ 100 void 101 store_32_bits(uchar_t **bp, uint32_t v) 102 { 103 uchar_t *l = *bp; 104 int b; 105 106 for (b = 0; b < 4; b++) { 107 *l++ = v & 0xff; 108 v = v >> 8; 109 } 110 *bp += 4; 111 } 112 113 void 114 read_32_bits(uchar_t *bp, uint32_t *value) 115 { 116 *value = *bp++; 117 *value += *bp++ << 8; 118 *value += *bp++ << 16; 119 *value += *bp++ << 24; 120 } 121 122 /* 123 * dump_bytes -- display bytes as hex numbers. 124 * b is the pointer to the byte buffer 125 * n is the number of bytes in the buffer 126 */ 127 /* Note: BPL = bytes to display per line */ 128 #define BPL 16 129 130 void 131 dump_bytes(uchar_t *buf, int n) 132 { 133 int printedCount; 134 int countdown = n; 135 int countup = 0; 136 int offset = 0; 137 int byte; 138 139 /* Display offset, 16 bytes per line, and printable ascii version */ 140 while (countdown > 0) { 141 printedCount = 0; 142 (void) fprintf(stderr, "\n%06x: ", offset); 143 /* 144 * Print Hex value of characters in columns on left 145 */ 146 for (byte = 0; byte < BPL; byte++) { 147 if (countup + byte < n) { 148 (void) fprintf(stderr, 149 "%02x ", (buf[countup + byte] & 0xff)); 150 printedCount++; 151 } else { 152 (void) fprintf(stderr, " "); 153 } 154 } 155 /* 156 * Right side has the printable character or '.' for 157 * unprintable for each column of the left. 158 */ 159 for (byte = 0; byte < BPL; byte++) { 160 if ((countup + byte < n) && 161 ((buf[countup + byte] >= ' ') && 162 (buf[countup + byte] <= '~'))) { 163 (void) fprintf(stderr, "%c", 164 buf[countup + byte]); 165 } else { 166 (void) fprintf(stderr, "."); 167 } 168 } 169 countup += printedCount; 170 offset += printedCount; 171 countdown -= printedCount; 172 } 173 (void) fprintf(stderr, "\n\n"); 174 } 175 176 /* 177 * header_for_dump -- display simple header over what will be output. 178 */ 179 void 180 header_for_dump(void) 181 { 182 int byte; 183 184 (void) fprintf(stderr, "\n "); 185 for (byte = 0; byte < BPL; byte++) 186 (void) fprintf(stderr, "%02x ", byte); 187 (void) fprintf(stderr, "\n "); 188 byte = 3*BPL; 189 while (byte-- > 0) 190 (void) fprintf(stderr, "-"); 191 } 192 193 /* 194 * We are basically (incorrectly) assuming that if you aren't running 195 * on x86 the BPB has to be packed by hand AND that the bytes must 196 * be swapped. One or both of these assumptions may one day be invalid. 197 * (if they aren't already :-)) 198 */ 199 #ifdef _BIG_ENDIAN 200 /* 201 * swap_pack_grab{32}bpb 202 * If not on an x86 we assume the structures making up the bpb 203 * were not packed and that longs and shorts need to be byte swapped 204 * (we've kept everything in host order up until now). A new architecture 205 * might not need to swap or might not need to pack, in which case 206 * new routines will have to be written. Of course if an architecture 207 * supports both packing and little-endian host order, it can follow the 208 * same path as the x86 code. 209 */ 210 void 211 swap_pack_grabbpb(bpb_t *wbpb, struct _boot_sector *bsp) 212 { 213 uchar_t *grabp; 214 215 grabp = (uchar_t *)&(bsp->bs_filler[ORIG_BPB_START_INDEX]); 216 217 ((uchar_t *)&(wbpb->bpb.bytes_per_sector))[1] = *grabp++; 218 ((uchar_t *)&(wbpb->bpb.bytes_per_sector))[0] = *grabp++; 219 wbpb->bpb.sectors_per_cluster = *grabp++; 220 ((uchar_t *)&(wbpb->bpb.resv_sectors))[1] = *grabp++; 221 ((uchar_t *)&(wbpb->bpb.resv_sectors))[0] = *grabp++; 222 wbpb->bpb.num_fats = *grabp++; 223 ((uchar_t *)&(wbpb->bpb.num_root_entries))[1] = *grabp++; 224 ((uchar_t *)&(wbpb->bpb.num_root_entries))[0] = *grabp++; 225 ((uchar_t *)&(wbpb->bpb.sectors_in_volume))[1] = *grabp++; 226 ((uchar_t *)&(wbpb->bpb.sectors_in_volume))[0] = *grabp++; 227 wbpb->bpb.media = *grabp++; 228 ((uchar_t *)&(wbpb->bpb.sectors_per_fat))[1] = *grabp++; 229 ((uchar_t *)&(wbpb->bpb.sectors_per_fat))[0] = *grabp++; 230 ((uchar_t *)&(wbpb->bpb.sectors_per_track))[1] = *grabp++; 231 ((uchar_t *)&(wbpb->bpb.sectors_per_track))[0] = *grabp++; 232 ((uchar_t *)&(wbpb->bpb.heads))[1] = *grabp++; 233 ((uchar_t *)&(wbpb->bpb.heads))[0] = *grabp++; 234 ((uchar_t *)&(wbpb->bpb.hidden_sectors))[3] = *grabp++; 235 ((uchar_t *)&(wbpb->bpb.hidden_sectors))[2] = *grabp++; 236 ((uchar_t *)&(wbpb->bpb.hidden_sectors))[1] = *grabp++; 237 ((uchar_t *)&(wbpb->bpb.hidden_sectors))[0] = *grabp++; 238 ((uchar_t *)&(wbpb->bpb.sectors_in_logical_volume))[3] = *grabp++; 239 ((uchar_t *)&(wbpb->bpb.sectors_in_logical_volume))[2] = *grabp++; 240 ((uchar_t *)&(wbpb->bpb.sectors_in_logical_volume))[1] = *grabp++; 241 ((uchar_t *)&(wbpb->bpb.sectors_in_logical_volume))[0] = *grabp++; 242 wbpb->ebpb.phys_drive_num = *grabp++; 243 wbpb->ebpb.reserved = *grabp++; 244 wbpb->ebpb.ext_signature = *grabp++; 245 ((uchar_t *)&(wbpb->ebpb.volume_id))[3] = *grabp++; 246 ((uchar_t *)&(wbpb->ebpb.volume_id))[2] = *grabp++; 247 ((uchar_t *)&(wbpb->ebpb.volume_id))[1] = *grabp++; 248 ((uchar_t *)&(wbpb->ebpb.volume_id))[0] = *grabp++; 249 250 (void) strncpy((char *)wbpb->ebpb.volume_label, (char *)grabp, 11); 251 grabp += 11; 252 (void) strncpy((char *)wbpb->ebpb.type, (char *)grabp, 8); 253 } 254 255 void 256 swap_pack_grab32bpb(bpb_t *wbpb, struct _boot_sector *bsp) 257 { 258 uchar_t *grabp; 259 260 grabp = (uchar_t *)&(bsp->bs_filler[BPB_32_START_INDEX]); 261 262 ((uchar_t *)&(wbpb->bpb32.big_sectors_per_fat))[3] = *grabp++; 263 ((uchar_t *)&(wbpb->bpb32.big_sectors_per_fat))[2] = *grabp++; 264 ((uchar_t *)&(wbpb->bpb32.big_sectors_per_fat))[1] = *grabp++; 265 ((uchar_t *)&(wbpb->bpb32.big_sectors_per_fat))[0] = *grabp++; 266 ((uchar_t *)&(wbpb->bpb32.ext_flags))[1] = *grabp++; 267 ((uchar_t *)&(wbpb->bpb32.ext_flags))[0] = *grabp++; 268 wbpb->bpb32.fs_vers_lo = *grabp++; 269 wbpb->bpb32.fs_vers_hi = *grabp++; 270 ((uchar_t *)&(wbpb->bpb32.root_dir_clust))[3] = *grabp++; 271 ((uchar_t *)&(wbpb->bpb32.root_dir_clust))[2] = *grabp++; 272 ((uchar_t *)&(wbpb->bpb32.root_dir_clust))[1] = *grabp++; 273 ((uchar_t *)&(wbpb->bpb32.root_dir_clust))[0] = *grabp++; 274 ((uchar_t *)&(wbpb->bpb32.fsinfosec))[1] = *grabp++; 275 ((uchar_t *)&(wbpb->bpb32.fsinfosec))[0] = *grabp++; 276 ((uchar_t *)&(wbpb->bpb32.backupboot))[1] = *grabp++; 277 ((uchar_t *)&(wbpb->bpb32.backupboot))[0] = *grabp++; 278 ((uchar_t *)&(wbpb->bpb32.reserved[0]))[1] = *grabp++; 279 ((uchar_t *)&(wbpb->bpb32.reserved[0]))[0] = *grabp++; 280 ((uchar_t *)&(wbpb->bpb32.reserved[1]))[1] = *grabp++; 281 ((uchar_t *)&(wbpb->bpb32.reserved[1]))[0] = *grabp++; 282 ((uchar_t *)&(wbpb->bpb32.reserved[2]))[1] = *grabp++; 283 ((uchar_t *)&(wbpb->bpb32.reserved[2]))[0] = *grabp++; 284 ((uchar_t *)&(wbpb->bpb32.reserved[3]))[1] = *grabp++; 285 ((uchar_t *)&(wbpb->bpb32.reserved[3]))[0] = *grabp++; 286 ((uchar_t *)&(wbpb->bpb32.reserved[4]))[1] = *grabp++; 287 ((uchar_t *)&(wbpb->bpb32.reserved[4]))[0] = *grabp++; 288 ((uchar_t *)&(wbpb->bpb32.reserved[5]))[1] = *grabp++; 289 ((uchar_t *)&(wbpb->bpb32.reserved[5]))[0] = *grabp++; 290 } 291 #endif /* _BIG_ENDIAN */ 292 293 int 294 yes(void) 295 { 296 char *affirmative = gettext("yY"); 297 char *a = affirmative; 298 char input[80]; 299 300 if (AlwaysYes) { 301 (void) printf("y\n"); 302 return (1); 303 } else if (AlwaysNo) { 304 (void) printf("n\n"); 305 return (0); 306 } 307 if (fgets(input, sizeof (input), stdin) == NULL) { 308 AlwaysNo = 1; 309 (void) printf("n\n"); 310 return (0); 311 } 312 while (*a) { 313 if (input[0] == (int)*a) 314 break; 315 a++; 316 } 317 return ((int)*a); 318 } 319 320 char * 321 stat_actual_disk(char *diskname, struct stat *info, char **suffix) 322 { 323 char *actualdisk; 324 325 if (stat(diskname, info)) { 326 /* 327 * Device named on command line doesn't exist. That 328 * probably means there is a partition-specifying 329 * suffix attached to the actual disk name. 330 */ 331 if ((actualdisk = strdup(diskname)) == NULL) { 332 (void) fprintf(stderr, 333 gettext("Out of memory for disk name.\n")); 334 exit(2); 335 } 336 if ((*suffix = strchr(actualdisk, ':')) != NULL) { 337 **suffix = '\0'; 338 (*suffix)++; 339 } 340 341 if (stat(actualdisk, info)) { 342 perror(actualdisk); 343 exit(2); 344 } 345 } else { 346 if ((actualdisk = strdup(diskname)) == NULL) { 347 (void) fprintf(stderr, 348 gettext("Out of memory for disk name.\n")); 349 exit(2); 350 } 351 } 352 353 return (actualdisk); 354 } 355 356 extern void usage(void); 357 358 void 359 bad_arg(char *option) 360 { 361 (void) fprintf(stderr, 362 gettext("Unrecognized option -o %s.\n"), option); 363 usage(); 364 exit(2); 365 } 366 367 void 368 missing_arg(char *option) 369 { 370 (void) fprintf(stderr, 371 gettext("Option %s requires a value.\n"), option); 372 usage(); 373 exit(3); 374 } 375 376 static int 377 parse_drvnum(char *pn) 378 { 379 int drvnum; 380 381 /* 382 * Determine logical drive to seek after. 383 */ 384 if ((strlen(pn) == 1) && ((*pn >= 'c') && (*pn <= 'z'))) { 385 drvnum = *pn - 'c' + 1; 386 } else if ((*pn >= '0') && (*pn <= '9')) { 387 char *d; 388 int v = 0; 389 390 d = pn; 391 while ((*d != '\0') && (*d >= '0') && (*d <= '9')) { 392 v *= 10; 393 v += *d - '0'; 394 d++; 395 } 396 if ((*d != '\0') || (v > 24)) { 397 (void) fprintf(stderr, 398 gettext("%s: bogus logical drive specification.\n"), 399 pn); 400 return (-1); 401 } 402 drvnum = v; 403 } else if (strcmp(pn, "boot") == 0) { 404 drvnum = 99; 405 } else { 406 (void) fprintf(stderr, 407 gettext("%s: bogus logical drive specification.\n"), pn); 408 return (-1); 409 } 410 411 return (drvnum); 412 } 413 414 /* 415 * isDosDrive() 416 * Boolean function. Give it the systid field for an fdisk partition 417 * and it decides if that's a systid that describes a DOS drive. We 418 * use systid values defined in sys/dktp/fdisk.h. 419 */ 420 static int 421 isDosDrive(uchar_t checkMe) 422 { 423 return ((checkMe == DOSOS12) || (checkMe == DOSOS16) || 424 (checkMe == DOSHUGE) || (checkMe == FDISK_WINDOWS) || 425 (checkMe == FDISK_EXT_WIN) || (checkMe == FDISK_FAT95) || 426 (checkMe == DIAGPART)); 427 } 428 429 /* 430 * isDosExtended() 431 * Boolean function. Give it the systid field for an fdisk partition 432 * and it decides if that's a systid that describes an extended DOS 433 * partition. 434 */ 435 static int 436 isDosExtended(uchar_t checkMe) 437 { 438 return ((checkMe == EXTDOS) || (checkMe == FDISK_EXTLBA)); 439 } 440 441 /* 442 * isBootPart() 443 * Boolean function. Give it the systid field for an fdisk partition 444 * and it decides if that's a systid that describes a Solaris boot 445 * partition. 446 */ 447 static int 448 isBootPart(uchar_t checkMe) 449 { 450 return (checkMe == X86BOOT); 451 } 452 453 off64_t 454 findPartitionOffset(int fd, char *ldrive) 455 { 456 struct ipart part[FD_NUMPART]; 457 struct mboot extmboot; 458 struct mboot mb; 459 diskaddr_t xstartsect; 460 off64_t nextseek = 0; 461 off64_t lastseek = 0; 462 off64_t found = 0; 463 off64_t error = -1; 464 int logicalDriveCount = 0; 465 int extendedPart = -1; 466 int primaryPart = -1; 467 int bootPart = -1; 468 uint32_t xnumsect = 0; 469 int drvnum; 470 int driveIndex; 471 int i; 472 /* 473 * Count of drives in the current extended partition's 474 * FDISK table, and indexes of the drives themselves. 475 */ 476 int extndDrives[FD_NUMPART]; 477 int numDrives = 0; 478 /* 479 * Count of drives (beyond primary) in master boot record's 480 * FDISK table, and indexes of the drives themselves. 481 */ 482 int extraDrives[FD_NUMPART]; 483 int numExtraDrives = 0; 484 485 if ((drvnum = parse_drvnum(ldrive)) < 0) 486 return (error); 487 488 if (read(fd, &mb, sizeof (mb)) != sizeof (mb)) { 489 (void) fprintf(stderr, 490 gettext("Couldn't read a Master Boot Record\n")); 491 return (error); 492 } 493 494 if (ltohs(mb.signature) != BOOTSECSIG) { 495 (void) fprintf(stderr, 496 gettext("Bad signature on master boot record (%x)\n"), 497 ltohs(mb.signature)); 498 return (error); 499 } 500 501 /* 502 * Copy partition table into memory 503 */ 504 (void) memcpy(part, mb.parts, sizeof (part)); 505 506 /* 507 * Get a summary of what is in the Master FDISK table. 508 * Normally we expect to find one partition marked as a DOS drive. 509 * This partition is the one Windows calls the primary dos partition. 510 * If the machine has any logical drives then we also expect 511 * to find a partition marked as an extended DOS partition. 512 * 513 * Sometimes we'll find multiple partitions marked as DOS drives. 514 * The Solaris fdisk program allows these partitions 515 * to be created, but Windows fdisk no longer does. We still need 516 * to support these, though, since Windows does. We also need to fix 517 * our fdisk to behave like the Windows version. 518 * 519 * It turns out that some off-the-shelf media have *only* an 520 * Extended partition, so we need to deal with that case as 521 * well. 522 * 523 * Only a single (the first) Extended or Boot Partition will 524 * be recognized. Any others will be ignored. 525 */ 526 for (i = 0; i < FD_NUMPART; i++) { 527 if (isDosDrive(part[i].systid)) { 528 if (primaryPart < 0) { 529 logicalDriveCount++; 530 primaryPart = i; 531 } else { 532 extraDrives[numExtraDrives++] = i; 533 } 534 continue; 535 } 536 if ((extendedPart < 0) && isDosExtended(part[i].systid)) { 537 extendedPart = i; 538 continue; 539 } 540 if ((bootPart < 0) && isBootPart(part[i].systid)) { 541 bootPart = i; 542 continue; 543 } 544 } 545 546 if (drvnum == BOOT_PARTITION_DRIVE) { 547 if (bootPart < 0) { 548 (void) fprintf(stderr, 549 gettext("No boot partition found on drive\n")); 550 return (error); 551 } 552 found = ltohi(part[bootPart].relsect) * BPSEC; 553 return (found); 554 } 555 556 if (drvnum == PRIMARY_DOS_DRIVE && primaryPart >= 0) { 557 found = ltohi(part[primaryPart].relsect) * BPSEC; 558 return (found); 559 } 560 561 /* 562 * We are not looking for the C: drive (or there was no primary 563 * drive found), so we had better have an extended partition or 564 * extra drives in the Master FDISK table. 565 */ 566 if ((extendedPart < 0) && (numExtraDrives == 0)) { 567 (void) fprintf(stderr, 568 gettext("No such logical drive " 569 "(missing extended partition entry)\n")); 570 return (error); 571 } 572 573 if (extendedPart >= 0) { 574 nextseek = xstartsect = ltohi(part[extendedPart].relsect); 575 xnumsect = ltohi(part[extendedPart].numsect); 576 do { 577 /* 578 * If the seek would not cause us to change 579 * position on the drive, then we're out of 580 * extended partitions to examine. 581 */ 582 if (nextseek == lastseek) 583 break; 584 logicalDriveCount += numDrives; 585 /* 586 * Seek the next extended partition, and find 587 * logical drives within it. 588 */ 589 if (lseek64(fd, nextseek * BPSEC, SEEK_SET) < 0 || 590 read(fd, &extmboot, sizeof (extmboot)) != 591 sizeof (extmboot)) { 592 perror(gettext("Unable to read extended " 593 "partition record")); 594 return (error); 595 } 596 (void) memcpy(part, extmboot.parts, sizeof (part)); 597 lastseek = nextseek; 598 if (ltohs(extmboot.signature) != MBB_MAGIC) { 599 (void) fprintf(stderr, 600 gettext("Bad signature on " 601 "extended partition\n")); 602 return (error); 603 } 604 /* 605 * Count up drives, and track where the next 606 * extended partition is in case we need it. We 607 * are expecting only one extended partition. If 608 * there is more than one we'll only go to the 609 * first one we see, but warn about ignoring. 610 */ 611 numDrives = 0; 612 for (i = 0; i < FD_NUMPART; i++) { 613 if (isDosDrive(part[i].systid)) { 614 extndDrives[numDrives++] = i; 615 continue; 616 } else if (isDosExtended(part[i].systid)) { 617 if (nextseek != lastseek) { 618 /* 619 * Already found an extended 620 * partition in this table. 621 */ 622 (void) fprintf(stderr, 623 gettext("WARNING: " 624 "Ignoring unexpected " 625 "additional extended " 626 "partition")); 627 continue; 628 } 629 nextseek = xstartsect + 630 ltohi(part[i].relsect); 631 continue; 632 } 633 } 634 } while (drvnum > logicalDriveCount + numDrives); 635 636 if (drvnum <= logicalDriveCount + numDrives) { 637 /* 638 * The number of logical drives we've found thus 639 * far is enough to get us to the one we were 640 * searching for. 641 */ 642 driveIndex = logicalDriveCount + numDrives - drvnum; 643 found = 644 ltohi(part[extndDrives[driveIndex]].relsect) + 645 lastseek; 646 if (found > (xstartsect + xnumsect)) { 647 (void) fprintf(stderr, 648 gettext("Logical drive start sector (%d) " 649 "is not within the partition!\n"), found); 650 return (error); 651 } else { 652 found *= BPSEC; 653 } 654 return (found); 655 } else { 656 /* 657 * We ran out of extended dos partition 658 * drives. The only hope now is to go 659 * back to extra drives defined in the master 660 * fdisk table. But we overwrote that table 661 * already, so we must load it in again. 662 */ 663 logicalDriveCount += numDrives; 664 (void) memcpy(part, mb.parts, sizeof (part)); 665 } 666 } 667 /* 668 * Still haven't found the drive, is it an extra 669 * drive defined in the main FDISK table? 670 */ 671 if (drvnum <= logicalDriveCount + numExtraDrives) { 672 driveIndex = logicalDriveCount + numExtraDrives - drvnum; 673 found = ltohi(part[extraDrives[driveIndex]].relsect) * BPSEC; 674 return (found); 675 } 676 return (error); 677 } 678