1 /* $OpenBSD: efidev.c,v 1.16 2016/01/06 02:10:03 krw Exp $ */ 2 3 /* 4 * Copyright (c) 1996 Michael Shalayeff 5 * Copyright (c) 2003 Tobias Weingartner 6 * Copyright (c) 2015 YASUOKA Masahiko <yasuoka@yasuoka.net> 7 * All rights reserved. 8 * 9 * Redistribution and use in source and binary forms, with or without 10 * modification, are permitted provided that the following conditions 11 * are met: 12 * 1. Redistributions of source code must retain the above copyright 13 * notice, this list of conditions and the following disclaimer. 14 * 2. Redistributions in binary form must reproduce the above copyright 15 * notice, this list of conditions and the following disclaimer in the 16 * documentation and/or other materials provided with the distribution. 17 * 18 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 19 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 20 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 21 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 22 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 24 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 25 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 26 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 27 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 28 * SUCH DAMAGE. 29 * 30 */ 31 #include <sys/param.h> 32 #include <sys/reboot.h> 33 #include <sys/disklabel.h> 34 #include <lib/libz/zlib.h> 35 36 #include "libsa.h" 37 #include "disk.h" 38 39 #ifdef SOFTRAID 40 #include <dev/softraidvar.h> 41 #include "softraid.h" 42 #endif 43 44 #include <efi.h> 45 #include "eficall.h" 46 47 extern int debug; 48 49 #include "efidev.h" 50 #include "biosdev.h" /* for dklookup() */ 51 52 #define EFI_BLKSPERSEC(_ed) ((_ed)->blkio->Media->BlockSize / DEV_BSIZE) 53 #define EFI_SECTOBLK(_ed, _n) ((_n) * EFI_BLKSPERSEC(_ed)) 54 55 struct efi_diskinfo { 56 EFI_BLOCK_IO *blkio; 57 UINT32 mediaid; 58 }; 59 60 int bios_bootdev; 61 static EFI_STATUS 62 efid_io(int, efi_diskinfo_t, u_int, int, void *); 63 static int efid_diskio(int, struct diskinfo *, u_int, int, void *); 64 static u_int findopenbsd(efi_diskinfo_t, const char **); 65 static uint64_t findopenbsd_gpt(efi_diskinfo_t, const char **); 66 static int gpt_chk_mbr(struct dos_partition *, u_int64_t); 67 68 void 69 efid_init(struct diskinfo *dip, void *handle) 70 { 71 EFI_BLOCK_IO *blkio = handle; 72 73 memset(dip, 0, sizeof(struct diskinfo)); 74 dip->efi_info = alloc(sizeof(struct efi_diskinfo)); 75 dip->efi_info->blkio = blkio; 76 dip->efi_info->mediaid = blkio->Media->MediaId; 77 dip->diskio = efid_diskio; 78 dip->strategy = efistrategy; 79 } 80 81 static EFI_STATUS 82 efid_io(int rw, efi_diskinfo_t ed, u_int off, int nsect, void *buf) 83 { 84 u_int blks, lba, i_lblks, i_tblks, i_nblks; 85 EFI_STATUS status = EFI_SUCCESS; 86 static u_char *iblk = NULL; 87 static u_int iblksz = 0; 88 89 /* block count of the intrisic block size in DEV_BSIZE */ 90 blks = EFI_BLKSPERSEC(ed); 91 lba = off / blks; 92 93 /* leading and trailing unaligned blocks in intrisic block */ 94 i_lblks = ((off % blks) == 0)? 0 : blks - (off % blks); 95 i_tblks = (off + nsect) % blks; 96 97 /* aligned blocks in intrisic block */ 98 i_nblks = nsect - (i_lblks + i_tblks); 99 100 switch (rw) { 101 case F_READ: 102 /* allocate the space for reading unaligned blocks */ 103 if (ed->blkio->Media->BlockSize != DEV_BSIZE) { 104 if (iblk && iblksz < ed->blkio->Media->BlockSize) { 105 free(iblk, iblksz); 106 iblk = NULL; 107 } 108 if (iblk == NULL) { 109 iblk = alloc(ed->blkio->Media->BlockSize); 110 iblksz = ed->blkio->Media->BlockSize; 111 } 112 } 113 if (i_lblks > 0) { 114 status = EFI_CALL(ed->blkio->ReadBlocks, 115 ed->blkio, ed->mediaid, lba - 1, 116 ed->blkio->Media->BlockSize, iblk); 117 if (EFI_ERROR(status)) 118 goto on_eio; 119 memcpy(buf, iblk + (blks - i_lblks), 120 i_lblks * DEV_BSIZE); 121 } 122 if (i_nblks > 0) { 123 status = EFI_CALL(ed->blkio->ReadBlocks, 124 ed->blkio, ed->mediaid, lba, 125 ed->blkio->Media->BlockSize * (i_nblks / blks), 126 buf + (i_lblks * DEV_BSIZE)); 127 if (EFI_ERROR(status)) 128 goto on_eio; 129 } 130 if (i_tblks > 0) { 131 status = EFI_CALL(ed->blkio->ReadBlocks, 132 ed->blkio, ed->mediaid, lba + (i_nblks / blks), 133 ed->blkio->Media->BlockSize, iblk); 134 if (EFI_ERROR(status)) 135 goto on_eio; 136 memcpy(buf + (i_lblks + i_nblks) * DEV_BSIZE, iblk, 137 i_tblks * DEV_BSIZE); 138 } 139 break; 140 case F_WRITE: 141 if (ed->blkio->Media->ReadOnly) 142 goto on_eio; 143 /* XXX not yet */ 144 goto on_eio; 145 break; 146 } 147 return (EFI_SUCCESS); 148 149 on_eio: 150 return (status); 151 } 152 153 static int 154 efid_diskio(int rw, struct diskinfo *dip, u_int off, int nsect, void *buf) 155 { 156 EFI_STATUS status; 157 158 status = efid_io(rw, dip->efi_info, off, nsect, buf); 159 160 return ((EFI_ERROR(status))? -1 : 0); 161 } 162 163 /* 164 * Returns 0 if the MBR with the provided partition array is a GPT protective 165 * MBR, and returns 1 otherwise. A GPT protective MBR would have one and only 166 * one MBR partition, an EFI partition that either covers the whole disk or as 167 * much of it as is possible with a 32bit size field. 168 * 169 * Taken from kern/subr_disk.c. 170 * 171 * NOTE: MS always uses a size of UINT32_MAX for the EFI partition!** 172 */ 173 static int 174 gpt_chk_mbr(struct dos_partition *dp, u_int64_t dsize) 175 { 176 struct dos_partition *dp2; 177 int efi, found, i; 178 u_int32_t psize; 179 180 found = efi = 0; 181 for (dp2=dp, i=0; i < NDOSPART; i++, dp2++) { 182 if (dp2->dp_typ == DOSPTYP_UNUSED) 183 continue; 184 found++; 185 if (dp2->dp_typ != DOSPTYP_EFI) 186 continue; 187 psize = letoh32(dp2->dp_size); 188 if (psize == (dsize - 1) || 189 psize == UINT32_MAX) { 190 if (letoh32(dp2->dp_start) == 1) 191 efi++; 192 } 193 } 194 if (found == 1 && efi == 1) 195 return (0); 196 197 return (1); 198 } 199 200 /* 201 * Try to read the bsd label on the given BIOS device. 202 */ 203 static u_int 204 findopenbsd(efi_diskinfo_t ed, const char **err) 205 { 206 EFI_STATUS status; 207 struct dos_mbr mbr; 208 struct dos_partition *dp; 209 uint64_t gptoff; 210 u_int mbroff = DOSBBSECTOR; 211 u_int mbr_eoff = DOSBBSECTOR; /* Offset of MBR extended partition. */ 212 int i, maxebr = DOS_MAXEBR, nextebr; 213 214 again: 215 if (!maxebr--) { 216 *err = "too many extended partitions"; 217 return (-1); 218 } 219 220 /* Read MBR */ 221 bzero(&mbr, sizeof(mbr)); 222 status = efid_io(F_READ, ed, mbroff, 1, &mbr); 223 if (EFI_ERROR(status)) { 224 *err = "Disk I/O Error"; 225 return (-1); 226 } 227 228 /* check mbr signature */ 229 if (mbr.dmbr_sign != DOSMBR_SIGNATURE) { 230 *err = "bad MBR signature\n"; 231 return (-1); 232 } 233 234 /* check for GPT protective MBR. */ 235 if (mbroff == DOSBBSECTOR && gpt_chk_mbr(mbr.dmbr_parts, 236 ed->blkio->Media->LastBlock + 1) == 0) { 237 gptoff = findopenbsd_gpt(ed, err); 238 if (gptoff > UINT_MAX || EFI_SECTOBLK(ed, gptoff) > UINT_MAX) { 239 *err = "Paritition LBA > 2**32"; 240 return (-1); 241 } 242 if (gptoff == -1) 243 return (-1); 244 return EFI_SECTOBLK(ed, gptoff); 245 } 246 247 /* Search for OpenBSD partition */ 248 nextebr = 0; 249 for (i = 0; i < NDOSPART; i++) { 250 dp = &mbr.dmbr_parts[i]; 251 if (!dp->dp_size) 252 continue; 253 #ifdef BIOS_DEBUG 254 if (debug) 255 printf("found partition %u: " 256 "type %u (0x%x) offset %u (0x%x)\n", 257 (int)(dp - mbr.dmbr_parts), 258 dp->dp_typ, dp->dp_typ, 259 dp->dp_start, dp->dp_start); 260 #endif 261 if (dp->dp_typ == DOSPTYP_OPENBSD) { 262 if (dp->dp_start > (dp->dp_start + mbroff)) 263 continue; 264 return (dp->dp_start + mbroff); 265 } 266 267 /* 268 * Record location of next ebr if and only if this is the first 269 * extended partition in this boot record! 270 */ 271 if (!nextebr && (dp->dp_typ == DOSPTYP_EXTEND || 272 dp->dp_typ == DOSPTYP_EXTENDL)) { 273 nextebr = dp->dp_start + mbr_eoff; 274 if (nextebr < dp->dp_start) 275 nextebr = (u_int)-1; 276 if (mbr_eoff == DOSBBSECTOR) 277 mbr_eoff = dp->dp_start; 278 } 279 } 280 281 if (nextebr && nextebr != (u_int)-1) { 282 mbroff = nextebr; 283 goto again; 284 } 285 286 return (-1); 287 } 288 289 /* call this only if LBA1 == GPT */ 290 static uint64_t 291 findopenbsd_gpt(efi_diskinfo_t ed, const char **err) 292 { 293 EFI_STATUS status; 294 struct gpt_header gh; 295 int i, part, found; 296 uint64_t lba; 297 uint32_t orig_csum, new_csum; 298 uint32_t ghsize, ghpartsize, ghpartnum, ghpartspersec; 299 uint32_t gpsectors; 300 const char openbsd_uuid_code[] = GPT_UUID_OPENBSD; 301 struct gpt_partition gp; 302 static struct uuid *openbsd_uuid = NULL, openbsd_uuid_space; 303 static u_char buf[4096]; 304 305 /* Prepare OpenBSD UUID */ 306 if (openbsd_uuid == NULL) { 307 /* XXX: should be replaced by uuid_dec_be() */ 308 memcpy(&openbsd_uuid_space, openbsd_uuid_code, 309 sizeof(openbsd_uuid_space)); 310 openbsd_uuid_space.time_low = 311 betoh32(openbsd_uuid_space.time_low); 312 openbsd_uuid_space.time_mid = 313 betoh16(openbsd_uuid_space.time_mid); 314 openbsd_uuid_space.time_hi_and_version = 315 betoh16(openbsd_uuid_space.time_hi_and_version); 316 317 openbsd_uuid = &openbsd_uuid_space; 318 } 319 320 if (EFI_BLKSPERSEC(ed) > 8) { 321 *err = "disk sector > 4096 bytes\n"; 322 return (-1); 323 } 324 325 /* LBA1: GPT Header */ 326 lba = 1; 327 status = efid_io(F_READ, ed, EFI_SECTOBLK(ed, lba), EFI_BLKSPERSEC(ed), 328 buf); 329 if (EFI_ERROR(status)) { 330 *err = "Disk I/O Error"; 331 return (-1); 332 } 333 memcpy(&gh, buf, sizeof(gh)); 334 335 /* Check signature */ 336 if (letoh64(gh.gh_sig) != GPTSIGNATURE) { 337 *err = "bad GPT signature\n"; 338 return (-1); 339 } 340 341 if (letoh32(gh.gh_rev) != GPTREVISION) { 342 *err = "bad GPT revision\n"; 343 return (-1); 344 } 345 346 ghsize = letoh32(gh.gh_size); 347 if (ghsize < GPTMINHDRSIZE || ghsize > sizeof(struct gpt_header)) { 348 *err = "bad GPT header size\n"; 349 return (-1); 350 } 351 352 /* Check checksum */ 353 orig_csum = gh.gh_csum; 354 gh.gh_csum = 0; 355 new_csum = crc32(0, (unsigned char *)&gh, ghsize); 356 gh.gh_csum = orig_csum; 357 if (letoh32(orig_csum) != new_csum) { 358 *err = "bad GPT header checksum\n"; 359 return (-1); 360 } 361 362 lba = letoh64(gh.gh_part_lba); 363 ghpartsize = letoh32(gh.gh_part_size); 364 ghpartspersec = ed->blkio->Media->BlockSize / ghpartsize; 365 ghpartnum = letoh32(gh.gh_part_num); 366 gpsectors = (ghpartnum + ghpartspersec - 1) / ghpartspersec; 367 new_csum = crc32(0L, Z_NULL, 0); 368 found = 0; 369 for (i = 0; i < gpsectors; i++, lba++) { 370 status = efid_io(F_READ, ed, EFI_SECTOBLK(ed, lba), 371 EFI_BLKSPERSEC(ed), buf); 372 if (EFI_ERROR(status)) { 373 *err = "Disk I/O Error"; 374 return (-1); 375 } 376 for (part = 0; part < ghpartspersec; part++) { 377 if (ghpartnum == 0) 378 break; 379 new_csum = crc32(new_csum, buf + part * sizeof(gp), 380 sizeof(gp)); 381 ghpartnum--; 382 if (found) 383 continue; 384 memcpy(&gp, buf + part * sizeof(gp), sizeof(gp)); 385 if (memcmp(&gp.gp_type, openbsd_uuid, 386 sizeof(struct uuid)) == 0) 387 found = 1; 388 } 389 } 390 if (new_csum != letoh32(gh.gh_part_csum)) { 391 *err = "bad GPT entries checksum\n"; 392 return (-1); 393 } 394 if (found) 395 return (letoh64(gp.gp_lba_start)); 396 397 return (-1); 398 } 399 400 const char * 401 efi_getdisklabel(efi_diskinfo_t ed, struct disklabel *label) 402 { 403 u_int start = 0; 404 char buf[DEV_BSIZE]; 405 const char *err = NULL; 406 int error; 407 408 /* Sanity check */ 409 /* XXX */ 410 411 start = findopenbsd(ed, &err); 412 if (start == (u_int)-1) { 413 if (err != NULL) 414 return (err); 415 return "no OpenBSD partition\n"; 416 } 417 418 /* Load BSD disklabel */ 419 #ifdef BIOS_DEBUG 420 if (debug) 421 printf("loading disklabel @ %u\n", start + DOS_LABELSECTOR); 422 #endif 423 /* read disklabel */ 424 error = efid_io(F_READ, ed, start + DOS_LABELSECTOR, 1, buf); 425 426 if (error) 427 return "failed to read disklabel"; 428 429 /* Fill in disklabel */ 430 return (getdisklabel(buf, label)); 431 } 432 433 int 434 efiopen(struct open_file *f, ...) 435 { 436 #ifdef SOFTRAID 437 struct sr_boot_volume *bv; 438 #endif 439 register char *cp, **file; 440 dev_t maj, unit, part; 441 struct diskinfo *dip; 442 int biosdev, devlen; 443 #if 0 444 const char *st; 445 #endif 446 va_list ap; 447 char *dev; 448 449 va_start(ap, f); 450 cp = *(file = va_arg(ap, char **)); 451 va_end(ap); 452 453 #ifdef EFI_DEBUG 454 if (debug) 455 printf("%s\n", cp); 456 #endif 457 458 f->f_devdata = NULL; 459 460 /* Search for device specification. */ 461 dev = cp; 462 if (cp[4] == ':') 463 devlen = 2; 464 else if (cp[5] == ':') 465 devlen = 3; 466 else 467 return ENOENT; 468 cp += devlen; 469 470 /* Get unit. */ 471 if ('0' <= *cp && *cp <= '9') 472 unit = *cp++ - '0'; 473 else { 474 printf("Bad unit number\n"); 475 return EUNIT; 476 } 477 478 /* Get partition. */ 479 if ('a' <= *cp && *cp <= 'p') 480 part = *cp++ - 'a'; 481 else { 482 printf("Bad partition\n"); 483 return EPART; 484 } 485 486 /* Get filename. */ 487 cp++; /* skip ':' */ 488 if (*cp != 0) 489 *file = cp; 490 else 491 f->f_flags |= F_RAW; 492 493 #ifdef SOFTRAID 494 /* Intercept softraid disks. */ 495 if (strncmp("sr", dev, 2) == 0) { 496 497 /* Create a fake diskinfo for this softraid volume. */ 498 SLIST_FOREACH(bv, &sr_volumes, sbv_link) 499 if (bv->sbv_unit == unit) 500 break; 501 if (bv == NULL) { 502 printf("Unknown device: sr%d\n", unit); 503 return EADAPT; 504 } 505 506 if (bv->sbv_level == 'C' && bv->sbv_keys == NULL) 507 if (sr_crypto_decrypt_keys(bv) != 0) 508 return EPERM; 509 510 if (bv->sbv_diskinfo == NULL) { 511 dip = alloc(sizeof(struct diskinfo)); 512 bzero(dip, sizeof(*dip)); 513 dip->diskio = efid_diskio; 514 dip->strategy = efistrategy; 515 bv->sbv_diskinfo = dip; 516 dip->sr_vol = bv; 517 dip->bios_info.flags |= BDI_BADLABEL; 518 } 519 520 dip = bv->sbv_diskinfo; 521 522 if (dip->bios_info.flags & BDI_BADLABEL) { 523 /* Attempt to read disklabel. */ 524 bv->sbv_part = 'c'; 525 if (sr_getdisklabel(bv, &dip->disklabel)) 526 return ERDLAB; 527 dip->bios_info.flags &= ~BDI_BADLABEL; 528 } 529 530 bv->sbv_part = part + 'a'; 531 532 bootdev_dip = dip; 533 f->f_devdata = dip; 534 535 return 0; 536 } 537 #endif 538 for (maj = 0; maj < nbdevs && 539 strncmp(dev, bdevs[maj], devlen); maj++); 540 if (maj >= nbdevs) { 541 printf("Unknown device: "); 542 for (cp = *file; *cp != ':'; cp++) 543 putchar(*cp); 544 putchar('\n'); 545 return EADAPT; 546 } 547 548 biosdev = unit; 549 switch (maj) { 550 case 0: /* wd */ 551 case 4: /* sd */ 552 case 17: /* hd */ 553 biosdev |= 0x80; 554 break; 555 case 2: /* fd */ 556 break; 557 case 6: /* cd */ 558 biosdev = bios_bootdev & 0xff; 559 break; 560 default: 561 return ENXIO; 562 } 563 564 /* Find device */ 565 dip = dklookup(biosdev); 566 if (dip == NULL) 567 return ENXIO; 568 bootdev_dip = dip; 569 570 /* Fix up bootdev */ 571 { dev_t bsd_dev; 572 bsd_dev = dip->bios_info.bsd_dev; 573 dip->bsddev = MAKEBOOTDEV(B_TYPE(bsd_dev), B_ADAPTOR(bsd_dev), 574 B_CONTROLLER(bsd_dev), unit, part); 575 dip->bootdev = MAKEBOOTDEV(B_TYPE(bsd_dev), B_ADAPTOR(bsd_dev), 576 B_CONTROLLER(bsd_dev), B_UNIT(bsd_dev), part); 577 } 578 579 #if 0 580 dip->bios_info.bsd_dev = dip->bootdev; 581 bootdev = dip->bootdev; 582 #endif 583 584 #ifdef EFI_DEBUG 585 if (debug) { 586 printf("BIOS geometry: heads=%u, s/t=%u; EDD=%d\n", 587 dip->bios_info.bios_heads, dip->bios_info.bios_sectors, 588 dip->bios_info.bios_edd); 589 } 590 #endif 591 592 #if 0 593 /* 594 * XXX In UEFI, media change can be detected by MediaID 595 */ 596 /* Try for disklabel again (might be removable media) */ 597 if (dip->bios_info.flags & BDI_BADLABEL) { 598 st = efi_getdisklabel(dip->efi_info, &dip->disklabel); 599 #ifdef EFI_DEBUG 600 if (debug && st) 601 printf("%s\n", st); 602 #endif 603 if (!st) { 604 dip->bios_info.flags &= ~BDI_BADLABEL; 605 dip->bios_info.flags |= BDI_GOODLABEL; 606 } else 607 return ERDLAB; 608 } 609 #endif 610 f->f_devdata = dip; 611 612 return 0; 613 } 614 615 int 616 efistrategy(void *devdata, int rw, daddr32_t blk, size_t size, void *buf, 617 size_t *rsize) 618 { 619 struct diskinfo *dip = (struct diskinfo *)devdata; 620 u_int8_t error = 0; 621 size_t nsect; 622 623 #ifdef SOFTRAID 624 /* Intercept strategy for softraid volumes. */ 625 if (dip->sr_vol) 626 return sr_strategy(dip->sr_vol, rw, blk, size, buf, rsize); 627 #endif 628 nsect = (size + DEV_BSIZE - 1) / DEV_BSIZE; 629 blk += dip->disklabel.d_partitions[B_PARTITION(dip->bsddev)].p_offset; 630 631 if (blk < 0) 632 error = EINVAL; 633 else 634 error = dip->diskio(rw, dip, blk, nsect, buf); 635 636 #ifdef EFI_DEBUG 637 if (debug) { 638 if (error != 0) 639 printf("=0x%x(%s)", error, error); 640 putchar('\n'); 641 } 642 #endif 643 if (rsize != NULL) 644 *rsize = nsect * DEV_BSIZE; 645 646 return (error); 647 } 648 649 int 650 eficlose(struct open_file *f) 651 { 652 f->f_devdata = NULL; 653 654 return 0; 655 } 656 657 int 658 efiioctl(struct open_file *f, u_long cmd, void *data) 659 { 660 661 return 0; 662 } 663 664 void 665 efi_dump_diskinfo(void) 666 { 667 efi_diskinfo_t ed; 668 struct diskinfo *dip; 669 bios_diskinfo_t *bdi; 670 uint64_t siz; 671 const char *sizu; 672 673 printf("Disk\tBlkSiz\tIoAlign\tSize\tFlags\tChecksum\n"); 674 TAILQ_FOREACH(dip, &disklist, list) { 675 bdi = &dip->bios_info; 676 ed = dip->efi_info; 677 678 siz = (ed->blkio->Media->LastBlock + 1) * 679 ed->blkio->Media->BlockSize; 680 siz /= 1024 * 1024; 681 if (siz < 10000) 682 sizu = "MB"; 683 else { 684 siz /= 1024; 685 sizu = "GB"; 686 } 687 688 printf("hd%d\t%u\t%u\t%u%s\t0x%x\t0x%x\t%s\n", 689 (bdi->bios_number & 0x7f), 690 ed->blkio->Media->BlockSize, 691 ed->blkio->Media->IoAlign, siz, sizu, 692 bdi->flags, bdi->checksum, 693 (ed->blkio->Media->RemovableMedia)? "Removable" : ""); 694 } 695 } 696