Fucked laptop CD-ROM eject button

It would appear that the whole business of ejecting CD-ROMs (or DVDs) from a laptop using the eject button on the drive itself is a complete dog's dinner. For some people, it doesn't work, and for other people, it does. But crossing from one group into the other is like crossing the tachyon wall - everyone wants to do it, but nobody can. Google brings up many, many instances of people making futile requests for help in crossing it one way or the other, and bugger all by way of answers. Hence this page.

Note that this page is addressed to people for whom it doesn't work and who wish it did. It does not cater for the people for whom it does work and who wish it didn't, for three reasons: (1) for that case you can fudge it by running eject -i 1 from a terminal or the "Run" dialogue most of the time; (2) I don't have the problem so I haven't investigated it beyond seeing stuff on Google while looking up the opposite problem; (3) they mostly want it for stupid reasons, like preventing the button from doing anything if it accidentally gets pushed while the laptop is in a bag so they can get away with leaving it switched on so the battery runs down faster, and bollocks to that.

Also, the problem occurred on a Debian system running LXDE, so if you haven't got one like that this page may not be a complete or correct answer.

One thing that seems to crop up a lot is this. I found a bunch of guff concerning these lines in /lib/udev/rules.d/60-cdrom_id.rules:

# import device and media properties and lock tray to # enable the receiving of media eject button events IMPORT{program}="cdrom_id --lock-media $devnode"

People keep suggesting either removing the --lock-media option or putting it back in, and they suggest both possibilities as solutions to either problem, which is, like, really useful, or nut. I tried fucking with it anyway and it didn't make any difference. On further investigation I found that according to udevadm monitor pressing the eject button never generated any kind of event on my laptop under any circumstances, and then looking through the list of ioctls in /usr/include/linux/cdrom.h it seemed there was no way to query the drive and ask it if the button had been pressed either. So the approach of using software to detect the button being pressed and respond by unmounting the disc and then ejecting the drive, which is what I had been wanting to do, appeared to be coal-boxed.

So I figured I'd have to do it in a kludgey and uukous manner instead of doing it properly, and then I went and did it in a kludgey and uukous manner. But it seems to be working fine so far. Of course one big advantage of read-only media is that you can't fuck them up from software...

On my machine, udev does pick up an event when a disc is inserted; it's only ejecting that it doesn't see. So automounting using udev and spacefm (I am using that instead of pcmanfm because pcmanfm keeps going bonkers and eating 100% CPU whereas spacefm does not do that but does still seem to do everything I want and is faster anyway) works as standard.

Some pages I found directed the questioner to other pages which purported to give a solution using hal and/or udisks. I didn't bother to read any of them because I don't have either of those on the system and I am keen to keep it that way because the laptop is very old and shit and has a 32GB hard disk and a 1.5GHz Celeron which has to cope with decrypting everything it gets off the hard drive before it can start doing anything with it and you can't put more than 512MB of RAM in it so I want to keep the installation as lean as possible. And in any case since there's no way of knowing that this machine wants the CD ejected other than making it eject it and catching the error somehow I can't see those solutions avoiding being just as kludgey and uukous as mine would be. So instead I wrote a wee proggie myself for which the executable is only 10k in size and basically does nothing except issue 3 system calls every 4 seconds so its resource usage is minimal.

The ickle proglet, named cddrvmon (for "CD drive monitor"), is fired up by udev when it sees a new disk going in. It checks that the device it's been called for is indeed a block device, since it can do that without having to wait while the drive fucks about, and then daemonises itself to get its arse out of the way and let udev get on with its shit. It makes sure the "door" is not "locked" (which is the poncey terminology used, despite the absence of either a lock or a door, to mean "the eject button is not disabled"), checks /proc/mounts for up to a minute (I guess /etc/mtab would do as well) waiting for the CD to be mounted, and then once that has happened, it looks at the CD drive every 4 seconds to see if there's still a CD in it (4 seconds is easily often enough given how long it takes for the drive to spin down and that it starts reporting "tray open" even while it is still spinning down). When it sees the CD is gone, it tries to unmount it, first with a "normal" unmount, then, if that fails, with a "lazy" unmount, and finally, if that too fails, with a "forced" unmount. If that last fails as well, then we're fucked, but at least we've done our best. Either way, it then exits.

