/* 
    mount.c - A better mount command for Linux 0.99-patchlevel 8 or later.
    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 _BSD_SOURCE
#define _POSIX_SOURCE
#include <stdio.h>
#include <errno.h>
#include <unistd.h>
#include <fcntl.h>
#include <mntent.h>
#include <getopt.h>
#include <string.h>
#include <errno.h>
#include <sys/stat.h>
#include <sys/mount.h>

/*#define _LINUX_TYPES_H*/
#include <linux/fs.h>

/* these really should be defined in <mntent.h>. */
#define MNTOPT_NODEV    "nodev"
#define MNTOPT_NOEXEC   "noexec"
#define MNTOPT_SYNC     "sync"

#ifndef __STDC__
#error Error: This program requires ANSI C to compile
#endif

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

#define FSTAB  MNTTAB
#define UFSTAB "/etc/ufstab"

int opt_mountall = 0;
int opt_print    = 0;
int opt_verbose  = 0;
int opt_std      = 0;
int opt_usermount= 0;
int opt_fakemtab = 0;
int opt_nomtab   = 0;

char opt_nostd[200];
char mntoptions[200] = "defaults";

char mnttype[MNTMAXSTR] = "";
char *mntdev = NULL, *mntdir = NULL;
char mntdevbuf[MNTMAXSTR];

void usage(), process_options(char *opts);
void err(char *str);
void print_mtab(char *);
int do_mount(struct mntent *);
int lock_mtab();
void unlock_mtab();
void print_mntent(struct mntent *);

void usage()
{
    fprintf(stderr,
	    "Usage: mount [-afnpruv] [-t type] [-o options] [[dev] dir]\n");

    err(NULL);
}

int main(int argc, char *argv[])
{
    char c;

    /* parse options */

    while((c = getopt(argc, argv, "afnput:rvo:")) != EOF) {
	switch(c) {
	  case 'a':
	    opt_mountall = 1;
	    break;
	  case 'p':
	    opt_print = 1;
	    break;
	  case 't':
	    strncpy(mnttype, optarg, MNTMAXSTR-1);
	    break;
	  case 'r':
	    if(mntoptions[0] != '\0') strcat(mntoptions, ",");
	    strcat(mntoptions, "ro");
	    break;
	  case 'v':
	    opt_verbose = 1;
	    break;
	  case 'o':
	    strncpy(mntoptions, optarg, sizeof(mntoptions)-1);
	    break;
	  case 'u':
	    opt_usermount = 1;
	    break;
	  case 'f':
	    opt_fakemtab = 1;
	    break;
	  case 'n':
	    opt_nomtab = 1;
	    break;
	  default:
	    usage();
	}
    }

    if(getuid()) opt_usermount = 1;

    if(optind < argc) {
	mntdev = argv[optind++];
	if(optind >= argc) {
	    char *filename = opt_usermount ? UFSTAB : FSTAB;
	    FILE *fstab;
	    struct mntent *mp;
	    

	    /* only a mount-point was given, look up device in fstab */
	    mntdir = mntdev; mntdev = NULL;

	    if(!(fstab = setmntent(filename, "r"))) err(filename);
	    while((mp = getmntent(fstab))) {
		if(!strcmp(mntdir, mp->mnt_dir)) {
		    strncpy(mntdevbuf, mp->mnt_fsname, sizeof(mntdevbuf)-1);
		    mntdev = mntdevbuf;
		    break;
		} else if(!strcmp(mntdir, mp->mnt_fsname)) {
		    strncpy(mntdevbuf, mp->mnt_dir, sizeof(mntdevbuf)-1);
		    mntdev = mntdir;
		    mntdir = mntdevbuf;
		}
	    }
	    endmntent(fstab);

	    if(!mntdev) {
		fprintf(stderr, "mount: Don't know what to mount on %s\n",
			mntdir);
		err(NULL);
	    }
	} else mntdir = argv[optind];

    } else if(argc == 1) {
	/* with no args, just print mtab */
	print_mtab(MTAB_FILE);
	unlock_mtab();
	exit(0);
    }

    /* done with the arguments, now do some work */

    if(geteuid()) {
	fprintf(stderr, "mount: You must be root to mount a filesystem.\n");
	err(NULL);
    }

    /* check for lock */
    if(!lock_mtab()) {
	/* don't use err() for this */
	fprintf(stderr, "mount: A lock-file exists, mount denied.\n");
	exit(1);
    }

    if(opt_mountall) {
	/* read the /etc/fstab or /etc/ufstab and mount the filesystems */
	char *filename = opt_usermount ? UFSTAB : FSTAB;
	FILE *fstab;
	struct mntent *mp;

	if(!(fstab = setmntent(filename, "r"))) err(filename);
	while((mp = getmntent(fstab))) {
	    if(mnttype[0] == '\0' || !strcmp(mnttype, mp->mnt_type)) {
		if(opt_usermount 
		   && (mp->mnt_opts == NULL || mp->mnt_opts[0] == '\0')) {
		    mp->mnt_opts = "ro,nosuid,noexec,nodev";
		}
		if(opt_print) 
		  print_mntent(mp);
		else
		  (void) do_mount(mp);
	    }
	}
	endmntent(fstab);

    } else if(mntdir) {
	struct mntent mnt;

	if(opt_usermount) {
	    FILE *fstab;
	    struct mntent *mp;
	    int flag = 0;

	    if(!(fstab = setmntent(UFSTAB, "r"))) err(UFSTAB);
	    while((mp = getmntent(fstab))) {
		if(!strcmp(mntdev, mp->mnt_fsname) 
		   && !strcmp(mntdir, mp->mnt_dir)) {
		    flag = 1;
		    strncpy(mntoptions, mp->mnt_opts, sizeof(mntoptions)-1);
		    strncpy(mnttype, mp->mnt_type, sizeof(mnttype)-1);
		    break;
		}
	    }
	    endmntent(fstab);
	    if(!flag) {
		fprintf(stderr, "mount: Can't usermount %s.\n", mntdir);
		err(NULL);
	    }
	}

	mnt.mnt_type   = mnttype;
	mnt.mnt_fsname = mntdev;
	mnt.mnt_dir    = mntdir;
	mnt.mnt_opts   = mntoptions;
	mnt.mnt_freq   = 0;
	mnt.mnt_passno = 0;

	if(!do_mount(&mnt)) {
	    unlock_mtab();
	    exit(1);
	}
    }

    unlock_mtab();

    exit(0);
}

