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