I stuck the executable in /usr/local/bin and edited /etc/udev/rules.d/70-persistent-cd.rules to call it when required. Originally that file contained just this:

# TOSHIBA_DVD-ROM_SD-C2612 (pci-0000:00:1f.1-scsi-1:0:0:0) SUBSYSTEM=="block", ENV{ID_CDROM}=="?*", ENV{DEVNAME}=="/dev/sr0", SYMLINK+="cdrom", ENV{GENERATED}="1" SUBSYSTEM=="block", ENV{ID_CDROM}=="?*", ENV{DEVNAME}=="/dev/sr0", SYMLINK+="dvd", ENV{GENERATED}="1"

Immediately after that, I added this line:

SUBSYSTEM=="block", ENV{ID_CDROM}=="?*", ENV{DEVNAME}=="/dev/sr0", RUN+="/usr/local/bin/cddrvmon $env{DEVNAME}", ENV{GENERATED}="1"

I then restarted udev and fucked about a bit to test it - doing things like inserting a CD, browsing to a subdirectory on it with spacemanfm, also cding to a subdirectory on it in a shell at the same time, ejecting the CD, and seeing if anything blew up. Nothing did. The CD was unmounted with no worse consequences than the usual mild confusion experienced by a program that's had a read-only working directory pulled out from underneath it. There may indeed be some hidden nasties waiting to bite me in the arse but that's only what one would expect from waiting until a drive has already gone offline before trying to unmount it and if such things do show up I reckon it'll only be due to my own error in ejecting the CD when I should have bloody well known better so it doesn't really matter anyway.

Here is the source code for cddrvmon. Compile with gcc cddrvmon.c -o cddrvmon and copy the resulting binary to /usr/local/bin or wherever else seems a sensible place to put it. If you run it from a terminal you can set the environment variable DEBUG (to anything; as long as it is set, it doesn't matter what it's set to) and cddrvmon will not daemonise itself but will spew out loads of debugging shite.


