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