/* 
    umount.c - by Peter Orbaek <poe@daimi.aau.dk> 

    Copyright (C) 1992,93 Peter Orbaek.

    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.
*/


#define _POSIX_SOURCE 1
#include <stdio.h>
#include <mntent.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <errno.h>
#include <getopt.h>
#include <sys/mount.h>

#define MTAB_FILE MOUNTED
#define LOCK_FILE "/etc/mtab~"
#define UFSTAB    "/etc/ufstab"

int opt_all          = 0;
int opt_verbose      = 0;
int opt_usermount    = 0;

char mnttype[200] = "";
char *mntdev;

void usage(), err(char *);
void lock_mtab();
int do_umount(struct mntent *);

void usage()
{
    fprintf(stderr,
	    "Usage: umount -a [-v] [-t type]\n"
	    "       umount [-uv] dev|dir\n");
    exit(1);
}

int main(int argc, char *argv[])
{
    char c;
    FILE *mtab, *newmtab;
    struct mntent *mp;
    struct mntent mnt;

    int flag;

    while((c = getopt(argc, argv, "avut:")) != EOF) {
	switch(c) {
	  case 'a':
	    opt_all = 1;
	    break;
	  case 'v':
	    opt_verbose = 1;
	    break;
	  case 't':
	    strncpy(mnttype, optarg, sizeof(mnttype));
	    break;
	  case 'u':
	    opt_usermount = 1;
	    break;
	  default:
	    usage();
	    break;
	}
    }
    if(optind < argc) mntdev = argv[optind++];
    if(optind < argc) usage();

    if((!opt_all && !mntdev) || (opt_all && mntdev)) usage();

    if(getuid()) opt_usermount = 1;

    if(geteuid()) {
	fprintf(stderr, "umount: You must be root to umount filesystems.\n");
	exit(1);
    }

    if(opt_usermount) {
	if(opt_all) {
	    fprintf(stderr, "umount: Ordinary users can't umount all filesystems.\n");
	    exit(1);
	}
	/* scan ufstab for filesystems that ordinary users can umount */
	flag = 0;
	if(!(mtab = setmntent(UFSTAB, "r"))) err(UFSTAB);
	while((mp = getmntent(mtab))) {
	    if(!strcmp(mntdev, mp->mnt_fsname)
	       || !strcmp(mntdev, mp->mnt_dir)) {
		flag = 1;
		break;
	    }
	}
	endmntent(mtab);

	if(!flag) {
	    fprintf(stderr, "umount: %s isn't user-unmountable\n", mntdev);
	    exit(1);
	}
    }

    if(!(newmtab = fdopen(open(LOCK_FILE, O_WRONLY|O_CREAT|O_EXCL, 0666), "w"))) {
	fprintf(stderr, "umount: A lockfile exists, unmount denied\n");
	exit(1);
    }
    if(!(mtab = setmntent(MTAB_FILE, "r"))) err(MTAB_FILE);

    if(opt_all) {
	while((mp = getmntent(mtab))) {
	    if(!mp->mnt_opts || !mp->mnt_opts[0]) mp->mnt_opts = "defaults";
	    if(mnttype[0] == '\0' || !strcmp(mnttype, mp->mnt_type)) {
		if(do_umount(mp) < 0) {
		    addmntent(newmtab, mp);
		    fprintf(stderr, "umount: Umount of %s failed\n",
			    mp->mnt_fsname);
		}
	    } else addmntent(newmtab, mp);
	}
    } else {
	flag = 0;
	while((mp = getmntent(mtab))) {
	    if(!mp->mnt_opts || !mp->mnt_opts[0]) mp->mnt_opts = "defaults";
	    if(!strcmp(mntdev, mp->mnt_fsname)
	       || !strcmp(mntdev, mp->mnt_dir)) {
		flag = 1;
		if(do_umount(mp) < 0) {
		    addmntent(newmtab, mp);
		    fprintf(stderr, "umount: Umount of %s failed\n",
			    mp->mnt_fsname);
		}
	    } else addmntent(newmtab, mp);
	}

	/* if it wasn't in mtab, try it anyway... */
	if(!flag) {
	    mnt.mnt_type = mnttype;
	    mnt.mnt_fsname = mntdev;
	    if(do_umount(&mnt) < 0) {
		fprintf(stderr, "umount: Umount of %s failed\n", mntdev);
	    }
	}
    }
    endmntent(newmtab);
    endmntent(mtab);

    unlink(MTAB_FILE);
    link(LOCK_FILE, MTAB_FILE);
    unlink(LOCK_FILE);

    exit(0);
}

void err(char * str)
{
    fprintf(stderr, "umount: %s: %s\n", str, strerror(errno));
    if(access(LOCK_FILE, 0) == 0) unlink(LOCK_FILE);
    exit(1);
}

int do_umount(struct mntent *mp)
{
    if(mp->mnt_type && strcmp(mp->mnt_type, MNTTYPE_SWAP) == 0) {
	if(swapoff(mp->mnt_fsname) < 0) return -1;
    } else {
	if(umount(mp->mnt_fsname) < 0) return -1;
    }

    if(opt_verbose)
      fprintf(stderr, "Unmounted %s\n", mp->mnt_fsname);

    return 0;
}

