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

/*
 * $Log:	mount.c,v $
 */

#ident	"$Header: mount.c 1.8 89/10/10 $@(#)mount:mount.c	1.50"
#include <sys/types.h>
#include <sys/stropts.h>
#include <sys/param.h>
#include <sys/sysmacros.h>
#include <sys/stat.h>
#include <sys/statfs.h>
#include <signal.h>
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <limits.h>
#include <time.h>
#include <string.h>
#include <errno.h>
#include <sys/hetero.h>
#include <sys/mount.h>
#include <sys/conf.h>
#include <mntent.h>

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

#define MAXFIELD	4
#define	RO_BIT		1

extern int   errno;
extern int   optind;
extern char *optarg;
extern char *getcwd();

char *getrootfsname();

/* static int cacheflag = MS_CACHE;	*/
static int roflag = 0;
static int more_info = 0;
static char *fieldv[MAXFIELD];

static char *n_name = NULL;
static char buf[PATH_MAX+1];	/* array to hold the special file name *
			    	 * to be placed into MNTTAB            */


main(argc,argv)
int  argc;
char **argv;
{
	char	*usage = "usage: mount [[-r] [-f fstyp] special directory]\n";
	char	*special;
	char	*directory;
	char	*fsname = NULL;

	int	 errflag = 0, fflag = 0;
	int	 temprec, num_args, c;

	struct	 mntent newmnt;
	struct	 mntent *mntp;
	FILE	*mnttab;

	/*
	 *	If no arguments are given, print out MNTTAB.
	 */

	if (argc == 1) {
		if ((mnttab = setmntent(MNTTAB, "r")) == NULL) {
			fprintf(stderr, "mount: cannot open ");
			perror(MNTTAB);
			exit(2);
		}
		while ((mntp = getmntent(mnttab)) != NULL) {
			if (strcmp(mntp->mnt_type, MNTTYPE_IGNORE) == 0) {
				continue;
			}
			printent(mntp);
		}
		endmntent(mnttab);
		exit(0);
	}

	/*
	 *	check for proper arguments
	 */

	while ((c = getopt(argc, argv, "rf:n:o:")) != EOF) {
		switch (c) {
			case 'r':
				if (roflag & RO_BIT)
					errflag = 1;
				else
					roflag |= RO_BIT;
				break;
			case 'f':
				if (fflag)
					errflag = 1;
				else {
					fflag = 1;
					fsname = optarg;
				}
				break;
			case 'o':
				/*
				 * We tolerate the presence of this arguement
				 * in case this is for an NFS file system.
				 * But we set errflag in case it isn't.
				 */
				errflag = 1;
				break;
			case 'n':
				/*
				 *	The -n option is an undocumented
				 *	option used to override the name
				 *	server.  It takes a machine name
				 *	(in the form of "netspec:dom.name")
				 *	as an argument.
				 */
				n_name = optarg;
				break;
			case '?':
				errflag = 1;
		}
	}

	/*
	 *	There must be at least 2 more arguments, the
	 *	special file and the directory.  If the "-r"
	 *	option is the only option given, it can appear at
	 *	the end of the arg list.
	 */

	num_args = argc - optind;

	switch (num_args) {
		case 3:
			/*
			 * The following is for backward compatability.
			 */

			if (strcmp(argv[optind + 2], "-r") == 0) {
				if (roflag)
					errflag = 1;
				else
					roflag = RO_BIT;
			} else
				errflag = 1;
			/*
			 *  Fall through to set special and directory
			 */
		case 2:
			special = argv[optind];
			directory = argv[optind + 1];

			if (!fflag) {
				if (parse(argv[optind],argv[optind + 1])) {
					fsname = fieldv[2];
					more_info = 1;
				}
			}
			break;
		case 1:
			more_info = 1;
			if (parse(argv[optind],NULL)) {
				special = fieldv[0];
				directory = fieldv[1];
				if (!fflag)
					fsname = fieldv[2];
			} else
				errflag = 1;
			break;
		default:
			errflag = 1;
	}

	/*
	 *	If no fs type is given, make the default type that
	 *	of the root file system.
	 */

	if (fsname == NULL) {
		fsname = getrootfsname();
	}

	if (strcmp(fsname, getrootfsname()) != 0) {
		char path[PATH_MAX+1];

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

	if (errflag) {
		fprintf(stderr, "%s", usage);
		exit(2);
	}

	if (*directory != '/') {
		fprintf(stderr, "mount: directory argument <%s> must be a full path name\n",directory);
		exit(2);
	}

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

	/*
	 *	Lock a temporary file to prevent many mounts 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, "mount: warning: cannot lock temp file </etc/mnt.lock>\n");
	}

	/*
	 *	Only the low-order bit of "roflag" is used by the system
	 *	calls (to denote read-only or read-write).
	 */

	loc_mount(special, directory, fsname, roflag & RO_BIT);

	signal(SIGHUP,  SIG_IGN);
	signal(SIGQUIT, SIG_IGN);
	signal(SIGINT,  SIG_IGN);

	/*
	 *	report inconsistencies in MNTTAB.
	 *
	 *	Note that buf[] now contains the special name
	 *	to be placed into MNTTAB.
	 */

	if ((mnttab = setmntent(MNTTAB, "r")) == NULL) {
		fprintf(stderr, "mount: cannot open ");
		perror(MNTTAB);
		exit(2);
	}
	while ((mntp = getmntent(mnttab)) != NULL) {
		if (strcmp(mntp->mnt_type, MNTTYPE_IGNORE) == 0) {
			continue;
		}
		errflag = 0;
		if (strcmp(mntp->mnt_dir, directory) == 0)
			errflag = 1;
		if (strcmp(mntp->mnt_fsname, buf) == 0)
			errflag = 1;
		if (errflag)
			fprintf(stderr,
			       "mount: warning: %s\t%s already in %s\n",
				mntp->mnt_fsname, mntp->mnt_dir, MNTTAB);
	}

	endmntent(mnttab);

	/*
	 *	Put the entry into the mount table.
	 */

	newmnt.mnt_fsname = buf;
	newmnt.mnt_dir    = directory;
	newmnt.mnt_type   = fsname;
	newmnt.mnt_opts   = (roflag & RO_BIT) ? "ro" : "rw";
	time(&newmnt.mnt_time);

	/*
	 * (setmntent() won't let us specify the file mode for creation (*sigh*)
	 *  Should be 0644.  setmnt(1M) should always create it though.)
	 */
	if ((mnttab = setmntent(MNTTAB, "a")) == NULL) {
		fprintf(stderr, "mount: warning: cannot create ");
		perror(MNTTAB);
		exit(1);
	}
	if (addmntent(mnttab, &newmnt)) {
		fprintf(stderr, "mount: warning: cannot write ");
		perror(MNTTAB);
		exit(1);
	}
	endmntent(mnttab);
	exit(0);
}


/* 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);
	}
}

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

int mt_name_to_num(name)
char	*name;
{
	if (strcmp(name, MNTTYPE_UFS) == 0)
		return(MOUNT_UFS);

	return (MOUNT_INVAL);
}

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);
}

static
loc_mount(special, directory, fsname, rflag)
char 	*special;
char 	*directory;
char	*fsname;
int	rflag;
{
	register char *ptr;
	struct statfs stbuf;
	short fstyp = 0;

	if (more_info == 1) {
		printf("mount -f %s %s%s %s\n", fsname, roflag & RO_BIT? "-r " : "", special, directory);
	}

	if (*special == '/')
		strncpy(buf, special, sizeof(buf));
	else {
		/*
		 *	Generate the full path name.
		 */

		if (getcwd(buf,sizeof(buf)) == NULL) {
			fprintf(stderr,"mount: cannot get current directory\n");
			buf[0] = '\0';
		}
		strncat(buf,"/",sizeof(buf)-strlen(buf));
		strncat(buf,special,sizeof(buf)-strlen(buf));
	}

	if (buf[sizeof(buf) - 1] != '\0') {
		buf[sizeof(buf) - 1] = '\0';
	   	fprintf(stderr,"mount: warning: pathname will be truncated in  %s to <%s>\n",
						        MNTTAB, buf);
	}

	/*
	 *	Get the file system type from fsname and
	 *	mount the device.
	 */

	if (fstyp == 0) {
		fstyp = mt_name_to_num(fsname);
		if (fstyp == MOUNT_INVAL) {
			fprintf(stderr, "mount: invalid fs type <%s>\n", fsname);
			exit(2);
		}
	}

	if (mount(special, directory, rflag | MS_DATA, fstyp,
	  (char *)NULL, 0)) {
		rpterr(special, directory, fsname);
		exit(2);
	}

	/*
 	 *	If the newly mounted directory was not on a 
	 *	block special device, then return.
	 *	Otherwise, strip any "/"s in the fsname
	 *	and check the label against the mount point.
	 */

	if (statfs(directory, &stbuf, sizeof(stbuf), 0) == -1)
		return;

	ptr = stbuf.f_fname;
	while (*ptr == '/')
		ptr ++;

	if (strncmp(strrchr(directory, '/') + 1, ptr, sizeof(stbuf.f_fname)))
		fprintf(stderr,"mount: warning: <%.6s> mounted as <%s>\n",
			stbuf.f_fname, directory);
}

