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