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>
80 #include <sys/sysmacros.h>
82 /* Used by the ToggleTray() function. If ejecting the tray takes this
83 * time or less, the tray was probably already ejected, so we close it
86 #define TRAY_WAS_ALREADY_OPEN_USECS 200000 /* about 0.2 seconds */
89 #define CLOSE(fd) if (close(fd)==-1) { \
90 perror(programName); \
94 #define FCLOSE(fd) if (fclose(fd)==-1) { \
95 perror(programName); \
100 #define HAVE_EJECT_SCSI
102 #define HAVE_EJECT_FLOPPY
103 #define HAVE_EJECT_TAPE
105 #elif defined(__FreeBSD_kernel__)
106 #include <sys/cdio.h>
107 #endif /* if defined(__linux__) */
110 #define CLOSE(fd) if (close(fd)==-1) { \
111 perror(programName); \
115 #define FCLOSE(fd) if (fclose(fd)==-1) { \
116 perror(programName); \
120 /* Global Variables */
121 static const char *version = VERSION; /* program version */
122 int a_option = 0; /* command flags and arguments */
147 static char *programName; /* used in error messages */
150 * These are the basenames of devices which can have multiple
151 * partitions per device.
153 static const char *partitionDevice[] = {
166 /* Display command usage on standard error and exit. */
169 // perror(_("%s: device is `%s'\n"));
171 "Eject version %s by Jeff Tranter (tranter@pobox.com)\n"
173 " eject -h -- display command usage and exit\n"
174 " eject -V -- display program version and exit\n"
175 " eject [-vnrsfqpm] [<name>] -- eject device\n"
176 " eject [-vn] -d -- display default device\n"
177 " eject [-vn] -a on|off|1|0 [<name>] -- turn auto-eject feature on or off\n"
178 " eject [-vn] -c <slot> [<name>] -- switch discs on a CD-ROM changer\n"
179 " eject [-vn] -t [<name>] -- close tray\n"
180 " eject [-vn] -T [<name>] -- toggle tray\n"
181 " eject [-vn] -i on|off|1|0 [<name>] -- toggle manual eject protection on/off\n"
182 " eject [-vn] -x <speed> [<name>] -- set CD-ROM max speed\n"
183 " eject [-vn] -X [<name>] -- list CD-ROM available speeds\n"
185 " -v\t-- enable verbose output\n"
186 " -n\t-- don't eject, just show device found\n"
187 " -r\t-- eject CD-ROM\n"
188 #ifdef HAVE_EJECT_SCSI
189 " -s\t-- eject SCSI device\n"
191 #ifdef HAVE_EJECT_FLOPPY
192 " -f\t-- eject floppy\n"
194 #ifdef HAVE_EJECT_TAPE
195 " -q\t-- eject tape\n"
197 " -p\t-- use /proc/mounts instead of /etc/mtab\n"
198 " -m\t-- do not unmount device even if it is mounted\n"
204 " -h --help -v --verbose -d --default\n"
205 " -a --auto -c --changerslot -t --trayclose -x --cdspeed\n"
207 #ifdef HAVE_EJECT_SCSI
210 #ifdef HAVE_EJECT_FLOPPY
214 #ifdef HAVE_EJECT_TAPE
218 " -n --noop -V --version\n"
219 " -p --proc -m --no-unmount -T --traytoggle -i --manualeject\n"));
220 #endif /* GETOPTLONG */
222 "Parameter <name> can be a device file or a mount point.\n"
223 "If omitted, name defaults to `%s'.\n"
224 "By default tries -r, -s, -f, and -q in order until success.\n"),
230 /* Handle command line options. */
231 static void parse_args(int argc, char **argv, char **device)
233 const char *flags = "a:c:x:i:dfhnqrstTXvVpm";
235 static struct option long_options[] =
237 {"help", no_argument, NULL, 'h'},
238 {"verbose", no_argument, NULL, 'v'},
239 {"default", no_argument, NULL, 'd'},
240 {"auto", required_argument, NULL, 'a'},
241 {"changerslot", required_argument, NULL, 'c'},
242 {"manualeject", required_argument, NULL, 'i'},
243 {"trayclose", no_argument, NULL, 't'},
244 {"traytoggle", no_argument, NULL, 'T'},
245 {"cdspeed", required_argument, NULL, 'x'},
246 {"listspeed", no_argument, NULL, 'X'},
247 {"noop", no_argument, NULL, 'n'},
248 {"cdrom", no_argument, NULL, 'r'},
249 {"scsi", no_argument, NULL, 's'},
250 {"floppy", no_argument, NULL, 'f'},
251 {"tape", no_argument, NULL, 'q'},
252 {"version", no_argument, NULL, 'V'},
253 {"proc", no_argument, NULL, 'p'},
254 {"no-unmount", no_argument, NULL, 'm'},
258 #endif /* GETOPTLONG */
262 while ((c = getopt_long(argc, argv, flags, long_options, &option_index)) != EOF) {
264 while ((c = getopt(argc, argv, flags)) != EOF) {
265 #endif /* GETOPTLONG */
269 if (!strcmp(optarg, "0"))
271 else if (!strcmp(optarg, "off"))
273 else if (!strcmp(optarg, "1"))
275 else if (!strcmp(optarg, "on"))
278 fprintf(stderr, _("%s: invalid argument to --auto/-a option\n"), programName);
284 /* atoi() returns 0 on error, so "0" must be parsed separately */
285 if (!strcmp(optarg, "0"))
288 c_arg = atoi(optarg);
290 fprintf(stderr, _("%s: invalid argument to --changerslot/-c option\n"), programName);
297 if (!strcmp(optarg, "0"))
300 x_arg = atoi(optarg);
302 fprintf(stderr, _("%s: invalid argument to --cdspeed/-x option\n"), programName);
319 if (!strcmp(optarg, "0"))
321 else if (!strcmp(optarg, "off"))
323 else if (!strcmp(optarg, "1"))
325 else if (!strcmp(optarg, "on"))
328 fprintf(stderr, _("%s: invalid argument to -i option\n"), programName);
363 printf(_("eject version %s by Jeff Tranter (tranter@pobox.com)\n"), version);
371 /* check for a single additional argument */
372 if ((argc - optind) > 1) {
373 fprintf(stderr, _("%s: too many arguments\n"), programName);
376 if ((argc - optind) == 1) { /* one argument */
377 *device = strdup(argv[optind]);
382 /* Return 1 if file/device exists, 0 otherwise. */
383 static int FileExists(const char *name, const int try, int *found)
386 if (!found) return -1;
388 * access() uses the UID, not the EUID. This way a normal user
389 * cannot find out if a file (say, /root/fubar) exists or not, even
390 * if eject is SUID root
392 if (access (name, F_OK) == 0) {
405 * Linux mangles spaces in mount points by changing them to an octal string
406 * of '\040'. So lets scan the mount point and fix it up by replacing all
407 * occurrences off '\0##' with the ASCII value of 0##. Requires a writable
408 * string as input as we mangle in place. Some of this was taken from the
409 * util-linux package.
411 #define octalify(a) ((a) & 7)
412 #define tooctal(s) (64*octalify(s[1]) + 8*octalify(s[2]) + octalify(s[3]))
413 #define isoctal(a) (((a) & ~7) == '0')
414 static char *DeMangleMount(char *s)
417 while ((tmp = strchr(tmp, '\\')) != NULL) {
418 if (isoctal(tmp[1]) && isoctal(tmp[2]) && isoctal(tmp[3])) {
419 tmp[0] = tooctal(tmp);
420 memmove(tmp+1, tmp+4, strlen(tmp)-3);
429 * Given name, such as foo, see if any of the following exist:
431 * foo (if foo starts with '.' or '/')
441 * If found, return the full path. If not found, return 0.
442 * Returns pointer to dynamically allocated string.
444 static char *FindDevice(const char *name)
450 buf = (char *) malloc(strlen(name)+14); /* to allow for "/dev/cdroms/ + "0" + null */
452 fprintf(stderr, _("%s: could not allocate memory\n"), programName);
456 if (try == INT_MAX) {
457 fprintf(stderr, _("%s: FindDevice called too often\n"), programName );;
462 if ((name[0] == '.') || (name[0] == '/')) {
464 if (FileExists(buf, try, &found))
468 strcpy(buf, "/dev/");
470 if (FileExists(buf, try, &found))
473 strcpy(buf, "/media/");
475 if (FileExists(buf, try, &found))
478 strcpy(buf, "/mnt/");
480 if (FileExists(buf, try, &found))
483 /* for devfs under Linux */
484 strcpy(buf, "/dev/cdroms/");
486 if (FileExists(buf, try, &found))
489 strcpy(buf, "/dev/cdroms/");
492 if (FileExists(buf, try, &found))
495 /* for devfs under Solaris */
496 strcpy(buf, "/dev/rdsk/");
498 if (FileExists(buf, try, &found))
501 strcpy(buf, "/dev/dsk/");
503 if (FileExists(buf, try, &found))
508 if (FileExists(buf, try, &found))
518 * Stops CDROM from opening on manual eject pressing the button.
519 * This can be useful when you carry your laptop
520 * in your bag while it's on and no CD inserted in it's drive.
521 * Implemented as found in Documentation/ioctl/cdrom.txt
523 * TODO: Maybe we should check this also:
524 * EDRIVE_CANT_DO_THIS Door lock function not supported.
525 * EBUSY Attempt to unlock when multiple users
526 * have the drive open and not CAP_SYS_ADMIN
528 static void ManualEject(int fd, int onOff)
530 if (ioctl(fd, CDROM_LOCKDOOR, onOff) < 0) {
531 perror("ioctl on CDROM_LOCKDOOR");
534 printf("CD-Drive may NOT be ejected with device button\n");
536 printf("CD-Drive may be ejected with device button\n");
541 /* Set or clear auto-eject mode. */
542 static void AutoEject(int fd, int onOff)
546 #if defined(CDROM_SET_OPTIONS) && defined(CDROM_CLEAR_OPTIONS)
548 status = ioctl(fd, CDROM_SET_OPTIONS, CDO_AUTO_EJECT);
550 status = ioctl(fd, CDROM_CLEAR_OPTIONS, CDO_AUTO_EJECT);
555 fprintf(stderr, _("%s: CD-ROM auto-eject command failed: %s\n"), programName, strerror(errno));
562 * Changer select. CDROM_SELECT_DISC is preferred, older kernels used
565 static void ChangerSelect(int fd, int slot)
569 #ifdef CDROM_SELECT_DISC
570 status = ioctl(fd, CDROM_SELECT_DISC, slot);
572 fprintf(stderr, _("%s: CD-ROM select disc command failed: %s\n"), programName, strerror(errno));
575 #elif defined CDROMLOADFROMSLOT
576 status = ioctl(fd, CDROMLOADFROMSLOT, slot);
578 fprintf(stderr, _("%s: CD-ROM load from slot command failed: %s\n"), programName, strerror(errno));
582 fprintf(stderr, _("%s: IDE/ATAPI CD-ROM changer not supported by this kernel\n"), programName);
588 * Close tray. Not supported by older kernels.
590 static void CloseTray(int fd)
594 #if defined(CDROMCLOSETRAY) || defined(CDIOCCLOSE)
595 #if defined(CDROMCLOSETRAY)
596 status = ioctl(fd, CDROMCLOSETRAY);
597 #elif defined(CDIOCCLOSE)
598 status = ioctl(fd, CDIOCCLOSE);
601 fprintf(stderr, _("%s: CD-ROM tray close command failed: %s\n"), programName, strerror(errno));
605 fprintf(stderr, _("%s: CD-ROM tray close command not supported by this kernel\n"), programName);
612 * Written by Benjamin Schwenk <benjaminschwenk@yahoo.de> and
613 * Sybren Stuvel <sybren@thirdtower.com>
615 * Not supported by older kernels because it might use
619 static void ToggleTray(int fd)
621 struct timeval time_start, time_stop;
624 #ifdef CDROMCLOSETRAY
626 /* Try to open the CDROM tray and measure the time needed.
627 * In my experience the function needs less than 0.05
628 * seconds if the tray was already open, and at least 1.5 seconds
631 gettimeofday(&time_start, NULL);
633 /* Send the CDROMEJECT command to the device. */
634 if (ioctl(fd, CDROMEJECT, 0) < 0) {
639 /* Get the second timestamp, to measure the time needed to open
641 gettimeofday(&time_stop, NULL);
643 time_elapsed = (time_stop.tv_sec * 1000000 + time_stop.tv_usec) -
644 (time_start.tv_sec * 1000000 + time_start.tv_usec);
646 /* If the tray "opened" too fast, we can be nearly sure, that it
647 * was already open. In this case, close it now. Else the tray was
648 * closed before. This would mean that we are done. */
649 if (time_elapsed < TRAY_WAS_ALREADY_OPEN_USECS)
653 fprintf(stderr, _("%s: CD-ROM tray toggle command not supported by this kernel\n"), programName);
659 * Select Speed of CD-ROM drive.
660 * Thanks to Roland Krivanek (krivanek@fmph.uniba.sk)
661 * http://dmpc.dbp.fmph.uniba.sk/~krivanek/cdrom_speed/
663 static void SelectSpeedCdrom(int fd, int speed)
667 #ifdef CDROM_SELECT_SPEED
668 status = ioctl(fd, CDROM_SELECT_SPEED, speed);
670 fprintf(stderr, _("%s: CD-ROM select speed command failed: %s\n"), programName, strerror(errno));
674 fprintf(stderr, _("%s: CD-ROM select speed command not supported by this kernel\n"), programName);
679 * Read Speed of CD-ROM drive. From Linux 2.6.13, the current speed is correctly reported
681 static int ReadSpeedCdrom(const char *shortName)
684 char *str_speed, *str_name;
685 int drive_number = -1, i;
686 FILE *f = fopen("/proc/sys/dev/cdrom/info", "r");
689 fprintf(stderr, _("%s: unable to read the speed from /proc/sys/dev/cdrom/info\n"), programName);
694 fgets(line, sizeof(line), f);
696 /* find drive number from shortName in line "drive name" */
697 if (drive_number == -1) {
698 if (strncmp(line, "drive name:", 11) == 0) {
699 str_name = strtok(&line[11], "\t ");
701 while (strncmp(shortName, str_name, strlen(shortName)) != 0) {
703 str_name = strtok(NULL, "\t ");
704 if (str_name == NULL) {
705 fprintf(stderr, _("%s: error while finding CD-ROM name\n"), programName);
710 /* find line "drive speed" and read the correct speed */
712 if (strncmp(line, "drive speed:", 12) == 0) {
713 str_speed = strtok(&line[12], "\t ");
714 for (i = 1; i < drive_number; i++)
715 str_speed = strtok(NULL, "\t ");
717 if (str_speed == NULL) {
718 fprintf(stderr, _("%s: error while reading speed\n"), programName);
721 return atoi(str_speed);
726 fprintf(stderr, _("%s: error while reading speed\n"), programName);
733 * List Speed of CD-ROM drive.
735 static void ListSpeedCdrom(const char *fullName, int fd)
737 #ifdef CDROM_SELECT_SPEED
738 int max_speed, curr_speed = 0, prev_speed;
739 char *shortName = strrchr(fullName, '/') + 1;
741 SelectSpeedCdrom(fd, 0);
742 max_speed = ReadSpeedCdrom(shortName);
743 while (curr_speed < max_speed) {
744 prev_speed = curr_speed;
745 SelectSpeedCdrom(fd, prev_speed + 1);
746 curr_speed = ReadSpeedCdrom(shortName);
747 if (curr_speed > prev_speed)
748 printf("%d ", curr_speed);
750 curr_speed = prev_speed + 1;
755 fprintf(stderr, _("%s: CD-ROM select speed command not supported by this kernel\n"), programName);
760 * Eject using CDROMEJECT ioctl. Return 1 if successful, 0 otherwise.
762 static int EjectCdrom(int fd)
766 #if defined(CDROMEJECT)
767 status = ioctl(fd, CDROMEJECT);
768 #elif defined(CDIOCEJECT)
769 status = ioctl(fd, CDIOCEJECT);
771 /* Some kernels implement cdrom-eject only, but I don't think any kernel in the
772 world would implement eject only for non-cdrom drives. Let's die. */
775 return (status == 0);
778 #ifdef HAVE_EJECT_SCSI
780 * Eject using SCSI SG_IO commands. Return 1 if successful, 0 otherwise.
782 static int EjectScsi(int fd)
786 unsigned char allowRmBlk[6] = {ALLOW_MEDIUM_REMOVAL, 0, 0, 0, 0, 0};
787 unsigned char startStop1Blk[6] = {START_STOP, 0, 0, 0, 1, 0};
788 unsigned char startStop2Blk[6] = {START_STOP, 0, 0, 0, 2, 0};
789 unsigned char inqBuff[2];
790 unsigned char sense_buffer[32];
792 if ((ioctl(fd, SG_GET_VERSION_NUM, &k) < 0) || (k < 30000)) {
794 printf(_("not an sg device, or old sg driver\n"));
799 memset(&io_hdr, 0, sizeof(sg_io_hdr_t));
800 io_hdr.interface_id = 'S';
802 io_hdr.mx_sb_len = sizeof(sense_buffer);
803 io_hdr.dxfer_direction = SG_DXFER_NONE;
804 io_hdr.dxfer_len = 0;
805 io_hdr.dxferp = inqBuff;
806 io_hdr.sbp = sense_buffer;
807 io_hdr.timeout = 10000;
809 io_hdr.cmdp = allowRmBlk;
810 status = ioctl(fd, SG_IO, (void *)&io_hdr);
814 io_hdr.cmdp = startStop1Blk;
815 status = ioctl(fd, SG_IO, (void *)&io_hdr);
819 io_hdr.cmdp = startStop2Blk;
820 status = ioctl(fd, SG_IO, (void *)&io_hdr);
824 /* force kernel to reread partition table when new disc inserted */
825 status = ioctl(fd, BLKRRPART);
831 #ifdef HAVE_EJECT_FLOPPY
833 * Eject using FDEJECT ioctl. Return 1 if successful, 0 otherwise.
835 static int EjectFloppy(int fd)
839 status = ioctl(fd, FDEJECT);
840 return (status >= 0);
845 #ifdef HAVE_EJECT_TAPE
847 * Eject using tape ioctl. Return 1 if successful, 0 otherwise.
849 static int EjectTape(int fd)
854 op.mt_op = MTOFFL; /* rewind and eject */
855 op.mt_count = 0; /* not used */
856 status = ioctl(fd, MTIOCTOP, &op);
857 return (status >= 0);
862 /* Unmount a device. */
863 static void Unmount(const char *fullName)
869 setuid(getuid()); /* reduce likelyhood of security holes when running setuid */
871 execlp("pumount", "pumount", fullName, "-n", NULL);
872 execlp("umount", "umount", fullName, "-n", NULL);
874 execlp("pumount", "pumount", fullName, NULL);
875 execlp("umount", "umount", fullName, NULL);
877 fprintf(stderr, _("%s: unable to exec umount of `%s': %s\n"),
878 programName, fullName, strerror(errno));
882 fprintf(stderr, _("%s: unable to fork: %s\n"), programName, strerror(errno));
884 default: /* parent */
886 if (WIFEXITED(status) == 0) {
887 fprintf(stderr, _("%s: unmount of `%s' did not exit normally\n"), programName, fullName);
890 if (WEXITSTATUS(status) != 0) {
891 fprintf(stderr, _("%s: unmount of `%s' failed\n"), programName, fullName);
899 /* Open a device file. Try opening first read/write, and if that fails then read only. */
900 static int OpenDevice(const char *fullName)
904 fd = open(fullName, O_RDWR|O_NONBLOCK);
909 fd = open(fullName, O_RDONLY|O_NONBLOCK);
911 fprintf(stderr, _("%s: unable to open `%s'\n"), programName, fullName);
919 * Get major and minor device numbers for a device file name, so we
920 * can check for duplicate devices.
922 static int GetMajorMinor(const char *name, int *maj, int *min)
927 if (stat(name, &sstat) == -1)
929 if (! S_ISBLK(sstat.st_mode) && ! S_ISCHR(sstat.st_mode))
931 if (maj) *maj = major(sstat.st_rdev);
932 if (min) *min = minor(sstat.st_rdev);
938 * See if device has been mounted by looking in mount table. If so, set
939 * device name and mount point name, and return 1, otherwise return 0.
941 static int MountedDevice(const char *name, char **mountName, char **deviceName)
952 GetMajorMinor(name, &maj, &min);
954 fp = fopen((p_option ? "/proc/mounts" : "/etc/mtab"), "r");
957 fprintf(stderr, _("unable to open %s: %s\n"), (p_option ? "/proc/mounts" : "/etc/mtab"), strerror(errno));
961 while (fgets(line, sizeof(line), fp) != 0) {
962 rc = sscanf(line, "%1023s %1023s", s1, s2);
964 int mtabmaj, mtabmin;
967 GetMajorMinor(s1, &mtabmaj, &mtabmin);
968 if (((strcmp(s1, name) == 0) || (strcmp(s2, name) == 0)) ||
969 ((maj != -1) && (maj == mtabmaj) && (min == mtabmin))) {
971 *deviceName = strdup(s1);
972 *mountName = strdup(s2);
985 * See if device can be mounted by looking in /etc/fstab.
986 * If so, set device name and mount point name, and return 1,
987 * otherwise return 0.
989 static int MountableDevice(const char *name, char **mountName, char **deviceName)
997 fp = fopen("/etc/fstab", "r");
1000 * /etc/fstab may be unreadable in some situations due to passwords in the
1003 /* fprintf(stderr, _("%s: unable to open /etc/fstab: %s\n"), programName, strerror(errno));
1006 printf( _("%s: unable to open /etc/fstab: %s\n"), programName, strerror(errno));
1011 while (fgets(line, sizeof(line), fp) != 0) {
1012 rc = sscanf(line, "%1023s %1023s", s1, s2);
1015 if (rc >= 2 && s1[0] != '#' && strcmp(s2, name) == 0) {
1017 *deviceName = strdup(s1);
1018 *mountName = strdup(s2);
1029 * Step through mount table and unmount all devices that match a regular
1032 static void UnmountDevices(const char *pattern)
1041 if (regcomp(&preg, pattern, REG_EXTENDED)!=0) {
1042 perror(programName);
1046 fp = fopen((p_option ? "/proc/mounts" : "/etc/mtab"), "r");
1049 fprintf(stderr, _("unable to open %s: %s\n"),(p_option ? "/proc/mounts" : "/etc/mtab"), strerror(errno));
1053 while (fgets(line, sizeof(line), fp) != 0) {
1054 status = sscanf(line, "%1023s %1023s", s1, s2);
1056 status = regexec(&preg, s1, 0, 0, 0);
1059 printf(_("%s: unmounting `%s'\n"), programName, s2);
1070 /* Check if name is a symbolic link. If so, return what it points to. */
1071 static char *SymLink(const char *name)
1077 char result[PATH_MAX];
1080 memset(s1, 0, sizeof(s1));
1081 memset(s2, 0, sizeof(s2));
1082 memset(s4, 0, sizeof(s4));
1083 memset(result, 0, sizeof(result));
1085 status = readlink(name, s1, sizeof(s1) - 1);
1091 if (s1[0] == '/') { /* absolute link */
1093 } else { /* relative link, add base name */
1094 strncpy(s2, name, sizeof(s2)-1);
1095 s3 = strrchr(s2, '/');
1098 snprintf(result, sizeof(result)-1, "%s%s", s2, s1);
1101 realpath(result, s4);
1108 * Given a name, see if it matches a pattern for a device that can have
1109 * multiple partitions. If so, return a regular expression that matches
1110 * partitions for that device, otherwise return 0.
1112 static char *MultiplePartitions(const char *name)
1120 for (i = 0; partitionDevice[i] != 0; i++) {
1121 /* look for ^/dev/foo[a-z]([0-9]?[0-9])?$, e.g. /dev/hda1 */
1122 strcpy(pattern, "^/dev/");
1123 strcat(pattern, partitionDevice[i]);
1124 strcat(pattern, "[a-z]([0-9]?[0-9])?$");
1125 if (regcomp(&preg, pattern, REG_EXTENDED|REG_NOSUB) != 0) {
1126 perror(programName);
1129 status = regexec(&preg, name, 1, 0, 0);
1132 result = (char *) malloc(strlen(name) + 25);
1133 if (result == NULL) {
1134 fprintf(stderr, _("%s: could not allocate memory\n"), programName);
1137 strcpy(result, name);
1138 result[strlen(partitionDevice[i]) + 6] = 0;
1139 strcat(result, "([0-9]?[0-9])?$");
1141 printf(_("%s: `%s' is a multipartition device\n"), programName, name);
1146 printf(_("%s: `%s' is not a multipartition device\n"), programName, name);
1152 * Find device name in /sys/block/. Returns NULL if not
1153 * found. The returned pointer must be free()'d.
1155 static char* FindDeviceSysBlock(const char* deviceName)
1157 DIR *dir = opendir("/sys/block");
1159 const char *baseName = strrchr(deviceName, '/');
1163 baseName = baseName ? baseName + 1 : deviceName;
1165 fprintf(stderr, _("%s: can not open directory /sys/block/"), programName);
1168 while ((d = readdir(dir)) != NULL) {
1169 if (d->d_type != DT_DIR && d->d_type != DT_LNK && d->d_type != DT_UNKNOWN)
1171 len = strlen(d->d_name);
1172 if (!strncmp(baseName, d->d_name, len)) {
1173 if ((*(baseName+len) >= '0' &&
1174 *(baseName+len) <= '9') ||
1175 *(baseName+len) == '\0') {
1176 device = strdup(d->d_name);
1187 * From given path gets a subsystem. Returns subsystem if any found
1188 * otherwise returns NULL. Returned value must not be free()'d
1190 static char *GetSubSystem(const char *sysfspath)
1192 static char subsystem[PATH_MAX];
1193 char link_subsystem[PATH_MAX];
1197 snprintf(link_subsystem, sizeof(link_subsystem), "%s/subsystem", sysfspath);
1199 if (lstat(link_subsystem, &buf) == -1)
1201 if (!S_ISLNK(buf.st_mode))
1203 if (readlink(link_subsystem, subsystem, sizeof(subsystem)) == -1)
1205 if ((pos = strrchr(subsystem, '/')) == NULL)
1207 strncpy(subsystem, pos+1, sizeof(subsystem));
1213 * Check content of /sys/block/<dev>/removable. Returns 1 if the file
1214 * contains '1' otherwise returns 0.
1216 static int CheckRemovable(const char* deviceName)
1221 char path[PATH_MAX];
1223 if ((device = FindDeviceSysBlock(deviceName)) == NULL) {
1225 _("%s: did not find a device %s in /sys/block/\n"),
1226 programName, deviceName);
1229 snprintf(path, sizeof(path), "/sys/block/%s/removable", device);
1231 if((fp = fopen(path, "r")) == NULL)
1233 if (fgetc(fp) == '1')
1240 /* Check if a device is on hotpluggable subsystem. Returns 1 if is
1241 * otherwise returns 0.
1243 static int CheckHotpluggable(const char* deviceName)
1245 int hotpluggable = 0;
1247 char path[PATH_MAX];
1253 if ((device = FindDeviceSysBlock(deviceName)) == NULL) {
1254 fprintf(stderr, _("%s: did not find a device %s in /sys/block/\n"),
1255 programName, deviceName);
1258 snprintf(path, sizeof(path), "/sys/block/%s/device", device);
1261 if (lstat(path, &buf) == -1)
1262 return hotpluggable;
1263 if (!S_ISLNK(buf.st_mode))
1264 return hotpluggable;
1265 if ((device_chain = SymLink(path)) == NULL)
1266 return hotpluggable;
1267 while ( strncmp(device_chain, "", sizeof(device_chain) != 0)) {
1268 subsystem = GetSubSystem(device_chain);
1270 /* as hotpluggable we assume devices on these buses */
1271 if (strncmp("usb", subsystem, sizeof("usb")) == 0 ||
1272 strncmp("ieee1394", subsystem, sizeof("ieee1394")) == 0 ||
1273 strncmp("pcmcia", subsystem, sizeof("pcmcia")) == 0 ||
1274 strncmp("mmc", subsystem, sizeof("mmc")) == 0 ||
1275 strncmp("ccw", subsystem, sizeof("ccw")) == 0) {
1280 /* remove one member from devicechain */
1281 pos = strrchr(device_chain, '/');
1285 device_chain[0] = '\0';
1288 return hotpluggable;
1291 /* handle -x option */
1292 static void HandleXOption(char *deviceName)
1294 int fd; /* file descriptor for device */
1299 printf(_("%s: setting CD-ROM speed to auto\n"), programName);
1301 printf(_("%s: setting CD-ROM speed to %dX\n"), programName, x_arg);
1303 fd = OpenDevice(deviceName);
1304 SelectSpeedCdrom(fd, x_arg);
1311 int main(int argc, char **argv)
1313 const char *defaultDevice = DEFAULTDEVICE; /* default if no name passed by user */
1314 int worked = 0; /* set to 1 when successfully ejected */
1315 char *device = 0; /* name passed from user */
1316 char *fullName; /* expanded name */
1317 char *fullNameOrig;/* expanded name (links not resolved) */
1318 char *deviceName; /* name of device */
1319 char *linkName; /* name of device's symbolic link */
1320 char *mountName; /* name of device's mount point */
1321 int fd; /* file descriptor for device */
1322 int mounted = 0; /* true if device is mounted */
1323 int mountable = 0; /* true if device is in /etc/fstab */
1324 int result = 0; /* store the result of a operation */
1325 char *pattern = 0; /* regex for device if multiple partitions */
1326 int ld = 6; /* symbolic link max depth */
1330 /* program name is global variable used by other procedures */
1331 programName = strdup(argv[0]);
1333 /* parse the command line arguments */
1334 parse_args(argc, argv, &device);
1337 /* handle -d option */
1339 printf(_("%s: default device: `%s'\n"), programName, defaultDevice);
1343 /* if no device, use default */
1345 device = strdup(defaultDevice);
1347 printf(_("%s: using default device `%s'\n"), programName, device);
1350 /* Strip any trailing slash from name in case user used bash/tcsh
1351 style filename completion (e.g. /mnt/cdrom/) */
1352 if (device[strlen(device)-1] == '/')
1353 device[strlen(device)-1] = 0;
1356 printf(_("%s: device name is `%s'\n"), programName, device);
1359 /* figure out full device or mount point name */
1360 fullName = FindDevice(device);
1361 if (fullName == 0) {
1362 fprintf(stderr, _("%s: unable to find or open device for: `%s'\n"),
1363 programName, device);
1367 printf(_("%s: expanded name is `%s'\n"), programName, fullName);
1369 /* check for a symbolic link */
1370 /* /proc/mounts doesn't resolve symbolic links */
1371 fullNameOrig = strdup(fullName);
1372 linkName = strdup(fullName); /* ensure linkName is initialized */
1374 while ((linkName = SymLink(fullName)) && (ld > 0)) {
1376 printf(_("%s: `%s' is a link to `%s'\n"), programName,
1377 fullName, linkName);
1379 fullName = strdup(linkName);
1385 /* handle max depth exceeded option */
1387 printf(_("%s: maximum symbolic link depth exceeded: `%s'\n"), programName, fullName);
1391 /* if mount point, get device name */
1392 mounted = MountedDevice(fullName, &mountName, &deviceName);
1395 printf(_("%s: `%s' is mounted at `%s'\n"), programName,
1396 deviceName, mountName);
1398 printf(_("%s: `%s' is not mounted\n"), programName, fullName);
1401 deviceName = strdup(fullName);
1404 /* if not currently mounted, see if it is a possible mount point */
1406 mountable = MountableDevice(fullName, &mountName, &deviceName);
1407 /* if return value -1 then fstab could not be read */
1408 if (v_option && mountable >= 0) {
1410 printf(_("%s: `%s' can be mounted at `%s'\n"), programName, deviceName, mountName);
1412 printf(_("%s: `%s' is not a mount point\n"), programName, fullName);
1416 result = GetMajorMinor(deviceName, NULL, NULL);
1419 _("%s: tried to use `%s' as device name but it is no block device\n"),
1420 programName, deviceName);
1423 } while (result == -1);
1425 /* handle -n option */
1427 printf(_("%s: device is `%s'\n"), programName, deviceName);
1429 printf(_("%s: exiting due to -n/--noop option\n"), programName);
1433 /* Check if device has removable flag*/
1435 printf(_("%s: checking if device \"%s\" has a removable or hotpluggable flag\n"),
1436 programName, deviceName);
1437 if (!CheckRemovable(deviceName) && !CheckHotpluggable(deviceName))
1439 fprintf(stderr, _("%s: device \"%s\" doesn't have a removable or hotpluggable flag\n"),
1440 programName, deviceName);
1444 /* handle -i option */
1446 fd = OpenDevice(deviceName);
1447 ManualEject(fd, i_arg);
1451 /* handle -a option */
1455 printf(_("%s: enabling auto-eject mode for `%s'\n"), programName, deviceName);
1457 printf(_("%s: disabling auto-eject mode for `%s'\n"), programName, deviceName);
1459 fd = OpenDevice(deviceName);
1460 AutoEject(fd, a_arg);
1464 /* handle -t option */
1467 printf(_("%s: closing tray\n"), programName);
1468 fd = OpenDevice(deviceName);
1470 HandleXOption(deviceName);
1474 /* handle -X option */
1477 printf(_("%s: listing CD-ROM speed\n"), programName);
1478 fd = OpenDevice(deviceName);
1479 ListSpeedCdrom(deviceName, fd);
1483 /* handle -x option only */
1484 if (!c_option) HandleXOption(deviceName);
1486 /* unmount device if mounted */
1487 if ((m_option != 1) && mounted) {
1489 printf(_("%s: unmounting device `%s' from `%s'\n"), programName, deviceName, mountName);
1494 /* if it is a multipartition device, unmount any other partitions on
1496 pattern = MultiplePartitions(deviceName);
1497 if ((m_option != 1) && (pattern != 0))
1498 UnmountDevices(pattern);
1501 /* handle -T option */
1504 printf(_("%s: toggling tray\n"), programName);
1505 fd = OpenDevice(deviceName);
1507 HandleXOption(deviceName);
1511 /* handle -c option */
1514 printf(_("%s: selecting CD-ROM disc #%d\n"), programName, c_arg);
1515 fd = OpenDevice(deviceName);
1516 ChangerSelect(fd, c_arg);
1517 HandleXOption(deviceName);
1521 /* if user did not specify type of eject, try all four methods */
1522 if ((r_option + s_option + f_option + q_option) == 0) {
1523 r_option = s_option = f_option = q_option = 1;
1527 fd = OpenDevice(deviceName);
1529 /* try various methods of ejecting until it works */
1532 printf(_("%s: trying to eject `%s' using CD-ROM eject command\n"), programName, deviceName);
1533 worked = EjectCdrom(fd);
1536 printf(_("%s: CD-ROM eject command succeeded\n"), programName);
1538 printf(_("%s: CD-ROM eject command failed\n"), programName);
1542 #ifdef HAVE_EJECT_SCSI
1543 if (s_option && !worked) {
1545 printf(_("%s: trying to eject `%s' using SCSI commands\n"), programName, deviceName);
1546 worked = EjectScsi(fd);
1549 printf(_("%s: SCSI eject succeeded\n"), programName);
1551 printf(_("%s: SCSI eject failed\n"), programName);
1556 #ifdef HAVE_EJECT_FLOPPY
1557 if (f_option && !worked) {
1559 printf(_("%s: trying to eject `%s' using floppy eject command\n"), programName, deviceName);
1560 worked = EjectFloppy(fd);
1563 printf(_("%s: floppy eject command succeeded\n"), programName);
1565 printf(_("%s: floppy eject command failed\n"), programName);
1570 #ifdef HAVE_EJECT_TAPE
1571 if (q_option && !worked) {
1573 printf(_("%s: trying to eject `%s' using tape offline command\n"), programName, deviceName);
1574 worked = EjectTape(fd);
1577 printf(_("%s: tape offline command succeeded\n"), programName);
1579 printf(_("%s: tape offline command failed\n"), programName);
1585 fprintf(stderr, _("%s: unable to eject, last error: %s\n"), programName, strerror(errno));