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 */
54 #include <sys/types.h>
56 #include <sys/ioctl.h>
58 //#include <sys/mtio.h>
59 #include <sys/mount.h>
61 #if defined(__linux__)
62 #include <linux/version.h>
63 /* handy macro found in 2.1 kernels, but not in older ones */
64 #ifndef KERNEL_VERSION
65 #define KERNEL_VERSION(a,b,c) (((a) << 16) + ((b) << 8) + (c))
67 #include <linux/mtio.h>
68 #include <linux/types.h>
69 #include <linux/cdrom.h>
70 #if LINUX_VERSION_CODE < KERNEL_VERSION(2,1,0)
71 #include <linux/ucdrom.h>
75 #include <scsi/scsi.h>
77 #include <scsi/scsi_ioctl.h>
81 /* Used by the ToggleTray() function. If ejecting the tray takes this
82 * time or less, the tray was probably already ejected, so we close it
85 #define TRAY_WAS_ALREADY_OPEN_USECS 200000 /* about 0.2 seconds */
88 #define CLOSE(fd) if (close(fd)==-1) { \
89 perror(programName); \
93 #define FCLOSE(fd) if (fclose(fd)==-1) { \
94 perror(programName); \
99 #define HAVE_EJECT_SCSI
101 #define HAVE_EJECT_FLOPPY
102 #define HAVE_EJECT_TAPE
104 #elif defined(__FreeBSD_kernel__)
105 #include <sys/cdio.h>
106 #endif /* if defined(__linux__) */
109 #define CLOSE(fd) if (close(fd)==-1) { \
110 perror(programName); \
114 #define FCLOSE(fd) if (fclose(fd)==-1) { \
115 perror(programName); \
119 /* Global Variables */
120 static const char *version = VERSION; /* program version */
121 int a_option = 0; /* command flags and arguments */
146 static char *programName; /* used in error messages */
149 * These are the basenames of devices which can have multiple
150 * partitions per device.
152 static const char *partitionDevice[] = {
165 /* Display command usage on standard error and exit. */
168 // perror(_("%s: device is `%s'\n"));
170 "Eject version %s by Jeff Tranter (tranter@pobox.com)\n"
172 " eject -h -- display command usage and exit\n"
173 " eject -V -- display program version and exit\n"
174 " eject [-vnrsfqpm] [<name>] -- eject device\n"
175 " eject [-vn] -d -- display default device\n"
176 " eject [-vn] -a on|off|1|0 [<name>] -- turn auto-eject feature on or off\n"
177 " eject [-vn] -c <slot> [<name>] -- switch discs on a CD-ROM changer\n"
178 " eject [-vn] -t [<name>] -- close tray\n"
179 " eject [-vn] -T [<name>] -- toggle tray\n"
180 " eject [-vn] -i on|off|1|0 [<name>] -- toggle manual eject protection on/off\n"
181 " eject [-vn] -x <speed> [<name>] -- set CD-ROM max speed\n"
182 " eject [-vn] -X [<name>] -- list CD-ROM available speeds\n"
184 " -v\t-- enable verbose output\n"
185 " -n\t-- don't eject, just show device found\n"
186 " -r\t-- eject CD-ROM\n"
187 #ifdef HAVE_EJECT_SCSI
188 " -s\t-- eject SCSI device\n"
190 #ifdef HAVE_EJECT_FLOPPY
191 " -f\t-- eject floppy\n"
193 #ifdef HAVE_EJECT_TAPE
194 " -q\t-- eject tape\n"
196 " -p\t-- use /proc/mounts instead of /etc/mtab\n"
197 " -m\t-- do not unmount device even if it is mounted\n"
203 " -h --help -v --verbose -d --default\n"
204 " -a --auto -c --changerslot -t --trayclose -x --cdspeed\n"
206 #ifdef HAVE_EJECT_SCSI
209 #ifdef HAVE_EJECT_FLOPPY
213 #ifdef HAVE_EJECT_TAPE
217 " -n --noop -V --version\n"
218 " -p --proc -m --no-unmount -T --traytoggle -i --manualeject\n"));
219 #endif /* GETOPTLONG */
221 "Parameter <name> can be a device file or a mount point.\n"
222 "If omitted, name defaults to `%s'.\n"
223 "By default tries -r, -s, -f, and -q in order until success.\n"),
229 /* Handle command line options. */
230 static void parse_args(int argc, char **argv, char **device)
232 const char *flags = "a:c:x:i:dfhnqrstTXvVpm";
234 static struct option long_options[] =
236 {"help", no_argument, NULL, 'h'},
237 {"verbose", no_argument, NULL, 'v'},
238 {"default", no_argument, NULL, 'd'},
239 {"auto", required_argument, NULL, 'a'},
240 {"changerslot", required_argument, NULL, 'c'},
241 {"manualeject", required_argument, NULL, 'i'},
242 {"trayclose", no_argument, NULL, 't'},
243 {"traytoggle", no_argument, NULL, 'T'},
244 {"cdspeed", required_argument, NULL, 'x'},
245 {"listspeed", no_argument, NULL, 'X'},
246 {"noop", no_argument, NULL, 'n'},
247 {"cdrom", no_argument, NULL, 'r'},
248 {"scsi", no_argument, NULL, 's'},
249 {"floppy", no_argument, NULL, 'f'},
250 {"tape", no_argument, NULL, 'q'},
251 {"version", no_argument, NULL, 'V'},
252 {"proc", no_argument, NULL, 'p'},
253 {"no-unmount", no_argument, NULL, 'm'},
257 #endif /* GETOPTLONG */
261 while ((c = getopt_long(argc, argv, flags, long_options, &option_index)) != EOF) {
263 while ((c = getopt(argc, argv, flags)) != EOF) {
264 #endif /* GETOPTLONG */
268 if (!strcmp(optarg, "0"))
270 else if (!strcmp(optarg, "off"))
272 else if (!strcmp(optarg, "1"))
274 else if (!strcmp(optarg, "on"))
277 fprintf(stderr, _("%s: invalid argument to --auto/-a option\n"), programName);
283 /* atoi() returns 0 on error, so "0" must be parsed separately */
284 if (!strcmp(optarg, "0"))
287 c_arg = atoi(optarg);
289 fprintf(stderr, _("%s: invalid argument to --changerslot/-c option\n"), programName);
296 if (!strcmp(optarg, "0"))
299 x_arg = atoi(optarg);
301 fprintf(stderr, _("%s: invalid argument to --cdspeed/-x option\n"), programName);
318 if (!strcmp(optarg, "0"))
320 else if (!strcmp(optarg, "off"))
322 else if (!strcmp(optarg, "1"))
324 else if (!strcmp(optarg, "on"))
327 fprintf(stderr, _("%s: invalid argument to -i option\n"), programName);
362 printf(_("eject version %s by Jeff Tranter (tranter@pobox.com)\n"), version);
370 /* check for a single additional argument */
371 if ((argc - optind) > 1) {
372 fprintf(stderr, _("%s: too many arguments\n"), programName);
375 if ((argc - optind) == 1) { /* one argument */
376 *device = strdup(argv[optind]);
381 /* Return 1 if file/device exists, 0 otherwise. */
382 static int FileExists(const char *name, const int try, int *found)
385 if (!found) return -1;
387 * access() uses the UID, not the EUID. This way a normal user
388 * cannot find out if a file (say, /root/fubar) exists or not, even
389 * if eject is SUID root
391 if (access (name, F_OK) == 0) {
404 * Linux mangles spaces in mount points by changing them to an octal string
405 * of '\040'. So lets scan the mount point and fix it up by replacing all
406 * occurrences off '\0##' with the ASCII value of 0##. Requires a writable
407 * string as input as we mangle in place. Some of this was taken from the
408 * util-linux package.
410 #define octalify(a) ((a) & 7)
411 #define tooctal(s) (64*octalify(s[1]) + 8*octalify(s[2]) + octalify(s[3]))
412 #define isoctal(a) (((a) & ~7) == '0')
413 static char *DeMangleMount(char *s)
416 while ((tmp = strchr(tmp, '\\')) != NULL) {
417 if (isoctal(tmp[1]) && isoctal(tmp[2]) && isoctal(tmp[3])) {
418 tmp[0] = tooctal(tmp);
419 memmove(tmp+1, tmp+4, strlen(tmp)-3);
428 * Given name, such as foo, see if any of the following exist:
430 * foo (if foo starts with '.' or '/')
440 * If found, return the full path. If not found, return 0.
441 * Returns pointer to dynamically allocated string.
443 static char *FindDevice(const char *name)
449 buf = (char *) malloc(strlen(name)+14); /* to allow for "/dev/cdroms/ + "0" + null */
451 fprintf(stderr, _("%s: could not allocate memory\n"), programName);
455 if (try == INT_MAX) {
456 fprintf(stderr, _("%s: FindDevice called too often\n"), programName );;
461 if ((name[0] == '.') || (name[0] == '/')) {
463 if (FileExists(buf, try, &found))
467 strcpy(buf, "/dev/");
469 if (FileExists(buf, try, &found))
472 strcpy(buf, "/media/");
474 if (FileExists(buf, try, &found))
477 strcpy(buf, "/mnt/");
479 if (FileExists(buf, try, &found))
482 /* for devfs under Linux */
483 strcpy(buf, "/dev/cdroms/");
485 if (FileExists(buf, try, &found))
488 strcpy(buf, "/dev/cdroms/");
491 if (FileExists(buf, try, &found))
494 /* for devfs under Solaris */
495 strcpy(buf, "/dev/rdsk/");
497 if (FileExists(buf, try, &found))
500 strcpy(buf, "/dev/dsk/");
502 if (FileExists(buf, try, &found))
507 if (FileExists(buf, try, &found))
517 * Stops CDROM from opening on manual eject pressing the button.
518 * This can be useful when you carry your laptop
519 * in your bag while it's on and no CD inserted in it's drive.
520 * Implemented as found in Documentation/ioctl/cdrom.txt
522 * TODO: Maybe we should check this also:
523 * EDRIVE_CANT_DO_THIS Door lock function not supported.
524 * EBUSY Attempt to unlock when multiple users
525 * have the drive open and not CAP_SYS_ADMIN
527 static void ManualEject(int fd, int onOff)
529 if (ioctl(fd, CDROM_LOCKDOOR, onOff) < 0) {
530 perror("ioctl on CDROM_LOCKDOOR");
533 printf("CD-Drive may NOT be ejected with device button\n");
535 printf("CD-Drive may be ejected with device button\n");
540 /* Set or clear auto-eject mode. */
541 static void AutoEject(int fd, int onOff)
545 #if defined(CDROM_SET_OPTIONS) && defined(CDROM_CLEAR_OPTIONS)
547 status = ioctl(fd, CDROM_SET_OPTIONS, CDO_AUTO_EJECT);
549 status = ioctl(fd, CDROM_CLEAR_OPTIONS, CDO_AUTO_EJECT);
554 fprintf(stderr, _("%s: CD-ROM auto-eject command failed: %s\n"), programName, strerror(errno));
561 * Changer select. CDROM_SELECT_DISC is preferred, older kernels used
564 static void ChangerSelect(int fd, int slot)
568 #ifdef CDROM_SELECT_DISC
569 status = ioctl(fd, CDROM_SELECT_DISC, slot);
571 fprintf(stderr, _("%s: CD-ROM select disc command failed: %s\n"), programName, strerror(errno));
574 #elif defined CDROMLOADFROMSLOT
575 status = ioctl(fd, CDROMLOADFROMSLOT, slot);
577 fprintf(stderr, _("%s: CD-ROM load from slot command failed: %s\n"), programName, strerror(errno));
581 fprintf(stderr, _("%s: IDE/ATAPI CD-ROM changer not supported by this kernel\n"), programName);
587 * Close tray. Not supported by older kernels.
589 static void CloseTray(int fd)
593 #if defined(CDROMCLOSETRAY) || defined(CDIOCCLOSE)
594 #if defined(CDROMCLOSETRAY)
595 status = ioctl(fd, CDROMCLOSETRAY);
596 #elif defined(CDIOCCLOSE)
597 status = ioctl(fd, CDIOCCLOSE);
600 fprintf(stderr, _("%s: CD-ROM tray close command failed: %s\n"), programName, strerror(errno));
604 fprintf(stderr, _("%s: CD-ROM tray close command not supported by this kernel\n"), programName);
611 * Written by Benjamin Schwenk <benjaminschwenk@yahoo.de> and
612 * Sybren Stuvel <sybren@thirdtower.com>
614 * Not supported by older kernels because it might use
618 static void ToggleTray(int fd)
620 struct timeval time_start, time_stop;
623 #ifdef CDROMCLOSETRAY
625 /* Try to open the CDROM tray and measure the time needed.
626 * In my experience the function needs less than 0.05
627 * seconds if the tray was already open, and at least 1.5 seconds
630 gettimeofday(&time_start, NULL);
632 /* Send the CDROMEJECT command to the device. */
633 if (ioctl(fd, CDROMEJECT, 0) < 0) {
638 /* Get the second timestamp, to measure the time needed to open
640 gettimeofday(&time_stop, NULL);
642 time_elapsed = (time_stop.tv_sec * 1000000 + time_stop.tv_usec) -
643 (time_start.tv_sec * 1000000 + time_start.tv_usec);
645 /* If the tray "opened" too fast, we can be nearly sure, that it
646 * was already open. In this case, close it now. Else the tray was
647 * closed before. This would mean that we are done. */
648 if (time_elapsed < TRAY_WAS_ALREADY_OPEN_USECS)
652 fprintf(stderr, _("%s: CD-ROM tray toggle command not supported by this kernel\n"), programName);
658 * Select Speed of CD-ROM drive.
659 * Thanks to Roland Krivanek (krivanek@fmph.uniba.sk)
660 * http://dmpc.dbp.fmph.uniba.sk/~krivanek/cdrom_speed/
662 static void SelectSpeedCdrom(int fd, int speed)
666 #ifdef CDROM_SELECT_SPEED
667 status = ioctl(fd, CDROM_SELECT_SPEED, speed);
669 fprintf(stderr, _("%s: CD-ROM select speed command failed: %s\n"), programName, strerror(errno));
673 fprintf(stderr, _("%s: CD-ROM select speed command not supported by this kernel\n"), programName);
678 * Read Speed of CD-ROM drive. From Linux 2.6.13, the current speed is correctly reported
680 static int ReadSpeedCdrom(const char *shortName)
683 char *str_speed, *str_name;
684 int drive_number = -1, i;
685 FILE *f = fopen("/proc/sys/dev/cdrom/info", "r");
688 fprintf(stderr, _("%s: unable to read the speed from /proc/sys/dev/cdrom/info\n"), programName);
693 fgets(line, sizeof(line), f);
695 /* find drive number from shortName in line "drive name" */
696 if (drive_number == -1) {
697 if (strncmp(line, "drive name:", 11) == 0) {
698 str_name = strtok(&line[11], "\t ");
700 while (strncmp(shortName, str_name, strlen(shortName)) != 0) {
702 str_name = strtok(NULL, "\t ");
703 if (str_name == NULL) {
704 fprintf(stderr, _("%s: error while finding CD-ROM name\n"), programName);
709 /* find line "drive speed" and read the correct speed */
711 if (strncmp(line, "drive speed:", 12) == 0) {
712 str_speed = strtok(&line[12], "\t ");
713 for (i = 1; i < drive_number; i++)
714 str_speed = strtok(NULL, "\t ");
716 if (str_speed == NULL) {
717 fprintf(stderr, _("%s: error while reading speed\n"), programName);
720 return atoi(str_speed);
725 fprintf(stderr, _("%s: error while reading speed\n"), programName);
732 * List Speed of CD-ROM drive.
734 static void ListSpeedCdrom(const char *fullName, int fd)
736 #ifdef CDROM_SELECT_SPEED
737 int max_speed, curr_speed = 0, prev_speed;
738 char *shortName = strrchr(fullName, '/') + 1;
740 SelectSpeedCdrom(fd, 0);
741 max_speed = ReadSpeedCdrom(shortName);
742 while (curr_speed < max_speed) {
743 prev_speed = curr_speed;
744 SelectSpeedCdrom(fd, prev_speed + 1);
745 curr_speed = ReadSpeedCdrom(shortName);
746 if (curr_speed > prev_speed)
747 printf("%d ", curr_speed);
749 curr_speed = prev_speed + 1;
754 fprintf(stderr, _("%s: CD-ROM select speed command not supported by this kernel\n"), programName);
759 * Eject using CDROMEJECT ioctl. Return 1 if successful, 0 otherwise.
761 static int EjectCdrom(int fd)
765 #if defined(CDROMEJECT)
766 status = ioctl(fd, CDROMEJECT);
767 #elif defined(CDIOCEJECT)
768 status = ioctl(fd, CDIOCEJECT);
770 /* Some kernels implement cdrom-eject only, but I don't think any kernel in the
771 world would implement eject only for non-cdrom drives. Let's die. */
774 return (status == 0);
777 #ifdef HAVE_EJECT_SCSI
779 * Eject using SCSI SG_IO commands. Return 1 if successful, 0 otherwise.
781 static int EjectScsi(int fd)
785 unsigned char allowRmBlk[6] = {ALLOW_MEDIUM_REMOVAL, 0, 0, 0, 0, 0};
786 unsigned char startStop1Blk[6] = {START_STOP, 0, 0, 0, 1, 0};
787 unsigned char startStop2Blk[6] = {START_STOP, 0, 0, 0, 2, 0};
788 unsigned char inqBuff[2];
789 unsigned char sense_buffer[32];
791 if ((ioctl(fd, SG_GET_VERSION_NUM, &k) < 0) || (k < 30000)) {
793 printf(_("not an sg device, or old sg driver\n"));
798 memset(&io_hdr, 0, sizeof(sg_io_hdr_t));
799 io_hdr.interface_id = 'S';
801 io_hdr.mx_sb_len = sizeof(sense_buffer);
802 io_hdr.dxfer_direction = SG_DXFER_NONE;
803 io_hdr.dxfer_len = 0;
804 io_hdr.dxferp = inqBuff;
805 io_hdr.sbp = sense_buffer;
806 io_hdr.timeout = 10000;
808 io_hdr.cmdp = allowRmBlk;
809 status = ioctl(fd, SG_IO, (void *)&io_hdr);
813 io_hdr.cmdp = startStop1Blk;
814 status = ioctl(fd, SG_IO, (void *)&io_hdr);
818 io_hdr.cmdp = startStop2Blk;
819 status = ioctl(fd, SG_IO, (void *)&io_hdr);
823 /* force kernel to reread partition table when new disc inserted */
824 status = ioctl(fd, BLKRRPART);
830 #ifdef HAVE_EJECT_FLOPPY
832 * Eject using FDEJECT ioctl. Return 1 if successful, 0 otherwise.
834 static int EjectFloppy(int fd)
838 status = ioctl(fd, FDEJECT);
839 return (status >= 0);
844 #ifdef HAVE_EJECT_TAPE
846 * Eject using tape ioctl. Return 1 if successful, 0 otherwise.
848 static int EjectTape(int fd)
853 op.mt_op = MTOFFL; /* rewind and eject */
854 op.mt_count = 0; /* not used */
855 status = ioctl(fd, MTIOCTOP, &op);
856 return (status >= 0);
861 /* Unmount a device. */
862 static void Unmount(const char *fullName)
868 setuid(getuid()); /* reduce likelyhood of security holes when running setuid */
870 execlp("pumount", "pumount", fullName, "-n", NULL);
871 execlp("umount", "umount", fullName, "-n", NULL);
873 execlp("pumount", "pumount", fullName, NULL);
874 execlp("umount", "umount", fullName, NULL);
876 fprintf(stderr, _("%s: unable to exec umount of `%s': %s\n"),
877 programName, fullName, strerror(errno));
881 fprintf(stderr, _("%s: unable to fork: %s\n"), programName, strerror(errno));
883 default: /* parent */
885 if (WIFEXITED(status) == 0) {
886 fprintf(stderr, _("%s: unmount of `%s' did not exit normally\n"), programName, fullName);
889 if (WEXITSTATUS(status) != 0) {
890 fprintf(stderr, _("%s: unmount of `%s' failed\n"), programName, fullName);
898 /* Open a device file. Try opening first read/write, and if that fails then read only. */
899 static int OpenDevice(const char *fullName)
903 fd = open(fullName, O_RDWR|O_NONBLOCK);
908 fd = open(fullName, O_RDONLY|O_NONBLOCK);
910 fprintf(stderr, _("%s: unable to open `%s'\n"), programName, fullName);
918 * Get major and minor device numbers for a device file name, so we
919 * can check for duplicate devices.
921 static int GetMajorMinor(const char *name, int *maj, int *min)
926 if (stat(name, &sstat) == -1)
928 if (! S_ISBLK(sstat.st_mode) && ! S_ISCHR(sstat.st_mode))
930 if (maj) *maj = major(sstat.st_rdev);
931 if (min) *min = minor(sstat.st_rdev);
937 * See if device has been mounted by looking in mount table. If so, set
938 * device name and mount point name, and return 1, otherwise return 0.
940 static int MountedDevice(const char *name, char **mountName, char **deviceName)
951 GetMajorMinor(name, &maj, &min);
953 fp = fopen((p_option ? "/proc/mounts" : "/etc/mtab"), "r");
956 fprintf(stderr, _("unable to open %s: %s\n"), (p_option ? "/proc/mounts" : "/etc/mtab"), strerror(errno));
960 while (fgets(line, sizeof(line), fp) != 0) {
961 rc = sscanf(line, "%1023s %1023s", s1, s2);
963 int mtabmaj, mtabmin;
966 GetMajorMinor(s1, &mtabmaj, &mtabmin);
967 if (((strcmp(s1, name) == 0) || (strcmp(s2, name) == 0)) ||
968 ((maj != -1) && (maj == mtabmaj) && (min == mtabmin))) {
970 *deviceName = strdup(s1);
971 *mountName = strdup(s2);
984 * See if device can be mounted by looking in /etc/fstab.
985 * If so, set device name and mount point name, and return 1,
986 * otherwise return 0.
988 static int MountableDevice(const char *name, char **mountName, char **deviceName)
996 fp = fopen("/etc/fstab", "r");
999 * /etc/fstab may be unreadable in some situations due to passwords in the
1002 /* fprintf(stderr, _("%s: unable to open /etc/fstab: %s\n"), programName, strerror(errno));
1005 printf( _("%s: unable to open /etc/fstab: %s\n"), programName, strerror(errno));
1010 while (fgets(line, sizeof(line), fp) != 0) {
1011 rc = sscanf(line, "%1023s %1023s", s1, s2);
1014 if (rc >= 2 && s1[0] != '#' && strcmp(s2, name) == 0) {
1016 *deviceName = strdup(s1);
1017 *mountName = strdup(s2);
1028 * Step through mount table and unmount all devices that match a regular
1031 static void UnmountDevices(const char *pattern)
1040 if (regcomp(&preg, pattern, REG_EXTENDED)!=0) {
1041 perror(programName);
1045 fp = fopen((p_option ? "/proc/mounts" : "/etc/mtab"), "r");
1048 fprintf(stderr, _("unable to open %s: %s\n"),(p_option ? "/proc/mounts" : "/etc/mtab"), strerror(errno));
1052 while (fgets(line, sizeof(line), fp) != 0) {
1053 status = sscanf(line, "%1023s %1023s", s1, s2);
1055 status = regexec(&preg, s1, 0, 0, 0);
1058 printf(_("%s: unmounting `%s'\n"), programName, s2);
1069 /* Check if name is a symbolic link. If so, return what it points to. */
1070 static char *SymLink(const char *name)
1076 char result[PATH_MAX];
1079 memset(s1, 0, sizeof(s1));
1080 memset(s2, 0, sizeof(s2));
1081 memset(s4, 0, sizeof(s4));
1082 memset(result, 0, sizeof(result));
1084 status = readlink(name, s1, sizeof(s1) - 1);
1090 if (s1[0] == '/') { /* absolute link */
1092 } else { /* relative link, add base name */
1093 strncpy(s2, name, sizeof(s2)-1);
1094 s3 = strrchr(s2, '/');
1097 snprintf(result, sizeof(result)-1, "%s%s", s2, s1);
1100 realpath(result, s4);
1107 * Given a name, see if it matches a pattern for a device that can have
1108 * multiple partitions. If so, return a regular expression that matches
1109 * partitions for that device, otherwise return 0.
1111 static char *MultiplePartitions(const char *name)
1119 for (i = 0; partitionDevice[i] != 0; i++) {
1120 /* look for ^/dev/foo[a-z]([0-9]?[0-9])?$, e.g. /dev/hda1 */
1121 strcpy(pattern, "^/dev/");
1122 strcat(pattern, partitionDevice[i]);
1123 strcat(pattern, "[a-z]([0-9]?[0-9])?$");
1124 if (regcomp(&preg, pattern, REG_EXTENDED|REG_NOSUB) != 0) {
1125 perror(programName);
1128 status = regexec(&preg, name, 1, 0, 0);
1131 result = (char *) malloc(strlen(name) + 25);
1132 if (result == NULL) {
1133 fprintf(stderr, _("%s: could not allocate memory\n"), programName);
1136 strcpy(result, name);
1137 result[strlen(partitionDevice[i]) + 6] = 0;
1138 strcat(result, "([0-9]?[0-9])?$");
1140 printf(_("%s: `%s' is a multipartition device\n"), programName, name);
1145 printf(_("%s: `%s' is not a multipartition device\n"), programName, name);
1151 * Find device name in /sys/block/. Returns NULL if not
1152 * found. The returned pointer must be free()'d.
1154 static char* FindDeviceSysBlock(const char* deviceName)
1156 DIR *dir = opendir("/sys/block");
1158 const char *baseName = strrchr(deviceName, '/');
1162 baseName = baseName ? baseName + 1 : deviceName;
1164 fprintf(stderr, _("%s: can not open directory /sys/block/"), programName);
1167 while ((d = readdir(dir)) != NULL) {
1168 if (d->d_type != DT_DIR && d->d_type != DT_LNK && d->d_type != DT_UNKNOWN)
1170 len = strlen(d->d_name);
1171 if (!strncmp(baseName, d->d_name, len)) {
1172 if ((*(baseName+len) >= '0' &&
1173 *(baseName+len) <= '9') ||
1174 *(baseName+len) == '\0') {
1175 device = strdup(d->d_name);
1186 * From given path gets a subsystem. Returns subsystem if any found
1187 * otherwise returns NULL. Returned value must not be free()'d
1189 static char *GetSubSystem(const char *sysfspath)
1191 static char subsystem[PATH_MAX];
1192 char link_subsystem[PATH_MAX];
1196 snprintf(link_subsystem, sizeof(link_subsystem), "%s/subsystem", sysfspath);
1198 if (lstat(link_subsystem, &buf) == -1)
1200 if (!S_ISLNK(buf.st_mode))
1202 if (readlink(link_subsystem, subsystem, sizeof(subsystem)) == -1)
1204 if ((pos = strrchr(subsystem, '/')) == NULL)
1206 strncpy(subsystem, pos+1, sizeof(subsystem));
1212 * Check content of /sys/block/<dev>/removable. Returns 1 if the file
1213 * contains '1' otherwise returns 0.
1215 static int CheckRemovable(const char* deviceName)
1220 char path[PATH_MAX];
1222 if ((device = FindDeviceSysBlock(deviceName)) == NULL) {
1224 _("%s: did not find a device %s in /sys/block/\n"),
1225 programName, deviceName);
1228 snprintf(path, sizeof(path), "/sys/block/%s/removable", device);
1230 if((fp = fopen(path, "r")) == NULL)
1232 if (fgetc(fp) == '1')
1239 /* Check if a device is on hotpluggable subsystem. Returns 1 if is
1240 * otherwise returns 0.
1242 static int CheckHotpluggable(const char* deviceName)
1244 int hotpluggable = 0;
1246 char path[PATH_MAX];
1252 if ((device = FindDeviceSysBlock(deviceName)) == NULL) {
1253 fprintf(stderr, _("%s: did not find a device %s in /sys/block/\n"),
1254 programName, deviceName);
1257 snprintf(path, sizeof(path), "/sys/block/%s/device", device);
1260 if (lstat(path, &buf) == -1)
1261 return hotpluggable;
1262 if (!S_ISLNK(buf.st_mode))
1263 return hotpluggable;
1264 if ((device_chain = SymLink(path)) == NULL)
1265 return hotpluggable;
1266 while ( strncmp(device_chain, "", sizeof(device_chain) != 0)) {
1267 subsystem = GetSubSystem(device_chain);
1269 /* as hotpluggable we assume devices on these buses */
1270 if (strncmp("usb", subsystem, sizeof("usb")) == 0 ||
1271 strncmp("ieee1394", subsystem, sizeof("ieee1394")) == 0 ||
1272 strncmp("pcmcia", subsystem, sizeof("pcmcia")) == 0 ||
1273 strncmp("mmc", subsystem, sizeof("mmc")) == 0 ||
1274 strncmp("ccw", subsystem, sizeof("ccw")) == 0) {
1279 /* remove one member from devicechain */
1280 pos = strrchr(device_chain, '/');
1284 device_chain[0] = '\0';
1287 return hotpluggable;
1290 /* handle -x option */
1291 static void HandleXOption(char *deviceName)
1293 int fd; /* file descriptor for device */
1298 printf(_("%s: setting CD-ROM speed to auto\n"), programName);
1300 printf(_("%s: setting CD-ROM speed to %dX\n"), programName, x_arg);
1302 fd = OpenDevice(deviceName);
1303 SelectSpeedCdrom(fd, x_arg);
1310 int main(int argc, char **argv)
1312 const char *defaultDevice = DEFAULTDEVICE; /* default if no name passed by user */
1313 int worked = 0; /* set to 1 when successfully ejected */
1314 char *device = 0; /* name passed from user */
1315 char *fullName; /* expanded name */
1316 char *fullNameOrig;/* expanded name (links not resolved) */
1317 char *deviceName; /* name of device */
1318 char *linkName; /* name of device's symbolic link */
1319 char *mountName; /* name of device's mount point */
1320 int fd; /* file descriptor for device */
1321 int mounted = 0; /* true if device is mounted */
1322 int mountable = 0; /* true if device is in /etc/fstab */
1323 int result = 0; /* store the result of a operation */
1324 char *pattern = 0; /* regex for device if multiple partitions */
1325 int ld = 6; /* symbolic link max depth */
1329 /* program name is global variable used by other procedures */
1330 programName = strdup(argv[0]);
1332 /* parse the command line arguments */
1333 parse_args(argc, argv, &device);
1336 /* handle -d option */
1338 printf(_("%s: default device: `%s'\n"), programName, defaultDevice);
1342 /* if no device, use default */
1344 device = strdup(defaultDevice);
1346 printf(_("%s: using default device `%s'\n"), programName, device);
1349 /* Strip any trailing slash from name in case user used bash/tcsh
1350 style filename completion (e.g. /mnt/cdrom/) */
1351 if (device[strlen(device)-1] == '/')
1352 device[strlen(device)-1] = 0;
1355 printf(_("%s: device name is `%s'\n"), programName, device);
1358 /* figure out full device or mount point name */
1359 fullName = FindDevice(device);
1360 if (fullName == 0) {
1361 fprintf(stderr, _("%s: unable to find or open device for: `%s'\n"),
1362 programName, device);
1366 printf(_("%s: expanded name is `%s'\n"), programName, fullName);
1368 /* check for a symbolic link */
1369 /* /proc/mounts doesn't resolve symbolic links */
1370 fullNameOrig = strdup(fullName);
1371 linkName = strdup(fullName); /* ensure linkName is initialized */
1373 while ((linkName = SymLink(fullName)) && (ld > 0)) {
1375 printf(_("%s: `%s' is a link to `%s'\n"), programName,
1376 fullName, linkName);
1378 fullName = strdup(linkName);
1384 /* handle max depth exceeded option */
1386 printf(_("%s: maximum symbolic link depth exceeded: `%s'\n"), programName, fullName);
1390 /* if mount point, get device name */
1391 mounted = MountedDevice(fullName, &mountName, &deviceName);
1394 printf(_("%s: `%s' is mounted at `%s'\n"), programName,
1395 deviceName, mountName);
1397 printf(_("%s: `%s' is not mounted\n"), programName, fullName);
1400 deviceName = strdup(fullName);
1403 /* if not currently mounted, see if it is a possible mount point */
1405 mountable = MountableDevice(fullName, &mountName, &deviceName);
1406 /* if return value -1 then fstab could not be read */
1407 if (v_option && mountable >= 0) {
1409 printf(_("%s: `%s' can be mounted at `%s'\n"), programName, deviceName, mountName);
1411 printf(_("%s: `%s' is not a mount point\n"), programName, fullName);
1415 result = GetMajorMinor(deviceName, NULL, NULL);
1418 _("%s: tried to use `%s' as device name but it is no block device\n"),
1419 programName, deviceName);
1422 } while (result == -1);
1424 /* handle -n option */
1426 printf(_("%s: device is `%s'\n"), programName, deviceName);
1428 printf(_("%s: exiting due to -n/--noop option\n"), programName);
1432 /* Check if device has removable flag*/
1434 printf(_("%s: checking if device \"%s\" has a removable or hotpluggable flag\n"),
1435 programName, deviceName);
1436 if (!CheckRemovable(deviceName) && !CheckHotpluggable(deviceName))
1438 fprintf(stderr, _("%s: device \"%s\" doesn't have a removable or hotpluggable flag\n"),
1439 programName, deviceName);
1443 /* handle -i option */
1445 fd = OpenDevice(deviceName);
1446 ManualEject(fd, i_arg);
1450 /* handle -a option */
1454 printf(_("%s: enabling auto-eject mode for `%s'\n"), programName, deviceName);
1456 printf(_("%s: disabling auto-eject mode for `%s'\n"), programName, deviceName);
1458 fd = OpenDevice(deviceName);
1459 AutoEject(fd, a_arg);
1463 /* handle -t option */
1466 printf(_("%s: closing tray\n"), programName);
1467 fd = OpenDevice(deviceName);
1469 HandleXOption(deviceName);
1473 /* handle -X option */
1476 printf(_("%s: listing CD-ROM speed\n"), programName);
1477 fd = OpenDevice(deviceName);
1478 ListSpeedCdrom(deviceName, fd);
1482 /* handle -x option only */
1483 if (!c_option) HandleXOption(deviceName);
1485 /* unmount device if mounted */
1486 if ((m_option != 1) && mounted) {
1488 printf(_("%s: unmounting device `%s' from `%s'\n"), programName, deviceName, mountName);
1493 /* if it is a multipartition device, unmount any other partitions on
1495 pattern = MultiplePartitions(deviceName);
1496 if ((m_option != 1) && (pattern != 0))
1497 UnmountDevices(pattern);
1500 /* handle -T option */
1503 printf(_("%s: toggling tray\n"), programName);
1504 fd = OpenDevice(deviceName);
1506 HandleXOption(deviceName);
1510 /* handle -c option */
1513 printf(_("%s: selecting CD-ROM disc #%d\n"), programName, c_arg);
1514 fd = OpenDevice(deviceName);
1515 ChangerSelect(fd, c_arg);
1516 HandleXOption(deviceName);
1520 /* if user did not specify type of eject, try all four methods */
1521 if ((r_option + s_option + f_option + q_option) == 0) {
1522 r_option = s_option = f_option = q_option = 1;
1526 fd = OpenDevice(deviceName);
1528 /* try various methods of ejecting until it works */
1531 printf(_("%s: trying to eject `%s' using CD-ROM eject command\n"), programName, deviceName);
1532 worked = EjectCdrom(fd);
1535 printf(_("%s: CD-ROM eject command succeeded\n"), programName);
1537 printf(_("%s: CD-ROM eject command failed\n"), programName);
1541 #ifdef HAVE_EJECT_SCSI
1542 if (s_option && !worked) {
1544 printf(_("%s: trying to eject `%s' using SCSI commands\n"), programName, deviceName);
1545 worked = EjectScsi(fd);
1548 printf(_("%s: SCSI eject succeeded\n"), programName);
1550 printf(_("%s: SCSI eject failed\n"), programName);
1555 #ifdef HAVE_EJECT_FLOPPY
1556 if (f_option && !worked) {
1558 printf(_("%s: trying to eject `%s' using floppy eject command\n"), programName, deviceName);
1559 worked = EjectFloppy(fd);
1562 printf(_("%s: floppy eject command succeeded\n"), programName);
1564 printf(_("%s: floppy eject command failed\n"), programName);
1569 #ifdef HAVE_EJECT_TAPE
1570 if (q_option && !worked) {
1572 printf(_("%s: trying to eject `%s' using tape offline command\n"), programName, deviceName);
1573 worked = EjectTape(fd);
1576 printf(_("%s: tape offline command succeeded\n"), programName);
1578 printf(_("%s: tape offline command failed\n"), programName);
1584 fprintf(stderr, _("%s: unable to eject, last error: %s\n"), programName, strerror(errno));