/********************************************************************
 *
 *		L I N U X   E J E C T	C O M M A N D
 *
 *		  by Jeff Tranter (tranter@pobox.com)
 *
 ********************************************************************
 *
 * Copyright (C) 1994-2001 Jeff Tranter (tranter@pobox.com)
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 ********************************************************************
 *
 * See the man page for a description of what this program does and what
 * the requirements to run it are.
 *
 */

#include "i18n.h"

#ifndef DEFAULTDEVICE
#error DEFAULTDEVICE not set, check Makefile
#endif

#include <linux/version.h>
/* handy macro found in 2.1 kernels, but not in older ones */
#ifndef KERNEL_VERSION
#define KERNEL_VERSION(a,b,c) (((a) << 16) + ((b) << 8) + (c))
#endif

#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#ifdef GETOPTLONG
#include <getopt.h>
#endif /* GETOPTLONG */
#include <errno.h>
#include <regex.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <sys/wait.h>
#include <sys/mtio.h>
#include <linux/types.h>
#include <linux/cdrom.h>
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,1,0)
#include <linux/ucdrom.h>
#endif
#include <linux/fd.h>
#include <sys/mount.h>
#include <scsi/scsi.h>
#include <scsi/sg.h>
#include <scsi/scsi_ioctl.h>

/* Global Variables */
const char *version = VERSION; /* program version */
int a_option = 0; /* command flags and arguments */
int c_option = 0;
int d_option = 0;
int f_option = 0;
int h_option = 0;
int k_option = 0;
int n_option = 0;
int q_option = 0;
int r_option = 0;
int s_option = 0;
int t_option = 0;
int v_option = 0;
int x_option = 0;
int p_option = 0;
int a_arg = 0;
int c_arg = 0;
int x_arg = 0;
char *programName; /* used in error messages */

/*
 * These are the basenames of devices which can have multiple
 * partitions per device.
 */
const char *partitionDevice[] = {
	"hd",
	"sd",
	"xd",
	"dos_hd",
	"mfm",
	"ad",
	"ed",
	"ftl",
	"pd",
	0};


/* Display command usage on standard error and exit. */
static void usage()
{
//    perror(_("%s: device is `%s'\n"));
	fprintf(stderr,_(
"Eject version %s by Jeff Tranter (tranter@pobox.com)\n"
"Usage:\n"
"  eject -h				-- display command usage and exit\n"
"  eject -V				-- display program version and exit\n"
"  eject [-vnrsfq] [<name>]		-- eject device\n"
"  eject [-vn] -d			-- display default device\n"
"  eject [-vn] -a on|off|1|0 [<name>]	-- turn auto-eject feature on or off\n"
"  eject [-vn] -c <slot> [<name>]	-- switch discs on a CD-ROM changer\n"
"  eject [-vn] -t [<name>]		-- close tray\n"
"  eject [-vn] -k [<name>]		-- check device status\n"
"  eject [-vn] -x <speed> [<name>]	-- set CD-ROM max speed\n"
"Options:\n"
"  -v\t-- enable verbose output\n"
"  -n\t-- don't eject, just show device found\n"
"  -r\t-- eject CD-ROM\n"
"  -s\t-- eject SCSI device\n"
"  -f\t-- eject floppy\n"
"  -q\t-- eject tape\n"
"  -p\t-- use /proc/mounts instead /etc/mtab\n")
, version);
#ifdef GETOPTLONG
	fprintf(stderr,_(
"Long options:\n"
"  -h --help   -v --verbose	 -d --default	 -k --status\n"
"  -a --auto   -c --changerslot  -t --trayclose  -x --cdspeed\n"
"  -r --cdrom  -s --scsi	 -f --floppy\n"
"  -q --tape   -n --noop	 -V --version\n"
"  -p --proc\n"));
#endif /* GETOPTLONG */
	fprintf(stderr,_(
"Parameter <name> can be a device file or a mount point.\n"
"If omitted, name defaults to `%s'.\n"
"By default tries -r, -s, -f, and -q in order until success.\n"),
			DEFAULTDEVICE);
  exit(1);
}