rpterr(bs, mp, fsid)
register char *bs, *mp, *fsid;
{
	switch(errno) {
	case EPERM:
		fprintf(stderr,"mount: not super user\n");
		break;
	case ENXIO:
		fprintf(stderr,"mount: %s no such device\n",bs);
		break;
	case ENOTDIR:
		fprintf(stderr,"mount: %s not a directory\n",mp);
		break;
	case ENOENT:
		fprintf(stderr,"mount: %s or %s, no such file or directory\n", bs, mp);
		break;
	case EINVAL:
		fprintf(stderr,"mount: %s not a valid file system or not type %s\n",bs, fsid);
		break;
	case EBUSY:
		fprintf(stderr,"mount: %s is already mounted, %s is busy,\n", bs, mp);
		fprintf(stderr,"       or allowable number of mount points exceeded\n");
		break;
	case ENOTBLK:
		fprintf(stderr,"mount: %s not a block device\n",bs);
		break;
	case EROFS:
		fprintf(stderr,"mount: %s write-protected\n",bs);
		break;
	case ENOSPC:
		fprintf(stderr,"mount: possibly damaged file system\n");
		break;
	default:
		perror("mount");
		fprintf(stderr, "mount: cannot mount %s\n", bs);
	}
}

/*
 *	parse() gets missing information on command line from FSTAB.
 *	If arg2 is NULL, the arg1 is either a directory or special name.
 *	Otherwise, arg1 is special and arg2 is directory.
 */

