xref: /netbsd-src/usr.bin/eject/eject.c (revision 8b0f9554ff8762542c4defc4f70e1eb76fb508fa)
1 /*	$NetBSD: eject.c,v 1.20 2006/09/24 08:42:55 xtraeme Exp $	*/
2 
3 /*-
4  * Copyright (c) 1999 The NetBSD Foundation, Inc.
5  * All rights reserved.
6  *
7  * This code is derived from software contributed to The NetBSD Foundation
8  * by Chris Jones.
9  *
10  * Redistribution and use in source and binary forms, with or without
11  * modification, are permitted provided that the following conditions
12  * are met:
13  * 1. Redistributions of source code must retain the above copyright
14  *    notice, this list of conditions and the following disclaimer.
15  * 2. Redistributions in binary form must reproduce the above copyright
16  *    notice, this list of conditions and the following disclaimer in the
17  *    documentation and/or other materials provided with the distribution.
18  * 3. All advertising materials mentioning features or use of this software
19  *    must display the following acknowledgement:
20  *	This product includes software developed by the NetBSD
21  *	Foundation, Inc. and its contributors.
22  * 4. Neither the name of The NetBSD Foundation nor the names of its
23  *    contributors may be used to endorse or promote products derived
24  *    from this software without specific prior written permission.
25  *
26  * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
27  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
28  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
29  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
30  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
31  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
32  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
33  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
34  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
35  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
36  * POSSIBILITY OF SUCH DAMAGE.
37  */
38 
39 #include <sys/cdefs.h>
40 #ifndef lint
41 __COPYRIGHT("@(#) Copyright (c) 1999 The NetBSD Foundation, Inc.\n\
42 	All rights reserved.\n");
43 #endif				/* not lint */
44 
45 #ifndef lint
46 __RCSID("$NetBSD: eject.c,v 1.20 2006/09/24 08:42:55 xtraeme Exp $");
47 #endif				/* not lint */
48 
49 #include <sys/types.h>
50 #include <sys/cdio.h>
51 #include <sys/disklabel.h>
52 #include <sys/ioctl.h>
53 #include <sys/param.h>
54 #include <sys/ucred.h>
55 #include <sys/mount.h>
56 #include <sys/mtio.h>
57 
58 #include <ctype.h>
59 #include <err.h>
60 #include <fcntl.h>
61 #include <stdio.h>
62 #include <stdlib.h>
63 #include <string.h>
64 #include <unistd.h>
65 #include <util.h>
66 
67 struct nicknames_s {
68 	char *name;		/* The name given on the command line. */
69 	char *devname;		/* The base name of the device */
70 	int type;		/* The type of device, for determining what
71 				 * ioctl to use. */
72 #define TAPE 0x10
73 #define DISK 0x20
74 	/* OR one of the above with one of the below: */
75 #define NOTLOADABLE 0x00
76 #define LOADABLE 0x01
77 #define FLOPPY 0x2
78 #define TYPEMASK ((int)~0x01)
79 }           nicknames[] = {
80 	{ "diskette", "fd", DISK | FLOPPY | NOTLOADABLE },
81 	{ "floppy", "fd", DISK | FLOPPY | NOTLOADABLE },
82 	{ "fd", "fd", DISK | FLOPPY | NOTLOADABLE },
83 	{ "sd", "sd", DISK | NOTLOADABLE },
84 	{ "cdrom", "cd", DISK | LOADABLE },
85 	{ "cd", "cd", DISK | LOADABLE },
86 	{ "cdr", "cd", DISK | LOADABLE },
87 	{ "cdrw", "cd", DISK | LOADABLE },
88 	{ "dvdrom", "cd", DISK | LOADABLE },
89 	{ "dvd", "cd", DISK | LOADABLE },
90 	{ "dvdr", "cd", DISK | LOADABLE },
91 	{ "dvdrw", "cd", DISK | LOADABLE },
92 	{ "mcd", "mcd", DISK | LOADABLE },	/* XXX Is this true? */
93 	{ "tape", "st", TAPE | NOTLOADABLE },
94 	{ "st", "st", TAPE | NOTLOADABLE },
95 	{ "dat", "st", TAPE | NOTLOADABLE },
96 	{ "exabyte", "st", TAPE | NOTLOADABLE },
97 };
98 #define MAXNICKLEN 12		/* at least enough room for the longest
99 				 * nickname */
100 #define MAXDEVLEN (MAXNICKLEN + 7)	/* "/dev/r" ... "a" */
101 
102 struct devtypes_s {
103 	char *name;
104 	int type;
105 }          devtypes[] = {
106 	{ "diskette", DISK | NOTLOADABLE },
107 	{ "floppy", DISK | NOTLOADABLE },
108 	{ "cdrom", DISK | LOADABLE },
109 	{ "disk", DISK | NOTLOADABLE },
110 	{ "tape", TAPE | NOTLOADABLE },
111 };
112 
113 enum eject_op {
114 	OP_EJECT, OP_LOAD, OP_LOCK, OP_UNLOCK
115 };
116 
117 int verbose_f = 0;
118 int umount_f = 1;
119 
120 int main(int, char *[]);
121 void usage(void);
122 char *nick2dev(char *);
123 char *nick2rdev(char *);
124 int guess_devtype(char *);
125 char *guess_nickname(char *);
126 void eject_tape(char *, enum eject_op);
127 void eject_disk(char *, enum eject_op);
128 void unmount_dev(char *);
129 
130 int
131 main(int argc, char *argv[])
132 {
133 	int ch;
134 	int devtype = -1;
135 	int n, i;
136 	char *devname = NULL;
137 	enum eject_op op = OP_EJECT;
138 
139 	while ((ch = getopt(argc, argv, "d:flLnt:Uv")) != -1) {
140 		switch (ch) {
141 		case 'd':
142 			devname = optarg;
143 			break;
144 		case 'f':
145 			umount_f = 0;
146 			break;
147 		case 'l':
148 			if (op != OP_EJECT)
149 				usage();
150 			op = OP_LOAD;
151 			break;
152 		case 'L':
153 			if (op != OP_EJECT)
154 				usage();
155 			op = OP_LOCK;
156 			break;
157 		case 'n':
158 			for (n = 0; n < sizeof(nicknames) / sizeof(nicknames[0]);
159 			    n++) {
160 				struct nicknames_s *np = &nicknames[n];
161 
162 				printf("%s -> %s\n", np->name, nick2dev(np->name));
163 			}
164 			return (0);
165 		case 't':
166 			for (i = 0; i < sizeof(devtypes) / sizeof(devtypes[0]);
167 			    i++) {
168 				if (strcasecmp(devtypes[i].name, optarg) == 0) {
169 					devtype = devtypes[i].type;
170 					break;
171 				}
172 			}
173 			if (devtype == -1)
174 				errx(1, "%s: unknown device type", optarg);
175 			break;
176 		case 'U':
177 			if (op != OP_EJECT)
178 				usage();
179 			op = OP_UNLOCK;
180 			break;
181 		case 'v':
182 			verbose_f = 1;
183 			break;
184 		default:
185 			usage();
186 			/* NOTREACHED */
187 		}
188 	}
189 	argc -= optind;
190 	argv += optind;
191 
192 	if (devname == NULL) {
193 		if (argc == 0) {
194 			usage();
195 			/* NOTREACHED */
196 		} else
197 			devname = argv[0];
198 	}
199 	if (devtype == -1)
200 		devtype = guess_devtype(devname);
201 	if (devtype == -1)
202 		errx(1, "%s: unable to determine type of device",
203 		    devname);
204 	if (verbose_f) {
205 		printf("device type == ");
206 		if ((devtype & TYPEMASK) == TAPE)
207 			printf("tape\n");
208 		else
209 			printf("disk, floppy, or cdrom\n");
210 	}
211 	if (umount_f)
212 		unmount_dev(devname);
213 
214 	/* XXX Tapes and disks have different ioctl's: */
215 	if ((devtype & TYPEMASK) == TAPE)
216 		eject_tape(devname, op);
217 	else
218 		eject_disk(devname, op);
219 
220 	if (verbose_f)
221 		printf("done.\n");
222 
223 	return (0);
224 }
225 
226 void
227 usage(void)
228 {
229 
230 	fprintf(stderr, "usage: eject [-fv] [-l | -L | -U] "
231 	    "[-t device-type] [-d] device\n");
232 	fprintf(stderr, "       eject -n\n");
233 	exit(1);
234 }
235 
236 int
237 guess_devtype(char *devname)
238 {
239 	int n;
240 
241 	/* Nickname match: */
242 	for (n = 0; n < sizeof(nicknames) / sizeof(nicknames[0]);
243 	    n++) {
244 		if (strncasecmp(nicknames[n].name, devname,
245 			strlen(nicknames[n].name)) == 0)
246 			return (nicknames[n].type);
247 	}
248 
249 	/*
250          * If we still don't know it, then try to compare vs. dev
251          * and rdev names that we know.
252          */
253 	/* dev first: */
254 	for (n = 0; n < sizeof(nicknames) / sizeof(nicknames[0]); n++) {
255 		char *name;
256 		name = nick2dev(nicknames[n].name);
257 		/*
258 		 * Assume that the part of the name that distinguishes the
259 		 * instance of this device begins with a 0.
260 		 */
261 		*(strchr(name, '0')) = '\0';
262 		if (strncmp(name, devname, strlen(name)) == 0)
263 			return (nicknames[n].type);
264 	}
265 
266 	/* Now rdev: */
267 	for (n = 0; n < sizeof(nicknames) / sizeof(nicknames[0]); n++) {
268 		char *name = nick2rdev(nicknames[n].name);
269 		*(strchr(name, '0')) = '\0';
270 		if (strncmp(name, devname, strlen(name)) == 0)
271 			return (nicknames[n].type);
272 	}
273 
274 	/* Not found. */
275 	return (-1);
276 }
277 /* "floppy5" -> "/dev/fd5a".  Yep, this uses a static buffer. */
278 char *
279 nick2dev(char *nn)
280 {
281 	int n;
282 	static char devname[MAXDEVLEN];
283 	int devnum = 0;
284 
285 	for (n = 0; n < sizeof(nicknames) / sizeof(nicknames[0]); n++) {
286 		if (strncasecmp(nicknames[n].name, nn,
287 			strlen(nicknames[n].name)) == 0) {
288 			sscanf(nn, "%*[^0-9]%d", &devnum);
289 			sprintf(devname, "/dev/%s%d", nicknames[n].devname,
290 			    devnum);
291 			if ((nicknames[n].type & TYPEMASK) != TAPE)
292 				strcat(devname, "a");
293 			return (devname);
294 		}
295 	}
296 
297 	return (NULL);
298 }
299 /* "floppy5" -> "/dev/rfd5c".  Static buffer. */
300 char *
301 nick2rdev(char *nn)
302 {
303 	int n;
304 	static char devname[MAXDEVLEN];
305 	int devnum = 0;
306 
307 	for (n = 0; n < sizeof(nicknames) / sizeof(nicknames[0]); n++) {
308 		if (strncasecmp(nicknames[n].name, nn,
309 			strlen(nicknames[n].name)) == 0) {
310 			sscanf(nn, "%*[^0-9]%d", &devnum);
311 			sprintf(devname, "/dev/r%s%d", nicknames[n].devname,
312 			    devnum);
313 			if ((nicknames[n].type & TYPEMASK) != TAPE) {
314 				strcat(devname, "a");
315 				if ((nicknames[n].type & FLOPPY) != FLOPPY)
316 					devname[strlen(devname) - 1] += getrawpartition();
317 			}
318 			return (devname);
319 		}
320 	}
321 
322 	return (NULL);
323 }
324 /* Unmount all filesystems attached to dev. */
325 void
326 unmount_dev(char *name)
327 {
328 	struct statvfs *mounts;
329 	int i, nmnts, len;
330 	char *dn;
331 
332 	nmnts = getmntinfo(&mounts, MNT_NOWAIT);
333 	if (nmnts == 0) {
334 		err(1, "getmntinfo");
335 	}
336 	/* Make sure we have a device name: */
337 	dn = nick2dev(name);
338 	if (dn == NULL)
339 		dn = name;
340 
341 	/* Set len to strip off the partition name: */
342 	len = strlen(dn);
343 	if (!isdigit((unsigned char)dn[len - 1]))
344 		len--;
345 	if (!isdigit((unsigned char)dn[len - 1])) {
346 		errx(1, "Can't figure out base name for dev name %s", dn);
347 	}
348 	for (i = 0; i < nmnts; i++) {
349 		if (strncmp(mounts[i].f_mntfromname, dn, len) == 0) {
350 			if (verbose_f)
351 				printf("Unmounting %s from %s...\n",
352 				    mounts[i].f_mntfromname,
353 				    mounts[i].f_mntonname);
354 
355 			if (unmount(mounts[i].f_mntonname, 0) == -1) {
356 				err(1, "unmount: %s", mounts[i].f_mntonname);
357 			}
358 		}
359 	}
360 
361 	return;
362 }
363 
364 void
365 eject_tape(char *name, enum eject_op op)
366 {
367 	struct mtop m;
368 	int fd;
369 	char *dn;
370 
371 	dn = nick2rdev(name);
372 	if (dn == NULL)
373 		dn = name;	/* Hope for the best. */
374 	fd = open(dn, O_RDONLY);
375 	if (fd == -1)
376 		err(1, "open: %s", dn);
377 	switch (op) {
378 	case OP_EJECT:
379 		if (verbose_f)
380 			printf("Ejecting %s...\n", dn);
381 
382 		m.mt_op = MTOFFL;
383 		m.mt_count = 0;
384 		if (ioctl(fd, MTIOCTOP, &m) == -1)
385 			err(1, "ioctl: MTIOCTOP: %s", dn);
386 		break;
387 	case OP_LOAD:
388 		errx(1, "cannot load tapes");
389 		/* NOTREACHED */
390 	case OP_LOCK:
391 		errx(1, "cannot lock tapes");
392 		/* NOTREACHED */
393 	case OP_UNLOCK:
394 		errx(1, "cannot unlock tapes");
395 		/* NOTREACHED */
396 	}
397 	close(fd);
398 	return;
399 }
400 
401 void
402 eject_disk(char *name, enum eject_op op)
403 {
404 	int fd;
405 	char *dn;
406 	int arg;
407 
408 	dn = nick2rdev(name);
409 	if (dn == NULL)
410 		dn = name;	/* Hope for the best. */
411 	fd = open(dn, O_RDONLY);
412 	if (fd == -1)
413 		err(1, "open: %s", dn);
414 	switch (op) {
415 	case OP_LOAD:
416 		if (verbose_f)
417 			printf("Closing %s...\n", dn);
418 
419 		if (ioctl(fd, CDIOCCLOSE, NULL) == -1)
420 			err(1, "ioctl: CDIOCCLOSE: %s", dn);
421 		break;
422 	case OP_EJECT:
423 		if (verbose_f)
424 			printf("Ejecting %s...\n", dn);
425 
426 		arg = 0;
427 		if (umount_f == 0) {
428 			/* force eject, unlock the device first */
429 			if (ioctl(fd, DIOCLOCK, &arg) == -1)
430 				err(1, "ioctl: DIOCLOCK: %s", dn);
431 			arg = 1;
432 		}
433 		if (ioctl(fd, DIOCEJECT, &arg) == -1)
434 			err(1, "ioctl: DIOCEJECT: %s", dn);
435 		break;
436 	case OP_LOCK:
437 		if (verbose_f)
438 			printf("Locking %s...\n", dn);
439 
440 		arg = 1;
441 		if (ioctl(fd, DIOCLOCK, &arg) == -1)
442 			err(1, "ioctl: DIOCLOCK: %s", dn);
443 		break;
444 	case OP_UNLOCK:
445 		if (verbose_f)
446 			printf("Unlocking %s...\n", dn);
447 
448 		arg = 0;
449 		if (ioctl(fd, DIOCLOCK, &arg) == -1)
450 			err(1, "ioctl: DIOCLOCK: %s", dn);
451 		break;
452 	}
453 
454 	close(fd);
455 	return;
456 }
457