xref: /netbsd-src/usr.bin/eject/eject.c (revision 27578b9aac214cc7796ead81dcc5427e79d5f2a0)
1 /*	$NetBSD: eject.c,v 1.14 2001/08/20 08:21:10 ad 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.14 2001/08/20 08:21:10 ad 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
71 				   what 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
98 				   longest 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 int verbose_f = 0;
113 int umount_f = 1;
114 int load_f = 0;
115 
116 int main __P((int, char *[]));
117 void usage __P((void));
118 char *nick2dev __P((char *));
119 char *nick2rdev __P((char *));
120 int guess_devtype __P((char *));
121 char *guess_nickname __P((char *));
122 void eject_tape __P((char *));
123 void eject_disk __P((char *));
124 void unmount_dev __P((char *));
125 
126 int
127 main(int argc,
128      char *argv[])
129 {
130     int ch;
131     int devtype = -1;
132     int n, i;
133     char *devname = NULL;
134 
135     while((ch = getopt(argc, argv, "d:flnt:v")) != -1) {
136 	switch(ch) {
137 	case 'd':
138 	    devname = optarg;
139 	    break;
140 	case 'f':
141 	    umount_f = 0;
142 	    break;
143 	case 'l':
144 	    load_f = 1;
145 	    break;
146 	case 'n':
147 	    for(n = 0; n < sizeof(nicknames) / sizeof(nicknames[0]);
148 		n++) {
149 		struct nicknames_s *np = &nicknames[n];
150 
151 		printf("%s -> %s\n", np->name, nick2dev(np->name));
152 	    }
153 	    return(0);
154 	case 't':
155 	    for(i = 0; i < sizeof(devtypes) / sizeof(devtypes[0]);
156 		i++) {
157 		if(strcasecmp(devtypes[i].name, optarg) == 0) {
158 		    devtype = devtypes[i].type;
159 		    break;
160 		}
161 	    }
162 	    if(devtype == -1) {
163 		errx(1, "%s: unknown device type\n", optarg);
164 	    }
165 	    break;
166 	case 'v':
167 	    verbose_f = 1;
168 	    break;
169 	default:
170 	    warnx("-%c: unknown switch", ch);
171 	    usage();
172 	    /* NOTREACHED */
173 	}
174     }
175     argc -= optind;
176     argv += optind;
177 
178     if(devname == NULL) {
179 	if(argc == 0) {
180 	    usage();
181 	    /* NOTREACHED */
182 	} else
183 	    devname = argv[0];
184     }
185 
186 
187     if(devtype == -1) {
188 	devtype = guess_devtype(devname);
189     }
190     if(devtype == -1) {
191 	errx(1, "%s: unable to determine type of device",
192 	     devname);
193     }
194     if(verbose_f) {
195 	printf("device type == ");
196 	if((devtype & TYPEMASK) == TAPE)
197 	    printf("tape\n");
198 	else
199 	    printf("disk, floppy, or cdrom\n");
200     }
201 
202     if(umount_f)
203 	unmount_dev(devname);
204 
205     /* XXX Tapes and disks have different ioctl's: */
206     if((devtype & TYPEMASK) == TAPE)
207 	eject_tape(devname);
208     else
209 	eject_disk(devname);
210 
211     if(verbose_f)
212 	printf("done.\n");
213 
214     return(0);
215 }
216 
217 void
218 usage(void)
219 {
220     errx(1, "Usage: eject [-n][-f][-v][-l][-t type][-d] device | nickname");
221 }
222 
223 int
224 guess_devtype(char *devname)
225 {
226     int n;
227 
228     /* Nickname match: */
229     for(n = 0; n < sizeof(nicknames) / sizeof(nicknames[0]);
230 	n++) {
231 	if(strncasecmp(nicknames[n].name, devname,
232 		       strlen(nicknames[n].name)) == 0)
233 	    return(nicknames[n].type);
234     }
235 
236     /*
237      * If we still don't know it, then try to compare vs. dev
238      * and rdev names that we know.
239      */
240     /* dev first: */
241     for(n = 0; n < sizeof(nicknames) / sizeof(nicknames[0]); n++) {
242 	char *name;
243 	name = nick2dev(nicknames[n].name);
244 	/*
245 	 * Assume that the part of the name that distinguishes the
246 	 * instance of this device begins with a 0.
247 	 */
248 	*(strchr(name, '0')) = '\0';
249 	if(strncmp(name, devname, strlen(name)) == 0)
250 	    return(nicknames[n].type);
251     }
252 
253     /* Now rdev: */
254     for(n = 0; n < sizeof(nicknames) / sizeof(nicknames[0]); n++) {
255 	char *name = nick2rdev(nicknames[n].name);
256 	*(strchr(name, '0')) = '\0';
257 	if(strncmp(name, devname, strlen(name)) == 0)
258 	    return(nicknames[n].type);
259     }
260 
261     /* Not found. */
262     return(-1);
263 }
264 
265 /* "floppy5" -> "/dev/fd5a".  Yep, this uses a static buffer. */
266 char *
267 nick2dev(char *nn)
268 {
269     int n;
270     static char devname[MAXDEVLEN];
271     int devnum = 0;
272 
273     for(n = 0; n < sizeof(nicknames) / sizeof(nicknames[0]); n++) {
274 	if(strncasecmp(nicknames[n].name, nn,
275 		       strlen(nicknames[n].name)) == 0) {
276 	    sscanf(nn, "%*[^0-9]%d", &devnum);
277 	    sprintf(devname, "/dev/%s%d", nicknames[n].devname,
278 		    devnum);
279 	    if((nicknames[n].type & TYPEMASK) != TAPE)
280 		strcat(devname, "a");
281 	    return(devname);
282 	}
283     }
284 
285     return(NULL);
286 }
287 
288 /* "floppy5" -> "/dev/rfd5c".  Static buffer. */
289 char *
290 nick2rdev(char *nn)
291 {
292     int n;
293     static char devname[MAXDEVLEN];
294     int devnum = 0;
295 
296     for(n = 0; n < sizeof(nicknames) / sizeof(nicknames[0]); n++) {
297 	if(strncasecmp(nicknames[n].name, nn,
298 		       strlen(nicknames[n].name)) == 0) {
299 	    sscanf(nn, "%*[^0-9]%d", &devnum);
300 	    sprintf(devname, "/dev/r%s%d", nicknames[n].devname,
301 		    devnum);
302 	    if((nicknames[n].type & TYPEMASK) != TAPE) {
303 		strcat(devname, "a");
304 		devname[strlen(devname) - 1] += getrawpartition();
305 	    }
306 	    return(devname);
307 	}
308     }
309 
310     return(NULL);
311 }
312 
313 /* Unmount all filesystems attached to dev. */
314 void
315 unmount_dev(char *name)
316 {
317     struct statfs *mounts;
318     int i, nmnts, len;
319     char *dn;
320 
321     nmnts = getmntinfo(&mounts, MNT_NOWAIT);
322     if(nmnts == 0) {
323 	err(1, "getmntinfo");
324     }
325 
326     /* Make sure we have a device name: */
327     dn = nick2dev(name);
328     if(dn == NULL)
329 	dn = name;
330 
331     /* Set len to strip off the partition name: */
332     len = strlen(dn);
333     if(!isdigit(dn[len - 1]))
334 	len--;
335     if(!isdigit(dn[len - 1])) {
336 	errx(1, "Can't figure out base name for dev name %s", dn);
337     }
338 
339     for(i = 0; i < nmnts; i++) {
340 	if(strncmp(mounts[i].f_mntfromname, dn, len) == 0) {
341 	    if(verbose_f)
342 		printf("Unmounting %s from %s...\n",
343 		       mounts[i].f_mntfromname,
344 		       mounts[i].f_mntonname);
345 
346 	    if(unmount(mounts[i].f_mntonname, 0) == -1) {
347 		err(1, "unmount: %s", mounts[i].f_mntonname);
348 	    }
349 	}
350     }
351 
352     return;
353 }
354 
355 void
356 eject_tape(char *name)
357 {
358     struct mtop m;
359     int fd;
360     char *dn;
361 
362     dn = nick2rdev(name);
363     if(dn == NULL) {
364 	dn = name;		/* Hope for the best. */
365     }
366 
367     fd = open(dn, O_RDONLY);
368     if(fd == -1) {
369 	err(1, "open: %s", dn);
370     }
371 
372     if(verbose_f)
373 	printf("Ejecting %s...\n", dn);
374 
375     m.mt_op = MTOFFL;
376     m.mt_count = 0;
377     if(ioctl(fd, MTIOCTOP, &m) == -1) {
378 	err(1, "ioctl: MTIOCTOP: %s", dn);
379     }
380 
381     close(fd);
382     return;
383 }
384 
385 void
386 eject_disk(char *name)
387 {
388     int fd;
389     char *dn;
390     int arg;
391 
392     dn = nick2rdev(name);
393     if(dn == NULL) {
394 	dn = name;		/* Hope for the best. */
395     }
396 
397     fd = open(dn, O_RDONLY);
398     if(fd == -1) {
399 	err(1, "open: %s", dn);
400     }
401 
402     if(load_f) {
403 	if(verbose_f)
404 	    printf("Closing %s...\n", dn);
405 
406 	if(ioctl(fd, CDIOCCLOSE, NULL) == -1) {
407 	    err(1, "ioctl: CDIOCCLOSE: %s", dn);
408 	}
409     } else {
410 	if(verbose_f)
411 	    printf("Ejecting %s...\n", dn);
412 
413 	arg = 0;
414 	if (umount_f == 0) {
415 	    /* force eject, unlock the device first */
416 	    if(ioctl(fd, DIOCLOCK, &arg) == -1)
417 		err(1, "ioctl: DIOCEJECT: %s", dn);
418 	    arg = 1;
419 	}
420 	if(ioctl(fd, DIOCEJECT, &arg) == -1)
421 	    err(1, "ioctl: DIOCEJECT: %s", dn);
422     }
423 
424     close(fd);
425     return;
426 }
427