1 /* 2 * CDDL HEADER START 3 * 4 * The contents of this file are subject to the terms of the 5 * Common Development and Distribution License (the "License"). 6 * You may not use this file except in compliance with the License. 7 * 8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 9 * or http://www.opensolaris.org/os/licensing. 10 * See the License for the specific language governing permissions 11 * and limitations under the License. 12 * 13 * When distributing Covered Code, include this CDDL HEADER in each 14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE. 15 * If applicable, add the following below this CDDL HEADER, with the 16 * fields enclosed by brackets "[]" replaced with your own identifying 17 * information: Portions Copyright [yyyy] [name of copyright owner] 18 * 19 * CDDL HEADER END 20 */ 21 /* 22 * Copyright 2006 Sun Microsystems, Inc. All rights reserved. 23 * Use is subject to license terms. 24 * Copyright (c) 2016 by Delphix. All rights reserved. 25 */ 26 27 /* 28 * Program to eject one or more pieces of media. 29 */ 30 31 #include <stdio.h> 32 #include <stdlib.h> 33 #include <string.h> 34 #include <sys/types.h> 35 #include <sys/stat.h> 36 #include <sys/fdio.h> 37 #include <sys/dkio.h> 38 #include <sys/cdio.h> 39 #include <sys/param.h> 40 #include <sys/wait.h> 41 #include <dirent.h> 42 #include <fcntl.h> 43 #include <string.h> 44 #include <errno.h> 45 #include <locale.h> 46 #include <libintl.h> 47 #include <unistd.h> 48 #include <pwd.h> 49 #include <volmgt.h> 50 #include <sys/mnttab.h> 51 #include <signal.h> 52 53 static char *prog_name = NULL; 54 static boolean_t do_default = B_FALSE; 55 static boolean_t do_list = B_FALSE; 56 static boolean_t do_closetray = B_FALSE; 57 static boolean_t force_eject = B_FALSE; 58 static boolean_t do_query = B_FALSE; 59 static boolean_t is_direct = B_FALSE; 60 61 static int work(char *, char *); 62 static void usage(void); 63 static int ejectit(char *); 64 static boolean_t query(char *, boolean_t); 65 static boolean_t floppy_in_drive(char *, int, boolean_t *); 66 static boolean_t display_busy(char *, boolean_t); 67 static char *eject_getfullblkname(char *, boolean_t); 68 extern char *getfullrawname(char *); 69 70 /* 71 * ON-private libvolmgt routines 72 */ 73 int _dev_mounted(char *path); 74 int _dev_unmount(char *path); 75 char *_media_oldaliases(char *name); 76 void _media_printaliases(void); 77 78 79 /* 80 * Hold over from old eject. 81 * returns exit codes: (KEEP THESE - especially important for query) 82 * 0 = -n, -d or eject operation was ok, -q = media in drive 83 * 1 = -q only = media not in drive 84 * 2 = various parameter errors, etc. 85 * 3 = eject ioctl failed 86 * New Value (2/94) 87 * 4 = eject partially succeeded, but now manually remove media 88 */ 89 90 #define EJECT_OK 0 91 #define EJECT_NO_MEDIA 1 92 #define EJECT_PARM_ERR 2 93 #define EJECT_IOCTL_ERR 3 94 #define EJECT_MAN_EJ 4 95 96 #define AVAIL_MSG "%s is available\n" 97 #define NOT_AVAIL_MSG "%s is not available\n" 98 99 #define OK_TO_EJECT_MSG "%s can now be manually ejected\n" 100 101 #define FLOPPY_MEDIA_TYPE "floppy" 102 #define CDROM_MEDIA_TYPE "cdrom" 103 104 105 int 106 main(int argc, char **argv) 107 { 108 int c; 109 const char *opts = "dqflt"; 110 int excode; 111 int res; 112 boolean_t err_seen = B_FALSE; 113 boolean_t man_eject_seen = B_FALSE; 114 char *rmmount_opt = NULL; 115 116 (void) setlocale(LC_ALL, ""); 117 118 #if !defined(TEXT_DOMAIN) 119 #define TEXT_DOMAIN "SYS_TEST" 120 #endif 121 122 (void) textdomain(TEXT_DOMAIN); 123 124 prog_name = argv[0]; 125 126 is_direct = (getenv("EJECT_DIRECT") != NULL); 127 128 /* process arguments */ 129 while ((c = getopt(argc, argv, opts)) != EOF) { 130 switch (c) { 131 case 'd': 132 do_default = B_TRUE; 133 rmmount_opt = "-d"; 134 break; 135 case 'q': 136 do_query = B_TRUE; 137 break; 138 case 'l': 139 do_list = B_TRUE; 140 rmmount_opt = "-l"; 141 break; 142 case 'f': 143 force_eject = B_TRUE; 144 break; 145 case 't': 146 do_closetray = B_TRUE; 147 break; 148 default: 149 usage(); 150 exit(EJECT_PARM_ERR); 151 } 152 } 153 154 if (argc == optind) { 155 /* no argument -- use the default */ 156 excode = work(NULL, rmmount_opt); 157 } else { 158 /* multiple things to eject */ 159 for (; optind < argc; optind++) { 160 res = work(argv[optind], rmmount_opt); 161 if (res == EJECT_MAN_EJ) { 162 man_eject_seen = B_TRUE; 163 } else if (res != EJECT_OK) { 164 err_seen = B_TRUE; 165 } 166 } 167 if (err_seen) { 168 if (!is_direct) { 169 excode = res; 170 } else { 171 excode = EJECT_IOCTL_ERR; 172 } 173 } else if (man_eject_seen) { 174 excode = EJECT_MAN_EJ; 175 } else { 176 excode = EJECT_OK; 177 } 178 } 179 180 return (excode); 181 } 182 183 /* 184 * the the real work of ejecting (and notifying) 185 */ 186 static int 187 work(char *arg, char *rmmount_opt) 188 { 189 char *name; 190 int excode = EJECT_OK; 191 struct stat64 sb; 192 char *arg1, *arg2; 193 pid_t pid; 194 int status = 1; 195 196 if (!is_direct) { 197 /* exec rmmount */ 198 if (do_closetray) { 199 (void) putenv("EJECT_CLOSETRAY=1"); 200 } 201 if (do_query) { 202 (void) putenv("EJECT_QUERY=1"); 203 } 204 pid = fork(); 205 if (pid < 0) { 206 exit(1); 207 } else if (pid == 0) { 208 /* child */ 209 if (rmmount_opt != NULL) { 210 arg1 = rmmount_opt; 211 arg2 = arg; 212 } else { 213 arg1 = arg; 214 arg2 = NULL; 215 } 216 217 if (execl("/usr/bin/rmmount", "eject", 218 arg1, arg2, 0) < 0) { 219 excode = 99; 220 } else { 221 exit(0); 222 } 223 } else { 224 /* parent */ 225 if (waitpid(pid, &status, 0) != pid) { 226 excode = 1; 227 } else if (WIFEXITED(status) && 228 (WEXITSTATUS(status) != 0)) { 229 excode = WEXITSTATUS(status); 230 } else { 231 excode = 0; 232 } 233 } 234 } 235 236 /* 237 * rmmount returns 99 if HAL not running - 238 * fallback to direct in that case 239 */ 240 if (is_direct || (excode == 99)) { 241 excode = EJECT_OK; 242 243 if (arg == NULL) { 244 arg = "floppy"; 245 } 246 if ((name = _media_oldaliases(arg)) == NULL) { 247 name = arg; 248 } 249 if (do_default) { 250 (void) printf("%s\n", name); 251 goto out; 252 } 253 if (do_list) { 254 (void) printf("%s\t%s\n", name, arg); 255 goto out; 256 } 257 if (access(name, R_OK) != 0) { 258 if (do_query) { 259 (void) fprintf(stderr, 260 gettext("%s: no media\n"), name); 261 return (EJECT_NO_MEDIA); 262 } else { 263 perror(name); 264 return (EJECT_PARM_ERR); 265 } 266 } 267 268 if (do_query) { 269 if ((stat64(name, &sb) == 0) && S_ISDIR(sb.st_mode)) { 270 (void) fprintf(stderr, 271 gettext("%s: no media\n"), name); 272 return (EJECT_NO_MEDIA); 273 } 274 if (!query(name, B_TRUE)) { 275 excode = EJECT_NO_MEDIA; 276 } 277 } else { 278 excode = ejectit(name); 279 } 280 } 281 out: 282 return (excode); 283 } 284 285 286 static void 287 usage(void) 288 { 289 (void) fprintf(stderr, 290 gettext("usage: %s [-fldqt] [name | nickname]\n"), 291 prog_name); 292 (void) fprintf(stderr, 293 gettext("options:\t-f force eject\n")); 294 (void) fprintf(stderr, 295 gettext("\t\t-l list ejectable devices\n")); 296 (void) fprintf(stderr, 297 gettext("\t\t-d show default device\n")); 298 (void) fprintf(stderr, 299 gettext("\t\t-q query for media present\n")); 300 (void) fprintf(stderr, 301 gettext("\t\t-t close tray\n")); 302 } 303 304 305 static int 306 ejectit(char *name) 307 { 308 int fd, r; 309 boolean_t mejectable = B_FALSE; /* manually ejectable */ 310 int result = EJECT_OK; 311 312 /* 313 * If volume management is either not running or not being managed by 314 * vold, and the device is mounted, we try to umount the device. If we 315 * fail, we give up, unless it used the -f flag. 316 */ 317 318 if (_dev_mounted(name)) { 319 r = _dev_unmount(name); 320 if (r == 0) { 321 if (!force_eject) { 322 (void) fprintf(stderr, 323 gettext("WARNING: can not unmount %s, the file system is (probably) busy\n"), 324 name); 325 return (EJECT_PARM_ERR); 326 } else { 327 (void) fprintf(stderr, 328 gettext("WARNING: %s has a mounted filesystem, ejecting anyway\n"), 329 name); 330 } 331 } 332 } 333 334 /* 335 * Require O_NDELAY for when floppy is not formatted 336 * will still id floppy in drive 337 */ 338 339 /* 340 * make sure we are dealing with a raw device 341 * 342 * XXX: NOTE: results from getfullrawname() 343 * really should be free()d when no longer 344 * in use 345 */ 346 name = getfullrawname(name); 347 348 if ((fd = open(name, O_RDONLY | O_NDELAY)) < 0) { 349 if (errno == EBUSY) { 350 (void) fprintf(stderr, 351 gettext("%s is busy (try 'eject floppy' or 'eject cdrom'?)\n"), 352 name); 353 return (EJECT_PARM_ERR); 354 } 355 perror(name); 356 return (EJECT_PARM_ERR); 357 } 358 359 if (do_closetray) { 360 if (ioctl(fd, CDROMCLOSETRAY) < 0) { 361 result = EJECT_IOCTL_ERR; 362 } 363 } else if (ioctl(fd, DKIOCEJECT, 0) < 0) { 364 /* check on why eject failed */ 365 366 /* check for no floppy in manually ejectable drive */ 367 if ((errno == ENOSYS) && 368 !floppy_in_drive(name, fd, &mejectable)) { 369 /* use code below to handle "not present" */ 370 errno = ENXIO; 371 } 372 373 if (errno == ENOSYS || errno == ENOTSUP) { 374 (void) fprintf(stderr, gettext(OK_TO_EJECT_MSG), name); 375 } 376 377 if ((errno == ENOSYS || errno == ENOTSUP) && mejectable) { 378 /* 379 * keep track of the fact that this is a manual 380 * ejection 381 */ 382 result = EJECT_MAN_EJ; 383 384 } else if (errno == EBUSY) { 385 /* 386 * if our pathname is s slice (UFS is great) then 387 * check to see what really is busy 388 */ 389 if (!display_busy(name, B_FALSE)) { 390 perror(name); 391 } 392 result = EJECT_IOCTL_ERR; 393 394 } else if ((errno == EAGAIN) || (errno == ENODEV) || 395 (errno == ENXIO)) { 396 (void) fprintf(stderr, 397 gettext("%s not present in a drive\n"), 398 name); 399 result = EJECT_OK; 400 } else { 401 perror(name); 402 result = EJECT_IOCTL_ERR; 403 } 404 } 405 406 (void) close(fd); 407 return (result); 408 } 409 410 411 /* 412 * return B_TRUE if a floppy is in the drive, B_FALSE otherwise 413 * 414 * this routine assumes that the file descriptor passed in is for 415 * a floppy disk. this works because it's only called if the device 416 * is "manually ejectable", which only (currently) occurs for floppies. 417 */ 418 static boolean_t 419 floppy_in_drive(char *name, int fd, boolean_t *is_floppy) 420 { 421 int ival = 0; 422 boolean_t rval = B_FALSE; 423 424 425 if (ioctl(fd, FDGETCHANGE, &ival) >= 0) { 426 if (!(ival & FDGC_CURRENT)) { 427 rval = B_TRUE; 428 } 429 *is_floppy = B_TRUE; 430 } else { 431 *is_floppy = B_FALSE; 432 (void) fprintf(stderr, gettext("%s is not a floppy disk\n"), 433 name); 434 } 435 436 return (rval); 437 } 438 439 440 /* 441 * display a "busy" message for the supplied pathname 442 * 443 * if the pathname is not a slice, then just display a busy message 444 * else if the pathname is some slice subdirectory then look for the 445 * *real* culprits 446 * 447 * if this is not done then the user can get a message like 448 * /vol/dev/rdsk/c0t6d0/solaris_2_5_sparc/s5: Device busy 449 * when they try to eject "cdrom0", but "s0" (e.g.) may be the only busy 450 * slice 451 * 452 * return B_TRUE iff we printed the appropriate error message, else 453 * return B_FALSE (and caller will print error message itself) 454 */ 455 static boolean_t 456 display_busy(char *path, boolean_t vm_running) 457 { 458 int errno_save = errno; /* to save errno */ 459 char *blk; /* block name */ 460 FILE *fp = NULL; /* for scanning mnttab */ 461 struct mnttab mref; /* for scanning mnttab */ 462 struct mnttab mp; /* for scanning mnttab */ 463 boolean_t res = B_FALSE; /* return value */ 464 char busy_base[MAXPATHLEN]; /* for keeping base dir name */ 465 uint_t bblen; /* busy_base string length */ 466 char *cp; /* for truncating path */ 467 468 469 470 #ifdef DEBUG 471 (void) fprintf(stderr, "display_busy(\"%s\"): entering\n", path); 472 #endif 473 474 /* 475 * get the block pathname. 476 * eject_getfullblkname returns NULL or pathname which 477 * has length < MAXPATHLEN. 478 */ 479 blk = eject_getfullblkname(path, vm_running); 480 if (blk == NULL) 481 goto dun; 482 483 /* open mnttab for scanning */ 484 if ((fp = fopen(MNTTAB, "r")) == NULL) { 485 /* can't open mnttab!? -- give up */ 486 goto dun; 487 } 488 489 (void) memset((void *)&mref, '\0', sizeof (struct mnttab)); 490 mref.mnt_special = blk; 491 if (getmntany(fp, &mp, &mref) == 0) { 492 /* we found our entry -- we're done */ 493 goto dun; 494 } 495 496 /* perhaps we have a sub-slice (which is what we exist to test for) */ 497 498 /* create a base pathname */ 499 (void) strcpy(busy_base, blk); 500 if ((cp = strrchr(busy_base, '/')) == NULL) { 501 /* no last slash in pathname!!?? -- give up */ 502 goto dun; 503 } 504 *cp = '\0'; 505 bblen = strlen(busy_base); 506 /* bblen = (uint)(cp - busy_base); */ 507 508 /* scan for matches */ 509 rewind(fp); /* rescan mnttab */ 510 while (getmntent(fp, &mp) == 0) { 511 /* 512 * work around problem where '-' in /etc/mnttab for 513 * special device turns to NULL which isn't expected 514 */ 515 if (mp.mnt_special == NULL) 516 mp.mnt_special = "-"; 517 if (strncmp(busy_base, mp.mnt_special, bblen) == 0) { 518 res = B_TRUE; 519 (void) fprintf(stderr, "%s: %s\n", mp.mnt_special, 520 strerror(EBUSY)); 521 } 522 } 523 524 dun: 525 if (fp != NULL) { 526 (void) fclose(fp); 527 } 528 #ifdef DEBUG 529 (void) fprintf(stderr, "display_busy: returning %s\n", 530 res ? "B_TRUE" : "B_FALSE"); 531 #endif 532 errno = errno_save; 533 return (res); 534 } 535 536 537 /* 538 * In my experience with removable media drivers so far... the 539 * most reliable way to tell if a piece of media is in a drive 540 * is simply to open it. If the open works, there's something there, 541 * if it fails, there's not. We check for two errnos which we 542 * want to interpret for the user, ENOENT and EPERM. All other 543 * errors are considered to be "media isn't there". 544 * 545 * return B_TRUE if media found, else B_FALSE (XXX: was 0 and -1) 546 */ 547 static boolean_t 548 query(char *name, boolean_t doprint) 549 { 550 int fd; 551 int rval; /* FDGETCHANGE return value */ 552 enum dkio_state state; 553 554 if ((fd = open(name, O_RDONLY|O_NONBLOCK)) < 0) { 555 if ((errno == EPERM) || (errno == ENOENT)) { 556 if (doprint) { 557 perror(name); 558 } 559 } else { 560 if (doprint) { 561 (void) fprintf(stderr, gettext(NOT_AVAIL_MSG), 562 name); 563 } 564 } 565 return (B_FALSE); 566 } 567 568 rval = 0; 569 if (ioctl(fd, FDGETCHANGE, &rval) >= 0) { 570 /* hey, it worked, what a deal, it must be a floppy */ 571 (void) close(fd); 572 if (!(rval & FDGC_CURRENT)) { 573 if (doprint) { 574 (void) fprintf(stderr, gettext(AVAIL_MSG), 575 name); 576 } 577 return (B_TRUE); 578 } 579 if (rval & FDGC_CURRENT) { 580 if (doprint) { 581 (void) fprintf(stderr, gettext(NOT_AVAIL_MSG), 582 name); 583 } 584 return (B_FALSE); 585 } 586 } 587 588 again: 589 state = DKIO_NONE; 590 if (ioctl(fd, DKIOCSTATE, &state) >= 0) { 591 /* great, the fancy ioctl is supported. */ 592 if (state == DKIO_INSERTED) { 593 if (doprint) { 594 (void) fprintf(stderr, gettext(AVAIL_MSG), 595 name); 596 } 597 (void) close(fd); 598 return (B_TRUE); 599 } 600 if (state == DKIO_EJECTED) { 601 if (doprint) { 602 (void) fprintf(stderr, gettext(NOT_AVAIL_MSG), 603 name); 604 } 605 (void) close(fd); 606 return (B_FALSE); 607 } 608 /* 609 * Silly retry loop. 610 */ 611 (void) sleep(1); 612 goto again; 613 } 614 (void) close(fd); 615 616 /* 617 * Ok, we've tried the non-blocking/ioctl route. The 618 * device doesn't support any of our nice ioctls, so 619 * we'll just say that if it opens it's there, if it 620 * doesn't, it's not. 621 */ 622 if ((fd = open(name, O_RDONLY)) < 0) { 623 if (doprint) { 624 (void) fprintf(stderr, gettext(NOT_AVAIL_MSG), name); 625 } 626 return (B_FALSE); 627 } 628 629 (void) close(fd); 630 if (doprint) { 631 (void) fprintf(stderr, gettext(AVAIL_MSG), name); 632 } 633 return (B_TRUE); /* success */ 634 } 635 636 637 /* 638 * this routine will return the volmgt block name given the volmgt 639 * raw (char spcl) name 640 * 641 * if anything but a volmgt raw pathname is supplied that pathname will 642 * be returned 643 * 644 * NOTE: non-null return value will point to static data, overwritten with 645 * each call 646 * 647 * e.g. names starting with "/vol/r" will be changed to start with "/vol/", 648 * and names starting with "vol/dev/r" will be changed to start with 649 * "/vol/dev/" 650 */ 651 static char * 652 eject_getfullblkname(char *path, boolean_t vm_running) 653 { 654 char raw_root[MAXPATHLEN]; 655 const char *vm_root; 656 static char res_buf[MAXPATHLEN]; 657 uint_t raw_root_len; 658 659 #ifdef DEBUG 660 (void) fprintf(stderr, "eject_getfullblkname(\"%s\", %s): entering\n", 661 path, vm_running ? "B_TRUE" : "B_FALSE"); 662 #endif 663 /* 664 * try different strategies based on whether or not vold is running 665 */ 666 if (vm_running) { 667 668 /* vold IS running -- look in /vol (or its alternate) */ 669 670 /* get vm root dir */ 671 vm_root = volmgt_root(); 672 673 /* get first volmgt root dev directory (and its length) */ 674 (void) snprintf(raw_root, sizeof (raw_root), "%s/r", vm_root); 675 raw_root_len = strlen(raw_root); 676 677 /* see if we have a raw volmgt pathname (e.g. "/vol/r*") */ 678 if (strncmp(path, raw_root, raw_root_len) == 0) { 679 if (snprintf(res_buf, sizeof (res_buf), "%s/%s", 680 vm_root, path + raw_root_len) >= sizeof (res_buf)) { 681 return (NULL); 682 } 683 goto dun; /* found match in /vol */ 684 } 685 686 /* get second volmgt root dev directory (and its length) */ 687 (void) snprintf(raw_root, sizeof (raw_root), 688 "%s/dev/r", vm_root); 689 raw_root_len = strlen(raw_root); 690 691 /* see if we have a raw volmgt pathname (e.g. "/vol/dev/r*") */ 692 if (strncmp(path, raw_root, raw_root_len) == 0) { 693 if (snprintf(res_buf, sizeof (res_buf), "%s/dev/%s", 694 vm_root, path + raw_root_len) >= sizeof (res_buf)) { 695 return (NULL); 696 } 697 goto dun; /* found match in /vol/dev */ 698 } 699 700 } else { 701 702 /* vold is NOT running -- look in /dev */ 703 704 (void) strcpy(raw_root, "/dev/r"); 705 raw_root_len = strlen(raw_root); 706 if (strncmp(path, raw_root, raw_root_len) == 0) { 707 if (snprintf(res_buf, sizeof (res_buf), "/dev/%s", 708 path + raw_root_len) >= sizeof (res_buf)) { 709 return (NULL); 710 } 711 goto dun; /* found match in /dev */ 712 } 713 } 714 715 /* no match -- return what we got */ 716 (void) strcpy(res_buf, path); 717 718 dun: 719 #ifdef DEBUG 720 (void) fprintf(stderr, "eject_getfullblkname: returning %s\n", 721 res_buf ? res_buf : "<null ptr>"); 722 #endif 723 return (res_buf); 724 } 725