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