Merge branch 'cleanups'
[unleashed.git] / usr / src / cmd / eject / eject.c
blob32bc8c64d1f84c3a15b63aab31ddbe44e4510dc9
1 /*
2 * CDDL HEADER START
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.
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.
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]
19 * CDDL HEADER END
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.
27 #pragma ident "%Z%%M% %I% %E% SMI"
30 * Program to eject one or more pieces of media.
33 #include <stdio.h>
34 #include <stdlib.h>
35 #include <string.h>
36 #include <sys/types.h>
37 #include <sys/stat.h>
38 #include <sys/fdio.h>
39 #include <sys/dkio.h>
40 #include <sys/cdio.h>
41 #include <sys/param.h>
42 #include <sys/wait.h>
43 #include <dirent.h>
44 #include <fcntl.h>
45 #include <string.h>
46 #include <errno.h>
47 #include <locale.h>
48 #include <libintl.h>
49 #include <unistd.h>
50 #include <pwd.h>
51 #include <volmgt.h>
52 #include <sys/mnttab.h>
53 #include <signal.h>
55 static char *prog_name = NULL;
56 static boolean_t do_default = B_FALSE;
57 static boolean_t do_list = B_FALSE;
58 static boolean_t do_closetray = B_FALSE;
59 static boolean_t force_eject = B_FALSE;
60 static boolean_t do_query = B_FALSE;
61 static boolean_t is_direct = B_FALSE;
63 static int work(char *, char *);
64 static void usage(void);
65 static int ejectit(char *);
66 static boolean_t query(char *, boolean_t);
67 static boolean_t floppy_in_drive(char *, int, boolean_t *);
68 static boolean_t display_busy(char *, boolean_t);
69 static char *eject_getfullblkname(char *, boolean_t);
70 extern char *getfullrawname(char *);
73 * ON-private libvolmgt routines
75 int _dev_mounted(char *path);
76 int _dev_unmount(char *path);
77 char *_media_oldaliases(char *name);
78 void _media_printaliases(void);
82 * Hold over from old eject.
83 * returns exit codes: (KEEP THESE - especially important for query)
84 * 0 = -n, -d or eject operation was ok, -q = media in drive
85 * 1 = -q only = media not in drive
86 * 2 = various parameter errors, etc.
87 * 3 = eject ioctl failed
88 * New Value (2/94)
89 * 4 = eject partially succeeded, but now manually remove media
92 #define EJECT_OK 0
93 #define EJECT_NO_MEDIA 1
94 #define EJECT_PARM_ERR 2
95 #define EJECT_IOCTL_ERR 3
96 #define EJECT_MAN_EJ 4
98 #define AVAIL_MSG "%s is available\n"
99 #define NOT_AVAIL_MSG "%s is not available\n"
101 #define OK_TO_EJECT_MSG "%s can now be manually ejected\n"
103 #define FLOPPY_MEDIA_TYPE "floppy"
104 #define CDROM_MEDIA_TYPE "cdrom"
108 main(int argc, char **argv)
110 int c;
111 const char *opts = "dqflt";
112 int excode;
113 int res;
114 boolean_t err_seen = B_FALSE;
115 boolean_t man_eject_seen = B_FALSE;
116 char *rmmount_opt = NULL;
118 (void) setlocale(LC_ALL, "");
120 #if !defined(TEXT_DOMAIN)
121 #define TEXT_DOMAIN "SYS_TEST"
122 #endif
124 (void) textdomain(TEXT_DOMAIN);
126 prog_name = argv[0];
128 is_direct = (getenv("EJECT_DIRECT") != NULL);
130 /* process arguments */
131 while ((c = getopt(argc, argv, opts)) != EOF) {
132 switch (c) {
133 case 'd':
134 do_default = B_TRUE;
135 rmmount_opt = "-d";
136 break;
137 case 'q':
138 do_query = B_TRUE;
139 break;
140 case 'l':
141 do_list = B_TRUE;
142 rmmount_opt = "-l";
143 break;
144 case 'f':
145 force_eject = B_TRUE;
146 break;
147 case 't':
148 do_closetray = B_TRUE;
149 break;
150 default:
151 usage();
152 exit(EJECT_PARM_ERR);
156 if (argc == optind) {
157 /* no argument -- use the default */
158 excode = work(NULL, rmmount_opt);
159 } else {
160 /* multiple things to eject */
161 for (; optind < argc; optind++) {
162 res = work(argv[optind], rmmount_opt);
163 if (res == EJECT_MAN_EJ) {
164 man_eject_seen = B_TRUE;
165 } else if (res != EJECT_OK) {
166 err_seen = B_TRUE;
169 if (err_seen) {
170 if (!is_direct) {
171 excode = res;
172 } else {
173 excode = EJECT_IOCTL_ERR;
175 } else if (man_eject_seen) {
176 excode = EJECT_MAN_EJ;
177 } else {
178 excode = EJECT_OK;
182 return (excode);
186 * the the real work of ejecting (and notifying)
188 static int
189 work(char *arg, char *rmmount_opt)
191 char *name;
192 int excode = EJECT_OK;
193 struct stat64 sb;
194 char *arg1, *arg2;
195 pid_t pid;
196 int status = 1;
198 if (!is_direct) {
199 /* exec rmmount */
200 if (do_closetray) {
201 (void) putenv("EJECT_CLOSETRAY=1");
203 if (do_query) {
204 (void) putenv("EJECT_QUERY=1");
206 pid = fork();
207 if (pid < 0) {
208 exit(1);
209 } else if (pid == 0) {
210 /* child */
211 if (rmmount_opt != NULL) {
212 arg1 = rmmount_opt;
213 arg2 = arg;
214 } else {
215 arg1 = arg;
216 arg2 = NULL;
219 if (execl("/usr/bin/rmmount", "eject",
220 arg1, arg2, 0) < 0) {
221 excode = 99;
222 } else {
223 exit(0);
225 } else {
226 /* parent */
227 if (waitpid(pid, &status, 0) != pid) {
228 excode = 1;
229 } else if (WIFEXITED(status) &&
230 (WEXITSTATUS(status) != 0)) {
231 excode = WEXITSTATUS(status);
232 } else {
233 excode = 0;
239 * rmmount returns 99 if HAL not running -
240 * fallback to direct in that case
242 if (is_direct || (excode == 99)) {
243 excode = EJECT_OK;
245 if (arg == NULL) {
246 arg = "floppy";
248 if ((name = _media_oldaliases(arg)) == NULL) {
249 name = arg;
251 if (do_default) {
252 (void) printf("%s\n", name);
253 goto out;
255 if (do_list) {
256 (void) printf("%s\t%s\n", name, arg);
257 goto out;
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);
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);
276 if (!query(name, B_TRUE)) {
277 excode = EJECT_NO_MEDIA;
279 } else {
280 excode = ejectit(name);
283 out:
284 return (excode);
288 static void
289 usage(void)
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"));
307 static int
308 ejectit(char *name)
310 int fd, r;
311 boolean_t mejectable = B_FALSE; /* manually ejectable */
312 int result = EJECT_OK;
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 it used the -f flag.
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);
337 * Require O_NDELAY for when floppy is not formatted
338 * will still id floppy in drive
342 * make sure we are dealing with a raw device
344 * XXX: NOTE: results from getfullrawname()
345 * really should be free()d when no longer
346 * in use
348 name = getfullrawname(name);
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);
357 perror(name);
358 return (EJECT_PARM_ERR);
361 if (do_closetray) {
362 if (ioctl(fd, CDROMCLOSETRAY) < 0) {
363 result = EJECT_IOCTL_ERR;
365 } else if (ioctl(fd, DKIOCEJECT, 0) < 0) {
366 /* check on why eject failed */
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;
375 if (errno == ENOSYS || errno == ENOTSUP) {
376 (void) fprintf(stderr, gettext(OK_TO_EJECT_MSG), name);
379 if ((errno == ENOSYS || errno == ENOTSUP) && mejectable) {
381 * keep track of the fact that this is a manual
382 * ejection
384 result = EJECT_MAN_EJ;
386 } else if (errno == EBUSY) {
388 * if our pathname is s slice (UFS is great) then
389 * check to see what really is busy
391 if (!display_busy(name, B_FALSE)) {
392 perror(name);
394 result = EJECT_IOCTL_ERR;
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;
408 (void) close(fd);
409 return (result);
414 * return B_TRUE if a floppy is in the drive, B_FALSE otherwise
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.
420 static boolean_t
421 floppy_in_drive(char *name, int fd, boolean_t *is_floppy)
423 int ival = 0;
424 boolean_t rval = B_FALSE;
427 if (ioctl(fd, FDGETCHANGE, &ival) >= 0) {
428 if (!(ival & FDGC_CURRENT)) {
429 rval = B_TRUE;
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);
438 return (rval);
443 * display a "busy" message for the supplied pathname
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
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
454 * return B_TRUE iff we printed the appropriate error message, else
455 * return B_FALSE (and caller will print error message itself)
457 static boolean_t
458 display_busy(char *path, boolean_t vm_running)
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 */
472 #ifdef DEBUG
473 (void) fprintf(stderr, "display_busy(\"%s\"): entering\n", path);
474 #endif
477 * get the block pathname.
478 * eject_getfullblkname returns NULL or pathname which
479 * has length < MAXPATHLEN.
481 blk = eject_getfullblkname(path, vm_running);
482 if (blk == NULL)
483 goto dun;
485 /* open mnttab for scanning */
486 if ((fp = fopen(MNTTAB, "r")) == NULL) {
487 /* can't open mnttab!? -- give up */
488 goto dun;
491 (void) memset(&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;
498 /* perhaps we have a sub-slice (which is what we exist to test for) */
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;
506 *cp = '\0';
507 bblen = strlen(busy_base);
508 /* bblen = (uint)(cp - busy_base); */
510 /* scan for matches */
511 rewind(fp); /* rescan mnttab */
512 while (getmntent(fp, &mp) == 0) {
514 * work around problem where '-' in /etc/mnttab for
515 * special device turns to NULL which isn't expected
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));
526 dun:
527 if (fp != NULL) {
528 (void) fclose(fp);
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);
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".
547 * return B_TRUE if media found, else B_FALSE (XXX: was 0 and -1)
549 static boolean_t
550 query(char *name, boolean_t doprint)
552 int fd;
553 int rval; /* FDGETCHANGE return value */
554 enum dkio_state state;
556 if ((fd = open(name, O_RDONLY|O_NONBLOCK)) < 0) {
557 if ((errno == EPERM) || (errno == ENOENT)) {
558 if (doprint) {
559 perror(name);
561 } else {
562 if (doprint) {
563 (void) fprintf(stderr, gettext(NOT_AVAIL_MSG),
564 name);
567 return (B_FALSE);
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);
579 return (B_TRUE);
581 if (rval & FDGC_CURRENT) {
582 if (doprint) {
583 (void) fprintf(stderr, gettext(NOT_AVAIL_MSG),
584 name);
586 return (B_FALSE);
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);
599 (void) close(fd);
600 return (B_TRUE);
602 if (state == DKIO_EJECTED) {
603 if (doprint) {
604 (void) fprintf(stderr, gettext(NOT_AVAIL_MSG),
605 name);
607 (void) close(fd);
608 return (B_FALSE);
611 * Silly retry loop.
613 (void) sleep(1);
614 goto again;
616 (void) close(fd);
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.
624 if ((fd = open(name, O_RDONLY)) < 0) {
625 if (doprint) {
626 (void) fprintf(stderr, gettext(NOT_AVAIL_MSG), name);
628 return (B_FALSE);
631 (void) close(fd);
632 if (doprint) {
633 (void) fprintf(stderr, gettext(AVAIL_MSG), name);
635 return (B_TRUE); /* success */
640 * this routine will return the volmgt block name given the volmgt
641 * raw (char spcl) name
643 * if anything but a volmgt raw pathname is supplied that pathname will
644 * be returned
646 * NOTE: non-null return value will point to static data, overwritten with
647 * each call
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/"
653 static char *
654 eject_getfullblkname(char *path, boolean_t vm_running)
656 char raw_root[MAXPATHLEN];
657 const char *vm_root;
658 static char res_buf[MAXPATHLEN];
659 uint_t raw_root_len;
661 #ifdef DEBUG
662 (void) fprintf(stderr, "eject_getfullblkname(\"%s\", %s): entering\n",
663 path, vm_running ? "B_TRUE" : "B_FALSE");
664 #endif
666 * try different strategies based on whether or not vold is running
668 if (vm_running) {
670 /* vold IS running -- look in /vol (or its alternate) */
672 /* get vm root dir */
673 vm_root = volmgt_root();
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);
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);
685 goto dun; /* found match in /vol */
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);
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);
699 goto dun; /* found match in /vol/dev */
702 } else {
704 /* vold is NOT running -- look in /dev */
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);
713 goto dun; /* found match in /dev */
717 /* no match -- return what we got */
718 (void) strcpy(res_buf, path);
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);