/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2006 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. * Copyright (c) 2016 by Delphix. All rights reserved. */ /* * Program to eject one or more pieces of media. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static char *prog_name = NULL; static boolean_t do_default = B_FALSE; static boolean_t do_list = B_FALSE; static boolean_t do_closetray = B_FALSE; static boolean_t force_eject = B_FALSE; static boolean_t do_query = B_FALSE; static boolean_t is_direct = B_FALSE; static int work(char *, char *); static void usage(void); static int ejectit(char *); static boolean_t query(char *, boolean_t); static boolean_t floppy_in_drive(char *, int, boolean_t *); static boolean_t display_busy(char *, boolean_t); static char *eject_getfullblkname(char *, boolean_t); extern char *getfullrawname(char *); /* * ON-private libvolmgt routines */ int _dev_mounted(char *path); int _dev_unmount(char *path); char *_media_oldaliases(char *name); void _media_printaliases(void); /* * Hold over from old eject. * returns exit codes: (KEEP THESE - especially important for query) * 0 = -n, -d or eject operation was ok, -q = media in drive * 1 = -q only = media not in drive * 2 = various parameter errors, etc. * 3 = eject ioctl failed * New Value (2/94) * 4 = eject partially succeeded, but now manually remove media */ #define EJECT_OK 0 #define EJECT_NO_MEDIA 1 #define EJECT_PARM_ERR 2 #define EJECT_IOCTL_ERR 3 #define EJECT_MAN_EJ 4 #define AVAIL_MSG "%s is available\n" #define NOT_AVAIL_MSG "%s is not available\n" #define OK_TO_EJECT_MSG "%s can now be manually ejected\n" #define FLOPPY_MEDIA_TYPE "floppy" #define CDROM_MEDIA_TYPE "cdrom" int main(int argc, char **argv) { int c; const char *opts = "dqflt"; int excode; int res; boolean_t err_seen = B_FALSE; boolean_t man_eject_seen = B_FALSE; char *rmmount_opt = NULL; (void) setlocale(LC_ALL, ""); #if !defined(TEXT_DOMAIN) #define TEXT_DOMAIN "SYS_TEST" #endif (void) textdomain(TEXT_DOMAIN); prog_name = argv[0]; is_direct = (getenv("EJECT_DIRECT") != NULL); /* process arguments */ while ((c = getopt(argc, argv, opts)) != EOF) { switch (c) { case 'd': do_default = B_TRUE; rmmount_opt = "-d"; break; case 'q': do_query = B_TRUE; break; case 'l': do_list = B_TRUE; rmmount_opt = "-l"; break; case 'f': force_eject = B_TRUE; break; case 't': do_closetray = B_TRUE; break; default: usage(); exit(EJECT_PARM_ERR); } } if (argc == optind) { /* no argument -- use the default */ excode = work(NULL, rmmount_opt); } else { /* multiple things to eject */ for (; optind < argc; optind++) { res = work(argv[optind], rmmount_opt); if (res == EJECT_MAN_EJ) { man_eject_seen = B_TRUE; } else if (res != EJECT_OK) { err_seen = B_TRUE; } } if (err_seen) { if (!is_direct) { excode = res; } else { excode = EJECT_IOCTL_ERR; } } else if (man_eject_seen) { excode = EJECT_MAN_EJ; } else { excode = EJECT_OK; } } return (excode); } /* * the the real work of ejecting (and notifying) */ static int work(char *arg, char *rmmount_opt) { char *name; int excode = EJECT_OK; struct stat64 sb; char *arg1, *arg2; pid_t pid; int status = 1; if (!is_direct) { /* exec rmmount */ if (do_closetray) { (void) putenv("EJECT_CLOSETRAY=1"); } if (do_query) { (void) putenv("EJECT_QUERY=1"); } pid = fork(); if (pid < 0) { exit(1); } else if (pid == 0) { /* child */ if (rmmount_opt != NULL) { arg1 = rmmount_opt; arg2 = arg; } else { arg1 = arg; arg2 = NULL; } if (execl("/usr/bin/rmmount", "eject", arg1, arg2, 0) < 0) { excode = 99; } else { exit(0); } } else { /* parent */ if (waitpid(pid, &status, 0) != pid) { excode = 1; } else if (WIFEXITED(status) && (WEXITSTATUS(status) != 0)) { excode = WEXITSTATUS(status); } else { excode = 0; } } } /* * rmmount returns 99 if HAL not running - * fallback to direct in that case */ if (is_direct || (excode == 99)) { excode = EJECT_OK; if (arg == NULL) { arg = "floppy"; } if ((name = _media_oldaliases(arg)) == NULL) { name = arg; } if (do_default) { (void) printf("%s\n", name); goto out; } if (do_list) { (void) printf("%s\t%s\n", name, arg); goto out; } if (access(name, R_OK) != 0) { if (do_query) { (void) fprintf(stderr, gettext("%s: no media\n"), name); return (EJECT_NO_MEDIA); } else { perror(name); return (EJECT_PARM_ERR); } } if (do_query) { if ((stat64(name, &sb) == 0) && S_ISDIR(sb.st_mode)) { (void) fprintf(stderr, gettext("%s: no media\n"), name); return (EJECT_NO_MEDIA); } if (!query(name, B_TRUE)) { excode = EJECT_NO_MEDIA; } } else { excode = ejectit(name); } } out: return (excode); } static void usage(void) { (void) fprintf(stderr, gettext("usage: %s [-fldqt] [name | nickname]\n"), prog_name); (void) fprintf(stderr, gettext("options:\t-f force eject\n")); (void) fprintf(stderr, gettext("\t\t-l list ejectable devices\n")); (void) fprintf(stderr, gettext("\t\t-d show default device\n")); (void) fprintf(stderr, gettext("\t\t-q query for media present\n")); (void) fprintf(stderr, gettext("\t\t-t close tray\n")); } static int ejectit(char *name) { int fd, r; boolean_t mejectable = B_FALSE; /* manually ejectable */ int result = EJECT_OK; /* * If volume management is either not running or not being managed by * vold, and the device is mounted, we try to umount the device. If we * fail, we give up, unless it used the -f flag. */ if (_dev_mounted(name)) { r = _dev_unmount(name); if (r == 0) { if (!force_eject) { (void) fprintf(stderr, gettext("WARNING: can not unmount %s, the file system is (probably) busy\n"), name); return (EJECT_PARM_ERR); } else { (void) fprintf(stderr, gettext("WARNING: %s has a mounted filesystem, ejecting anyway\n"), name); } } } /* * Require O_NDELAY for when floppy is not formatted * will still id floppy in drive */ /* * make sure we are dealing with a raw device * * XXX: NOTE: results from getfullrawname() * really should be free()d when no longer * in use */ name = getfullrawname(name); if ((fd = open(name, O_RDONLY | O_NDELAY)) < 0) { if (errno == EBUSY) { (void) fprintf(stderr, gettext("%s is busy (try 'eject floppy' or 'eject cdrom'?)\n"), name); return (EJECT_PARM_ERR); } perror(name); return (EJECT_PARM_ERR); } if (do_closetray) { if (ioctl(fd, CDROMCLOSETRAY) < 0) { result = EJECT_IOCTL_ERR; } } else if (ioctl(fd, DKIOCEJECT, 0) < 0) { /* check on why eject failed */ /* check for no floppy in manually ejectable drive */ if ((errno == ENOSYS) && !floppy_in_drive(name, fd, &mejectable)) { /* use code below to handle "not present" */ errno = ENXIO; } if (errno == ENOSYS || errno == ENOTSUP) { (void) fprintf(stderr, gettext(OK_TO_EJECT_MSG), name); } if ((errno == ENOSYS || errno == ENOTSUP) && mejectable) { /* * keep track of the fact that this is a manual * ejection */ result = EJECT_MAN_EJ; } else if (errno == EBUSY) { /* * if our pathname is s slice (UFS is great) then * check to see what really is busy */ if (!display_busy(name, B_FALSE)) { perror(name); } result = EJECT_IOCTL_ERR; } else if ((errno == EAGAIN) || (errno == ENODEV) || (errno == ENXIO)) { (void) fprintf(stderr, gettext("%s not present in a drive\n"), name); result = EJECT_OK; } else { perror(name); result = EJECT_IOCTL_ERR; } } (void) close(fd); return (result); } /* * return B_TRUE if a floppy is in the drive, B_FALSE otherwise * * this routine assumes that the file descriptor passed in is for * a floppy disk. this works because it's only called if the device * is "manually ejectable", which only (currently) occurs for floppies. */ static boolean_t floppy_in_drive(char *name, int fd, boolean_t *is_floppy) { int ival = 0; boolean_t rval = B_FALSE; if (ioctl(fd, FDGETCHANGE, &ival) >= 0) { if (!(ival & FDGC_CURRENT)) { rval = B_TRUE; } *is_floppy = B_TRUE; } else { *is_floppy = B_FALSE; (void) fprintf(stderr, gettext("%s is not a floppy disk\n"), name); } return (rval); } /* * display a "busy" message for the supplied pathname * * if the pathname is not a slice, then just display a busy message * else if the pathname is some slice subdirectory then look for the * *real* culprits * * if this is not done then the user can get a message like * /vol/dev/rdsk/c0t6d0/solaris_2_5_sparc/s5: Device busy * when they try to eject "cdrom0", but "s0" (e.g.) may be the only busy * slice * * return B_TRUE iff we printed the appropriate error message, else * return B_FALSE (and caller will print error message itself) */ static boolean_t display_busy(char *path, boolean_t vm_running) { int errno_save = errno; /* to save errno */ char *blk; /* block name */ FILE *fp = NULL; /* for scanning mnttab */ struct mnttab mref; /* for scanning mnttab */ struct mnttab mp; /* for scanning mnttab */ boolean_t res = B_FALSE; /* return value */ char busy_base[MAXPATHLEN]; /* for keeping base dir name */ uint_t bblen; /* busy_base string length */ char *cp; /* for truncating path */ #ifdef DEBUG (void) fprintf(stderr, "display_busy(\"%s\"): entering\n", path); #endif /* * get the block pathname. * eject_getfullblkname returns NULL or pathname which * has length < MAXPATHLEN. */ blk = eject_getfullblkname(path, vm_running); if (blk == NULL) goto dun; /* open mnttab for scanning */ if ((fp = fopen(MNTTAB, "r")) == NULL) { /* can't open mnttab!? -- give up */ goto dun; } (void) memset((void *)&mref, '\0', sizeof (struct mnttab)); mref.mnt_special = blk; if (getmntany(fp, &mp, &mref) == 0) { /* we found our entry -- we're done */ goto dun; } /* perhaps we have a sub-slice (which is what we exist to test for) */ /* create a base pathname */ (void) strcpy(busy_base, blk); if ((cp = strrchr(busy_base, '/')) == NULL) { /* no last slash in pathname!!?? -- give up */ goto dun; } *cp = '\0'; bblen = strlen(busy_base); /* bblen = (uint)(cp - busy_base); */ /* scan for matches */ rewind(fp); /* rescan mnttab */ while (getmntent(fp, &mp) == 0) { /* * work around problem where '-' in /etc/mnttab for * special device turns to NULL which isn't expected */ if (mp.mnt_special == NULL) mp.mnt_special = "-"; if (strncmp(busy_base, mp.mnt_special, bblen) == 0) { res = B_TRUE; (void) fprintf(stderr, "%s: %s\n", mp.mnt_special, strerror(EBUSY)); } } dun: if (fp != NULL) { (void) fclose(fp); } #ifdef DEBUG (void) fprintf(stderr, "display_busy: returning %s\n", res ? "B_TRUE" : "B_FALSE"); #endif errno = errno_save; return (res); } /* * In my experience with removable media drivers so far... the * most reliable way to tell if a piece of media is in a drive * is simply to open it. If the open works, there's something there, * if it fails, there's not. We check for two errnos which we * want to interpret for the user, ENOENT and EPERM. All other * errors are considered to be "media isn't there". * * return B_TRUE if media found, else B_FALSE (XXX: was 0 and -1) */ static boolean_t query(char *name, boolean_t doprint) { int fd; int rval; /* FDGETCHANGE return value */ enum dkio_state state; if ((fd = open(name, O_RDONLY|O_NONBLOCK)) < 0) { if ((errno == EPERM) || (errno == ENOENT)) { if (doprint) { perror(name); } } else { if (doprint) { (void) fprintf(stderr, gettext(NOT_AVAIL_MSG), name); } } return (B_FALSE); } rval = 0; if (ioctl(fd, FDGETCHANGE, &rval) >= 0) { /* hey, it worked, what a deal, it must be a floppy */ (void) close(fd); if (!(rval & FDGC_CURRENT)) { if (doprint) { (void) fprintf(stderr, gettext(AVAIL_MSG), name); } return (B_TRUE); } if (rval & FDGC_CURRENT) { if (doprint) { (void) fprintf(stderr, gettext(NOT_AVAIL_MSG), name); } return (B_FALSE); } } again: state = DKIO_NONE; if (ioctl(fd, DKIOCSTATE, &state) >= 0) { /* great, the fancy ioctl is supported. */ if (state == DKIO_INSERTED) { if (doprint) { (void) fprintf(stderr, gettext(AVAIL_MSG), name); } (void) close(fd); return (B_TRUE); } if (state == DKIO_EJECTED) { if (doprint) { (void) fprintf(stderr, gettext(NOT_AVAIL_MSG), name); } (void) close(fd); return (B_FALSE); } /* * Silly retry loop. */ (void) sleep(1); goto again; } (void) close(fd); /* * Ok, we've tried the non-blocking/ioctl route. The * device doesn't support any of our nice ioctls, so * we'll just say that if it opens it's there, if it * doesn't, it's not. */ if ((fd = open(name, O_RDONLY)) < 0) { if (doprint) { (void) fprintf(stderr, gettext(NOT_AVAIL_MSG), name); } return (B_FALSE); } (void) close(fd); if (doprint) { (void) fprintf(stderr, gettext(AVAIL_MSG), name); } return (B_TRUE); /* success */ } /* * this routine will return the volmgt block name given the volmgt * raw (char spcl) name * * if anything but a volmgt raw pathname is supplied that pathname will * be returned * * NOTE: non-null return value will point to static data, overwritten with * each call * * e.g. names starting with "/vol/r" will be changed to start with "/vol/", * and names starting with "vol/dev/r" will be changed to start with * "/vol/dev/" */ static char * eject_getfullblkname(char *path, boolean_t vm_running) { char raw_root[MAXPATHLEN]; const char *vm_root; static char res_buf[MAXPATHLEN]; uint_t raw_root_len; #ifdef DEBUG (void) fprintf(stderr, "eject_getfullblkname(\"%s\", %s): entering\n", path, vm_running ? "B_TRUE" : "B_FALSE"); #endif /* * try different strategies based on whether or not vold is running */ if (vm_running) { /* vold IS running -- look in /vol (or its alternate) */ /* get vm root dir */ vm_root = volmgt_root(); /* get first volmgt root dev directory (and its length) */ (void) snprintf(raw_root, sizeof (raw_root), "%s/r", vm_root); raw_root_len = strlen(raw_root); /* see if we have a raw volmgt pathname (e.g. "/vol/r*") */ if (strncmp(path, raw_root, raw_root_len) == 0) { if (snprintf(res_buf, sizeof (res_buf), "%s/%s", vm_root, path + raw_root_len) >= sizeof (res_buf)) { return (NULL); } goto dun; /* found match in /vol */ } /* get second volmgt root dev directory (and its length) */ (void) snprintf(raw_root, sizeof (raw_root), "%s/dev/r", vm_root); raw_root_len = strlen(raw_root); /* see if we have a raw volmgt pathname (e.g. "/vol/dev/r*") */ if (strncmp(path, raw_root, raw_root_len) == 0) { if (snprintf(res_buf, sizeof (res_buf), "%s/dev/%s", vm_root, path + raw_root_len) >= sizeof (res_buf)) { return (NULL); } goto dun; /* found match in /vol/dev */ } } else { /* vold is NOT running -- look in /dev */ (void) strcpy(raw_root, "/dev/r"); raw_root_len = strlen(raw_root); if (strncmp(path, raw_root, raw_root_len) == 0) { if (snprintf(res_buf, sizeof (res_buf), "/dev/%s", path + raw_root_len) >= sizeof (res_buf)) { return (NULL); } goto dun; /* found match in /dev */ } } /* no match -- return what we got */ (void) strcpy(res_buf, path); dun: #ifdef DEBUG (void) fprintf(stderr, "eject_getfullblkname: returning %s\n", res_buf ? res_buf : ""); #endif return (res_buf); }