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