int lock_mtab()
{
    int fd;

    if((fd = open(LOCK_FILE, O_WRONLY | O_CREAT | O_EXCL, 0744)) < 0) return 0;
    close(fd);
    return 1;
}

void unlock_mtab()
{
    unlink(LOCK_FILE);
}

int do_mount(struct mntent *mp)
{
    struct stat s;
    FILE *mtab;

    if(!mp->mnt_type || !mp->mnt_type[0]) mp->mnt_type = "minix";
    
    if(!opt_fakemtab) {
	/* check the directory */
	if(stat(mp->mnt_dir, &s) < 0) {
	    if(errno == ENOENT || errno == ESRCH) {
		if(mkdir(mp->mnt_dir, 0755) < 0) {
		    fprintf(stderr, "mount: mkdir(%s) failed.\n", mp->mnt_dir);
		    return 0;
		}

		if(opt_verbose) 
		  fprintf(stderr, "Made directory: %s\n", mp->mnt_dir);
		
	    } else {
		fprintf(stderr, "mount: stat(%s) failed.\n", mp->mnt_dir);
		return 0;
	    }

	} else if(!S_ISDIR(s.st_mode)) {
	    fprintf(stderr, "mount: %s already exists, and isn't a directory.\n",
		    mp->mnt_dir);
	    return 0;
	}

	process_options(mp->mnt_opts);

#if 0
	printf("mount(%s,%s,%s,%x,%s)\n", mp->mnt_fsname, mp->mnt_dir,
	       mp->mnt_type, opt_std, opt_nostd);
#endif

	if(strcmp(mp->mnt_dir, "/") == 0) {
	    if(opt_verbose)
	      fprintf(stderr, "mount: root already mounted by kernel\n");
	} else {
	    if(strcmp(mp->mnt_type, MNTTYPE_IGNORE) == 0) {
		if(opt_verbose)
		  fprintf(stderr, "mount: ignored %s\n", mp->mnt_fsname);
		return 1;
	    }

	    if(strcmp(mp->mnt_type, MNTTYPE_SWAP) == 0) {
		if(swapon(mp->mnt_fsname) < 0) {
		    fprintf(stderr, "mount: swapon(%s) failed: %s\n",
			    mp->mnt_fsname, strerror(errno));
		    return 0;
		}
		if(opt_verbose)
		  fprintf(stderr, "Added swap: %s\n", mp->mnt_fsname);
	    } else {
		if(mount(mp->mnt_fsname, mp->mnt_dir, mp->mnt_type,
			 0xc0ed0000|opt_std, opt_nostd) < 0) {
		    fprintf(stderr, "mount: mount(%s,%s) failed: %s\n", 
			    mp->mnt_fsname, mp->mnt_dir, strerror(errno));
		    return 0;
		}
		if(opt_verbose)
		  fprintf(stderr, "Mounted %s on %s.\n", mp->mnt_fsname, 
			  mp->mnt_dir);
	    }
	}
    } /* if(!opt_fakemtab) */

    if(!opt_nomtab) {
	if(!(mtab = setmntent(MTAB_FILE, "a"))) {
	    fprintf(stderr, "mount: open(%s) failed.\n", MTAB_FILE);
	    return 1;
	}
	addmntent(mtab, mp);
	
	endmntent(mtab);
    }

    return 1;
}

