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