static
parse(arg1,arg2)
char	*arg1, *arg2;
{
	FILE	*mnttab;
	struct	 mntent *mntp;
	int	 i, found = 0;
#define FIELDSIZE	PATH_MAX+1
	static char field[3][FIELDSIZE];

	for (i = 0; i < MAXFIELD; i++)
		fieldv[i] = NULL;

	if ((mnttab = setmntent(FSTAB, "r")) == NULL) {
		if (arg2 == NULL) {
			fprintf(stderr, "mount: cannot open ");
			perror(FSTAB);
		}
		return(0);
	}
	while ((mntp = getmntent(mnttab)) != NULL) {
		if ((strcmp(mntp->mnt_type, MNTTYPE_IGNORE) == 0) ||
		    (strcmp(mntp->mnt_type, MNTTYPE_SWAP) == 0)) {
			continue;
		}
		if (arg2 != NULL) {
			if (strcmp(arg1, mntp->mnt_fsname) == 0
			  && strcmp(arg2, mntp->mnt_dir) == 0) {
				found = 1;
				break;
			}
		} else {
			if (strcmp(arg1, mntp->mnt_fsname) == 0
			  || strcmp(arg1, mntp->mnt_dir) == 0) {
				found = 1;	
				break;
			}
		}
	}

	endmntent(mnttab);

	if (!found)
		return(0);

	/*
	 * Found an entry.  Copy the found values because a subsequent
	 * getmntent will overwrite.  Then setup fieldv[] and roflag.
	 */
	strncpy(field[0], mntp->mnt_fsname, FIELDSIZE);
	field[0][FIELDSIZE-1] = '\0';	/* ensure null terminated */

	strncpy(field[1], mntp->mnt_dir, FIELDSIZE);
	field[1][FIELDSIZE-1] = '\0';	/* ensure null terminated */

	strncpy(field[2], mntp->mnt_type, FIELDSIZE);
	field[1][FIELDSIZE-1] = '\0';	/* ensure null terminated */

	fieldv[0] = field[0];	/* special */
	fieldv[1] = field[1];	/* directory */
	fieldv[2] = field[2];	/* filesystem type */

	if (hasmntopt(mntp, "ro"))
		roflag |= RO_BIT;

	return(1);
}

/*
 * Just like strcpy() but ensure work with overlapped strings.
 */

char *
ovstrcpy(s1, s2)
register char *s1, *s2;
{
	register char *os1;

	os1 = s1;
	while(*s1++ = *s2++)
		;
	return(os1);
}

/*
 * Remove the specified mount option from the mnt_opts string if it is present.
 */

rmmntopt(mnt, opt)
struct mntent *mnt;
char *opt;
{
	char	*begin;
	char	*end;

	if (begin = hasmntopt(mnt, opt)) {
		end = strchr(begin, ',');	/* find where option ends */
		if (!end)
			end = "";		/* it's the last option */
		else
			end++;			/* skip over ',' */
		(void) ovstrcpy(begin, end);
	}
}

#define FORMAT		"%a %b %e %H:%M:%S %Y"   /* date time format */
				/* a  -  abbreviated month name */
				/* b  -  abbreviated weekday name */
				/* e  -  day of month */
				/* H  -  hour */
				/* M  -  minute */
				/* S  -  second */
				/* Y  -  Year */

printent(mnt)
	struct mntent *mnt;
{
	char time_buf[50];  /* array to hold data and time */

	cftime(time_buf, FORMAT, &mnt->mnt_time);
	printf("%s on %s %s on %s",
		mnt->mnt_dir, mnt->mnt_fsname,
		hasmntopt(mnt, "ro") ? "read only" : "read/write",
		time_buf);

	/* Don't print fstyp if the same as root filesys  */
	if (strcmp(mnt->mnt_type, getrootfsname()) != 0)
		printf(" type %s", mnt->mnt_type);

	/* remove the options already printed and print the rest, if any */
	rmmntopt(mnt, "ro");
	rmmntopt(mnt, "rw");
	if (*mnt->mnt_opts)
		printf(" (%s)", mnt->mnt_opts);

	printf("\n");
}