void err(char *str)
{
    if(str) fprintf(stderr, "mount: %s: %s\n", str, strerror(errno));

    if(access(LOCK_FILE, 0) == 0) unlink(LOCK_FILE);
    exit(1);
}

void print_mntent(struct mntent *mp)
{
    printf("%s\t%s\t%s\t%s\t%d\t%d\n", mp->mnt_fsname, mp->mnt_dir,
	   mp->mnt_type, (mp->mnt_opts[0] == 0) ? "defaults" : mp->mnt_opts, 
	   mp->mnt_freq, mp->mnt_passno);
}

void print_mtab(char *filename)
{
    FILE *mf;
    struct mntent *mp;

    if(!((mf = setmntent(filename, "r")))) err(filename);
    while((mp = getmntent(mf))) print_mntent(mp);
    endmntent(mf);
}


void process_options(char *opts)
{
    /* opts is a comma-separated list of options */
    char opt[MNTMAXSTR];
    char *p;

    opt_nostd[0] = '\0';
    opt_std = 0;

    while(opts && *opts) {
	for(p = opt; *opts && *opts != ','; *p++ = *opts++);
	*p = '\0';
	if(*opts == ',') opts++;

	/* standard options, should be supported by all filesystems */
	if(!strcmp(opt, MNTOPT_DEFAULTS)) {
	    if(opt_usermount)
	      opt_std = MS_NOSUID|MS_NODEV|MS_NODEV|MS_NOEXEC;
	    else
	      opt_std = 0;
	}
	else if(!strcmp(opt, MNTOPT_RO))     opt_std |= MS_RDONLY;
	else if(!strcmp(opt, MNTOPT_RW))     opt_std &= ~MS_RDONLY;
	else if(!strcmp(opt, MNTOPT_SUID))   opt_std &= ~MS_NOSUID;
	else if(!strcmp(opt, MNTOPT_NOSUID)) opt_std |= MS_NOSUID;
	else if(!strcmp(opt, MNTOPT_NODEV))  opt_std |= MS_NODEV;
	else if(!strcmp(opt, MNTOPT_NOEXEC)) opt_std |= MS_NOEXEC;
	else if(!strcmp(opt, MNTOPT_SYNC))   opt_std |= MS_SYNC;
	else {
	    /* non-standard options are put in a comma-sep. string */
	    strcat(opt_nostd, opt);
	    strcat(opt_nostd, ",");
	}
    }

    if(opt_nostd[0])
      opt_nostd[strlen(opt_nostd)-1] = '\0';
	
}
