xref: /illumos-gate/usr/src/cmd/eject/eject.c (revision 4c28a617e3922d92a58e813a5b955eb526b9c386)
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 #pragma ident	"%Z%%M%	%I%	%E% SMI"
28 
29 /*
30  * Program to eject one or more pieces of media.
31  */
32 
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>
54 
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;
62 
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 *);
71 
72 /*
73  * ON-private libvolmgt routines
74  */
75 int		_dev_mounted(char *path);
76 int		_dev_unmount(char *path);
77 char		*_media_oldaliases(char *name);
78 void		_media_printaliases(void);
79 
80 
81 /*
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
90  */
91 
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
97 
98 #define	AVAIL_MSG		"%s is available\n"
99 #define	NOT_AVAIL_MSG		"%s is not available\n"
100 
101 #define	OK_TO_EJECT_MSG		"%s can now be manually ejected\n"
102 
103 #define	FLOPPY_MEDIA_TYPE	"floppy"
104 #define	CDROM_MEDIA_TYPE	"cdrom"
105 
106 
107 int
108 main(int argc, char **argv)
109 {
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;
117 
118 	(void) setlocale(LC_ALL, "");
119 
120 #if !defined(TEXT_DOMAIN)
121 #define	TEXT_DOMAIN	"SYS_TEST"
122 #endif
123 
124 	(void) textdomain(TEXT_DOMAIN);
125 
126 	prog_name = argv[0];
127 
128 	is_direct = (getenv("EJECT_DIRECT") != NULL);
129 
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);
153 		}
154 	}
155 
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;
167 			}
168 		}
169 		if (err_seen) {
170 			if (!is_direct) {
171 				excode = res;
172 			} else {
173 				excode = EJECT_IOCTL_ERR;
174 			}
175 		} else if (man_eject_seen) {
176 			excode = EJECT_MAN_EJ;
177 		} else {
178 			excode = EJECT_OK;
179 		}
180 	}
181 
182 	return (excode);
183 }
184 
185 /*
186  * the the real work of ejecting (and notifying)
187  */
188 static int
189 work(char *arg, char *rmmount_opt)
190 {
191 	char 		*name;
192 	int		excode = EJECT_OK;
193 	struct stat64	sb;
194 	char		*arg1, *arg2;
195 	pid_t		pid;
196 	int		status = 1;
197 
198 	if (!is_direct) {
199 		/* exec rmmount */
200 		if (do_closetray) {
201 			(void) putenv("EJECT_CLOSETRAY=1");
202 		}
203 		if (do_query) {
204 			(void) putenv("EJECT_QUERY=1");
205 		}
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;
217 			}
218 
219 			if (execl("/usr/bin/rmmount", "eject",
220 			    arg1, arg2, 0) < 0) {
221 				excode = 99;
222 			} else {
223 				exit(0);
224 			}
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;
234 			}
235 		}
236 	}
237 
238 	/*
239 	 * rmmount returns 99 if HAL not running -
240 	 * fallback to direct in that case
241 	 */
242 	if (is_direct || (excode == 99)) {
243 		excode = EJECT_OK;
244 
245 		if (arg == NULL) {
246 			arg = "floppy";
247 		}
248 		if ((name = _media_oldaliases(arg)) == NULL) {
249 			name = arg;
250 		}
251 		if (do_default) {
252 			(void) printf("%s\n", name);
253 			goto out;
254 		}
255 		if (do_list) {
256 			(void) printf("%s\t%s\n", name, arg);
257 			goto out;
258 		}
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);
267 			}
268 		}
269 
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);
275 			}
276 			if (!query(name, B_TRUE)) {
277 				excode = EJECT_NO_MEDIA;
278 			}
279 		} else {
280 			excode = ejectit(name);
281 		}
282 	}
283 out:
284 	return (excode);
285 }
286 
287 
288 static void
289 usage(void)
290 {
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"));
304 }
305 
306 
307 static int
308 ejectit(char *name)
309 {
310 	int 		fd, r;
311 	boolean_t	mejectable = B_FALSE;	/* manually ejectable */
312 	int		result = EJECT_OK;
313 
314 	/*
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.
318 	 */
319 
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);
332 			}
333 		}
334 	}
335 
336 	/*
337 	 * Require O_NDELAY for when floppy is not formatted
338 	 * will still id floppy in drive
339 	 */
340 
341 	/*
342 	 * make sure we are dealing with a raw device
343 	 *
344 	 * XXX: NOTE: results from getfullrawname()
345 	 * really should be free()d when no longer
346 	 * in use
347 	 */
348 	name = getfullrawname(name);
349 
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);
356 		}
357 		perror(name);
358 		return (EJECT_PARM_ERR);
359 	}
360 
361 	if (do_closetray) {
362 		if (ioctl(fd, CDROMCLOSETRAY) < 0) {
363 			result = EJECT_IOCTL_ERR;
364 		}
365 	} else if (ioctl(fd, DKIOCEJECT, 0) < 0) {
366 		/* check on why eject failed */
367 
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;
373 		}
374 
375 		if (errno == ENOSYS || errno == ENOTSUP) {
376 			(void) fprintf(stderr, gettext(OK_TO_EJECT_MSG), name);
377 		}
378 
379 		if ((errno == ENOSYS || errno == ENOTSUP) && mejectable) {
380 			/*
381 			 * keep track of the fact that this is a manual
382 			 * ejection
383 			 */
384 			result = EJECT_MAN_EJ;
385 
386 		} else if (errno == EBUSY) {
387 			/*
388 			 * if our pathname is s slice (UFS is great) then
389 			 * check to see what really is busy
390 			 */
391 			if (!display_busy(name, B_FALSE)) {
392 				perror(name);
393 			}
394 			result = EJECT_IOCTL_ERR;
395 
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;
405 		}
406 	}
407 
408 	(void) close(fd);
409 	return (result);
410 }
411 
412 
413 /*
414  * return B_TRUE if a floppy is in the drive, B_FALSE otherwise
415  *
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.
419  */
420 static boolean_t
421 floppy_in_drive(char *name, int fd, boolean_t *is_floppy)
422 {
423 	int	ival = 0;
424 	boolean_t rval = B_FALSE;
425 
426 
427 	if (ioctl(fd, FDGETCHANGE, &ival) >= 0) {
428 		if (!(ival & FDGC_CURRENT)) {
429 			rval = B_TRUE;
430 		}
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);
436 	}
437 
438 	return (rval);
439 }
440 
441 
442 /*
443  * display a "busy" message for the supplied pathname
444  *
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
448  *
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
453  *
454  * return B_TRUE iff we printed the appropriate error message, else
455  * return B_FALSE (and caller will print error message itself)
456  */
457 static boolean_t
458 display_busy(char *path, boolean_t vm_running)
459 {
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 */
469 
470 
471 
472 #ifdef	DEBUG
473 	(void) fprintf(stderr, "display_busy(\"%s\"): entering\n", path);
474 #endif
475 
476 	/*
477 	 * get the block pathname.
478 	 * eject_getfullblkname returns NULL or pathname which
479 	 * has length < MAXPATHLEN.
480 	 */
481 	blk = eject_getfullblkname(path, vm_running);
482 	if (blk == NULL)
483 		goto dun;
484 
485 	/* open mnttab for scanning */
486 	if ((fp = fopen(MNTTAB, "r")) == NULL) {
487 		/* can't open mnttab!? -- give up */
488 		goto dun;
489 	}
490 
491 	(void) memset((void *)&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;
496 	}
497 
498 	/* perhaps we have a sub-slice (which is what we exist to test for) */
499 
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;
505 	}
506 	*cp = '\0';
507 	bblen = strlen(busy_base);
508 	/* bblen = (uint)(cp - busy_base); */
509 
510 	/* scan for matches */
511 	rewind(fp);				/* rescan mnttab */
512 	while (getmntent(fp, &mp) == 0) {
513 		/*
514 		 * work around problem where '-' in /etc/mnttab for
515 		 * special device turns to NULL which isn't expected
516 		 */
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));
523 		}
524 	}
525 
526 dun:
527 	if (fp != NULL) {
528 		(void) fclose(fp);
529 	}
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);
536 }
537 
538 
539 /*
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".
546  *
547  * return B_TRUE if media found, else B_FALSE (XXX: was 0 and -1)
548  */
549 static boolean_t
550 query(char *name, boolean_t doprint)
551 {
552 	int		fd;
553 	int		rval;			/* FDGETCHANGE return value */
554 	enum dkio_state	state;
555 
556 	if ((fd = open(name, O_RDONLY|O_NONBLOCK)) < 0) {
557 		if ((errno == EPERM) || (errno == ENOENT)) {
558 			if (doprint) {
559 				perror(name);
560 			}
561 		} else {
562 			if (doprint) {
563 				(void) fprintf(stderr, gettext(NOT_AVAIL_MSG),
564 				    name);
565 			}
566 		}
567 		return (B_FALSE);
568 	}
569 
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);
578 			}
579 			return (B_TRUE);
580 		}
581 		if (rval & FDGC_CURRENT) {
582 			if (doprint) {
583 				(void) fprintf(stderr,	gettext(NOT_AVAIL_MSG),
584 				    name);
585 			}
586 			return (B_FALSE);
587 		}
588 	}
589 
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);
598 			}
599 			(void) close(fd);
600 			return (B_TRUE);
601 		}
602 		if (state == DKIO_EJECTED) {
603 			if (doprint) {
604 				(void) fprintf(stderr,	gettext(NOT_AVAIL_MSG),
605 				    name);
606 			}
607 			(void) close(fd);
608 			return (B_FALSE);
609 		}
610 		/*
611 		 * Silly retry loop.
612 		 */
613 		(void) sleep(1);
614 		goto again;
615 	}
616 	(void) close(fd);
617 
618 	/*
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.
623 	 */
624 	if ((fd = open(name, O_RDONLY)) < 0) {
625 		if (doprint) {
626 			(void) fprintf(stderr, gettext(NOT_AVAIL_MSG), name);
627 		}
628 		return (B_FALSE);
629 	}
630 
631 	(void) close(fd);
632 	if (doprint) {
633 		(void) fprintf(stderr, gettext(AVAIL_MSG), name);
634 	}
635 	return (B_TRUE);	/* success */
636 }
637 
638 
639 /*
640  * this routine will return the volmgt block name given the volmgt
641  *  raw (char spcl) name
642  *
643  * if anything but a volmgt raw pathname is supplied that pathname will
644  *  be returned
645  *
646  * NOTE: non-null return value will point to static data, overwritten with
647  *  each call
648  *
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/"
652  */
653 static char *
654 eject_getfullblkname(char *path, boolean_t vm_running)
655 {
656 	char		raw_root[MAXPATHLEN];
657 	const char	*vm_root;
658 	static char	res_buf[MAXPATHLEN];
659 	uint_t		raw_root_len;
660 
661 #ifdef	DEBUG
662 	(void) fprintf(stderr, "eject_getfullblkname(\"%s\", %s): entering\n",
663 	    path, vm_running ? "B_TRUE" : "B_FALSE");
664 #endif
665 	/*
666 	 * try different strategies based on whether or not vold is running
667 	 */
668 	if (vm_running) {
669 
670 		/* vold IS running -- look in /vol (or its alternate) */
671 
672 		/* get vm root dir */
673 		vm_root = volmgt_root();
674 
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);
678 
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);
684 			}
685 			goto dun;		/* found match in /vol */
686 		}
687 
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);
692 
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);
698 			}
699 			goto dun;		/* found match in /vol/dev */
700 		}
701 
702 	} else {
703 
704 		/* vold is NOT running -- look in /dev */
705 
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);
712 			}
713 			goto dun;		/* found match in /dev */
714 		}
715 	}
716 
717 	/* no match -- return what we got */
718 	(void) strcpy(res_buf, path);
719 
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);
726 }
727