#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/fdio.h>
#include <sys/dkio.h>
#include <sys/cdio.h>
#include <sys/param.h>
#include <sys/wait.h>
#include <dirent.h>
#include <fcntl.h>
#include <string.h>
#include <errno.h>
#include <locale.h>
#include <libintl.h>
#include <unistd.h>
#include <pwd.h>
#include <volmgt.h>
#include <sys/mnttab.h>
#include <signal.h>
static char *prog_name = NULL;
static boolean_t do_default = B_FALSE;
static boolean_t do_list = B_FALSE;
static boolean_t do_closetray = B_FALSE;
static boolean_t force_eject = B_FALSE;
static boolean_t do_query = B_FALSE;
static boolean_t is_direct = B_FALSE;
static int work(char *, char *);
static void usage(void);
static int ejectit(char *);
static boolean_t query(char *, boolean_t);
static boolean_t floppy_in_drive(char *, int, boolean_t *);
static boolean_t display_busy(char *, boolean_t);
static char *eject_getfullblkname(char *, boolean_t);
extern char *getfullrawname(char *);
int _dev_mounted(char *path);
int _dev_unmount(char *path);
char *_media_oldaliases(char *name);
void _media_printaliases(void);
#define EJECT_OK 0
#define EJECT_NO_MEDIA 1
#define EJECT_PARM_ERR 2
#define EJECT_IOCTL_ERR 3
#define EJECT_MAN_EJ 4
#define AVAIL_MSG "%s is available\n"
#define NOT_AVAIL_MSG "%s is not available\n"
#define OK_TO_EJECT_MSG "%s can now be manually ejected\n"
#define FLOPPY_MEDIA_TYPE "floppy"
#define CDROM_MEDIA_TYPE "cdrom"
int
main(int argc, char **argv)
{
int c;
const char *opts = "dqflt";
int excode;
int res;
boolean_t err_seen = B_FALSE;
boolean_t man_eject_seen = B_FALSE;
char *rmmount_opt = NULL;
(void) setlocale(LC_ALL, "");
#if !defined(TEXT_DOMAIN)
#define TEXT_DOMAIN "SYS_TEST"
#endif
(void) textdomain(TEXT_DOMAIN);
prog_name = argv[0];
is_direct = (getenv("EJECT_DIRECT") != NULL);
while ((c = getopt(argc, argv, opts)) != EOF) {
switch (c) {
case 'd':
do_default = B_TRUE;
rmmount_opt = "-d";
break;
case 'q':
do_query = B_TRUE;
break;
case 'l':
do_list = B_TRUE;
rmmount_opt = "-l";
break;
case 'f':
force_eject = B_TRUE;
break;
case 't':
do_closetray = B_TRUE;
break;
default:
usage();
exit(EJECT_PARM_ERR);
}
}
if (argc == optind) {
excode = work(NULL, rmmount_opt);
} else {
for (; optind < argc; optind++) {
res = work(argv[optind], rmmount_opt);
if (res == EJECT_MAN_EJ) {
man_eject_seen = B_TRUE;
} else if (res != EJECT_OK) {
err_seen = B_TRUE;
}
}
if (err_seen) {
if (!is_direct) {
excode = res;
} else {
excode = EJECT_IOCTL_ERR;
}
} else if (man_eject_seen) {
excode = EJECT_MAN_EJ;
} else {
excode = EJECT_OK;
}
}
return (excode);
}
static int
work(char *arg, char *rmmount_opt)
{
char *name;
int excode = EJECT_OK;
struct stat64 sb;
char *arg1, *arg2;
pid_t pid;
int status = 1;
if (!is_direct) {
if (do_closetray) {
(void) putenv("EJECT_CLOSETRAY=1");
}
if (do_query) {
(void) putenv("EJECT_QUERY=1");
}
pid = fork();
if (pid < 0) {
exit(1);
} else if (pid == 0) {
if (rmmount_opt != NULL) {
arg1 = rmmount_opt;
arg2 = arg;
} else {
arg1 = arg;
arg2 = NULL;
}
if (execl("/usr/bin/rmmount", "eject",
arg1, arg2, 0) < 0) {
excode = 99;
} else {
exit(0);
}
} else {
if (waitpid(pid, &status, 0) != pid) {
excode = 1;
} else if (WIFEXITED(status) &&
(WEXITSTATUS(status) != 0)) {
excode = WEXITSTATUS(status);
} else {
excode = 0;
}
}
}
if (is_direct || (excode == 99)) {
excode = EJECT_OK;
if (arg == NULL) {
arg = "floppy";
}
if ((name = _media_oldaliases(arg)) == NULL) {
name = arg;
}
if (do_default) {
(void) printf("%s\n", name);
goto out;
}
if (do_list) {
(void) printf("%s\t%s\n", name, arg);
goto out;
}
if (access(name, R_OK) != 0) {
if (do_query) {
(void) fprintf(stderr,
gettext("%s: no media\n"), name);
return (EJECT_NO_MEDIA);
} else {
perror(name);
return (EJECT_PARM_ERR);
}
}
if (do_query) {
if ((stat64(name, &sb) == 0) && S_ISDIR(sb.st_mode)) {
(void) fprintf(stderr,
gettext("%s: no media\n"), name);
return (EJECT_NO_MEDIA);
}
if (!query(name, B_TRUE)) {
excode = EJECT_NO_MEDIA;
}
} else {
excode = ejectit(name);
}
}
out:
return (excode);
}
static void
usage(void)
{
(void) fprintf(stderr,
gettext("usage: %s [-fldqt] [name | nickname]\n"),
prog_name);
(void) fprintf(stderr,
gettext("options:\t-f force eject\n"));
(void) fprintf(stderr,
gettext("\t\t-l list ejectable devices\n"));
(void) fprintf(stderr,
gettext("\t\t-d show default device\n"));
(void) fprintf(stderr,
gettext("\t\t-q query for media present\n"));
(void) fprintf(stderr,
gettext("\t\t-t close tray\n"));
}
static int
ejectit(char *name)
{
int fd, r;
boolean_t mejectable = B_FALSE;
int result = EJECT_OK;
if (_dev_mounted(name)) {
r = _dev_unmount(name);
if (r == 0) {
if (!force_eject) {
(void) fprintf(stderr,
gettext("WARNING: can not unmount %s, the file system is (probably) busy\n"),
name);
return (EJECT_PARM_ERR);
} else {
(void) fprintf(stderr,
gettext("WARNING: %s has a mounted filesystem, ejecting anyway\n"),
name);
}
}
}
name = getfullrawname(name);
if ((fd = open(name, O_RDONLY | O_NDELAY)) < 0) {
if (errno == EBUSY) {
(void) fprintf(stderr,
gettext("%s is busy (try 'eject floppy' or 'eject cdrom'?)\n"),
name);
return (EJECT_PARM_ERR);
}
perror(name);
return (EJECT_PARM_ERR);
}
if (do_closetray) {
if (ioctl(fd, CDROMCLOSETRAY) < 0) {
result = EJECT_IOCTL_ERR;
}
} else if (ioctl(fd, DKIOCEJECT, 0) < 0) {
if ((errno == ENOSYS) &&
!floppy_in_drive(name, fd, &mejectable)) {
errno = ENXIO;
}
if (errno == ENOSYS || errno == ENOTSUP) {
(void) fprintf(stderr, gettext(OK_TO_EJECT_MSG), name);
}
if ((errno == ENOSYS || errno == ENOTSUP) && mejectable) {
result = EJECT_MAN_EJ;
} else if (errno == EBUSY) {
if (!display_busy(name, B_FALSE)) {
perror(name);
}
result = EJECT_IOCTL_ERR;
} else if ((errno == EAGAIN) || (errno == ENODEV) ||
(errno == ENXIO)) {
(void) fprintf(stderr,
gettext("%s not present in a drive\n"),
name);
result = EJECT_OK;
} else {
perror(name);
result = EJECT_IOCTL_ERR;
}
}
(void) close(fd);
return (result);
}
static boolean_t
floppy_in_drive(char *name, int fd, boolean_t *is_floppy)
{
int ival = 0;
boolean_t rval = B_FALSE;
if (ioctl(fd, FDGETCHANGE, &ival) >= 0) {
if (!(ival & FDGC_CURRENT)) {
rval = B_TRUE;
}
*is_floppy = B_TRUE;
} else {
*is_floppy = B_FALSE;
(void) fprintf(stderr, gettext("%s is not a floppy disk\n"),
name);
}
return (rval);
}
static boolean_t
display_busy(char *path, boolean_t vm_running)
{
int errno_save = errno;
char *blk;
FILE *fp = NULL;
struct mnttab mref;
struct mnttab mp;
boolean_t res = B_FALSE;
char busy_base[MAXPATHLEN];
uint_t bblen;
char *cp;
#ifdef DEBUG
(void) fprintf(stderr, "display_busy(\"%s\"): entering\n", path);
#endif
blk = eject_getfullblkname(path, vm_running);
if (blk == NULL)
goto dun;
if ((fp = fopen(MNTTAB, "r")) == NULL) {
goto dun;
}
(void) memset((void *)&mref, '\0', sizeof (struct mnttab));
mref.mnt_special = blk;
if (getmntany(fp, &mp, &mref) == 0) {
goto dun;
}
(void) strcpy(busy_base, blk);
if ((cp = strrchr(busy_base, '/')) == NULL) {
goto dun;
}
*cp = '\0';
bblen = strlen(busy_base);
rewind(fp);
while (getmntent(fp, &mp) == 0) {
if (mp.mnt_special == NULL)
mp.mnt_special = "-";
if (strncmp(busy_base, mp.mnt_special, bblen) == 0) {
res = B_TRUE;
(void) fprintf(stderr, "%s: %s\n", mp.mnt_special,
strerror(EBUSY));
}
}
dun:
if (fp != NULL) {
(void) fclose(fp);
}
#ifdef DEBUG
(void) fprintf(stderr, "display_busy: returning %s\n",
res ? "B_TRUE" : "B_FALSE");
#endif
errno = errno_save;
return (res);
}
static boolean_t
query(char *name, boolean_t doprint)
{
int fd;
int rval;
enum dkio_state state;
if ((fd = open(name, O_RDONLY|O_NONBLOCK)) < 0) {
if ((errno == EPERM) || (errno == ENOENT)) {
if (doprint) {
perror(name);
}
} else {
if (doprint) {
(void) fprintf(stderr, gettext(NOT_AVAIL_MSG),
name);
}
}
return (B_FALSE);
}
rval = 0;
if (ioctl(fd, FDGETCHANGE, &rval) >= 0) {
(void) close(fd);
if (!(rval & FDGC_CURRENT)) {
if (doprint) {
(void) fprintf(stderr, gettext(AVAIL_MSG),
name);
}
return (B_TRUE);
}
if (rval & FDGC_CURRENT) {
if (doprint) {
(void) fprintf(stderr, gettext(NOT_AVAIL_MSG),
name);
}
return (B_FALSE);
}
}
again:
state = DKIO_NONE;
if (ioctl(fd, DKIOCSTATE, &state) >= 0) {
if (state == DKIO_INSERTED) {
if (doprint) {
(void) fprintf(stderr, gettext(AVAIL_MSG),
name);
}
(void) close(fd);
return (B_TRUE);
}
if (state == DKIO_EJECTED) {
if (doprint) {
(void) fprintf(stderr, gettext(NOT_AVAIL_MSG),
name);
}
(void) close(fd);
return (B_FALSE);
}
(void) sleep(1);
goto again;
}
(void) close(fd);
if ((fd = open(name, O_RDONLY)) < 0) {
if (doprint) {
(void) fprintf(stderr, gettext(NOT_AVAIL_MSG), name);
}
return (B_FALSE);
}
(void) close(fd);
if (doprint) {
(void) fprintf(stderr, gettext(AVAIL_MSG), name);
}
return (B_TRUE);
}
static char *
eject_getfullblkname(char *path, boolean_t vm_running)
{
char raw_root[MAXPATHLEN];
const char *vm_root;
static char res_buf[MAXPATHLEN];
uint_t raw_root_len;
#ifdef DEBUG
(void) fprintf(stderr, "eject_getfullblkname(\"%s\", %s): entering\n",
path, vm_running ? "B_TRUE" : "B_FALSE");
#endif
if (vm_running) {
vm_root = volmgt_root();
(void) snprintf(raw_root, sizeof (raw_root), "%s/r", vm_root);
raw_root_len = strlen(raw_root);
if (strncmp(path, raw_root, raw_root_len) == 0) {
if (snprintf(res_buf, sizeof (res_buf), "%s/%s",
vm_root, path + raw_root_len) >= sizeof (res_buf)) {
return (NULL);
}
goto dun;
}
(void) snprintf(raw_root, sizeof (raw_root),
"%s/dev/r", vm_root);
raw_root_len = strlen(raw_root);
if (strncmp(path, raw_root, raw_root_len) == 0) {
if (snprintf(res_buf, sizeof (res_buf), "%s/dev/%s",
vm_root, path + raw_root_len) >= sizeof (res_buf)) {
return (NULL);
}
goto dun;
}
} else {
(void) strcpy(raw_root, "/dev/r");
raw_root_len = strlen(raw_root);
if (strncmp(path, raw_root, raw_root_len) == 0) {
if (snprintf(res_buf, sizeof (res_buf), "/dev/%s",
path + raw_root_len) >= sizeof (res_buf)) {
return (NULL);
}
goto dun;
}
}
(void) strcpy(res_buf, path);
dun:
#ifdef DEBUG
(void) fprintf(stderr, "eject_getfullblkname: returning %s\n",
res_buf ? res_buf : "<null ptr>");
#endif
return (res_buf);
}