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]
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.
36 #include <sys/types.h>
41 #include <sys/param.h>
52 #include <sys/mnttab.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
89 * 4 = eject partially succeeded, but now manually remove media
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
)
111 const char *opts
= "dqflt";
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"
124 (void) textdomain(TEXT_DOMAIN
);
128 is_direct
= (getenv("EJECT_DIRECT") != NULL
);
130 /* process arguments */
131 while ((c
= getopt(argc
, argv
, opts
)) != EOF
) {
145 force_eject
= B_TRUE
;
148 do_closetray
= B_TRUE
;
152 exit(EJECT_PARM_ERR
);
156 if (argc
== optind
) {
157 /* no argument -- use the default */
158 excode
= work(NULL
, rmmount_opt
);
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
) {
173 excode
= EJECT_IOCTL_ERR
;
175 } else if (man_eject_seen
) {
176 excode
= EJECT_MAN_EJ
;
186 * the the real work of ejecting (and notifying)
189 work(char *arg
, char *rmmount_opt
)
192 int excode
= EJECT_OK
;
201 (void) putenv("EJECT_CLOSETRAY=1");
204 (void) putenv("EJECT_QUERY=1");
209 } else if (pid
== 0) {
211 if (rmmount_opt
!= NULL
) {
219 if (execl("/usr/bin/rmmount", "eject",
220 arg1
, arg2
, 0) < 0) {
227 if (waitpid(pid
, &status
, 0) != pid
) {
229 } else if (WIFEXITED(status
) &&
230 (WEXITSTATUS(status
) != 0)) {
231 excode
= WEXITSTATUS(status
);
239 * rmmount returns 99 if HAL not running -
240 * fallback to direct in that case
242 if (is_direct
|| (excode
== 99)) {
248 if ((name
= _media_oldaliases(arg
)) == NULL
) {
252 (void) printf("%s\n", name
);
256 (void) printf("%s\t%s\n", name
, arg
);
259 if (access(name
, R_OK
) != 0) {
261 (void) fprintf(stderr
,
262 gettext("%s: no media\n"), name
);
263 return (EJECT_NO_MEDIA
);
266 return (EJECT_PARM_ERR
);
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
;
280 excode
= ejectit(name
);
291 (void) fprintf(stderr
,
292 gettext("usage: %s [-fldqt] [name | nickname]\n"),
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"));
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
);
324 (void) fprintf(stderr
,
325 gettext("WARNING: can not unmount %s, the file system is (probably) busy\n"),
327 return (EJECT_PARM_ERR
);
329 (void) fprintf(stderr
,
330 gettext("WARNING: %s has a mounted filesystem, ejecting anyway\n"),
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
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"),
355 return (EJECT_PARM_ERR
);
358 return (EJECT_PARM_ERR
);
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" */
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
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
)) {
394 result
= EJECT_IOCTL_ERR
;
396 } else if ((errno
== EAGAIN
) || (errno
== ENODEV
) ||
398 (void) fprintf(stderr
,
399 gettext("%s not present in a drive\n"),
404 result
= EJECT_IOCTL_ERR
;
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.
421 floppy_in_drive(char *name
, int fd
, boolean_t
*is_floppy
)
424 boolean_t rval
= B_FALSE
;
427 if (ioctl(fd
, FDGETCHANGE
, &ival
) >= 0) {
428 if (!(ival
& FDGC_CURRENT
)) {
433 *is_floppy
= B_FALSE
;
434 (void) fprintf(stderr
, gettext("%s is not a floppy disk\n"),
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
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
454 * return B_TRUE iff we printed the appropriate error message, else
455 * return B_FALSE (and caller will print error message itself)
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 */
473 (void) fprintf(stderr
, "display_busy(\"%s\"): entering\n", path
);
477 * get the block pathname.
478 * eject_getfullblkname returns NULL or pathname which
479 * has length < MAXPATHLEN.
481 blk
= eject_getfullblkname(path
, vm_running
);
485 /* open mnttab for scanning */
486 if ((fp
= fopen(MNTTAB
, "r")) == NULL
) {
487 /* can't open mnttab!? -- give up */
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 */
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 */
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) {
521 (void) fprintf(stderr
, "%s: %s\n", mp
.mnt_special
,
531 (void) fprintf(stderr
, "display_busy: returning %s\n",
532 res
? "B_TRUE" : "B_FALSE");
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)
550 query(char *name
, boolean_t doprint
)
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
)) {
563 (void) fprintf(stderr
, gettext(NOT_AVAIL_MSG
),
571 if (ioctl(fd
, FDGETCHANGE
, &rval
) >= 0) {
572 /* hey, it worked, what a deal, it must be a floppy */
574 if (!(rval
& FDGC_CURRENT
)) {
576 (void) fprintf(stderr
, gettext(AVAIL_MSG
),
581 if (rval
& FDGC_CURRENT
) {
583 (void) fprintf(stderr
, gettext(NOT_AVAIL_MSG
),
592 if (ioctl(fd
, DKIOCSTATE
, &state
) >= 0) {
593 /* great, the fancy ioctl is supported. */
594 if (state
== DKIO_INSERTED
) {
596 (void) fprintf(stderr
, gettext(AVAIL_MSG
),
602 if (state
== DKIO_EJECTED
) {
604 (void) fprintf(stderr
, gettext(NOT_AVAIL_MSG
),
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
624 if ((fd
= open(name
, O_RDONLY
)) < 0) {
626 (void) fprintf(stderr
, gettext(NOT_AVAIL_MSG
), name
);
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
646 * NOTE: non-null return value will point to static data, overwritten with
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
654 eject_getfullblkname(char *path
, boolean_t vm_running
)
656 char raw_root
[MAXPATHLEN
];
658 static char res_buf
[MAXPATHLEN
];
662 (void) fprintf(stderr
, "eject_getfullblkname(\"%s\", %s): entering\n",
663 path
, vm_running
? "B_TRUE" : "B_FALSE");
666 * try different strategies based on whether or not vold is 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
)) {
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
)) {
699 goto dun
; /* found match in /vol/dev */
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
)) {
713 goto dun
; /* found match in /dev */
717 /* no match -- return what we got */
718 (void) strcpy(res_buf
, path
);
722 (void) fprintf(stderr
, "eject_getfullblkname: returning %s\n",
723 res_buf
? res_buf
: "<null ptr>");