1 /********************************************************************
3 * L I N U X E J E C T C O M M A N D
5 * by Jeff Tranter (tranter@pobox.com)
7 ********************************************************************
9 * Copyright (C) 1994-2001 Jeff Tranter (tranter@pobox.com)
10 * Copyright (C) 2004, 2005 Frank Lichtenheld (djpig@debian.org)
12 * This program is free software; you can redistribute it and/or modify
13 * it under the terms of the GNU General Public License as published by
14 * the Free Software Foundation; either version 2 of the License, or
15 * (at your option) any later version.
17 * This program is distributed in the hope that it will be useful,
18 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 * GNU General Public License for more details.
22 * You should have received a copy of the GNU General Public License
23 * along with this program; if not, write to the Free Software
24 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
26 ********************************************************************
28 * See the man page for a description of what this program does and what
29 * the requirements to run it are.
36 #error DEFAULTDEVICE not set, check Makefile
49 #endif /* GETOPTLONG */
52 #include <sys/types.h>
54 #include <sys/ioctl.h>
56 //#include <sys/mtio.h>
57 #include <sys/mount.h>
59 #if defined(__linux__)
60 #include <linux/version.h>
61 /* handy macro found in 2.1 kernels, but not in older ones */
62 #ifndef KERNEL_VERSION
63 #define KERNEL_VERSION(a,b,c) (((a) << 16) + ((b) << 8) + (c))
65 #include <linux/mtio.h>
66 #include <linux/types.h>
67 #include <linux/cdrom.h>
68 #if LINUX_VERSION_CODE < KERNEL_VERSION(2,1,0)
69 #include <linux/ucdrom.h>
72 #include <scsi/scsi.h>
74 #include <scsi/scsi_ioctl.h>
77 /* Used by the ToggleTray() function. If ejecting the tray takes this
78 * time or less, the tray was probably already ejected, so we close it
81 #define TRAY_WAS_ALREADY_OPEN_USECS 200000 /* about 0.2 seconds */
84 #define CLOSE(fd) if (close(fd)==-1) { \
85 perror(programName); \
89 #define FCLOSE(fd) if (fclose(fd)==-1) { \
90 perror(programName); \
94 #define HAVE_EJECT_SCSI
95 #define HAVE_EJECT_FLOPPY
96 #define HAVE_EJECT_TAPE
98 #elif defined(__FreeBSD_kernel__)
100 #endif /* if defined(__linux__) */
103 #define CLOSE(fd) if (close(fd)==-1) { \
104 perror(programName); \
108 #define FCLOSE(fd) if (fclose(fd)==-1) { \
109 perror(programName); \
113 /* Global Variables */
114 static const char *version = VERSION; /* program version */
115 int a_option = 0; /* command flags and arguments */
140 static char *programName; /* used in error messages */
143 * These are the basenames of devices which can have multiple
144 * partitions per device.
146 static const char *partitionDevice[] = {
159 /* Display command usage on standard error and exit. */
162 // perror(_("%s: device is `%s'\n"));
164 "Eject version %s by Jeff Tranter (tranter@pobox.com)\n"
166 " eject -h -- display command usage and exit\n"
167 " eject -V -- display program version and exit\n"
168 " eject [-vnrsfqpm] [<name>] -- eject device\n"
169 " eject [-vn] -d -- display default device\n"
170 " eject [-vn] -a on|off|1|0 [<name>] -- turn auto-eject feature on or off\n"
171 " eject [-vn] -c <slot> [<name>] -- switch discs on a CD-ROM changer\n"
172 " eject [-vn] -t [<name>] -- close tray\n"
173 " eject [-vn] -T [<name>] -- toggle tray\n"
174 " eject [-vn] -i on|off|1|0 [<name>] -- toggle manual eject protection on/off\n"
175 " eject [-vn] -x <speed> [<name>] -- set CD-ROM max speed\n"
176 " eject [-vn] -X [<name>] -- list CD-ROM available speeds\n"
178 " -v\t-- enable verbose output\n"
179 " -n\t-- don't eject, just show device found\n"
180 " -r\t-- eject CD-ROM\n"
181 #ifdef HAVE_EJECT_SCSI
182 " -s\t-- eject SCSI device\n"
184 #ifdef HAVE_EJECT_FLOPPY
185 " -f\t-- eject floppy\n"
187 #ifdef HAVE_EJECT_TAPE
188 " -q\t-- eject tape\n"
190 " -p\t-- use /proc/mounts instead of /etc/mtab\n"
191 " -m\t-- do not unmount device even if it is mounted\n"
197 " -h --help -v --verbose -d --default\n"
198 " -a --auto -c --changerslot -t --trayclose -x --cdspeed\n"
200 #ifdef HAVE_EJECT_SCSI
203 #ifdef HAVE_EJECT_FLOPPY
207 #ifdef HAVE_EJECT_TAPE
211 " -n --noop -V --version\n"
212 " -p --proc -m --no-unmount -T --traytoggle -i --manualeject\n"));
213 #endif /* GETOPTLONG */
215 "Parameter <name> can be a device file or a mount point.\n"
216 "If omitted, name defaults to `%s'.\n"
217 "By default tries -r, -s, -f, and -q in order until success.\n"),
223 /* Handle command line options. */
224 static void parse_args(int argc, char **argv, char **device)
226 const char *flags = "a:c:x:i:dfhnqrstTXvVpm";
228 static struct option long_options[] =
230 {"help", no_argument, NULL, 'h'},
231 {"verbose", no_argument, NULL, 'v'},
232 {"default", no_argument, NULL, 'd'},
233 {"auto", required_argument, NULL, 'a'},
234 {"changerslot", required_argument, NULL, 'c'},
235 {"manualeject", required_argument, NULL, 'i'},
236 {"trayclose", no_argument, NULL, 't'},
237 {"traytoggle", no_argument, NULL, 'T'},
238 {"cdspeed", required_argument, NULL, 'x'},
239 {"listspeed", no_argument, NULL, 'X'},
240 {"noop", no_argument, NULL, 'n'},
241 {"cdrom", no_argument, NULL, 'r'},
242 {"scsi", no_argument, NULL, 's'},
243 {"floppy", no_argument, NULL, 'f'},
244 {"tape", no_argument, NULL, 'q'},
245 {"version", no_argument, NULL, 'V'},
246 {"proc", no_argument, NULL, 'p'},
247 {"no-unmount", no_argument, NULL, 'm'},
251 #endif /* GETOPTLONG */
255 while ((c = getopt_long(argc, argv, flags, long_options, &option_index)) != EOF) {
257 while ((c = getopt(argc, argv, flags)) != EOF) {
258 #endif /* GETOPTLONG */
262 if (!strcmp(optarg, "0"))
264 else if (!strcmp(optarg, "off"))
266 else if (!strcmp(optarg, "1"))
268 else if (!strcmp(optarg, "on"))
271 fprintf(stderr, _("%s: invalid argument to --auto/-a option\n"), programName);
277 /* atoi() returns 0 on error, so "0" must be parsed separately */
278 if (!strcmp(optarg, "0"))
281 c_arg = atoi(optarg);
283 fprintf(stderr, _("%s: invalid argument to --changerslot/-c option\n"), programName);
290 if (!strcmp(optarg, "0"))
293 x_arg = atoi(optarg);
295 fprintf(stderr, _("%s: invalid argument to --cdspeed/-x option\n"), programName);
312 if (!strcmp(optarg, "0"))
314 else if (!strcmp(optarg, "off"))
316 else if (!strcmp(optarg, "1"))
318 else if (!strcmp(optarg, "on"))
321 fprintf(stderr, _("%s: invalid argument to -i option\n"), programName);
356 printf(_("eject version %s by Jeff Tranter (tranter@pobox.com)\n"), version);
364 /* check for a single additional argument */
365 if ((argc - optind) > 1) {
366 fprintf(stderr, _("%s: too many arguments\n"), programName);
369 if ((argc - optind) == 1) { /* one argument */
370 *device = strdup(argv[optind]);
375 /* Return 1 if file/device exists, 0 otherwise. */
376 static int FileExists(const char *name, const int try, int *found)
379 if (!found) return -1;
381 * access() uses the UID, not the EUID. This way a normal user
382 * cannot find out if a file (say, /root/fubar) exists or not, even
383 * if eject is SUID root
385 if (access (name, F_OK) == 0) {
398 * Linux mangles spaces in mount points by changing them to an octal string
399 * of '\040'. So lets scan the mount point and fix it up by replacing all
400 * occurrences off '\0##' with the ASCII value of 0##. Requires a writable
401 * string as input as we mangle in place. Some of this was taken from the
402 * util-linux package.
404 #define octalify(a) ((a) & 7)
405 #define tooctal(s) (64*octalify(s[1]) + 8*octalify(s[2]) + octalify(s[3]))
406 #define isoctal(a) (((a) & ~7) == '0')
407 static char *DeMangleMount(char *s)
410 while ((tmp = strchr(tmp, '\\')) != NULL) {
411 if (isoctal(tmp[1]) && isoctal(tmp[2]) && isoctal(tmp[3])) {
412 tmp[0] = tooctal(tmp);
413 memmove(tmp+1, tmp+4, strlen(tmp)-3);
422 * Given name, such as foo, see if any of the following exist:
424 * foo (if foo starts with '.' or '/')
434 * If found, return the full path. If not found, return 0.
435 * Returns pointer to dynamically allocated string.
437 static char *FindDevice(const char *name)
443 buf = (char *) malloc(strlen(name)+14); /* to allow for "/dev/cdroms/ + "0" + null */
445 fprintf(stderr, _("%s: could not allocate memory\n"), programName);
449 if (try == INT_MAX) {
450 fprintf(stderr, _("%s: FindDevice called too often\n"), programName );;
455 if ((name[0] == '.') || (name[0] == '/')) {
457 if (FileExists(buf, try, &found))
461 strcpy(buf, "/dev/");
463 if (FileExists(buf, try, &found))
466 strcpy(buf, "/media/");
468 if (FileExists(buf, try, &found))
471 strcpy(buf, "/mnt/");
473 if (FileExists(buf, try, &found))
476 /* for devfs under Linux */
477 strcpy(buf, "/dev/cdroms/");
479 if (FileExists(buf, try, &found))
482 strcpy(buf, "/dev/cdroms/");
485 if (FileExists(buf, try, &found))
488 /* for devfs under Solaris */
489 strcpy(buf, "/dev/rdsk/");
491 if (FileExists(buf, try, &found))
494 strcpy(buf, "/dev/dsk/");
496 if (FileExists(buf, try, &found))
501 if (FileExists(buf, try, &found))
511 * Stops CDROM from opening on manual eject pressing the button.
512 * This can be useful when you carry your laptop
513 * in your bag while it's on and no CD inserted in it's drive.
514 * Implemented as found in Documentation/ioctl/cdrom.txt
516 * TODO: Maybe we should check this also:
517 * EDRIVE_CANT_DO_THIS Door lock function not supported.
518 * EBUSY Attempt to unlock when multiple users
519 * have the drive open and not CAP_SYS_ADMIN
521 static void ManualEject(int fd, int onOff)
523 if (ioctl(fd, CDROM_LOCKDOOR, onOff) < 0) {
524 perror("ioctl on CDROM_LOCKDOOR");
527 printf("CD-Drive may NOT be ejected with device button\n");
529 printf("CD-Drive may be ejected with device button\n");
534 /* Set or clear auto-eject mode. */
535 static void AutoEject(int fd, int onOff)
539 #if defined(CDROM_SET_OPTIONS) && defined(CDROM_CLEAR_OPTIONS)
541 status = ioctl(fd, CDROM_SET_OPTIONS, CDO_AUTO_EJECT);
543 status = ioctl(fd, CDROM_CLEAR_OPTIONS, CDO_AUTO_EJECT);
548 fprintf(stderr, _("%s: CD-ROM auto-eject command failed: %s\n"), programName, strerror(errno));
555 * Changer select. CDROM_SELECT_DISC is preferred, older kernels used
558 static void ChangerSelect(int fd, int slot)
562 #ifdef CDROM_SELECT_DISC
563 status = ioctl(fd, CDROM_SELECT_DISC, slot);
565 fprintf(stderr, _("%s: CD-ROM select disc command failed: %s\n"), programName, strerror(errno));
568 #elif defined CDROMLOADFROMSLOT
569 status = ioctl(fd, CDROMLOADFROMSLOT, slot);
571 fprintf(stderr, _("%s: CD-ROM load from slot command failed: %s\n"), programName, strerror(errno));
575 fprintf(stderr, _("%s: IDE/ATAPI CD-ROM changer not supported by this kernel\n"), programName);
581 * Close tray. Not supported by older kernels.
583 static void CloseTray(int fd)
587 #if defined(CDROMCLOSETRAY) || defined(CDIOCCLOSE)
588 #if defined(CDROMCLOSETRAY)
589 status = ioctl(fd, CDROMCLOSETRAY);
590 #elif defined(CDIOCCLOSE)
591 status = ioctl(fd, CDIOCCLOSE);
594 fprintf(stderr, _("%s: CD-ROM tray close command failed: %s\n"), programName, strerror(errno));
598 fprintf(stderr, _("%s: CD-ROM tray close command not supported by this kernel\n"), programName);
605 * Written by Benjamin Schwenk <benjaminschwenk@yahoo.de> and
606 * Sybren Stuvel <sybren@thirdtower.com>
608 * Not supported by older kernels because it might use
612 static void ToggleTray(int fd)
614 struct timeval time_start, time_stop;
617 #ifdef CDROMCLOSETRAY
619 /* Try to open the CDROM tray and measure the time needed.
620 * In my experience the function needs less than 0.05
621 * seconds if the tray was already open, and at least 1.5 seconds
624 gettimeofday(&time_start, NULL);
626 /* Send the CDROMEJECT command to the device. */
627 if (ioctl(fd, CDROMEJECT, 0) < 0) {
632 /* Get the second timestamp, to measure the time needed to open
634 gettimeofday(&time_stop, NULL);
636 time_elapsed = (time_stop.tv_sec * 1000000 + time_stop.tv_usec) -
637 (time_start.tv_sec * 1000000 + time_start.tv_usec);
639 /* If the tray "opened" too fast, we can be nearly sure, that it
640 * was already open. In this case, close it now. Else the tray was
641 * closed before. This would mean that we are done. */
642 if (time_elapsed < TRAY_WAS_ALREADY_OPEN_USECS)
646 fprintf(stderr, _("%s: CD-ROM tray toggle command not supported by this kernel\n"), programName);
652 * Select Speed of CD-ROM drive.
653 * Thanks to Roland Krivanek (krivanek@fmph.uniba.sk)
654 * http://dmpc.dbp.fmph.uniba.sk/~krivanek/cdrom_speed/
656 static void SelectSpeedCdrom(int fd, int speed)
660 #ifdef CDROM_SELECT_SPEED
661 status = ioctl(fd, CDROM_SELECT_SPEED, speed);
663 fprintf(stderr, _("%s: CD-ROM select speed command failed: %s\n"), programName, strerror(errno));
667 fprintf(stderr, _("%s: CD-ROM select speed command not supported by this kernel\n"), programName);
672 * Read Speed of CD-ROM drive. From Linux 2.6.13, the current speed is correctly reported
674 static int ReadSpeedCdrom(const char *shortName)
677 char *str_speed, *str_name;
678 int drive_number = -1, i;
679 FILE *f = fopen("/proc/sys/dev/cdrom/info", "r");
682 fprintf(stderr, _("%s: unable to read the speed from /proc/sys/dev/cdrom/info\n"), programName);
687 fgets(line, sizeof(line), f);
689 /* find drive number from shortName in line "drive name" */
690 if (drive_number == -1) {
691 if (strncmp(line, "drive name:", 11) == 0) {
692 str_name = strtok(&line[11], "\t ");
694 while (strncmp(shortName, str_name, strlen(shortName)) != 0) {
696 str_name = strtok(NULL, "\t ");
697 if (str_name == NULL) {
698 fprintf(stderr, _("%s: error while finding CD-ROM name\n"), programName);
703 /* find line "drive speed" and read the correct speed */
705 if (strncmp(line, "drive speed:", 12) == 0) {
706 str_speed = strtok(&line[12], "\t ");
707 for (i = 1; i < drive_number; i++)
708 str_speed = strtok(NULL, "\t ");
710 if (str_speed == NULL) {
711 fprintf(stderr, _("%s: error while reading speed\n"), programName);
714 return atoi(str_speed);
719 fprintf(stderr, _("%s: error while reading speed\n"), programName);
726 * List Speed of CD-ROM drive.
728 static void ListSpeedCdrom(const char *fullName, int fd)
730 #ifdef CDROM_SELECT_SPEED
731 int max_speed, curr_speed = 0, prev_speed;
732 char *shortName = strrchr(fullName, '/') + 1;
734 SelectSpeedCdrom(fd, 0);
735 max_speed = ReadSpeedCdrom(shortName);
736 while (curr_speed < max_speed) {
737 prev_speed = curr_speed;
738 SelectSpeedCdrom(fd, prev_speed + 1);
739 curr_speed = ReadSpeedCdrom(shortName);
740 if (curr_speed > prev_speed)
741 printf("%d ", curr_speed);
743 curr_speed = prev_speed + 1;
748 fprintf(stderr, _("%s: CD-ROM select speed command not supported by this kernel\n"), programName);
753 * Eject using CDROMEJECT ioctl. Return 1 if successful, 0 otherwise.
755 static int EjectCdrom(int fd)
759 #if defined(CDROMEJECT)
760 status = ioctl(fd, CDROMEJECT);
761 #elif defined(CDIOCEJECT)
762 status = ioctl(fd, CDIOCEJECT);
764 /* Some kernels implement cdrom-eject only, but I don't think any kernel in the
765 world would implement eject only for non-cdrom drives. Let's die. */
768 return (status == 0);
771 #ifdef HAVE_EJECT_SCSI
773 * Eject using SCSI SG_IO commands. Return 1 if successful, 0 otherwise.
775 static int EjectScsi(int fd)
779 unsigned char allowRmBlk[6] = {ALLOW_MEDIUM_REMOVAL, 0, 0, 0, 0, 0};
780 unsigned char startStop1Blk[6] = {START_STOP, 0, 0, 0, 1, 0};
781 unsigned char startStop2Blk[6] = {START_STOP, 0, 0, 0, 2, 0};
782 unsigned char inqBuff[2];
783 unsigned char sense_buffer[32];
785 if ((ioctl(fd, SG_GET_VERSION_NUM, &k) < 0) || (k < 30000)) {
787 printf(_("not an sg device, or old sg driver\n"));
792 memset(&io_hdr, 0, sizeof(sg_io_hdr_t));
793 io_hdr.interface_id = 'S';
795 io_hdr.mx_sb_len = sizeof(sense_buffer);
796 io_hdr.dxfer_direction = SG_DXFER_NONE;
797 io_hdr.dxfer_len = 0;
798 io_hdr.dxferp = inqBuff;
799 io_hdr.sbp = sense_buffer;
800 io_hdr.timeout = 10000;
802 io_hdr.cmdp = allowRmBlk;
803 status = ioctl(fd, SG_IO, (void *)&io_hdr);
807 io_hdr.cmdp = startStop1Blk;
808 status = ioctl(fd, SG_IO, (void *)&io_hdr);
812 io_hdr.cmdp = startStop2Blk;
813 status = ioctl(fd, SG_IO, (void *)&io_hdr);
817 /* force kernel to reread partition table when new disc inserted */
818 status = ioctl(fd, BLKRRPART);
824 #ifdef HAVE_EJECT_FLOPPY
826 * Eject using FDEJECT ioctl. Return 1 if successful, 0 otherwise.
828 static int EjectFloppy(int fd)
832 status = ioctl(fd, FDEJECT);
833 return (status >= 0);
838 #ifdef HAVE_EJECT_TAPE
840 * Eject using tape ioctl. Return 1 if successful, 0 otherwise.
842 static int EjectTape(int fd)
847 op.mt_op = MTOFFL; /* rewind and eject */
848 op.mt_count = 0; /* not used */
849 status = ioctl(fd, MTIOCTOP, &op);
850 return (status >= 0);
855 /* Unmount a device. */
856 static void Unmount(const char *fullName)
862 setuid(getuid()); /* reduce likelyhood of security holes when running setuid */
864 execlp("pumount", "pumount", fullName, "-n", NULL);
865 execlp("umount", "umount", fullName, "-n", NULL);
867 execlp("pumount", "pumount", fullName, NULL);
868 execlp("umount", "umount", fullName, NULL);
870 fprintf(stderr, _("%s: unable to exec umount of `%s': %s\n"),
871 programName, fullName, strerror(errno));
875 fprintf(stderr, _("%s: unable to fork: %s\n"), programName, strerror(errno));
877 default: /* parent */
879 if (WIFEXITED(status) == 0) {
880 fprintf(stderr, _("%s: unmount of `%s' did not exit normally\n"), programName, fullName);
883 if (WEXITSTATUS(status) != 0) {
884 fprintf(stderr, _("%s: unmount of `%s' failed\n"), programName, fullName);
892 /* Open a device file. Try opening first read/write, and if that fails then read only. */
893 static int OpenDevice(const char *fullName)
897 fd = open(fullName, O_RDWR|O_NONBLOCK);
902 fd = open(fullName, O_RDONLY|O_NONBLOCK);
904 fprintf(stderr, _("%s: unable to open `%s'\n"), programName, fullName);
912 * Get major and minor device numbers for a device file name, so we
913 * can check for duplicate devices.
915 static int GetMajorMinor(const char *name, int *maj, int *min)
920 if (stat(name, &sstat) == -1)
922 if (! S_ISBLK(sstat.st_mode) && ! S_ISCHR(sstat.st_mode))
924 if (maj) *maj = major(sstat.st_rdev);
925 if (min) *min = minor(sstat.st_rdev);
931 * See if device has been mounted by looking in mount table. If so, set
932 * device name and mount point name, and return 1, otherwise return 0.
934 static int MountedDevice(const char *name, char **mountName, char **deviceName)
945 GetMajorMinor(name, &maj, &min);
947 fp = fopen((p_option ? "/proc/mounts" : "/etc/mtab"), "r");
950 fprintf(stderr, _("unable to open %s: %s\n"), (p_option ? "/proc/mounts" : "/etc/mtab"), strerror(errno));
954 while (fgets(line, sizeof(line), fp) != 0) {
955 rc = sscanf(line, "%1023s %1023s", s1, s2);
957 int mtabmaj, mtabmin;
960 GetMajorMinor(s1, &mtabmaj, &mtabmin);
961 if (((strcmp(s1, name) == 0) || (strcmp(s2, name) == 0)) ||
962 ((maj != -1) && (maj == mtabmaj) && (min == mtabmin))) {
964 *deviceName = strdup(s1);
965 *mountName = strdup(s2);
978 * See if device can be mounted by looking in /etc/fstab.
979 * If so, set device name and mount point name, and return 1,
980 * otherwise return 0.
982 static int MountableDevice(const char *name, char **mountName, char **deviceName)
990 fp = fopen("/etc/fstab", "r");
993 * /etc/fstab may be unreadable in some situations due to passwords in the
996 /* fprintf(stderr, _("%s: unable to open /etc/fstab: %s\n"), programName, strerror(errno));
999 printf( _("%s: unable to open /etc/fstab: %s\n"), programName, strerror(errno));
1004 while (fgets(line, sizeof(line), fp) != 0) {
1005 rc = sscanf(line, "%1023s %1023s", s1, s2);
1008 if (rc >= 2 && s1[0] != '#' && strcmp(s2, name) == 0) {
1010 *deviceName = strdup(s1);
1011 *mountName = strdup(s2);
1022 * Step through mount table and unmount all devices that match a regular
1025 static void UnmountDevices(const char *pattern)
1034 if (regcomp(&preg, pattern, REG_EXTENDED)!=0) {
1035 perror(programName);
1039 fp = fopen((p_option ? "/proc/mounts" : "/etc/mtab"), "r");
1042 fprintf(stderr, _("unable to open %s: %s\n"),(p_option ? "/proc/mounts" : "/etc/mtab"), strerror(errno));
1046 while (fgets(line, sizeof(line), fp) != 0) {
1047 status = sscanf(line, "%1023s %1023s", s1, s2);
1049 status = regexec(&preg, s1, 0, 0, 0);
1052 printf(_("%s: unmounting `%s'\n"), programName, s2);
1063 /* Check if name is a symbolic link. If so, return what it points to. */
1064 static char *SymLink(const char *name)
1070 char result[PATH_MAX];
1073 memset(s1, 0, sizeof(s1));
1074 memset(s2, 0, sizeof(s2));
1075 memset(s4, 0, sizeof(s4));
1076 memset(result, 0, sizeof(result));
1078 status = readlink(name, s1, sizeof(s1) - 1);
1084 if (s1[0] == '/') { /* absolute link */
1086 } else { /* relative link, add base name */
1087 strncpy(s2, name, sizeof(s2)-1);
1088 s3 = strrchr(s2, '/');
1091 snprintf(result, sizeof(result)-1, "%s%s", s2, s1);
1094 realpath(result, s4);
1101 * Given a name, see if it matches a pattern for a device that can have
1102 * multiple partitions. If so, return a regular expression that matches
1103 * partitions for that device, otherwise return 0.
1105 static char *MultiplePartitions(const char *name)
1113 for (i = 0; partitionDevice[i] != 0; i++) {
1114 /* look for ^/dev/foo[a-z]([0-9]?[0-9])?$, e.g. /dev/hda1 */
1115 strcpy(pattern, "^/dev/");
1116 strcat(pattern, partitionDevice[i]);
1117 strcat(pattern, "[a-z]([0-9]?[0-9])?$");
1118 if (regcomp(&preg, pattern, REG_EXTENDED|REG_NOSUB) != 0) {
1119 perror(programName);
1122 status = regexec(&preg, name, 1, 0, 0);
1125 result = (char *) malloc(strlen(name) + 25);
1126 if (result == NULL) {
1127 fprintf(stderr, _("%s: could not allocate memory\n"), programName);
1130 strcpy(result, name);
1131 result[strlen(partitionDevice[i]) + 6] = 0;
1132 strcat(result, "([0-9]?[0-9])?$");
1134 printf(_("%s: `%s' is a multipartition device\n"), programName, name);
1139 printf(_("%s: `%s' is not a multipartition device\n"), programName, name);
1145 * Find device name in /sys/block/. Returns NULL if not
1146 * found. The returned pointer must be free()'d.
1148 static char* FindDeviceSysBlock(const char* deviceName)
1150 DIR *dir = opendir("/sys/block");
1152 const char *baseName = strrchr(deviceName, '/');
1156 baseName = baseName ? baseName + 1 : deviceName;
1158 fprintf(stderr, _("%s: can not open directory /sys/block/"), programName);
1161 while ((d = readdir(dir)) != NULL) {
1162 if (d->d_type != DT_DIR && d->d_type != DT_LNK && d->d_type != DT_UNKNOWN)
1164 len = strlen(d->d_name);
1165 if (!strncmp(baseName, d->d_name, len)) {
1166 if ((*(baseName+len) >= '0' &&
1167 *(baseName+len) <= '9') ||
1168 *(baseName+len) == '\0') {
1169 device = strdup(d->d_name);
1180 * From given path gets a subsystem. Returns subsystem if any found
1181 * otherwise returns NULL. Returned value must not be free()'d
1183 static char *GetSubSystem(const char *sysfspath)
1185 static char subsystem[PATH_MAX];
1186 char link_subsystem[PATH_MAX];
1190 snprintf(link_subsystem, sizeof(link_subsystem), "%s/subsystem", sysfspath);
1192 if (lstat(link_subsystem, &buf) == -1)
1194 if (!S_ISLNK(buf.st_mode))
1196 if (readlink(link_subsystem, subsystem, sizeof(subsystem)) == -1)
1198 if ((pos = strrchr(subsystem, '/')) == NULL)
1200 strncpy(subsystem, pos+1, sizeof(subsystem));
1206 * Check content of /sys/block/<dev>/removable. Returns 1 if the file
1207 * contains '1' otherwise returns 0.
1209 static int CheckRemovable(const char* deviceName)
1214 char path[PATH_MAX];
1216 if ((device = FindDeviceSysBlock(deviceName)) == NULL) {
1218 _("%s: did not find a device %s in /sys/block/\n"),
1219 programName, deviceName);
1222 snprintf(path, sizeof(path), "/sys/block/%s/removable", device);
1224 if((fp = fopen(path, "r")) == NULL)
1226 if (fgetc(fp) == '1')
1233 /* Check if a device is on hotpluggable subsystem. Returns 1 if is
1234 * otherwise returns 0.
1236 static int CheckHotpluggable(const char* deviceName)
1238 int hotpluggable = 0;
1240 char path[PATH_MAX];
1246 if ((device = FindDeviceSysBlock(deviceName)) == NULL) {
1247 fprintf(stderr, _("%s: did not find a device %s in /sys/block/\n"),
1248 programName, deviceName);
1251 snprintf(path, sizeof(path), "/sys/block/%s/device", device);
1254 if (lstat(path, &buf) == -1)
1255 return hotpluggable;
1256 if (!S_ISLNK(buf.st_mode))
1257 return hotpluggable;
1258 if ((device_chain = SymLink(path)) == NULL)
1259 return hotpluggable;
1260 while ( strncmp(device_chain, "", sizeof(device_chain) != 0)) {
1261 subsystem = GetSubSystem(device_chain);
1263 /* as hotpluggable we assume devices on these buses */
1264 if (strncmp("usb", subsystem, sizeof("usb")) == 0 ||
1265 strncmp("ieee1394", subsystem, sizeof("ieee1394")) == 0 ||
1266 strncmp("pcmcia", subsystem, sizeof("pcmcia")) == 0 ||
1267 strncmp("mmc", subsystem, sizeof("mmc")) == 0 ||
1268 strncmp("ccw", subsystem, sizeof("ccw")) == 0) {
1273 /* remove one member from devicechain */
1274 pos = strrchr(device_chain, '/');
1278 device_chain[0] = '\0';
1281 return hotpluggable;
1284 /* handle -x option */
1285 static void HandleXOption(char *deviceName)
1287 int fd; /* file descriptor for device */
1292 printf(_("%s: setting CD-ROM speed to auto\n"), programName);
1294 printf(_("%s: setting CD-ROM speed to %dX\n"), programName, x_arg);
1296 fd = OpenDevice(deviceName);
1297 SelectSpeedCdrom(fd, x_arg);
1304 int main(int argc, char **argv)
1306 const char *defaultDevice = DEFAULTDEVICE; /* default if no name passed by user */
1307 int worked = 0; /* set to 1 when successfully ejected */
1308 char *device = 0; /* name passed from user */
1309 char *fullName; /* expanded name */
1310 char *fullNameOrig;/* expanded name (links not resolved) */
1311 char *deviceName; /* name of device */
1312 char *linkName; /* name of device's symbolic link */
1313 char *mountName; /* name of device's mount point */
1314 int fd; /* file descriptor for device */
1315 int mounted = 0; /* true if device is mounted */
1316 int mountable = 0; /* true if device is in /etc/fstab */
1317 int result = 0; /* store the result of a operation */
1318 char *pattern = 0; /* regex for device if multiple partitions */
1319 int ld = 6; /* symbolic link max depth */
1323 /* program name is global variable used by other procedures */
1324 programName = strdup(argv[0]);
1326 /* parse the command line arguments */
1327 parse_args(argc, argv, &device);
1330 /* handle -d option */
1332 printf(_("%s: default device: `%s'\n"), programName, defaultDevice);
1336 /* if no device, use default */
1338 device = strdup(defaultDevice);
1340 printf(_("%s: using default device `%s'\n"), programName, device);
1343 /* Strip any trailing slash from name in case user used bash/tcsh
1344 style filename completion (e.g. /mnt/cdrom/) */
1345 if (device[strlen(device)-1] == '/')
1346 device[strlen(device)-1] = 0;
1349 printf(_("%s: device name is `%s'\n"), programName, device);
1352 /* figure out full device or mount point name */
1353 fullName = FindDevice(device);
1354 if (fullName == 0) {
1355 fprintf(stderr, _("%s: unable to find or open device for: `%s'\n"),
1356 programName, device);
1360 printf(_("%s: expanded name is `%s'\n"), programName, fullName);
1362 /* check for a symbolic link */
1363 /* /proc/mounts doesn't resolve symbolic links */
1364 fullNameOrig = strdup(fullName);
1365 linkName = strdup(fullName); /* ensure linkName is initialized */
1367 while ((linkName = SymLink(fullName)) && (ld > 0)) {
1369 printf(_("%s: `%s' is a link to `%s'\n"), programName,
1370 fullName, linkName);
1372 fullName = strdup(linkName);
1378 /* handle max depth exceeded option */
1380 printf(_("%s: maximum symbolic link depth exceeded: `%s'\n"), programName, fullName);
1384 /* if mount point, get device name */
1385 mounted = MountedDevice(fullName, &mountName, &deviceName);
1388 printf(_("%s: `%s' is mounted at `%s'\n"), programName,
1389 deviceName, mountName);
1391 printf(_("%s: `%s' is not mounted\n"), programName, fullName);
1394 deviceName = strdup(fullName);
1397 /* if not currently mounted, see if it is a possible mount point */
1399 mountable = MountableDevice(fullName, &mountName, &deviceName);
1400 /* if return value -1 then fstab could not be read */
1401 if (v_option && mountable >= 0) {
1403 printf(_("%s: `%s' can be mounted at `%s'\n"), programName, deviceName, mountName);
1405 printf(_("%s: `%s' is not a mount point\n"), programName, fullName);
1409 result = GetMajorMinor(deviceName, NULL, NULL);
1412 _("%s: tried to use `%s' as device name but it is no block device\n"),
1413 programName, deviceName);
1416 } while (result == -1);
1418 /* handle -n option */
1420 printf(_("%s: device is `%s'\n"), programName, deviceName);
1422 printf(_("%s: exiting due to -n/--noop option\n"), programName);
1426 /* Check if device has removable flag*/
1428 printf(_("%s: checking if device \"%s\" has a removable or hotpluggable flag\n"),
1429 programName, deviceName);
1430 if (!CheckRemovable(deviceName) && !CheckHotpluggable(deviceName))
1432 fprintf(stderr, _("%s: device \"%s\" doesn't have a removable or hotpluggable flag\n"),
1433 programName, deviceName);
1437 /* handle -i option */
1439 fd = OpenDevice(deviceName);
1440 ManualEject(fd, i_arg);
1444 /* handle -a option */
1448 printf(_("%s: enabling auto-eject mode for `%s'\n"), programName, deviceName);
1450 printf(_("%s: disabling auto-eject mode for `%s'\n"), programName, deviceName);
1452 fd = OpenDevice(deviceName);
1453 AutoEject(fd, a_arg);
1457 /* handle -t option */
1460 printf(_("%s: closing tray\n"), programName);
1461 fd = OpenDevice(deviceName);
1463 HandleXOption(deviceName);
1467 /* handle -X option */
1470 printf(_("%s: listing CD-ROM speed\n"), programName);
1471 fd = OpenDevice(deviceName);
1472 ListSpeedCdrom(deviceName, fd);
1476 /* handle -x option only */
1477 if (!c_option) HandleXOption(deviceName);
1479 /* unmount device if mounted */
1480 if ((m_option != 1) && mounted) {
1482 printf(_("%s: unmounting device `%s' from `%s'\n"), programName, deviceName, mountName);
1487 /* if it is a multipartition device, unmount any other partitions on
1489 pattern = MultiplePartitions(deviceName);
1490 if ((m_option != 1) && (pattern != 0))
1491 UnmountDevices(pattern);
1494 /* handle -T option */
1497 printf(_("%s: toggling tray\n"), programName);
1498 fd = OpenDevice(deviceName);
1500 HandleXOption(deviceName);
1504 /* handle -c option */
1507 printf(_("%s: selecting CD-ROM disc #%d\n"), programName, c_arg);
1508 fd = OpenDevice(deviceName);
1509 ChangerSelect(fd, c_arg);
1510 HandleXOption(deviceName);
1514 /* if user did not specify type of eject, try all four methods */
1515 if ((r_option + s_option + f_option + q_option) == 0) {
1516 r_option = s_option = f_option = q_option = 1;
1520 fd = OpenDevice(deviceName);
1522 /* try various methods of ejecting until it works */
1525 printf(_("%s: trying to eject `%s' using CD-ROM eject command\n"), programName, deviceName);
1526 worked = EjectCdrom(fd);
1529 printf(_("%s: CD-ROM eject command succeeded\n"), programName);
1531 printf(_("%s: CD-ROM eject command failed\n"), programName);
1535 #ifdef HAVE_EJECT_SCSI
1536 if (s_option && !worked) {
1538 printf(_("%s: trying to eject `%s' using SCSI commands\n"), programName, deviceName);
1539 worked = EjectScsi(fd);
1542 printf(_("%s: SCSI eject succeeded\n"), programName);
1544 printf(_("%s: SCSI eject failed\n"), programName);
1549 #ifdef HAVE_EJECT_FLOPPY
1550 if (f_option && !worked) {
1552 printf(_("%s: trying to eject `%s' using floppy eject command\n"), programName, deviceName);
1553 worked = EjectFloppy(fd);
1556 printf(_("%s: floppy eject command succeeded\n"), programName);
1558 printf(_("%s: floppy eject command failed\n"), programName);
1563 #ifdef HAVE_EJECT_TAPE
1564 if (q_option && !worked) {
1566 printf(_("%s: trying to eject `%s' using tape offline command\n"), programName, deviceName);
1567 worked = EjectTape(fd);
1570 printf(_("%s: tape offline command succeeded\n"), programName);
1572 printf(_("%s: tape offline command failed\n"), programName);
1578 fprintf(stderr, _("%s: unable to eject, last error: %s\n"), programName, strerror(errno));