1 /*************************************************************************** 2 * 3 * probe-volume.c : probe volumes 4 * 5 * Copyright 2006 Sun Microsystems, Inc. All rights reserved. 6 * Use is subject to license terms. 7 * 8 * Licensed under the Academic Free License version 2.1 9 * 10 **************************************************************************/ 11 12 #pragma ident "%Z%%M% %I% %E% SMI" 13 14 #ifdef HAVE_CONFIG_H 15 # include <config.h> 16 #endif 17 18 #include <errno.h> 19 #include <string.h> 20 #include <stdlib.h> 21 #include <stdio.h> 22 #include <sys/ioctl.h> 23 #include <sys/types.h> 24 #include <sys/stat.h> 25 #include <fcntl.h> 26 #include <unistd.h> 27 #include <ctype.h> 28 #include <time.h> 29 #include <sys/time.h> 30 #include <sys/dkio.h> 31 #include <sys/cdio.h> 32 #include <sys/fdio.h> 33 #include <libnvpair.h> 34 #include <libfstyp.h> 35 #include <sys/vtoc.h> 36 #include <sys/efi_partition.h> 37 #include <sys/fs/hsfs_spec.h> 38 #include <sys/fs/hsfs_isospec.h> 39 #include <priv.h> 40 41 #include <libhal.h> 42 #include <cdutils.h> 43 #include <fsutils.h> 44 #include <logger.h> 45 46 static void 47 my_dbus_error_free(DBusError *error) 48 { 49 if (dbus_error_is_set(error)) { 50 dbus_error_free(error); 51 } 52 } 53 54 /* 55 * Return a copy of a string without trailing spaces. If 'len' is non-zero, 56 * it specifies max length, otherwise the string must be null-terminated. 57 */ 58 static char * 59 rtrim_copy(char *src, int len) 60 { 61 char *dst, *p; 62 63 if (len == 0) { 64 len = strlen(src); 65 } 66 if ((dst = calloc(1, len + 1)) != NULL) { 67 strncpy(dst, src, len); 68 p = dst + len - 1; 69 while ((p >= dst) && (isspace(*p))) { 70 *p-- = '\0'; 71 } 72 } 73 return (dst); 74 } 75 76 static void 77 set_fstyp_properties (LibHalContext *ctx, const char *udi, const char *fstype, nvlist_t *fsattr) 78 { 79 char buf[256]; 80 DBusError error; 81 char *uuid = NULL; 82 char *label_orig = NULL; 83 char *label = NULL; 84 LibHalChangeSet *cs; 85 86 dbus_error_init (&error); 87 88 if ((cs = libhal_device_new_changeset (udi)) == NULL) { 89 return; 90 } 91 92 libhal_changeset_set_property_string (cs, "volume.fsusage", "filesystem"); 93 libhal_changeset_set_property_string (cs, "volume.fstype", fstype); 94 95 /* label */ 96 (void) nvlist_lookup_string(fsattr, "gen_volume_label", &label_orig); 97 if (label_orig != NULL) { 98 label = rtrim_copy(label_orig, 0); 99 } 100 if ((label != NULL) && (label[0] != '\0')) { 101 libhal_changeset_set_property_string (cs, "volume.label", label); 102 libhal_changeset_set_property_string (cs, "info.product", label); 103 } else { 104 libhal_changeset_set_property_string (cs, "volume.label", ""); 105 snprintf (buf, sizeof (buf), "Volume (%s)", fstype); 106 libhal_changeset_set_property_string (cs, "info.product", buf); 107 } 108 free(label); 109 110 /* uuid */ 111 if (nvlist_lookup_string(fsattr, "gen_uuid", &uuid) == 0) { 112 libhal_changeset_set_property_string (cs, "volume.uuid", uuid); 113 } else { 114 libhal_changeset_set_property_string (cs, "volume.uuid", ""); 115 } 116 117 libhal_device_commit_changeset (ctx, cs, &error); 118 libhal_device_free_changeset (cs); 119 120 my_dbus_error_free (&error); 121 } 122 123 /* 124 * hsfs/iso9660 contents detection: Video DVD, Video CD, etc. 125 */ 126 static void 127 hsfs_contents(int fd, off_t probe_offset, LibHalContext *ctx, const char *udi) 128 { 129 size_t secsz = ISO_SECTOR_SIZE; 130 uchar_t buf[ISO_SECTOR_SIZE]; 131 int ptbl_lbn, ptbl_size; 132 int off, reloff, readoff; 133 uchar_t *p; 134 char *name; 135 int name_len; 136 int ipe_len; 137 DBusError error; 138 139 /* 140 * find 1st Primary Volume Descriptor 141 */ 142 readoff = probe_offset + ISO_VOLDESC_SEC * secsz; 143 if (pread (fd, buf, secsz, readoff) != secsz) { 144 return; 145 } 146 while (ISO_DESC_TYPE (buf) != ISO_VD_PVD) { 147 if (ISO_DESC_TYPE (buf) == ISO_VD_EOV) { 148 return; 149 } 150 readoff += secsz; 151 if (pread (fd, buf, secsz, readoff) != secsz) { 152 return; 153 } 154 } 155 156 /* 157 * PVD contains size and offset of the LSB/MSB path table 158 */ 159 ptbl_size = ISO_PTBL_SIZE (buf); 160 #if defined(_LITTLE_ENDIAN) 161 ptbl_lbn = ISO_PTBL_MAN_LS (buf); 162 #else 163 ptbl_lbn = ISO_PTBL_MAN_MS (buf); 164 #endif 165 166 /* 167 * Look through path table entries 168 */ 169 readoff = probe_offset + ptbl_lbn * secsz; 170 if (pread (fd, buf, secsz, readoff) != secsz) { 171 return; 172 } 173 dbus_error_init (&error); 174 175 for (off = reloff = 0; 176 off < ptbl_size; 177 off += ipe_len, reloff += ipe_len) { 178 179 /* load sectors on demand */ 180 if (reloff >= secsz) { 181 readoff += secsz; 182 if (pread (fd, buf, secsz, readoff) != secsz) { 183 break; 184 } 185 reloff -= secsz; 186 } 187 188 p = buf + reloff; 189 name_len = IPE_NAME_LEN(p); 190 ipe_len = IPE_FPESIZE + name_len + (name_len % 2); 191 192 /* only interested in root directories */ 193 if (IPE_PARENT_NO (p) != 1) { 194 continue; 195 } 196 if ((name_len < 2) || (name_len > IDE_MAX_NAME_LEN)) { 197 continue; 198 } 199 200 name = (char *)IPE_NAME (p); 201 if (strncasecmp (name, "VIDEO_TS", min (8, name_len)) == 0) { 202 libhal_device_set_property_bool (ctx, udi, 203 "volume.disc.is_videodvd", TRUE, &error); 204 } else if (strncasecmp (name, "VCD", min (3, name_len)) == 0) { 205 libhal_device_set_property_bool (ctx, udi, 206 "volume.disc.is_vcd", TRUE, &error); 207 } else if (strncasecmp (name, "SVCD", min (4, name_len)) == 0) { 208 libhal_device_set_property_bool (ctx, udi, 209 "volume.disc.is_svcd", TRUE, &error); 210 } 211 } 212 213 my_dbus_error_free (&error); 214 } 215 216 static dbus_bool_t 217 probe_disc (int fd, LibHalContext *ctx, const char *udi, dbus_bool_t *should_probe_for_fs) 218 { 219 DBusError error; 220 disc_info_t di; 221 int profile; 222 dbus_bool_t has_audio, has_data, is_blank, is_appendable, is_rewritable; 223 char *disc_type = "cd_rom"; 224 uint64_t capacity = 0; 225 int i; 226 LibHalChangeSet *cs; 227 228 dbus_error_init (&error); 229 230 if (get_disc_info (fd, &di)) { 231 is_blank = (di.disc_status == 0); 232 is_appendable = (di.disc_status == 1); 233 is_rewritable = (di.erasable != 0); 234 } else { 235 is_blank = is_appendable = is_rewritable = FALSE; 236 } 237 238 if (get_current_profile (fd, &profile)) { 239 switch (profile) { 240 case 0x08: /* CD-ROM */ 241 disc_type = "cd_rom"; 242 break; 243 case 0x09: /* CD-R */ 244 disc_type = "cd_r"; 245 break; 246 case 0x0A: /* CD-RW */ 247 disc_type = "cd_rw"; 248 is_rewritable = TRUE; 249 break; 250 case 0x10: /* DVD-ROM */ 251 disc_type = "dvd_rom"; 252 break; 253 case 0x11: /* DVD-R Sequential */ 254 disc_type = "dvd_r"; 255 break; 256 case 0x12: /* DVD-RAM */ 257 disc_type = "dvd_ram"; 258 is_rewritable = TRUE; 259 break; 260 case 0x13: /* DVD-RW Restricted Overwrite */ 261 disc_type = "dvd_rw"; 262 is_rewritable = TRUE; 263 break; 264 case 0x14: /* DVD-RW Sequential */ 265 disc_type = "dvd_rw"; 266 is_rewritable = TRUE; 267 break; 268 case 0x1A: /* DVD+RW */ 269 disc_type = "dvd_plus_rw"; 270 is_rewritable = TRUE; 271 break; 272 case 0x1B: /* DVD+R */ 273 disc_type = "dvd_plus_r"; 274 break; 275 case 0x2B: /* DVD+R Double Layer */ 276 disc_type = "dvd_plus_r_dl"; 277 break; 278 case 0x40: /* BD-ROM */ 279 disc_type = "bd_rom"; 280 break; 281 case 0x41: /* BD-R Sequential */ 282 disc_type = "bd_r"; 283 break; 284 case 0x42: /* BD-R Random */ 285 disc_type = "bd_r"; 286 break; 287 case 0x43: /* BD-RE */ 288 disc_type = "bd_re"; 289 is_rewritable = TRUE; 290 break; 291 case 0x50: /* HD DVD-ROM */ 292 disc_type = "hddvd_rom"; 293 break; 294 case 0x51: /* HD DVD-R */ 295 disc_type = "hddvd_r"; 296 break; 297 case 0x52: /* HD DVD-Rewritable */ 298 disc_type = "hddvd_rw"; 299 is_rewritable = TRUE; 300 break; 301 } 302 303 (void) get_disc_capacity_for_profile(fd, profile, &capacity); 304 } 305 306 has_audio = has_data = FALSE; 307 if (!is_blank) { 308 uchar_t smalltoc[12]; 309 size_t toc_size; 310 uchar_t *toc, *p; 311 312 /* 313 * XXX for some reason CDROMREADTOCENTRY fails on video DVDs, 314 * but extracting the toc directly works okay. 315 */ 316 if (!read_toc(fd, 0, 1, 4, smalltoc)) { 317 HAL_DEBUG(("read_toc failed")); 318 has_data = B_TRUE; /* probe for fs anyway */ 319 } else { 320 toc_size = smalltoc[0] * 256 + smalltoc[1] + 2; 321 toc = (uchar_t *)calloc(1, toc_size); 322 if (toc == NULL || !read_toc(fd, 0, 1, toc_size, toc)) { 323 HAL_DEBUG (("read_toc again failed")); 324 } else { 325 for (p = &toc[4]; p < (toc + toc_size); p += 8) { 326 /* skip leadout */ 327 if (p[2] == 0xAA) { 328 continue; 329 } 330 if (p[1] & 4) { 331 has_data = B_TRUE; 332 } else { 333 has_audio = B_TRUE; 334 } 335 } 336 } 337 free(toc); 338 } 339 } 340 341 if ((cs = libhal_device_new_changeset (udi)) == NULL) { 342 return (FALSE); 343 } 344 libhal_changeset_set_property_string (cs, "volume.disc.type", disc_type); 345 libhal_changeset_set_property_bool (cs, "volume.disc.is_blank", is_blank); 346 libhal_changeset_set_property_bool (cs, "volume.disc.has_audio", has_audio); 347 libhal_changeset_set_property_bool (cs, "volume.disc.has_data", has_data); 348 libhal_changeset_set_property_bool (cs, "volume.disc.is_appendable", is_appendable); 349 libhal_changeset_set_property_bool (cs, "volume.disc.is_rewritable", is_rewritable); 350 libhal_changeset_set_property_uint64 (cs, "volume.disc.capacity", capacity); 351 352 libhal_changeset_set_property_bool (cs, "volume.disc.is_videodvd", FALSE); 353 libhal_changeset_set_property_bool (cs, "volume.disc.is_vcd", FALSE); 354 libhal_changeset_set_property_bool (cs, "volume.disc.is_svcd", FALSE); 355 356 libhal_device_commit_changeset (ctx, cs, &error); 357 libhal_device_free_changeset (cs); 358 359 out: 360 361 *should_probe_for_fs = has_data; 362 363 my_dbus_error_free (&error); 364 365 return (TRUE); 366 } 367 368 static void 369 drop_privileges () 370 { 371 priv_set_t *pPrivSet = NULL; 372 priv_set_t *lPrivSet = NULL; 373 374 /* 375 * Start with the 'basic' privilege set and then remove any 376 * of the 'basic' privileges that will not be needed. 377 */ 378 if ((pPrivSet = priv_str_to_set("basic", ",", NULL)) == NULL) { 379 return; 380 } 381 382 /* Clear privileges we will not need from the 'basic' set */ 383 (void) priv_delset(pPrivSet, PRIV_FILE_LINK_ANY); 384 (void) priv_delset(pPrivSet, PRIV_PROC_INFO); 385 (void) priv_delset(pPrivSet, PRIV_PROC_SESSION); 386 (void) priv_delset(pPrivSet, PRIV_PROC_EXEC); 387 (void) priv_delset(pPrivSet, PRIV_PROC_FORK); 388 389 /* for uscsi */ 390 (void) priv_addset(pPrivSet, PRIV_SYS_DEVICES); 391 392 393 /* to open logindevperm'd devices */ 394 (void) priv_addset(pPrivSet, PRIV_FILE_DAC_READ); 395 396 /* Set the permitted privilege set. */ 397 if (setppriv(PRIV_SET, PRIV_PERMITTED, pPrivSet) != 0) { 398 return; 399 } 400 401 /* Clear the limit set. */ 402 if ((lPrivSet = priv_allocset()) == NULL) { 403 return; 404 } 405 406 priv_emptyset(lPrivSet); 407 408 if (setppriv(PRIV_SET, PRIV_LIMIT, lPrivSet) != 0) { 409 return; 410 } 411 412 priv_freeset(lPrivSet); 413 } 414 415 int 416 main (int argc, char *argv[]) 417 { 418 int fd, rfd; 419 int ret; 420 char *udi; 421 char *device_file, *raw_device_file; 422 char *devpath, *rdevpath; 423 boolean_t is_dos; 424 int dos_num; 425 LibHalContext *ctx = NULL; 426 DBusError error; 427 DBusConnection *conn; 428 char *parent_udi; 429 char *storage_device; 430 char *is_disc_str; 431 int fdc; 432 dbus_bool_t is_disc = FALSE; 433 dbus_bool_t is_floppy = FALSE; 434 unsigned int block_size; 435 dbus_uint64_t vol_size; 436 dbus_bool_t should_probe_for_fs; 437 char *partition_scheme = NULL; 438 dbus_uint64_t partition_start = 0; 439 int partition_number = 0; 440 struct vtoc vtoc; 441 dk_gpt_t *gpt; 442 struct dk_minfo mi; 443 int i, dos_cnt; 444 fstyp_handle_t fstyp_handle; 445 int systid, relsect, numsect; 446 off_t probe_offset = 0; 447 int num_volumes; 448 char **volumes; 449 dbus_uint64_t v_start; 450 const char *fstype; 451 nvlist_t *fsattr; 452 453 fd = rfd = -1; 454 455 ret = 1; 456 457 if ((udi = getenv ("UDI")) == NULL) { 458 goto out; 459 } 460 if ((device_file = getenv ("HAL_PROP_BLOCK_DEVICE")) == NULL) { 461 goto out; 462 } 463 if ((raw_device_file = getenv ("HAL_PROP_BLOCK_SOLARIS_RAW_DEVICE")) == NULL) { 464 goto out; 465 } 466 if (!dos_to_dev(device_file, &rdevpath, &dos_num)) { 467 rdevpath = raw_device_file; 468 } 469 if (!(is_dos = dos_to_dev(device_file, &devpath, &dos_num))) { 470 devpath = device_file; 471 } 472 if ((parent_udi = getenv ("HAL_PROP_INFO_PARENT")) == NULL) { 473 goto out; 474 } 475 if ((storage_device = getenv ("HAL_PROP_BLOCK_STORAGE_DEVICE")) == NULL) { 476 goto out; 477 } 478 479 is_disc_str = getenv ("HAL_PROP_VOLUME_IS_DISC"); 480 if (is_disc_str != NULL && strcmp (is_disc_str, "true") == 0) { 481 is_disc = TRUE; 482 } else { 483 is_disc = FALSE; 484 } 485 486 drop_privileges (); 487 488 setup_logger (); 489 490 dbus_error_init (&error); 491 if ((ctx = libhal_ctx_init_direct (&error)) == NULL) 492 goto out; 493 494 HAL_DEBUG (("Doing probe-volume for %s\n", device_file)); 495 496 fd = open (devpath, O_RDONLY | O_NONBLOCK); 497 if (fd < 0) { 498 goto out; 499 } 500 rfd = open (rdevpath, O_RDONLY | O_NONBLOCK); 501 if (rfd < 0) { 502 goto out; 503 } 504 505 /* if it's a floppy with no media, bail out */ 506 if (ioctl(rfd, FDGETCHANGE, &fdc) == 0) { 507 is_floppy = TRUE; 508 if (fdc & FDGC_CURRENT) { 509 goto out; 510 } 511 } 512 513 /* block size and total size */ 514 if (ioctl(rfd, DKIOCGMEDIAINFO, &mi) != -1) { 515 block_size = mi.dki_lbsize; 516 vol_size = mi.dki_capacity * block_size; 517 } else { 518 block_size = 512; 519 vol_size = 0; 520 } 521 libhal_device_set_property_int (ctx, udi, "volume.block_size", block_size, &error); 522 my_dbus_error_free (&error); 523 libhal_device_set_property_uint64 (ctx, udi, "volume.size", vol_size, &error); 524 my_dbus_error_free (&error); 525 526 should_probe_for_fs = TRUE; 527 528 if (is_disc) { 529 if (!probe_disc (rfd, ctx, udi, &should_probe_for_fs)) { 530 HAL_DEBUG (("probe_disc failed, skipping fstyp")); 531 goto out; 532 } 533 /* XXX vol_probe_offset for multisession discs? */ 534 } 535 536 if (!should_probe_for_fs) { 537 goto skip_fs; 538 } 539 540 /* don't support partitioned floppy */ 541 if (is_floppy) { 542 goto skip_part; 543 } 544 545 /* 546 * first get partitioning info 547 */ 548 if (is_dos) { 549 /* for a dos drive find partition offset */ 550 if (!find_dos_drive(fd, dos_num, &relsect, &numsect, &systid)) { 551 goto out; 552 } 553 partition_scheme = "mbr"; 554 partition_start = (dbus_uint64_t)relsect * 512; 555 partition_number = dos_num; 556 probe_offset = (off_t)relsect * 512; 557 } else { 558 if ((partition_number = read_vtoc(rfd, &vtoc)) >= 0) { 559 if (!vtoc_one_slice_entire_disk(&vtoc)) { 560 partition_scheme = "smi"; 561 if (partition_number < vtoc.v_nparts) { 562 if (vtoc.v_part[partition_number].p_size == 0) { 563 HAL_DEBUG (("zero size partition")); 564 } 565 partition_start = vtoc.v_part[partition_number].p_start * block_size; 566 } 567 } 568 } else if ((partition_number = efi_alloc_and_read(rfd, &gpt)) >= 0) { 569 partition_scheme = "gpt"; 570 if (partition_number < gpt->efi_nparts) { 571 if (gpt->efi_parts[partition_number].p_size == 0) { 572 HAL_DEBUG (("zero size partition")); 573 } 574 partition_start = gpt->efi_parts[partition_number].p_start * block_size; 575 } 576 efi_free(gpt); 577 } 578 probe_offset = 0; 579 } 580 581 if (partition_scheme != NULL) { 582 libhal_device_set_property_string (ctx, udi, "volume.partition.scheme", partition_scheme, &error); 583 my_dbus_error_free (&error); 584 libhal_device_set_property_int (ctx, udi, "volume.partition.number", partition_number, &error); 585 my_dbus_error_free (&error); 586 libhal_device_set_property_uint64 (ctx, udi, "volume.partition.start", partition_start, &error); 587 my_dbus_error_free (&error); 588 libhal_device_set_property_bool (ctx, udi, "volume.is_partition", TRUE, &error); 589 my_dbus_error_free (&error); 590 } else { 591 libhal_device_set_property_bool (ctx, udi, "volume.is_partition", FALSE, &error); 592 my_dbus_error_free (&error); 593 } 594 595 /* 596 * ignore duplicate partitions 597 */ 598 if ((volumes = libhal_manager_find_device_string_match ( 599 ctx, "block.storage_device", storage_device, &num_volumes, &error)) != NULL) { 600 my_dbus_error_free (&error); 601 for (i = 0; i < num_volumes; i++) { 602 if (strcmp (udi, volumes[i]) == 0) { 603 continue; /* skip self */ 604 } 605 v_start = libhal_device_get_property_uint64 (ctx, volumes[i], "volume.partition.start", &error); 606 if (dbus_error_is_set(&error)) { 607 dbus_error_free(&error); 608 continue; 609 } 610 if (v_start == partition_start) { 611 HAL_DEBUG (("duplicate partition")); 612 goto out; 613 } 614 } 615 libhal_free_string_array (volumes); 616 } 617 618 skip_part: 619 620 /* 621 * now determine fs type 622 */ 623 if (fstyp_init(fd, probe_offset, NULL, &fstyp_handle) != 0) { 624 HAL_DEBUG (("fstyp_init failed")); 625 goto out; 626 } 627 if ((fstyp_ident(fstyp_handle, NULL, &fstype) != 0) || 628 (fstyp_get_attr(fstyp_handle, &fsattr) != 0)) { 629 HAL_DEBUG (("fstyp ident or get_attr failed")); 630 631 /* 632 * XXX fstyp_udfs has a bug that it only works on raw, 633 * but we don't want to slow down the fast path above. 634 * Try raw for just udfs here until the bug is fixed. 635 */ 636 HAL_DEBUG (("trying udfs workaround")); 637 fstyp_fini(fstyp_handle); 638 if (fstyp_init(rfd, probe_offset, NULL, &fstyp_handle) != 0) { 639 goto out; 640 } 641 if ((fstyp_ident(fstyp_handle, "udfs", &fstype) != 0) || 642 (fstyp_get_attr(fstyp_handle, &fsattr) != 0)) { 643 fstyp_fini(fstyp_handle); 644 goto out; 645 } 646 } 647 set_fstyp_properties (ctx, udi, fstype, fsattr); 648 649 if (strcmp (fstype, "hsfs") == 0) { 650 hsfs_contents (fd, probe_offset, ctx, udi); 651 } 652 653 fstyp_fini(fstyp_handle); 654 655 skip_fs: 656 657 ret = 0; 658 659 out: 660 if (fd >= 0) 661 close (fd); 662 if (rfd >= 0) 663 close (rfd); 664 665 if (ctx != NULL) { 666 my_dbus_error_free (&error); 667 libhal_ctx_shutdown (ctx, &error); 668 libhal_ctx_free (ctx); 669 } 670 671 return ret; 672 673 } 674