xref: /netbsd/usr.bin/eject/eject.c (revision bf9ec67e)
1 /*	$NetBSD: eject.c,v 1.16 2001/10/06 15:43:33 bjh21 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.16 2001/10/06 15:43:33 bjh21 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 TYPEMASK ((int)~0x01)
78 }           nicknames[] = {
79 	{ "diskette", "fd", DISK | NOTLOADABLE },
80 	{ "floppy", "fd", DISK | NOTLOADABLE },
81 	{ "fd", "fd", DISK | NOTLOADABLE },
82 	{ "sd", "sd", DISK | NOTLOADABLE },
83 	{ "cdrom", "cd", DISK | LOADABLE },
84 	{ "cd", "cd", DISK | LOADABLE },
85 	{ "cdr", "cd", DISK | LOADABLE },
86 	{ "cdrw", "cd", DISK | LOADABLE },
87 	{ "dvdrom", "cd", DISK | LOADABLE },
88 	{ "dvd", "cd", DISK | LOADABLE },
89 	{ "dvdr", "cd", DISK | LOADABLE },
90 	{ "dvdrw", "cd", DISK | LOADABLE },
91 	{ "mcd", "mcd", DISK | LOADABLE },	/* XXX Is this true? */
92 	{ "tape", "st", TAPE | NOTLOADABLE },
93 	{ "st", "st", TAPE | NOTLOADABLE },
94 	{ "dat", "st", TAPE | NOTLOADABLE },
95 	{ "exabyte", "st", TAPE | NOTLOADABLE },
96 };
97 #define MAXNICKLEN 12		/* at least enough room for the longest
98 				 * nickname */
99 #define MAXDEVLEN (MAXNICKLEN + 7)	/* "/dev/r" ... "a" */
100 
101 struct devtypes_s {
102 	char *name;
103 	int type;
104 }          devtypes[] = {
105 	{ "diskette", DISK | NOTLOADABLE },
106 	{ "floppy", DISK | NOTLOADABLE },
107 	{ "cdrom", DISK | LOADABLE },
108 	{ "disk", DISK | NOTLOADABLE },
109 	{ "tape", TAPE | NOTLOADABLE },
110 };
111 
112 enum eject_op {
113 	OP_EJECT, OP_LOAD, OP_LOCK, OP_UNLOCK
114 };
115 
116 int verbose_f = 0;
117 int umount_f = 1;
118 
119 int main(int, char *[]);
120 void usage(void);
121 char *nick2dev(char *);
122 char *nick2rdev(char *);
123 int guess_devtype(char *);
124 char *guess_nickname(char *);
125 void eject_tape(char *, enum eject_op);
126 void eject_disk(char *, enum eject_op);
127 void unmount_dev(char *);
128 
129 int
130 main(int argc, char *argv[])
131 {
132 	int ch;
133 	int devtype = -1;
134 	int n, i;
135 	char *devname = NULL;
136 	enum eject_op op = OP_EJECT;
137 
138 	while ((ch = getopt(argc, argv, "d:flLnt:Uv")) != -1) {
139 		switch (ch) {
140 		case 'd':
141 			devname = optarg;
142 			break;
143 		case 'f':
144 			umount_f = 0;
145 			break;
146 		case 'l':
147 			if (op != OP_EJECT)
148 				usage();
149 			op = OP_LOAD;
150 			break;
151 		case 'L':
152 			if (op != OP_EJECT)
153 				usage();
154 			op = OP_LOCK;
155 			break;
156 		case 'n':
157 			for (n = 0; n < sizeof(nicknames) / sizeof(nicknames[0]);
158 			    n++) {
159 				struct nicknames_s *np = &nicknames[n];
160 
161 				printf("%s -> %s\n", np->name, nick2dev(np->name));
162 			}
163 			return (0);
164 		case 't':
165 			for (i = 0; i < sizeof(devtypes) / sizeof(devtypes[0]);
166 			    i++) {
167 				if (strcasecmp(devtypes[i].name, optarg) == 0) {
168 					devtype = devtypes[i].type;
169 					break;
170 				}
171 			}
172 			if (devtype == -1)
173 				errx(1, "%s: unknown device type", optarg);
174 			break;
175 		case 'U':
176 			if (op != OP_EJECT)
177 				usage();
178 			op = OP_UNLOCK;
179 			break;
180 		case 'v':
181 			verbose_f = 1;
182 			break;
183 		default:
184 			usage();
185 			/* NOTREACHED */
186 		}
187 	}
188 	argc -= optind;
189 	argv += optind;
190 
191 	if (devname == NULL) {
192 		if (argc == 0) {
193 			usage();
194 			/* NOTREACHED */
195 		} else
196 			devname = argv[0];
197 	}
198 	if (devtype == -1)
199 		devtype = guess_devtype(devname);
200 	if (devtype == -1)
201 		errx(1, "%s: unable to determine type of device",
202 		    devname);
203 	if (verbose_f) {
204 		printf("device type == ");
205 		if ((devtype & TYPEMASK) == TAPE)
206 			printf("tape\n");
207 		else
208 			printf("disk, floppy, or cdrom\n");
209 	}
210 	if (umount_f)
211 		unmount_dev(devname);
212 
213 	/* XXX Tapes and disks have different ioctl's: */
214 	if ((devtype & TYPEMASK) == TAPE)
215 		eject_tape(devname, op);
216 	else
217 		eject_disk(devname, op);
218 
219 	if (verbose_f)
220 		printf("done.\n");
221 
222 	return (0);
223 }
224 
225 void
226 usage(void)
227 {
228 
229 	fprintf(stderr, "Usage: eject [-fv] [-l | -L | -U] "
230 	    "[-t device-type] [-d] device\n");
231 	fprintf(stderr, "       eject -n\n");
232 	exit(1);
233 }
234 
235 int
236 guess_devtype(char *devname)
237 {
238 	int n;
239 
240 	/* Nickname match: */
241 	for (n = 0; n < sizeof(nicknames) / sizeof(nicknames[0]);
242 	    n++) {
243 		if (strncasecmp(nicknames[n].name, devname,
244 			strlen(nicknames[n].name)) == 0)
245 			return (nicknames[n].type);
246 	}
247 
248 	/*
249          * If we still don't know it, then try to compare vs. dev
250          * and rdev names that we know.
251          */
252 	/* dev first: */
253 	for (n = 0; n < sizeof(nicknames) / sizeof(nicknames[0]); n++) {
254 		char *name;
255 		name = nick2dev(nicknames[n].name);
256 		/*
257 		 * Assume that the part of the name that distinguishes the
258 		 * instance of this device begins with a 0.
259 		 */
260 		*(strchr(name, '0')) = '\0';
261 		if (strncmp(name, devname, strlen(name)) == 0)
262 			return (nicknames[n].type);
263 	}
264 
265 	/* Now rdev: */
266 	for (n = 0; n < sizeof(nicknames) / sizeof(nicknames[0]); n++) {
267 		char *name = nick2rdev(nicknames[n].name);
268 		*(strchr(name, '0')) = '\0';
269 		if (strncmp(name, devname, strlen(name)) == 0)
270 			return (nicknames[n].type);
271 	}
272 
273 	/* Not found. */
274 	return (-1);
275 }
276 /* "floppy5" -> "/dev/fd5a".  Yep, this uses a static buffer. */
277 char *
278 nick2dev(char *nn)
279 {
280 	int n;
281 	static char devname[MAXDEVLEN];
282 	int devnum = 0;
283 
284 	for (n = 0; n < sizeof(nicknames) / sizeof(nicknames[0]); n++) {
285 		if (strncasecmp(nicknames[n].name, nn,
286 			strlen(nicknames[n].name)) == 0) {
287 			sscanf(nn, "%*[^0-9]%d", &devnum);
288 			sprintf(devname, "/dev/%s%d", nicknames[n].devname,
289 			    devnum);
290 			if ((nicknames[n].type & TYPEMASK) != TAPE)
291 				strcat(devname, "a");
292 			return (devname);
293 		}
294 	}
295 
296 	return (NULL);
297 }
298 /* "floppy5" -> "/dev/rfd5c".  Static buffer. */
299 char *
300 nick2rdev(char *nn)
301 {
302 	int n;
303 	static char devname[MAXDEVLEN];
304 	int devnum = 0;
305 
306 	for (n = 0; n < sizeof(nicknames) / sizeof(nicknames[0]); n++) {
307 		if (strncasecmp(nicknames[n].name, nn,
308 			strlen(nicknames[n].name)) == 0) {
309 			sscanf(nn, "%*[^0-9]%d", &devnum);
310 			sprintf(devname, "/dev/r%s%d", nicknames[n].devname,
311 			    devnum);
312 			if ((nicknames[n].type & TYPEMASK) != TAPE) {
313 				strcat(devname, "a");
314 				devname[strlen(devname) - 1] += getrawpartition();
315 			}
316 			return (devname);
317 		}
318 	}
319 
320 	return (NULL);
321 }
322 /* Unmount all filesystems attached to dev. */
323 void
324 unmount_dev(char *name)
325 {
326 	struct statfs *mounts;
327 	int i, nmnts, len;
328 	char *dn;
329 
330 	nmnts = getmntinfo(&mounts, MNT_NOWAIT);
331 	if (nmnts == 0) {
332 		err(1, "getmntinfo");
333 	}
334 	/* Make sure we have a device name: */
335 	dn = nick2dev(name);
336 	if (dn == NULL)
337 		dn = name;
338 
339 	/* Set len to strip off the partition name: */
340 	len = strlen(dn);
341 	if (!isdigit(dn[len - 1]))
342 		len--;
343 	if (!isdigit(dn[len - 1])) {
344 		errx(1, "Can't figure out base name for dev name %s", dn);
345 	}
346 	for (i = 0; i < nmnts; i++) {
347 		if (strncmp(mounts[i].f_mntfromname, dn, len) == 0) {
348 			if (verbose_f)
349 				printf("Unmounting %s from %s...\n",
350 				    mounts[i].f_mntfromname,
351 				    mounts[i].f_mntonname);
352 
353 			if (unmount(mounts[i].f_mntonname, 0) == -1) {
354 				err(1, "unmount: %s", mounts[i].f_mntonname);
355 			}
356 		}
357 	}
358 
359 	return;
360 }
361 
362 void
363 eject_tape(char *name, enum eject_op op)
364 {
365 	struct mtop m;
366 	int fd;
367 	char *dn;
368 
369 	dn = nick2rdev(name);
370 	if (dn == NULL)
371 		dn = name;	/* Hope for the best. */
372 	fd = open(dn, O_RDONLY);
373 	if (fd == -1)
374 		err(1, "open: %s", dn);
375 	switch (op) {
376 	case OP_EJECT:
377 		if (verbose_f)
378 			printf("Ejecting %s...\n", dn);
379 
380 		m.mt_op = MTOFFL;
381 		m.mt_count = 0;
382 		if (ioctl(fd, MTIOCTOP, &m) == -1)
383 			err(1, "ioctl: MTIOCTOP: %s", dn);
384 		break;
385 	case OP_LOAD:
386 		errx(1, "cannot load tapes");
387 		/* NOTREACHED */
388 	case OP_LOCK:
389 		errx(1, "cannot lock tapes");
390 		/* NOTREACHED */
391 	case OP_UNLOCK:
392 		errx(1, "cannot unlock tapes");
393 		/* NOTREACHED */
394 	}
395 	close(fd);
396 	return;
397 }
398 
399 void
400 eject_disk(char *name, enum eject_op op)
401 {
402 	int fd;
403 	char *dn;
404 	int arg;
405 
406 	dn = nick2rdev(name);
407 	if (dn == NULL)
408 		dn = name;	/* Hope for the best. */
409 	fd = open(dn, O_RDONLY);
410 	if (fd == -1)
411 		err(1, "open: %s", dn);
412 	switch (op) {
413 	case OP_LOAD:
414 		if (verbose_f)
415 			printf("Closing %s...\n", dn);
416 
417 		if (ioctl(fd, CDIOCCLOSE, NULL) == -1)
418 			err(1, "ioctl: CDIOCCLOSE: %s", dn);
419 		break;
420 	case OP_EJECT:
421 		if (verbose_f)
422 			printf("Ejecting %s...\n", dn);
423 
424 		arg = 0;
425 		if (umount_f == 0) {
426 			/* force eject, unlock the device first */
427 			if (ioctl(fd, DIOCLOCK, &arg) == -1)
428 				err(1, "ioctl: DIOCLOCK: %s", dn);
429 			arg = 1;
430 		}
431 		if (ioctl(fd, DIOCEJECT, &arg) == -1)
432 			err(1, "ioctl: DIOCEJECT: %s", dn);
433 		break;
434 	case OP_LOCK:
435 		if (verbose_f)
436 			printf("Locking %s...\n", dn);
437 
438 		arg = 1;
439 		if (ioctl(fd, DIOCLOCK, &arg) == -1)
440 			err(1, "ioctl: DIOCLOCK: %s", dn);
441 		break;
442 	case OP_UNLOCK:
443 		if (verbose_f)
444 			printf("Unlocking %s...\n", dn);
445 
446 		arg = 0;
447 		if (ioctl(fd, DIOCLOCK, &arg) == -1)
448 			err(1, "ioctl: DIOCLOCK: %s", dn);
449 		break;
450 	}
451 
452 	close(fd);
453 	return;
454 }
455