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