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