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