#include <stdio.h> #include <stdlib.h> #include <linux/cdrom.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <string.h> #include <errno.h> #include <sys/ioctl.h> #include <unistd.h> #include <sys/mount.h> #include <signal.h> #define OK 0 #define ERR -1 #define DEFAULT_DRIVE "/dev/sr0" #define POLL_INTERVAL 4 #define MOUNT_WAIT_COUNT 15 #define MOUNTSFILE "/proc/mounts" #define SCRATCH 4096 char *results[]={ "CDS_NO_INFO - No info (CDROM_DRIVE_STATUS unsupported)", "CDS_NO_DISC - No disc", "CDS_TRAY_OPEN - Tray open", "CDS_DRIVE_NOT_READY - Drive not ready", "CDS_DISC_OK - Disc OK" }; int debug=0; int check_drive(char *drive, int unlock) { int fd, ret; if ((fd = open(drive, O_RDONLY | O_NONBLOCK)) == -1) { if (debug) fprintf(stderr, "%s: cannot open: %s\n", drive, strerror(errno)); return(ERR); } if (unlock) if ((ret = ioctl(fd, CDROM_LOCKDOOR, 0)) == -1) { if (debug) fprintf(stderr, "ioctl(CDROM_LOCKDOOR, 0) failed: %s\n", strerror(errno)); close(fd); return(ERR); } if ((ret = ioctl(fd, CDROM_DRIVE_STATUS)) == -1) { if (debug) fprintf(stderr, "ioctl(CDROM_DRIVE_STATUS) failed: %s\n", strerror(errno)); close(fd); return(ERR); } close(fd); if (debug) fprintf(stderr, "ioctl(CDROM_DRIVE_STATUS) returned %d: %s\n", ret, results[ret]); if (ret == CDS_NO_INFO) { if (debug) fprintf(stderr, "Drive %s does not support CDROM_DRIVE_STATUS\n", drive); return(ERR); } return(ret); } int poll_drive(char *drive, char *mountpoint) { int ret=CDS_DISC_OK; if (debug) fprintf(stderr, "Polling drive: %s\nPoll interval: %d seconds\n", drive, POLL_INTERVAL); while (ret & CDS_DISC_OK) { if (debug) fprintf(stderr, "Waiting %d seconds... ", POLL_INTERVAL); sleep(POLL_INTERVAL); ret = check_drive(drive, 0); if (ret == ERR) { if (debug) fprintf(stderr, "Error polling drive %s - exiting\n", drive); return(ERR); } } if (debug) fprintf(stderr, "Unmounting drive %s from %s... ", drive, mountpoint); if (umount(mountpoint) && umount2(mountpoint, MNT_DETACH) && umount2(mountpoint, MNT_FORCE)) { if (debug) fprintf(stderr, "failed: %s\n", strerror(errno)); return(ERR); } if (debug) fprintf(stderr, "OK\n"); return(OK); } int check_drive_ok(char *drive) { int chk; chk = check_drive(drive, 1); if (chk == ERR) { if (debug) fprintf(stderr, "Error checking drive %s - exiting\n", drive); return(ERR); } if (chk & CDS_TRAY_OPEN) { if (debug) fprintf(stderr, "Drive %s tray is open - exiting\n", drive); return(ERR); } return(OK); } char *check_and_get_mount_point(char *drive) { char *mountpoint=NULL; char buf[SCRATCH]; int count=MOUNT_WAIT_COUNT; char *s; FILE *fp; if (debug) fprintf(stderr, "Waiting for drive %s to be mounted\n", drive); while (count--) { sleep(POLL_INTERVAL); if (check_drive_ok(drive) == ERR) return(NULL); if ((fp = fopen(MOUNTSFILE, "r")) == NULL) { if (debug) fprintf(stderr, "%s: cannot open: %s\n", MOUNTSFILE, strerror(errno)); return(NULL); } while (fgets(buf, SCRATCH, fp)) { if ((s = strtok(buf, "\t ")) && (!strcmp(s, drive))) { if (s = strtok(NULL, "\t ")) { if (mountpoint) free(mountpoint); mountpoint = strdup(s); } } } fclose(fp); if (debug) { if (mountpoint) { fprintf(stderr, "Drive %s is mounted on %s\n", drive, mountpoint); } else { fprintf(stderr, "%2d Cannot find mount point for drive %s\n", count, drive); } } if (mountpoint) break; } return(mountpoint); } int check_block(char *drive) { struct stat buf; int ret=ERR; if (stat(drive, &buf)) { if (debug) fprintf(stderr, "%s: cannot stat(): %s\n", drive, strerror(errno)); } else { if (S_ISBLK(buf.st_mode) && (buf.st_mode & S_IRUSR)) ret = OK; } if (debug) { if (ret == OK) { fprintf(stderr, "%s is a block device and is readable\n", drive); } else { fprintf(stderr, "%s is not a readable block device - st_mode is 0%06o\n", drive, buf.st_mode); } } return(ret); } void daemonise() { signal(SIGCHLD, SIG_IGN); switch (fork()) { case -1: fprintf(stderr, "fork() failed: %s - exiting\n", strerror(errno)); exit(ERR); case 0 : break; default: exit(OK); } } int main(int argc, char *argv[]) { char *mountpoint; char *drive=DEFAULT_DRIVE; if (getenv("DEBUG")) debug=1; if (argc == 2) drive = argv[1]; if (check_block(drive) == ERR) exit(ERR); if (!debug) daemonise(); if ((mountpoint = check_and_get_mount_point(drive)) == NULL) exit(ERR); if (poll_drive(drive, mountpoint) == ERR) exit(ERR); exit(0); }

Back to Pigeon's Nest

Be kind to pigeons

Valid HTML 4.01!