/* Handle command line options. */
static void parse_args(int argc, char **argv, char **device)
{
	const char *flags = "a:c:x:dfhknqrstvVp";
#ifdef GETOPTLONG
	static struct option long_options[] =
	{
		{"help",	no_argument,	   NULL, 'h'},
		{"verbose",	no_argument,	   NULL, 'v'},
		{"default",	no_argument,	   NULL, 'd'},
		{"status",	no_argument,	   NULL, 'k'},
		{"auto",	required_argument, NULL, 'a'},
		{"changerslot", required_argument, NULL, 'c'},
		{"trayclose",	no_argument,	   NULL, 't'},
		{"cdspeed",	required_argument, NULL, 'x'},
		{"noop",	no_argument,	   NULL, 'n'},
		{"cdrom",	no_argument,	   NULL, 'r'},
		{"scsi",	no_argument,	   NULL, 's'},
		{"floppy",	no_argument,	   NULL, 'f'},
		{"tape",	no_argument,	   NULL, 'q'},
		{"version",	no_argument,	   NULL, 'V'},
		{"proc",	no_argument,	   NULL, 'p'},
		{0, 0, 0, 0}
	};
	int option_index;
#endif /* GETOPTLONG */
	int c;

#ifdef GETOPTLONG
	while ((c = getopt_long(argc, argv, flags, long_options, &option_index)) != EOF) {
#else
	while ((c = getopt(argc, argv, flags)) != EOF) {
#endif /* GETOPTLONG */
		switch (c) {
		  case 'a':
			  a_option = 1;
			  if (!strcmp(optarg, "0"))
				  a_arg = 0;
			  else if (!strcmp(optarg, "off"))
				  a_arg = 0;
			  else if (!strcmp(optarg, "1"))
				  a_arg = 1;
			  else if (!strcmp(optarg, "on"))
				  a_arg = 1;
			  else {
				  fprintf(stderr, _("%s: invalid argument to --auto/-a option\n"), programName);
				  exit(1);
			  }
			  break;
		  case 'c':
			  c_option = 1;
			  /* atoi() returns 0 on error, so "0" must be parsed separately */
			  if (!strcmp(optarg, "0"))
				  c_arg = 0;
			  else {
				  c_arg = atoi(optarg);
				  if (c_arg <= 0) {
					  fprintf(stderr, _("%s: invalid argument to --changerslot/-c option\n"), programName);
					  exit(1);
				  }
			  }
			  break;
		  case 'x':
			  x_option = 1;
			  if (!strcmp(optarg, "0"))
				  x_arg = 0;
			  else {
				  x_arg = atoi(optarg);
				  if (x_arg <= 0) {
					  fprintf(stderr, _("%s: invalid argument to --cdspeed/-x option\n"), programName);
					  exit(1);
				  }
			  }
			  break;
		  case 'd':
			  d_option = 1;
			  break;
		  case 'f':
			  f_option = 1;
			  break;
		  case 'h':
			  usage();
			  exit(0);
			  break;
		  case 'k':
			  k_option = 1;
			  break;
		  case 'n':
			  n_option = 1;
			  break;
		  case 'p':
			  p_option = 1;
			  break;
		  case 'q':
			  q_option = 1;
			  break;
		  case 'r':
			  r_option = 1;
			  break;
		  case 's':
			  s_option = 1;
			  break;
		  case 't':
			  t_option = 1;
			  break;
		  case 'v':
			  v_option = 1;
			  break;
		  case 'V':
			  printf(_("eject version %s by Jeff Tranter (tranter@pobox.com)\n"), version);
			  exit(0);
			  break;
		  case '?':
			  exit(1);
			  break;
		}
	}
	/* check for a single additional argument */
	if ((argc - optind) > 1) {
		fprintf(stderr, _("%s: too many arguments\n"), programName);
		exit(1);
	}
	if ((argc - optind) == 1) { /* one argument */
		*device = strdup(argv[optind]);
	}
}


/* Return 1 if file/device exists, 0 otherwise. */
static int FileExists(const char *name)
{
	int fd;

	fd = open(name, O_RDONLY|O_NONBLOCK);
	if (fd == -1) {
		return 0;
	} else {
		close(fd);
		return 1;
	}
}


/*
 * Given name, such as foo, see if any of the following exist:
 *
 * foo (if foo starts with '.' or '/')
 * /dev/foo
 * /mnt/foo
 * /dev/cdroms/foo
 * /dev/cdroms/foo0
 * /dev/dsk/foo
 * /dev/rdsk/foo
 * ./foo
 *
 * If found, return the full path. If not found, return 0.
 * Returns pointer to dynamically allocated string.
 */
static char *FindDevice(const char *name)
{
	char *buf;

	buf = (char *) malloc(strlen(name)+14); /* to allow for "/dev/cdroms/ + "0" + null */

	if ((name[0] == '.') || (name[0] == '/')) {
		strcpy(buf, name);
		if (FileExists(buf))
			return buf;
	}

	strcpy(buf, "/dev/");
	strcat(buf, name);
	if (FileExists(buf))
		return buf;

	strcpy(buf, "/mnt/");
	strcat(buf, name);
	if (FileExists(buf))
		return buf;

	/* for devfs under Linux */
	strcpy(buf, "/dev/cdroms/");
	strcat(buf, name);
	if (FileExists(buf))
		return buf;

	strcpy(buf, "/dev/cdroms/");
	strcat(buf, name);
	strcat(buf, "0");
	if (FileExists(buf))
		return buf;

	/* for devfs under Solaris */
	strcpy(buf, "/dev/rdsk/");
	strcat(buf, name);
	if (FileExists(buf))
		return buf;

	strcpy(buf, "/dev/dsk/");
	strcat(buf, name);
	if (FileExists(buf))
		return buf;

	strcpy(buf, "./");
	strcat(buf, name);
	if (FileExists(buf))
		return buf;

	free(buf);
	buf = 0;
	return 0;
}


/* Set or clear auto-eject mode. */
static void AutoEject(int fd, int onOff)
{
	int status;

	status = ioctl(fd, CDROMEJECT_SW, onOff);
	if (status != 0) {
		fprintf(stderr, _("%s: CD-ROM auto-eject command failed: %s\n"), programName, strerror(errno));
		exit(1);
	}
}


/*
 * Changer select. CDROM_SELECT_DISC is preferred, older kernels used
 * CDROMLOADFROMSLOT.
 */
static void ChangerSelect(int fd, int slot)
{
	int status;

#ifdef CDROM_SELECT_DISC
	status = ioctl(fd, CDROM_SELECT_DISC, slot);
	if (status < 0) {
		fprintf(stderr, _("%s: CD-ROM select disc command failed: %s\n"), programName, strerror(errno));
		exit(1);
	}
#elif defined CDROMLOADFROMSLOT
	status = ioctl(fd, CDROMLOADFROMSLOT, slot);
	if (status != 0) {
		fprintf(stderr, _("%s: CD-ROM load from slot command failed: %s\n"), programName, strerror(errno));
		exit(1);
	}
#else
    fprintf(stderr, _("%s: IDE/ATAPI CD-ROM changer not supported by this kernel\n"), programName);
#endif
}


/*
 * Close tray. Not supported by older kernels.
 */
static void CloseTray(int fd)
{
	int status;

#ifdef CDROMCLOSETRAY
	status = ioctl(fd, CDROMCLOSETRAY);
	if (status != 0) {
		fprintf(stderr, _("%s: CD-ROM tray close command failed: %s\n"), programName, strerror(errno));
		exit(1);
	}
#else
    fprintf(stderr, _("%s: CD-ROM tray close command not supported by this kernel\n"), programName);
#endif
}


/*
 * Checks status.
 */
static void CheckStatus(int fd)
{
#ifdef CDROM_DRIVE_STATUS
	switch(ioctl(fd, CDROM_DRIVE_STATUS))
	{
	case CDS_NO_INFO:
		fprintf(stderr, _("%s: Status is not supported by your drive.\n"), programName);
		exit(1);
	case CDS_NO_DISC:
		printf(_("%s: No disc in drive\n"), programName);
		break;
	case CDS_TRAY_OPEN:
		printf(_("%s: Tray is open\n"), programName);
		break;
	case CDS_DRIVE_NOT_READY:
		printf(_("%s: Drive not ready\n"), programName);
		break;
	case CDS_DISC_OK:
		printf(_("%s: Disk OK.\n"), programName);
		break;
	default:
		fprintf(stderr, _("%s: Unknown ioctl() error: %s\n"), programName, strerror(errno));
		exit(1);
	}
#else
    fprintf(stderr, _("%s: Drive status command not supported by this kernel\n"), programName);
#endif
}


/*
 * Select Speed of CD-ROM drive.
 * Thanks to Roland Krivanek (krivanek@fmph.uniba.sk)
 * http://dmpc.dbp.fmph.uniba.sk/~krivanek/cdrom_speed/
 */
static void SelectSpeedCdrom(int fd, int speed)
{
	int status;

#ifdef CDROM_SELECT_SPEED
	status = ioctl(fd, CDROM_SELECT_SPEED, speed);
	if (status != 0) {
		fprintf(stderr, _("%s: CD-ROM select speed command failed: %s\n"), programName, strerror(errno));
		exit(1);
	}
#else
    fprintf(stderr, _("%s: CD-ROM select speed command not supported by this kernel\n"), programName);
#endif
}


/*
 * Eject using CDROMEJECT ioctl. Return 1 if successful, 0 otherwise.
 */
static int EjectCdrom(int fd)
{
	int status;

	status = ioctl(fd, CDROMEJECT);
	return (status == 0);
}


/*
 * Eject using SCSI commands. Return 1 if successful, 0 otherwise.
 */
static int EjectScsi(int fd)
{
	int status;
	struct sdata {
		int  inlen;
		int  outlen;
		char cmd[256];
	} scsi_cmd;

	scsi_cmd.inlen	= 0;
	scsi_cmd.outlen = 0;
	scsi_cmd.cmd[0] = ALLOW_MEDIUM_REMOVAL;
	scsi_cmd.cmd[1] = 0;
	scsi_cmd.cmd[2] = 0;
	scsi_cmd.cmd[3] = 0;
	scsi_cmd.cmd[4] = 0;
	scsi_cmd.cmd[5] = 0;
	status = ioctl(fd, SCSI_IOCTL_SEND_COMMAND, (void *)&scsi_cmd);
	if (status != 0)
		return 0;

	scsi_cmd.inlen	= 0;
	scsi_cmd.outlen = 0;
	scsi_cmd.cmd[0] = START_STOP;
	scsi_cmd.cmd[1] = 0;
	scsi_cmd.cmd[2] = 0;
	scsi_cmd.cmd[3] = 0;
	scsi_cmd.cmd[4] = 1;
	scsi_cmd.cmd[5] = 0;
	status = ioctl(fd, SCSI_IOCTL_SEND_COMMAND, (void *)&scsi_cmd);
	if (status != 0)
		return 0;

	scsi_cmd.inlen	= 0;
	scsi_cmd.outlen = 0;
	scsi_cmd.cmd[0] = START_STOP;
	scsi_cmd.cmd[1] = 0;
	scsi_cmd.cmd[2] = 0;
	scsi_cmd.cmd[3] = 0;
	scsi_cmd.cmd[4] = 2;
	scsi_cmd.cmd[5] = 0;
	status = ioctl(fd, SCSI_IOCTL_SEND_COMMAND, (void *)&scsi_cmd);
	if (status != 0)
		return 0;

	/* force kernel to reread partition table when new disc inserted */
	status = ioctl(fd, BLKRRPART);
	return (status == 0);
}


/*
 * Eject using FDEJECT ioctl. Return 1 if successful, 0 otherwise.
 */
static int EjectFloppy(int fd)
{
	int status;

	status = ioctl(fd, FDEJECT);
	return (status == 0);
}


/*
 * Eject using tape ioctl. Return 1 if successful, 0 otherwise.
 */
static int EjectTape(int fd)
{
	int status;
	struct mtop op;

	op.mt_op = MTOFFL; /* rewind and eject */
	op.mt_count = 0;   /* not used */
	status = ioctl(fd, MTIOCTOP, &op);
	return (status == 0);
}


/* Unmount a device. */
static void Unmount(const char *fullName)
{
	int status;

	switch (fork()) {
	  case 0: /* child */
		  seteuid(getuid()); /* reduce likelyhood of security holes when running setuid */
		  if(p_option)
			  execl("/bin/umount", "/bin/umount", fullName, "-n", NULL);
		  else
			  execl("/bin/umount", "/bin/umount", fullName, NULL);
		  fprintf(stderr, _("%s: unable to exec /bin/umount of `%s': %s\n"),
				  programName, fullName, strerror(errno));
		  exit(1);
		  break;
	  case -1:
		  fprintf(stderr, _("%s: unable to fork: %s\n"), programName, strerror(errno));
		  break;
	  default: /* parent */
		  wait(&status);
		  if (WIFEXITED(status) == 0) {
			  fprintf(stderr, _("%s: unmount of `%s' did not exit normally\n"), programName, fullName);
			  exit(1);
		  }
		  if (WEXITSTATUS(status) != 0) {
			  fprintf(stderr, _("%s: unmount of `%s' failed\n"), programName, fullName);
			  exit(1);
		  }
		  break;
	}
}


/* Open a device file. */
static int OpenDevice(const char *fullName)
{
	int fd = open(fullName, O_RDONLY|O_NONBLOCK);
	if (fd == -1) {
		fprintf(stderr, _("%s: unable to open `%s'\n"), programName, fullName);
		exit(1);
	}
	return fd;
}


/*
 * Get major and minor device numbers for a device file name, so we
 * can check for duplicate devices.
 */
static int GetMajorMinor(const char *name, int *maj, int *min)
{
	struct stat sstat;
	*maj = *min = -1;
	if (stat(name, &sstat) == -1)
		return -1;
	if (! S_ISBLK(sstat.st_mode))
		return -1;
	*maj = major(sstat.st_rdev);
	*min = minor(sstat.st_rdev);
	return 0;
}


/*
 * See if device has been mounted by looking in mount table.  If so, set
 * device name and mount point name, and return 1, otherwise return 0.
 */
static int MountedDevice(const char *name, char **mountName, char **deviceName)
{
	FILE *fp;
	char line[1024];
	char s1[1024];
	char s2[1024];
	int rc;

	int maj;
	int min;

	GetMajorMinor(name, &maj, &min);

	fp = fopen((p_option ? "/proc/mounts" : "/etc/mtab"), "r");
	if (fp == NULL)
	{
		fprintf(stderr, _("unable to open %s: %s\n"), (p_option ? "/proc/mounts" : "/etc/mtab"), strerror(errno));
		exit(1);
	}

	while (fgets(line, sizeof(line), fp) != 0) {
		rc = sscanf(line, "%1023s %1023s", s1, s2);
		if (rc >= 2) {
			int mtabmaj, mtabmin;
			GetMajorMinor(s1, &mtabmaj, &mtabmin);
			if (((strcmp(s1, name) == 0) || (strcmp(s2, name) == 0)) ||
				((maj != -1) && (maj == mtabmaj) && (min == mtabmin))) {
				fclose(fp);
				*deviceName = strdup(s1);
				*mountName = strdup(s2);
				return 1;
			}
		}
	}
	*deviceName = 0;
	*mountName = 0;
	fclose(fp);
	return 0;
}


/*
 * See if device can be mounted by looking in /etc/fstab.
 * If so, set device name and mount point name, and return 1,
 * otherwise return 0.
 */
static int MountableDevice(const char *name, char **mountName, char **deviceName)
{
	FILE *fp;
	char line[1024];
	char s1[1024];
	char s2[1024];
	int rc;

	fp = fopen("/etc/fstab", "r");
	if (fp == NULL) {
		fprintf(stderr, _("%s: unable to open /etc/fstab: %s\n"), programName, strerror(errno));
		exit(1);
	}

	while (fgets(line, sizeof(line), fp) != 0) {
		rc = sscanf(line, "%1023s %1023s", s1, s2);
		if (rc >= 2 && s1[0] != '#' && strcmp(s2, name) == 0) {
			fclose(fp);
			*deviceName = strdup(s1);
			*mountName = strdup(s2);
			return 1;
		}
	}
	fclose(fp);
	return 0;
}


/*
 * Step through mount table and unmount all devices that match a regular
 * expression.
 */
static void UnmountDevices(const char *pattern)
{
	regex_t preg;
	FILE *fp;
	char s1[1024];
	char s2[1024];
	char line[1024];
	int status;

	regcomp(&preg, pattern, REG_EXTENDED);

	fp = fopen((p_option ? "/proc/mounts" : "/etc/mtab"), "r");
	if (fp == NULL)
	{
		fprintf(stderr, _("unable to open %s: %s\n"),(p_option ? "/proc/mounts" : "/etc/mtab"), strerror(errno));
		exit(1);
	}

	while (fgets(line, sizeof(line), fp) != 0) {
		status = sscanf(line, "%1023s %1023s", s1, s2);
		if (status >= 2) {
			status = regexec(&preg, s1, 0, 0, 0);
			if (status == 0) {
				if (v_option)
					printf(_("%s: unmounting `%s'\n"), programName, s1);
				Unmount(s1);
				regfree(&preg);
			}
		}
	}
	fclose(fp);
}


/* Check if name is a symbolic link. If so, return what it points to. */
static char *SymLink(const char *name)
{
	int status;
	char s1[1024];
	char s2[1024];
	char s4[1024];
	char *s3;

	status = readlink(name, s1, sizeof(s1));

	if (status == -1)
		return 0;

	s1[status] = 0;
	if (s1[0] == '/') { /* absolute link */
		return strdup(s1);
	} else { /* relative link, add base name */
		strncpy(s2, name, sizeof(s2)-1);
		s3 = strrchr(s2, '/');
		if (s3 != 0) {
			s3[1] = 0;
			strcat(s2, s1);
		}
	}
	realpath(s2, s4);
	return strdup(s4);
}


/*
 * Given a name, see if it matches a pattern for a device that can have
 * multiple partitions.  If so, return a regular expression that matches
 * partitions for that device, otherwise return 0.
 */
static char *MultiplePartitions(const char *name)
{
	int i = 0;
	int status;
	regex_t preg;
	char pattern[256];
	char *result = 0;

	for (i = 0; partitionDevice[i] != 0; i++) {
		/* look for ^/dev/foo[a-z]([0-9]?[0-9])?$, e.g. /dev/hda1 */
		strcpy(pattern, "^/dev/");
		strcat(pattern, partitionDevice[i]);
		strcat(pattern, "[a-z]([0-9]?[0-9])?$");
		regcomp(&preg, pattern, REG_EXTENDED|REG_NOSUB);
		status = regexec(&preg, name, 1, 0, 0);
		regfree(&preg);
		if (status == 0) {
			result = (char *) malloc(strlen(name) + 25);
			strcpy(result, name);
			result[strlen(partitionDevice[i]) + 6] = 0;
			strcat(result, "([0-9]?[0-9])?$");
			if (v_option)
				printf(_("%s: `%s' is a multipartition device\n"), programName, name);
			return result;
		}
	}
	if (v_option)
		printf(_("%s: `%s' is not a multipartition device\n"), programName, name);
	return 0;
}


/* handle -x option */
void HandleXOption(char *deviceName)
{
	int fd; 	   /* file descriptor for device */
	if (x_option) {
		if (v_option)
		{
			if (x_arg == 0)
				printf(_("%s: setting CD-ROM speed to auto\n"), programName);
			else
				printf(_("%s: setting CD-ROM speed to %dX\n"), programName, x_arg);
		}
		fd = OpenDevice(deviceName);
		SelectSpeedCdrom(fd, x_arg);
		exit(0);
	}
}


/* main program */
int main(int argc, char **argv)
{
	const char *defaultDevice = DEFAULTDEVICE;  /* default if no name passed by user */
	int worked = 0;    /* set to 1 when successfully ejected */
	char *device = 0;  /* name passed from user */
	char *fullName;    /* expanded name */
	char *deviceName;  /* name of device */
	char *linkName;    /* name of device's symbolic link */
	char *mountName;   /* name of device's mount point */
	int fd; 	   /* file descriptor for device */
	int mounted = 0;   /* true if device is mounted */
	int mountable = 0; /* true if device is in /etc/fstab */
	char *pattern;	   /* regex for device if multiple partitions */
	int ld = 6;	   /* symbolic link max depth */

	I18NCODE

	/* program name is global variable used by other procedures */
	programName = strdup(argv[0]);

	/* parse the command line arguments */
	parse_args(argc, argv, &device);


	/* handle -d option */
	if (d_option) {
		printf(_("%s: default device: `%s'\n"), programName, defaultDevice);
		exit(0);
	}

	/* if no device, use default */
	if (device == 0) {
		device = strdup(defaultDevice);
		if (v_option)
			printf(_("%s: using default device `%s'\n"), programName, device);
	}

	/* Strip any trailing slash from name in case user used bash/tcsh
	   style filename completion (e.g. /mnt/cdrom/) */
	if (device[strlen(device)-1] == '/')
		device[strlen(device)-1] = 0;

	if (v_option)
		printf(_("%s: device name is `%s'\n"), programName, device);

	/* figure out full device or mount point name */
	fullName = FindDevice(device);
	if (fullName == 0) {
		fprintf(stderr, _("%s: unable to find or open device for: `%s'\n"), programName, device);
		exit(1);
	}
	if (v_option)
		printf(_("%s: expanded name is `%s'\n"), programName, fullName);

	/* check for a symbolic link */
	while ((linkName = SymLink(fullName)) && (ld > 0)) {
		if (v_option)
			printf(_("%s: `%s' is a link to `%s'\n"), programName, fullName, linkName);
		free(fullName);
		fullName = strdup(linkName);
		free(linkName);
		linkName = 0;
		ld--;
	}
	/* handle max depth exceeded option */
	if (ld <= 0) {
		printf(_("%s: maximum symbolic link depth exceeded: `%s'\n"), programName, fullName);
		exit(1);
	}

	/* if mount point, get device name */
	mounted = MountedDevice(fullName, &mountName, &deviceName);
	if (v_option) {
		if (mounted)
			printf(_("%s: `%s' is mounted at `%s'\n"), programName, deviceName, mountName);
		else
			printf(_("%s: `%s' is not mounted\n"), programName, fullName);
	}
	if (!mounted) {
		deviceName = strdup(fullName);
	}

	/* if not currently mounted, see if it is a possible mount point */
	if (!mounted) {
		mountable = MountableDevice(fullName, &mountName, &deviceName);
		if (v_option) {
			if (mountable)
				printf(_("%s: `%s' can be mounted at `%s'\n"), programName, deviceName, mountName);
			else
				printf(_("%s: `%s' is not a mount point\n"), programName, fullName);
		}
	}

	/* handle -n option */
	if (n_option) {
		printf(_("%s: device is `%s'\n"), programName, deviceName);
		if (v_option)
			printf(_("%s: exiting due to -n/--noop option\n"), programName);
		exit(0);
	}

	/* handle -a option */
	if (a_option) {
		if (v_option) {
			if (a_arg)
				printf(_("%s: enabling auto-eject mode for `%s'\n"), programName, deviceName);
			else
				printf(_("%s: disabling auto-eject mode for `%s'\n"), programName, deviceName);
		}
		fd = OpenDevice(deviceName);
		AutoEject(fd, a_arg);
		exit(0);
	}

	/* handle -k option */
	if (k_option) {
		if (v_option)
			printf(_("%s: checking drive status\n"), programName);
		fd = OpenDevice(deviceName);
		CheckStatus(fd);
		HandleXOption(deviceName);
		exit(0);
	}

	/* handle -t option */
	if (t_option) {
		if (v_option)
			printf(_("%s: closing tray\n"), programName);
		fd = OpenDevice(deviceName);
		CloseTray(fd);
		HandleXOption(deviceName);
		exit(0);
	}

	/* handle -x option only */
	if (!c_option) HandleXOption(deviceName);

	/* unmount device if mounted */
	if (mounted) {
		if (v_option)
			printf(_("%s: unmounting `%s'\n"), programName, deviceName);
		Unmount(deviceName);
	}

	/* if it is a multipartition device, unmount any other partitions on
	   the device */
	pattern = MultiplePartitions(deviceName);
	if (pattern != 0)
		UnmountDevices(pattern);

	/* handle -c option */
	if (c_option) {
		if (v_option)
			printf(_("%s: selecting CD-ROM disc #%d\n"), programName, c_arg);
		fd = OpenDevice(deviceName);
		ChangerSelect(fd, c_arg);
		HandleXOption(deviceName);
		exit(0);
	}

	/* if user did not specify type of eject, try all four methods */
	if ((r_option + s_option + f_option + q_option) == 0) {
		r_option = s_option = f_option = q_option = 1;
	}

	/* open device */
	fd = OpenDevice(deviceName);

	/* try various methods of ejecting until it works */
	if (r_option) {
		if (v_option)
			printf(_("%s: trying to eject `%s' using CD-ROM eject command\n"), programName, deviceName);
		worked = EjectCdrom(fd);
		if (v_option) {
			if (worked)
				printf(_("%s: CD-ROM eject command succeeded\n"), programName);
			else
				printf(_("%s: CD-ROM eject command failed\n"), programName);
		}
	}

	if (s_option && !worked) {
		if (v_option)
			printf(_("%s: trying to eject `%s' using SCSI commands\n"), programName, deviceName);
		worked = EjectScsi(fd);
		if (v_option) {
			if (worked)
				printf(_("%s: SCSI eject succeeded\n"), programName);
			else
				printf(_("%s: SCSI eject failed\n"), programName);
		}
	}

	if (f_option && !worked) {
		if (v_option)
			printf(_("%s: trying to eject `%s' using floppy eject command\n"), programName, deviceName);
		worked = EjectFloppy(fd);
		if (v_option) {
			if (worked)
				printf(_("%s: floppy eject command succeeded\n"), programName);
			else
				printf(_("%s: floppy eject command failed\n"), programName);
		}
	}

	if (q_option && !worked) {
		if (v_option)
			printf(_("%s: trying to eject `%s' using tape offline command\n"), programName, deviceName);
		worked = EjectTape(fd);
		if (v_option) {
			if (worked)
				printf(_("%s: tape offline command succeeded\n"), programName);
			else
				printf(_("%s: tape offline command failed\n"), programName);
		}
	}

	if (!worked) {
		fprintf(stderr, _("%s: unable to eject, last error: %s\n"), programName, strerror(errno));
		exit(1);
	}

	/* cleanup */
	close(fd);
	free(device);
	free(deviceName);
	free(fullName);
	free(linkName);
	free(mountName);
	free(pattern);
	exit(0);
}
