xref: /illumos-gate/usr/src/cmd/eject/eject.c (revision 2bbdd445a21f9d61f4a0ca0faf05d5ceb2bd91f3)
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
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
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
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
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
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
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
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 *
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