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