disk.c revision 114300
150476Speter/* 21987Swollman * ---------------------------------------------------------------------------- 31987Swollman * "THE BEER-WARE LICENSE" (Revision 42): 41987Swollman * <phk@FreeBSD.org> wrote this file. As long as you retain this notice you 5100346Sru * can do whatever you want with this stuff. If we meet some day, and you think 6100346Sru * this stuff is worth it, you can buy me a beer in return. Poul-Henning Kamp 7100346Sru * ---------------------------------------------------------------------------- 8100346Sru */ 9100346Sru 10100346Sru#include <sys/cdefs.h> 11100346Sru__FBSDID("$FreeBSD: head/lib/libdisk/disk.c 114300 2003-04-30 17:14:58Z obrien $"); 12100346Sru 13100346Sru#include <stdio.h> 14100346Sru#include <stdlib.h> 15100346Sru#include <unistd.h> 16100346Sru#include <fcntl.h> 17100346Sru#include <string.h> 18100346Sru#include <inttypes.h> 19100346Sru#include <err.h> 20100346Sru#include <sys/sysctl.h> 21100346Sru#include <sys/stdint.h> 22100346Sru#include <sys/types.h> 23100346Sru#include <sys/stat.h> 24100346Sru#include <sys/ioctl.h> 25100346Sru#include <sys/disklabel.h> 26100346Sru#include <sys/uuid.h> 27100346Sru#include <sys/gpt.h> 28100346Sru#include <paths.h> 29100346Sru#include "libdisk.h" 30100346Sru 31100346Sru#include <ctype.h> 32100346Sru#include <errno.h> 33100346Sru#include <assert.h> 34100346Sru#include <uuid.h> 35100346Sru 36100346Sru#ifdef DEBUG 374257Sphk#define DPRINT(x) warn x 38100346Sru#define DPRINTX(x) warnx x 39100346Sru#else 40100346Sru#define DPRINT(x) 41100346Sru#define DPRINTX(x) 42100346Sru#endif 43100346Sru 44100346Sruconst enum platform platform = 45100346Sru#if defined (P_DEBUG) 46100346Sru P_DEBUG 47100346Sru#elif defined (PC98) 48100346Sru p_pc98 49100346Sru#elif defined(__i386__) 50100346Sru p_i386 51100346Sru#elif defined(__alpha__) 52100346Sru p_alpha 53100346Sru#elif defined(__sparc64__) 54100346Sru p_sparc64 55100346Sru#elif defined(__ia64__) 56100346Sru p_ia64 57100346Sru#elif defined(__ppc__) 58100346Sru p_ppc 59100346Sru#else 60100346Sru IHAVENOIDEA 61100346Sru#endif 62100346Sru ; 63100346Sru 64100346Sruconst char * 65100346Sruchunk_name(chunk_e type) 66100346Sru{ 67100346Sru switch(type) { 68100346Sru case unused: return ("unused"); 69100346Sru case mbr: return ("mbr"); 70100346Sru case part: return ("part"); 71100346Sru case gpt: return ("gpt"); 72100346Sru case pc98: return ("pc98"); 73100346Sru case sun: return ("sun"); 74100346Sru case freebsd: return ("freebsd"); 75100346Sru case fat: return ("fat"); 76100346Sru case spare: return ("spare"); 77100346Sru case efi: return ("efi"); 78100346Sru default: return ("??"); 79100346Sru } 80100346Sru}; 81100346Sru 82100346Srustatic chunk_e 83100346Sruuuid_type(uuid_t *uuid) 84100346Sru{ 85100346Sru static uuid_t _efi = GPT_ENT_TYPE_EFI; 86100346Sru static uuid_t _mbr = GPT_ENT_TYPE_MBR; 87100346Sru static uuid_t _fbsd = GPT_ENT_TYPE_FREEBSD; 88100346Sru static uuid_t _swap = GPT_ENT_TYPE_FREEBSD_SWAP; 89100346Sru static uuid_t _ufs = GPT_ENT_TYPE_FREEBSD_UFS; 90100346Sru static uuid_t _vinum = GPT_ENT_TYPE_FREEBSD_VINUM; 91100346Sru 92100346Sru if (uuid_is_nil(uuid, NULL)) 93100346Sru return (unused); 94100346Sru if (uuid_equal(uuid, &_efi, NULL)) 95100346Sru return (efi); 96100346Sru if (uuid_equal(uuid, &_mbr, NULL)) 97100346Sru return (mbr); 98100346Sru if (uuid_equal(uuid, &_fbsd, NULL)) 99100346Sru return (freebsd); 100100346Sru if (uuid_equal(uuid, &_swap, NULL)) 101100346Sru return (part); 102100346Sru if (uuid_equal(uuid, &_ufs, NULL)) 103100346Sru return (part); 104100346Sru if (uuid_equal(uuid, &_vinum, NULL)) 105100346Sru return (part); 106100346Sru return (spare); 107100346Sru} 108100346Sru 109100346Srustruct disk * 110100346SruOpen_Disk(const char *name) 111100346Sru{ 112100346Sru 113100346Sru return Int_Open_Disk(name); 114100346Sru} 115100346Sru 116100346Srustruct disk * 117100346SruInt_Open_Disk(const char *name) 118100346Sru{ 119100346Sru uuid_t uuid; 120100346Sru char *conftxt = NULL; 121100346Sru struct disk *d; 122100346Sru size_t txtsize; 123100346Sru int error, i; 124100346Sru char *p, *q, *r, *a, *b, *n, *t, *sn; 125100346Sru off_t o, len, off; 126100346Sru u_int l, s, ty, sc, hd, alt; 127100346Sru off_t lo[10]; 128100346Sru 129100346Sru error = sysctlbyname("kern.geom.conftxt", NULL, &txtsize, NULL, 0); 130100346Sru if (error) { 131100346Sru warn("kern.geom.conftxt sysctl not available, giving up!"); 132100346Sru return (NULL); 133100346Sru } 134100346Sru conftxt = (char *) malloc(txtsize+1); 135100346Sru if (conftxt == NULL) { 136100346Sru DPRINT(("cannot malloc memory for conftxt")); 137100346Sru return (NULL); 138100346Sru } 139100346Sru error = sysctlbyname("kern.geom.conftxt", conftxt, &txtsize, NULL, 0); 140100346Sru if (error) { 141100346Sru DPRINT(("error reading kern.geom.conftxt from the system")); 142100346Sru free(conftxt); 143100346Sru return (NULL); 144100346Sru } 145100346Sru conftxt[txtsize] = '\0'; /* in case kernel bug is still there */ 146100346Sru 147100346Sru for (p = conftxt; p != NULL && *p; p = strchr(p, '\n')) { 148100346Sru if (*p == '\n') 149100346Sru p++; 150100346Sru a = strsep(&p, " "); 151100346Sru if (strcmp(a, "0")) 152100346Sru continue; 153100346Sru 154100346Sru a = strsep(&p, " "); 155100346Sru if (strcmp(a, "DISK")) 156100346Sru continue; 157100346Sru 158100346Sru a = strsep(&p, " "); 159100346Sru if (strcmp(a, name)) 160100346Sru continue; 161100346Sru break; 162100346Sru } 163100346Sru 164100346Sru q = strchr(p, '\n'); 165100346Sru if (q != NULL) 166100346Sru *q++ = '\0'; 167100346Sru 168100346Sru d = (struct disk *)calloc(sizeof *d, 1); 169100346Sru if(d == NULL) 170100346Sru return NULL; 171100346Sru 172100346Sru d->name = strdup(name); 173100346Sru 174100346Sru a = strsep(&p, " "); /* length in bytes */ 175100346Sru len = strtoimax(a, &r, 0); 176100346Sru if (*r) { 177100346Sru printf("BARF %d <%d>\n", __LINE__, *r); 178100346Sru exit (0); 179100346Sru } 180100346Sru 181100346Sru a = strsep(&p, " "); /* sectorsize */ 182100346Sru s = strtoul(a, &r, 0); 183100346Sru if (*r) { 184100346Sru printf("BARF %d <%d>\n", __LINE__, *r); 185100346Sru exit (0); 186100346Sru } 187100346Sru 188100346Sru if (s == 0) 189100346Sru return (NULL); 190100346Sru d->sector_size = s; 191100346Sru len /= s; /* media size in number of sectors. */ 192100346Sru 193100346Sru if (Add_Chunk(d, 0, len, name, whole, 0, 0, "-")) 194100346Sru DPRINT(("Failed to add 'whole' chunk")); 195100346Sru 196100346Sru for (;;) { 197100346Sru a = strsep(&p, " "); 198100346Sru if (a == NULL) 199100346Sru break; 200100346Sru b = strsep(&p, " "); 201100346Sru o = strtoul(b, &r, 0); 202100346Sru if (*r) { 203100346Sru printf("BARF %d <%d>\n", __LINE__, *r); 204100346Sru exit (0); 205100346Sru } 206100346Sru if (!strcmp(a, "hd")) 207100346Sru d->bios_hd = o; 208100346Sru else if (!strcmp(a, "sc")) 209100346Sru d->bios_sect = o; 210100346Sru else 211100346Sru printf("HUH ? <%s> <%s>\n", a, b); 212100346Sru } 213100346Sru 214100346Sru /* 215100346Sru * Calculate the number of cylinders this disk must have. If we have 216100346Sru * an obvious insanity, we set the number of cyclinders to zero. 217100346Sru */ 218100346Sru o = d->bios_hd * d->bios_sect; 219100346Sru d->bios_cyl = (o != 0) ? len / o : 0; 220100346Sru 221100346Sru p = q; 222100346Sru lo[0] = 0; 223100346Sru 224100346Sru for (; p != NULL && *p; p = q) { 225100346Sru q = strchr(p, '\n'); 226100346Sru if (q != NULL) 227100346Sru *q++ = '\0'; 228100346Sru a = strsep(&p, " "); /* Index */ 229100346Sru if (!strcmp(a, "0")) 230100346Sru break; 231100346Sru l = strtoimax(a, &r, 0); 232100346Sru if (*r) { 233100346Sru printf("BARF %d <%d>\n", __LINE__, *r); 234100346Sru exit (0); 235100346Sru } 236100346Sru t = strsep(&p, " "); /* Type {SUN, BSD, MBR, PC98, GPT} */ 237100346Sru n = strsep(&p, " "); /* name */ 238100346Sru a = strsep(&p, " "); /* len */ 239100346Sru len = strtoimax(a, &r, 0); 240100346Sru if (*r) { 241100346Sru printf("BARF %d <%d>\n", __LINE__, *r); 242100346Sru exit (0); 243100346Sru } 244100346Sru a = strsep(&p, " "); /* secsize */ 245100346Sru s = strtoimax(a, &r, 0); 246100346Sru if (*r) { 247100346Sru printf("BARF %d <%d>\n", __LINE__, *r); 248100346Sru exit (0); 249100346Sru } 2504257Sphk for (;;) { 251100346Sru a = strsep(&p, " "); 2521987Swollman if (a == NULL) 253100346Sru break; 254100346Sru /* XXX: Slice name may include a space. */ 25554351Smarcel if (!strcmp(a, "sn")) { 25654351Smarcel sn = p; 25754351Smarcel break; 25854351Smarcel } 25954351Smarcel b = strsep(&p, " "); 260100346Sru o = strtoimax(b, &r, 0); 261100346Sru if (*r) { 2622365Sbde uint32_t status; 263100346Sru 264100346Sru uuid_from_string(b, &uuid, &status); 265104288Sru if (status != uuid_s_ok) { 266100346Sru printf("BARF %d <%d>\n", __LINE__, *r); 267100346Sru exit (0); 26813537Sbde } 269100346Sru o = uuid_type(&uuid); 270100346Sru } 271100346Sru if (!strcmp(a, "o")) 272100872Sru off = o; 273100346Sru else if (!strcmp(a, "i")) 27413537Sbde i = o; 2751987Swollman else if (!strcmp(a, "ty")) 276100346Sru ty = o; 277100346Sru else if (!strcmp(a, "sc")) 278100346Sru sc = o; 279100346Sru else if (!strcmp(a, "hd")) 28054351Smarcel hd = o; 28154351Smarcel else if (!strcmp(a, "alt")) 28254351Smarcel alt = o; 28354351Smarcel } 28454351Smarcel 28554351Smarcel /* PLATFORM POLICY BEGIN ----------------------------------- */ 28654351Smarcel if (platform == p_sparc64 && !strcmp(t, "SUN") && i == 2) 28785214Sdarrenr continue; 28892868Sru if (platform == p_sparc64 && !strcmp(t, "SUN") && 28992868Sru d->chunks->part->part == NULL) { 29092868Sru d->bios_hd = hd; 29185214Sdarrenr d->bios_sect = sc; 29292868Sru o = d->chunks->size / (hd * sc); 29385214Sdarrenr o *= (hd * sc); 29485214Sdarrenr o -= alt * hd * sc; 295 if (Add_Chunk(d, 0, o, name, freebsd, 0, 0, "-")) 296 DPRINT(("Failed to add 'freebsd' chunk")); 297 } 298 if (platform == p_alpha && !strcmp(t, "BSD") && 299 d->chunks->part->part == NULL) { 300 if (Add_Chunk(d, 0, d->chunks->size, name, freebsd, 301 0, 0, "-")) 302 DPRINT(("Failed to add 'freebsd' chunk")); 303 } 304 if (!strcmp(t, "BSD") && i == RAW_PART) 305 continue; 306 /* PLATFORM POLICY END ------------------------------------- */ 307 308 off /= s; 309 len /= s; 310 off += lo[l - 1]; 311 lo[l] = off; 312 if (!strcmp(t, "SUN")) 313 i = Add_Chunk(d, off, len, n, part, 0, 0, 0); 314 else if (!strncmp(t, "MBR", 3)) { 315 switch (ty) { 316 case 0xa5: 317 i = Add_Chunk(d, off, len, n, freebsd, ty, 0, 0); 318 break; 319 case 0x01: 320 case 0x04: 321 case 0x06: 322 case 0x0b: 323 case 0x0c: 324 case 0x0e: 325 i = Add_Chunk(d, off, len, n, fat, ty, 0, 0); 326 break; 327 case 0xef: /* EFI */ 328 i = Add_Chunk(d, off, len, n, efi, ty, 0, 0); 329 break; 330 default: 331 i = Add_Chunk(d, off, len, n, mbr, ty, 0, 0); 332 break; 333 } 334 } else if (!strcmp(t, "BSD")) 335 i = Add_Chunk(d, off, len, n, part, ty, 0, 0); 336 else if (!strcmp(t, "PC98")) { 337 switch (ty & 0x7f) { 338 case 0x14: 339 i = Add_Chunk(d, off, len, n, freebsd, ty, 0, 340 sn); 341 break; 342 case 0x20: 343 case 0x21: 344 case 0x22: 345 case 0x23: 346 case 0x24: 347 i = Add_Chunk(d, off, len, n, fat, ty, 0, sn); 348 break; 349 default: 350 i = Add_Chunk(d, off, len, n, pc98, ty, 0, sn); 351 break; 352 } 353 } else if (!strcmp(t, "GPT")) 354 i = Add_Chunk(d, off, len, n, ty, 0, 0, 0); 355 else if (!strcmp(t, "BDE")) 356 ; /* nothing */ 357 else { 358 printf("BARF %d\n", __LINE__); 359 exit(0); 360 } 361 } 362 /* PLATFORM POLICY BEGIN ------------------------------------- */ 363 /* We have a chance to do things on a blank disk here */ 364 if (platform == p_sparc64 && d->chunks->part->part == NULL) { 365 hd = d->bios_hd; 366 sc = d->bios_sect; 367 o = d->chunks->size / (hd * sc); 368 o *= (hd * sc); 369 o -= 2 * hd * sc; 370 if (Add_Chunk(d, 0, o, name, freebsd, 0, 0, "-")) 371 DPRINT(("Failed to add 'freebsd' chunk")); 372 } 373 /* PLATFORM POLICY END --------------------------------------- */ 374 375 return (d); 376 i = 0; 377} 378 379void 380Debug_Disk(struct disk *d) 381{ 382 383 printf("Debug_Disk(%s)", d->name); 384#if 0 385 printf(" real_geom=%lu/%lu/%lu", 386 d->real_cyl, d->real_hd, d->real_sect); 387#endif 388 printf(" bios_geom=%lu/%lu/%lu = %lu\n", 389 d->bios_cyl, d->bios_hd, d->bios_sect, 390 d->bios_cyl * d->bios_hd * d->bios_sect); 391#if defined(PC98) 392 printf(" boot1=%p, boot2=%p, bootipl=%p, bootmenu=%p\n", 393 d->boot1, d->boot2, d->bootipl, d->bootmenu); 394#elif defined(__i386__) 395 printf(" boot1=%p, boot2=%p, bootmgr=%p\n", 396 d->boot1, d->boot2, d->bootmgr); 397#elif defined(__alpha__) 398 printf(" boot1=%p, bootmgr=%p\n", 399 d->boot1, d->bootmgr); 400#elif defined(__ia64__) 401 printf("\n"); 402#else 403/* Should be: error "Debug_Disk: unknown arch"; */ 404#endif 405 Debug_Chunk(d->chunks); 406} 407 408void 409Free_Disk(struct disk *d) 410{ 411 if (d->chunks) 412 Free_Chunk(d->chunks); 413 if (d->name) 414 free(d->name); 415#ifdef PC98 416 if (d->bootipl) 417 free(d->bootipl); 418 if (d->bootmenu) 419 free(d->bootmenu); 420#else 421#if !defined(__ia64__) 422 if (d->bootmgr) 423 free(d->bootmgr); 424#endif 425#endif 426#if !defined(__ia64__) 427 if (d->boot1) 428 free(d->boot1); 429#endif 430#if defined(__i386__) 431 if (d->boot2) 432 free(d->boot2); 433#endif 434 free(d); 435} 436 437#if 0 438void 439Collapse_Disk(struct disk *d) 440{ 441 442 while (Collapse_Chunk(d, d->chunks)) 443 ; 444} 445#endif 446 447static int 448qstrcmp(const void* a, const void* b) 449{ 450 char *str1 = *(char**)a; 451 char *str2 = *(char**)b; 452 453 return strcmp(str1, str2); 454} 455 456char ** 457Disk_Names() 458{ 459 int disk_cnt; 460 static char **disks; 461 int error; 462 size_t listsize; 463 char *disklist; 464 465 error = sysctlbyname("kern.disks", NULL, &listsize, NULL, 0); 466 if (error) { 467 warn("kern.disks sysctl not available"); 468 return NULL; 469 } 470 471 disks = malloc(sizeof *disks * (1 + MAX_NO_DISKS)); 472 if (disks == NULL) 473 return NULL; 474 disklist = (char *)malloc(listsize + 1); 475 if (disklist == NULL) { 476 free(disks); 477 return NULL; 478 } 479 memset(disks,0,sizeof *disks * (1 + MAX_NO_DISKS)); 480 memset(disklist, 0, listsize + 1); 481 error = sysctlbyname("kern.disks", disklist, &listsize, NULL, 0); 482 if (error) { 483 free(disklist); 484 free(disks); 485 return NULL; 486 } 487 for (disk_cnt = 0; disk_cnt < MAX_NO_DISKS; disk_cnt++) { 488 disks[disk_cnt] = strsep(&disklist, " "); 489 if (disks[disk_cnt] == NULL) 490 break; 491 } 492 qsort(disks, disk_cnt, sizeof(char*), qstrcmp); 493 return disks; 494} 495 496#ifdef PC98 497void 498Set_Boot_Mgr(struct disk *d, const u_char *bootipl, const size_t bootipl_size, 499 const u_char *bootmenu, const size_t bootmenu_size) 500#else 501void 502Set_Boot_Mgr(struct disk *d, const u_char *b, const size_t s) 503#endif 504{ 505#if !defined(__ia64__) 506#ifdef PC98 507 if (d->sector_size == 0) 508 return; 509 if (bootipl_size % d->sector_size != 0) 510 return; 511 if (d->bootipl) 512 free(d->bootipl); 513 if (!bootipl) { 514 d->bootipl = NULL; 515 } else { 516 d->bootipl_size = bootipl_size; 517 d->bootipl = malloc(bootipl_size); 518 if (!d->bootipl) 519 return; 520 memcpy(d->bootipl, bootipl, bootipl_size); 521 } 522 523 if (bootmenu_size % d->sector_size != 0) 524 return; 525 if (d->bootmenu) 526 free(d->bootmenu); 527 if (!bootmenu) { 528 d->bootmenu = NULL; 529 } else { 530 d->bootmenu_size = bootmenu_size; 531 d->bootmenu = malloc(bootmenu_size); 532 if (!d->bootmenu) 533 return; 534 memcpy(d->bootmenu, bootmenu, bootmenu_size); 535 } 536#else 537 if (d->sector_size == 0) 538 return; 539 if (s % d->sector_size != 0) 540 return; 541 if (d->bootmgr) 542 free(d->bootmgr); 543 if (!b) { 544 d->bootmgr = NULL; 545 } else { 546 d->bootmgr_size = s; 547 d->bootmgr = malloc(s); 548 if (!d->bootmgr) 549 return; 550 memcpy(d->bootmgr, b, s); 551 } 552#endif 553#endif 554} 555 556int 557Set_Boot_Blocks(struct disk *d, const u_char *b1, const u_char *b2) 558{ 559#if defined(__i386__) 560 if (d->boot1) 561 free(d->boot1); 562 d->boot1 = malloc(512); 563 if (!d->boot1) 564 return -1; 565 memcpy(d->boot1, b1, 512); 566 if (d->boot2) 567 free(d->boot2); 568 d->boot2 = malloc(15 * 512); 569 if (!d->boot2) 570 return -1; 571 memcpy(d->boot2, b2, 15 * 512); 572#elif defined(__alpha__) 573 if (d->boot1) 574 free(d->boot1); 575 d->boot1 = malloc(15 * 512); 576 if (!d->boot1) 577 return -1; 578 memcpy(d->boot1, b1, 15 * 512); 579#elif defined(__sparc64__) 580 if (d->boot1 != NULL) 581 free(d->boot1); 582 d->boot1 = malloc(16 * 512); 583 if (d->boot1 == NULL) 584 return (-1); 585 memcpy(d->boot1, b1, 16 * 512); 586#elif defined(__ia64__) 587 /* nothing */ 588#else 589/* Should be: #error "Set_Boot_Blocks: unknown arch"; */ 590#endif 591 return 0; 592} 593 594#ifdef PC98 595const char * 596slice_type_name( int type, int subtype ) 597{ 598 599 switch (type) { 600 case whole: 601 return "whole"; 602 case fat: 603 return "fat"; 604 case freebsd: 605 switch (subtype) { 606 case 0xc494: return "freebsd"; 607 default: return "unknown"; 608 } 609 case unused: 610 return "unused"; 611 default: 612 return "unknown"; 613 } 614} 615#else /* PC98 */ 616const char * 617slice_type_name( int type, int subtype ) 618{ 619 620 switch (type) { 621 case whole: 622 return "whole"; 623 case mbr: 624 switch (subtype) { 625 case 1: return "fat (12-bit)"; 626 case 2: return "XENIX /"; 627 case 3: return "XENIX /usr"; 628 case 4: return "fat (16-bit,<=32Mb)"; 629 case 5: return "extended DOS"; 630 case 6: return "fat (16-bit,>32Mb)"; 631 case 7: return "NTFS/HPFS/QNX"; 632 case 8: return "AIX bootable"; 633 case 9: return "AIX data"; 634 case 10: return "OS/2 bootmgr"; 635 case 11: return "fat (32-bit)"; 636 case 12: return "fat (32-bit,LBA)"; 637 case 14: return "fat (16-bit,>32Mb,LBA)"; 638 case 15: return "extended DOS, LBA"; 639 case 18: return "Compaq Diagnostic"; 640 case 84: return "OnTrack diskmgr"; 641 case 100: return "Netware 2.x"; 642 case 101: return "Netware 3.x"; 643 case 115: return "SCO UnixWare"; 644 case 128: return "Minix 1.1"; 645 case 129: return "Minix 1.5"; 646 case 130: return "linux_swap"; 647 case 131: return "ext2fs"; 648 case 166: return "OpenBSD FFS"; /* 0xA6 */ 649 case 169: return "NetBSD FFS"; /* 0xA9 */ 650 case 182: return "OpenBSD"; /* dedicated */ 651 case 183: return "bsd/os"; 652 case 184: return "bsd/os swap"; 653 case 238: return "EFI GPT"; 654 case 239: return "EFI Sys. Part."; 655 default: return "unknown"; 656 } 657 case fat: 658 return "fat"; 659 case freebsd: 660 switch (subtype) { 661 case 165: return "freebsd"; 662 default: return "unknown"; 663 } 664 case extended: 665 return "extended"; 666 case part: 667 return "part"; 668 case efi: 669 return "efi"; 670 case unused: 671 return "unused"; 672 default: 673 return "unknown"; 674 } 675} 676#endif /* PC98 */ 677