/*
 * $Copyright:	$
 * Copyright (c) 1984, 1985, 1986, 1987, 1988, 1989, 1990 
 * Sequent Computer Systems, Inc.   All rights reserved.
 *  
 * This software is furnished under a license and may be used
 * only in accordance with the terms of that license and with the
 * inclusion of the above copyright notice.   This software may not
 * be provided or otherwise made available to, or used by, any
 * other person.  No title to or ownership of the software is
 * hereby transferred.
 *
 */

/*	Copyright (c) 1984 AT&T	*/
/*	  All Rights Reserved  	*/

/*	THIS IS UNPUBLISHED PROPRIETARY SOURCE CODE OF AT&T	*/
/*	The copyright notice above does not evidence any   	*/
/*	actual or intended publication of such source code.	*/

#ident	"$Header: umount.c 1.9 90/04/05 $@(#)umount:umount.c	1.19"
#include <stdio.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/statfs.h>
#include <sys/errno.h>
#include <sys/mount.h>
#include <mntent.h>
#include <unistd.h>
#include <limits.h>
#include <time.h>

#define EQ(a,b)		(!strcmp(a,b))
#define LOCAL(x)	(!((x) & 2))
#define REMOTE(x)	((x) & 2)

/* The following filenames are defined in <mntent.h> */
#define MNTTAB          MNTTABNAME
#define FSTAB           FSTABNAME
#define SEM_FILE        SEMFILENAME

#ifdef RFSSUPPORTED
static int   distflag = 0;
#endif

char *getrootfsname();
extern int errno;

main(argc, argv)
int  argc;
char **argv;
{
#ifdef RFSSUPPORTED
	char	*usage = "usage: umount [-d] special\n";
#else
	char	*usage = "usage: umount special\n";
#endif
	register char	*special;
	int 	 equal_flag = 0;
	int	 temprec;
	int	 field;
	int	 pr_warn = 1;

	dev_t	 r_rdev;
	dev_t	 r_dev;
	ino_t	 r_ino;

	struct	 stat sbuf;
#ifdef RFSSUPPORTED
	register struct mnttab *mp;
#endif

	int tmpfd;
	FILE *mnttab;
	FILE *tmpmnt;
	char *tmpname = "/etc/umountXXXXXX";
	struct mntent *mntp;

	/*
	 *	Process arguments...
	 */

	switch (argc) {
#ifdef RFSSUPPORTED
		case 3:
			if (strcmp(argv[1], "-d") == 0)
				distflag = 1;
			else {
				fprintf(stderr, "%s", usage);
				exit(2);
			}
			/*
			 *	Fall through to set special...
			 */
#endif
		case 2:
			special = argv[argc - 1];
			break;
		default:
			fprintf(stderr, "%s", usage);
			exit(2);
	}

	if (geteuid() != 0) {
		fprintf(stderr, "umount: not super user\n");
		exit(2);
	}

	/*
	 * make sure fopen keeps MNTTAB readable for
	 * normal users to do 'df' and 'mount'.
	 */
	umask(0);

	/* Lets see if this is a local  unmount or a remote */

	if ((mnttab = setmntent(MNTTAB, "r")) == NULL) {
		fprintf(stderr, "umount: cannot open ");
		perror(MNTTAB);
		exit(1);
	}

	while ((mntp = getmntent(mnttab)) != NULL) {
		if (EQ(mntp->mnt_type, MNTTYPE_IGNORE)) {
			continue;
		}

		if ((EQ(mntp->mnt_fsname, special)) ||
		    (EQ(mntp->mnt_dir, special))) {
			if (!EQ(mntp->mnt_type, getrootfsname())) {
				char path[PATH_MAX+1];

				path[0] = '\0';
				(void) strcat(path, "/etc/umount_");
				(void) strcat(path, mntp->mnt_type);
				argv[0] = path;
				execv(path, argv);
		/* couldn't find mount command for the specified fs type */
				fprintf(stderr,"umount: invalid fs type <%s>\n",
					mntp->mnt_type);
				exit(2);
			}
		}
	}
	endmntent(mnttab);

	/*
	 *	Lock a temporary file to prevent many unmounts at once.
	 *	This is done to ensure integrity of the MNTTAB.
	 */

	if ((temprec = creat(SEM_FILE, 0600)) == -1 ||
	     lockf(temprec, F_LOCK, 0L) < 0) {
		fprintf(stderr, "umount: warning: cannot lock temp file <%s>\n", SEM_FILE);
	}

#ifdef RFSSUPPORTED
	if (distflag) {
		if (rumount(special) < 0) {
			rpterr(special);
			/*
			 *	If the rumount failed due to a link
			 *	being down, continue and remove the
			 *	remote resource from MNTTAB.
			 */
			if (errno != ECOMM) {
				exit(2);
			}
		}
	} else 
#endif /*RFSSUPPORTED*/
	{
		if (umount(special) < 0) {
			rpterr(special);
			if (errno != EINVAL) {
				exit(2);
			}
			pr_warn = 0;
		}
	}

#ifdef RFSSUPPORTED
	if (!distflag) 
#endif
	{

		/*
		 *	get the device number of special so
		 *	it can be removed from MNTTAB.
		 */

		field = 2;
		if (stat(special, &sbuf) < 0) {
			r_rdev = -1;
		} else {
			if ((sbuf.st_mode & S_IFMT) == S_IFBLK) {
				r_rdev = sbuf.st_rdev;
			} else {
				r_ino = sbuf.st_ino;
				r_dev = sbuf.st_dev;
				field = 1;
			}
		}
	}

	/*
	 * Build new temporary mnttab by copying old mnttab except for
	 * the entry we just unmounted.
	 */

	/* Open old mnttab */

	if ((mnttab = setmntent(MNTTAB, "r")) == NULL) {
		fprintf(stderr, "umount: cannot open ");
		perror(MNTTAB);
		exit(1);
	}

	/* Create & open new mnttab with temporary name */

	(void) mktemp(tmpname);
	if ((tmpfd = open(tmpname, O_RDWR|O_CREAT|O_TRUNC, 0644)) < 0) {
		fprintf(stderr,"umount: cannot create ");
		perror(tmpname);
		exit(1);
	}
	close(tmpfd);
	tmpmnt = setmntent(tmpname, "w");
	if (tmpmnt == NULL) {
		fprintf(stderr,"umount: cannot open ");
		perror(tmpname);
		exit(1);
	}

	/*
	 * Copy old mnttab entries to new mnttab except for the 
	 * entry we just unmounted.
	 */
	while ((mntp = getmntent(mnttab)) != NULL) {
		equal_flag = 0;
		if (strcmp(mntp->mnt_type, MNTTYPE_IGNORE) == 0) {
			continue;
		}
#ifdef RFSSUPPORTED
		if (distflag)
			equal_flag = EQ(special, mntp->mnt_fsname) &&
				     REMOTE(mp->mt_ro_flg);
		else 
#endif /*RFSSUPPORTED*/
		if (EQ(mntp->mnt_type, getrootfsname())) {
			if (field == 2) {
				if (stat(mntp->mnt_fsname, &sbuf) < 0)
					equal_flag = EQ(special, mntp->mnt_fsname);
				else
					equal_flag = (sbuf.st_rdev == r_rdev);
			} else {
				if (stat(mntp->mnt_dir, &sbuf) < 0)
					equal_flag = EQ(special, mntp->mnt_dir);
				else
					equal_flag = (sbuf.st_ino == r_ino) &&
						     (sbuf.st_dev == r_dev);
			}
		}
		if (equal_flag) {
			pr_warn = 0;	/* found the entry */
			continue;
		}
		(void) addmntent(tmpmnt, mntp);
	}
	endmntent(tmpmnt);
	endmntent(mnttab);

	/* Move tmp mnttab to real mnttab */

	if (rename(tmpname, MNTTAB) < 0) {
		fprintf(stderr,"umount: cannot rename %s to ", tmpname);
		perror(MNTTAB);
		exit(1);
	}

	if (pr_warn)
		fprintf(stderr, "warning: %s was not in mount table\n", special);
	exit(0);
}

