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