/*
 * Copyright (c) 1995-2000 Shunsuke Akiyama <akiyama@FreeBSD.org>.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 *	$Id: eject.c,v 1.10 2000/09/06 13:40:12 akiyama Exp $
 */

#include <stdio.h>
#include <errno.h>
#include <fcntl.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <sys/param.h>
#include <sys/ucred.h>
#include <sys/mount.h>
#include <sys/cdio.h>
#include <sys/ioctl.h>
#include <sys/stat.h>

extern int optind;

void usage(void);
int check_device(char *, char **);
int unmount_fs(char *, char **);
int eject(char *, char *);

char *program = "eject";

int fflag;	/* force unmount filesystem */
int nflag;	/* not execute operation */
int vflag;	/* verbose operation */

/*
 *  simple eject program
 *
 *  usage: eject [-fnv] <device name>
 */

main(argc, argv)
    int argc;
    char *argv[];
{
    int ch;
    int sts;
    char *device, *name;
    char *err;
    char *defdev;

    fflag = nflag = vflag = 0;
    defdev = getenv("EJECT");

    while ((ch = getopt(argc, argv, "fnv?")) != EOF) {
	switch (ch) {
	  case 'f' :
	    fflag = 1;
	    break;
	  case 'n' :
	    nflag = 1;
	    break;
	  case 'v' :
	    vflag = 1;
	    break;
	  case '?' :
	  default :
	    usage();
	}
    }
    argc -= optind;
    argv += optind;

    if (defdev == NULL) {
	if (argc == 0) {
	    usage();
	}
	name = strdup(*argv);
    } else {
	name = strdup(defdev);
    }    

    sts = check_device(name, &device);
    if (sts < 0) {
	perror(program);
	exit(1);
    }

    sts = unmount_fs(name, &err);
    if (sts < 0) {
	perror(err);
	exit(1);
    }

    sts = eject(name, device);
    if (sts < 0) {
	perror(program);
	exit(1);
    }

    exit(0);
}

/*
 *  check whether device exists.
 */

int
check_device(name, device)
    char *name;
    char **device;
{
    int sts;
    struct stat sb;

    if (strncmp("/dev/", name, strlen("/dev/")) == 0) {
	    if (asprintf(device, "%s", name) == -1)
		return sts;
    }
    else { 
	    if (asprintf(device, "/dev/%s", name) == -1)
		return sts;
    }
    if (vflag || nflag) {
	printf("%s: using device %s\n", program, device);
    }
    sts = stat(*device, &sb);

    return sts;
}

/*
 *  unmount all ejectable filesystems.
 */

struct mntlist {
    struct mntlist *next;
    char *mntonname;
    char *mntfromname;
};

int
unmount_fs(name, err)
    char *name;
    char **err;
{
    int mnts;
    struct statfs *mntbuf;
    int len, i, n;
    char *p, *q;
    int sts, flag;
    struct mntlist *head, *mp, *nextp;

    head = NULL;
    mnts = getmntinfo(&mntbuf, MNT_NOWAIT);
    if (mnts == 0) {
	return -1;
    }

    /* get proper mount information into the list */
    len = strlen(name);
    for (n = 0; n < mnts; n++) {
	if (strncmp("/dev/", name, strlen("/dev/")) == 0) 
		p = mntbuf[n].f_mntfromname;
	else {
		p = rindex(mntbuf[n].f_mntfromname, '/');
		if (p == NULL)
		    continue;
		++p;
	}

	for (i = 0, p, q = name; *p != '\0' && *q != '\0'; ++i, ++p, ++q) {
	    if (*p != *q) {
		break;
	    }
	}
	if (i == len) {
	    if (*p == '\0' || strchr("abcdefghs", *p) != NULL) {
		if (nflag || vflag) {
		    printf("%s: %s mounted on %s\n", program,
			   mntbuf[n].f_mntfromname, mntbuf[n].f_mntonname);
		}
		mp = malloc(sizeof (struct mntlist));
		if (mp == NULL) {
		    return -1;
		}
		if (head) {
		    mp->next = head;
		} else {
		    mp->next = NULL;
		}
		mp->mntfromname = mntbuf[n].f_mntfromname;
		mp->mntonname = mntbuf[n].f_mntonname;
		head = mp;
	    }
	}
    }

    /* unmount filesystem(s) */
    for (mp = head; mp != NULL; mp = nextp) {
	if (nflag || vflag) {
	    printf("%s: ", program);
	    if (fflag) {
		printf("force ");
	    }
	    printf("unmounting %s\n", mp->mntonname);
	}
	if (!nflag) {
	    sync();
	    flag = fflag ? MNT_FORCE : 0;
	    sts = unmount(mp->mntonname, flag);
	} else {
	    sts = 0;
	}
	if (sts < 0 && fflag == 0) {
	    asprintf(err, "%s: %s", program, mp->mntonname);
	    return sts;
	}
	nextp = mp->next;
	free(mp);
    }

    return 0;
}

/*
 *  eject media from device
 */

int
eject(name, device)
    char *name;
    char *device;
{
    int fd, sts;

    fd = open(device, O_RDONLY);
    if (fd < 0) {
	if (errno == ENXIO) {
	    /* It's might be no medium */
	    if (vflag) {
		printf("%s: no media in %s\n", program, name);
	    }
	    return 0;
	}
	return -1;
    }
    if (!nflag) {
	if (vflag) {
	    printf("%s: ejecting media from %s\n", program, name);
	}
	sts = ioctl(fd, CDIOCALLOW);
	if (sts >= 0) {
	    sts = ioctl(fd, CDIOCEJECT);
	}
    } else {
	printf("%s: ejecting media from %s\n", program, name);
	sts = 0;
    }
    close(fd);

    return sts;
}

/*
 *  print short usage
 */

void
usage(void)
{
    fprintf(stderr, "usage: %s [-fnv] device\n", program);
    exit(1);
}


syntax highlighted by Code2HTML, v. 0.9.1