static
rpterr(s)
char *s;
{
	switch(errno){
	case EPERM:
		fprintf(stderr,"umount: not super user\n");
		break;
	case ENXIO:
		fprintf(stderr,"umount: %s no device\n",s);
		break;
	case ENOENT:
		fprintf(stderr,"umount: %s no such file or directory\n",s);
		break;
	case EINVAL:
		fprintf(stderr,"umount: %s not mounted\n",s);
		break;
	case EBUSY:
		fprintf(stderr,"umount: %s busy\n",s);
		break;
	case ENOTBLK:
		fprintf(stderr,"umount: %s block device required\n",s);
		break;
	case ECOMM:
		fprintf(stderr,"umount: warning: broken link detected\n");
		break;
	default:
		perror("umount");
		fprintf(stderr,"umount: cannot unmount %s\n",s);
	}
}


/* Routine to map from file system numbers to names. */

char *mt_num_to_name(type)
int	type;
{
	switch (type) {
	case MOUNT_UFS:
		return (MNTTYPE_UFS);
	default:
		return ((char *) NULL);
	}
}

char *
getrootfsname()
{
	struct statfs stbuf;
	static char *rootfsname = NULL;
	short fstyp = 0;

	if (rootfsname == NULL) {
		if (statfs("/", &stbuf, sizeof(stbuf), 0) == -1) {
			fprintf(stderr, "mount: cannot obtain the fs type of root\n");
			exit(2);
		}
		fstyp = stbuf.f_fstyp;
		rootfsname = mt_num_to_name(fstyp);
		if (rootfsname == NULL) {
			fprintf(stderr,
			     "mount: root filesystem has invalid fs type: %d\n",
			     fstyp);
			exit(2);
		}
	}
	return(rootfsname);
}
