/* $Id$ */
/* Copyright (c) 2007-2015 Pierre Pronchery <khorben@defora.org> */
/* This file is part of DeforaOS Unix others */
/* 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, version 3 of the License.
 *
 * 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, see <http://www.gnu.org/licenses/>. */



#include <sys/stat.h>
#include <sys/statvfs.h>
#include <sys/param.h>
#include <sys/mount.h>
#ifdef MOUNT_ADOSFS
# define MT_ADOSFS	MOUNT_ADOSFS
# include <adosfs/adosfs.h>
#endif
#ifdef MOUNT_CD9660
# define MT_ISO9660	MOUNT_CD9660
# include <isofs/cd9660/cd9660_mount.h>
#endif
#ifdef MOUNT_EXT2FS
# define MT_EXT2FS	MOUNT_EXT2FS
# include <ufs/ufs/ufsmount.h>
#endif
#ifdef MOUNT_FFS
# define MT_FFS		MOUNT_FFS
# include <ufs/ufs/ufsmount.h>
#endif
#ifdef MOUNT_HFS
# define MT_HFS		MOUNT_HFS
# include <fs/hfs/hfs.h>
#endif
#ifdef MOUNT_MSDOS
# define MT_FAT		MOUNT_MSDOS
# include <msdosfs/msdosfsmount.h>
#endif
#ifdef MOUNT_NFS
# define MT_NFS		MOUNT_NFS
# include <nfs/nfsmount.h>
#endif
#ifdef MOUNT_NTFS
# define MT_NTFS	MOUNT_NTFS
# include <ntfs/ntfsmount.h>
#endif
#ifdef MOUNT_NULLFS
# define MT_NULLFS	MOUNT_NULLFS
# include <miscfs/nullfs/null.h>
#endif
#ifdef MOUNT_PROCFS
# define MT_PROCFS	MOUNT_PROCFS
# include <miscfs/procfs/procfs.h>
# define PROCFS_ARGS_VERSION	PROCFS_ARGSVERSION
#endif
#ifdef MOUNT_TMPFS
# define MT_TMPFS	MOUNT_TMPFS
# include <fs/tmpfs/tmpfs_args.h>
#endif
#ifdef MOUNT_UNION
# define MT_UNIONFS	MOUNT_UNION
# include <miscfs/union/union.h>
#endif
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>

#ifndef PROGNAME
# define PROGNAME	"mount"
#endif

#ifndef FSTAB
# define FSTAB		"/etc/fstab"
#endif

/* portability */
#ifndef MT_ISO9660
# define MT_ISO9660	"iso9660"
struct iso_args
{
	char const * fspec;
};
#endif
#ifndef MT_PROCFS
# define MT_PROCFS	"proc"
struct procfs_args
{
	int version;
	int args;
};
# define PROCFS_ARGS_VERSION	0
#endif


/* mount */
/* private */
/* types */
typedef struct _Prefs
{
	int flags;
	char const * options;
	char const * type;
} Prefs;
#define PREFS_a 0x1
#define PREFS_f 0x2
#define PREFS_u	0x4


/* prototypes */
#ifdef MT_ADOSFS
static int _mount_callback_adosfs(char const * type, int flags,
		char const * special, char const * node);
#endif
#ifdef MT_EXT2FS
static int _mount_callback_ext2fs(char const * type, int flags,
		char const * special, char const * node);
#endif
#ifdef MT_FAT
static int _mount_callback_fat(char const * type, int flags,
		char const * special, char const * node);
#endif
#ifdef MT_FFS
static int _mount_callback_ffs(char const * type, int flags,
		char const * special, char const * node);
#endif
static int _mount_callback_generic(char const * type, int flags,
		char const * special, char const * node);
#ifdef MT_HFS
static int _mount_callback_hfs(char const * type, int flags,
		char const * special, char const * node);
#endif
#ifdef MT_ISO9660
static int _mount_callback_iso9660(char const * type, int flags,
		char const * special, char const * node);
#endif
#ifdef MT_MFS
static int _mount_callback_mfs(char const * type, int flags,
		char const * special, char const * node);
#endif
#ifdef MT_NFS
static int _mount_callback_nfs(char const * type, int flags,
		char const * special, char const * node);
#endif
#ifdef MT_NTFS
static int _mount_callback_ntfs(char const * type, int flags,
		char const * special, char const * node);
#endif
#ifdef MT_NULLFS
static int _mount_callback_nullfs(char const * type, int flags,
		char const * special, char const * node);
#endif
#ifdef MT_PROCFS
static int _mount_callback_procfs(char const * type, int flags,
		char const * special, char const * node);
#endif
#ifdef MT_TMPFS
static int _mount_callback_tmpfs(char const * type, int flags,
		char const * special, char const * node);
#endif
#ifdef MT_UNIONFS
static int _mount_callback_unionfs(char const * type, int flags,
		char const * special, char const * node);
#endif


/* constants */
/* options */
static const struct
{
	size_t len;
	char const * name;
	int flags;
} _mount_options[] =
{
#ifdef MNT_ASYNC
	{ 5,	"async",	MNT_ASYNC	},
# ifndef MNT_SYNCHRONOUS
	{ 4,	"sync",		-MNT_ASYNC	},
# endif
#endif
#ifdef MNT_LOCAL
	{ 5,	"local",	MNT_LOCAL	},
#endif
#ifdef MNT_LOG
	{ 3,	"log",		MNT_LOG		},
#endif
#ifdef MNT_NOATIME
	{ 5,	"atime",	-MNT_NOATIME	},
	{ 7,	"noatime",	MNT_NOATIME	},
#endif
#ifdef MS_NOATIME
	{ 5,	"atime",	-MS_NOATIME	},
	{ 7,	"noatime",	MS_NOATIME	},
#endif
#ifdef MNT_NOCOREDUMP
	{ 8,	"coredump",	-MNT_NOCOREDUMP	},
	{ 10,	"nocoredump",	MNT_NOCOREDUMP	},
#endif
#ifdef MNT_NODEV
	{ 3,	"dev",		-MNT_NODEV	},
	{ 5,	"nodev",	MNT_NODEV	},
#endif
#ifdef MS_NODEV
	{ 3,	"dev",		-MS_NODEV	},
	{ 5,	"nodev",	MS_NODEV	},
#endif
#ifdef MNT_NODEVMTIME
	{ 8,	"devmtime",	-MNT_NODEVMTIME	},
	{ 10,	"nodevmtime",	MNT_NODEVMTIME	},
#endif
#ifdef MNT_NOEXEC
	{ 4,	"exec",		-MNT_NOEXEC	},
	{ 6,	"noexec",	MNT_NOEXEC	},
#endif
#ifdef MS_NOEXEC
	{ 4,	"exec",		-MS_NOEXEC	},
	{ 6,	"noexec",	MS_NOEXEC	},
#endif
#ifdef MNT_NOSUID
	{ 4,	"suid",		-MNT_NOSUID	},
	{ 6,	"nosuid",	MNT_NOSUID	},
#endif
#ifdef MS_NOSUID
	{ 4,	"suid",		-MS_NOSUID	},
	{ 6,	"nosuid",	MS_NOSUID	},
#endif
#ifdef MNT_RDONLY
	{ 2,	"ro",		MNT_RDONLY	},
	{ 2,	"rw",		-MNT_RDONLY	},
#endif
#ifdef MS_RDONLY
	{ 2,	"ro",		MS_RDONLY	},
	{ 2,	"rw",		-MS_RDONLY	},
#endif
#ifdef MNT_ROOTFS
	{ 4,	"root",		MNT_ROOTFS	},
#endif
#ifdef MNT_SOFTDEP
	{ 7,	"softdep",	MNT_SOFTDEP	},
#endif
#ifdef MNT_SYNCHRONOUS
# ifndef MNT_ASYNC
	{ 5,	"async",	-MNT_SYNCHRONOUS},
# endif
	{ 4,	"sync",		MNT_SYNCHRONOUS	},
#endif
#ifdef MS_SYNCHRONOUS
# ifndef MS_ASYNC
	{ 5,	"async",	-MS_SYNCHRONOUS},
# endif
	{ 4,	"sync",		MS_SYNCHRONOUS	},
#endif
#ifdef MNT_UNION
	{ 5,	"union",	MNT_UNION	},
	{ 7,	"nounion",	-MNT_UNION	},
#endif
	{ 0,	NULL,		0		}
};

/* filesystems supported */
static const struct
{
	char * name;
	char * type;
	int (*callback)(char const * type, int flags, char const * special,
			char const * node);
} _mount_supported[] =
{
#ifdef MT_ADOSFS
	{ "adosfs",	MT_ADOSFS,	_mount_callback_adosfs	},
#endif
#ifdef MT_EXT2FS
	{ "ext2fs",	MT_EXT2FS,	_mount_callback_ext2fs	},
#endif
#ifdef MT_EXT3FS
	{ "ext3fs",	MT_EXT3FS,	_mount_callback_ext2fs	}, /* XXX */
#endif
#ifdef MT_FAT
	{ "fat",	MT_FAT,		_mount_callback_fat	},
#endif
#ifdef MT_FFS
	{ "ffs",	MT_FFS,		_mount_callback_ffs	},
#endif
#ifdef MT_HFS
	{ "hfs",	MT_HFS,		_mount_callback_hfs	},
#endif
#ifdef MT_ISO9660
	{ "iso9660",	MT_ISO9660,	_mount_callback_iso9660	},
#endif
#ifdef MT_MFS
	{ "mfs",	MT_MFS,		_mount_callback_mfs	},
#endif
#ifdef MT_NFS
	{ "nfs",	MT_NFS,		_mount_callback_nfs	},
#endif
#ifdef MT_NTFS
	{ "ntfs",	MT_NTFS,	_mount_callback_ntfs	},
#endif
#ifdef MT_NULLFS
	{ "nullfs",	MT_NULLFS,	_mount_callback_nullfs	},
#endif
#ifdef MT_PROCFS
	{ "procfs",	MT_PROCFS,	_mount_callback_procfs	},
#endif
#ifdef MT_TMPFS
	{ "tmpfs",	MT_TMPFS,	_mount_callback_tmpfs	},
#endif
#ifdef MT_UNIONFS
	{ "unionfs",	MT_UNIONFS,	_mount_callback_unionfs	},
#endif
	{ NULL,		NULL,		_mount_callback_generic	}
};


/* prototypes */
static int _mount(Prefs * prefs, char const * special, char const * node);

static int _mount_error(char const * message, int ret);
static int _mount_usage(void);


/* functions */
/* mount */
static int _mount_all(Prefs * prefs, char const * node);
static int _mount_print(void);
static int _mount_do(Prefs * prefs, char const * special, char const * node);
static int _mount_do_mount(char const * type, int flags, char const * special,
		char const * node, void * data, size_t datalen);
static void _mount_do_options(Prefs * prefs, int * flags);

static int _mount(Prefs * prefs, char const * special, char const * node)
{
	if(special == NULL && node == NULL)
		return (prefs != NULL && (prefs->flags & PREFS_a) == PREFS_a)
			? _mount_all(prefs, NULL) : _mount_print();
	return _mount_do(prefs, special, node);
}

static int _mount_all(Prefs * prefs, char const * node)
{
	int ret = 0;
	Prefs p;
	const char fstab[] = FSTAB;
	FILE * fp;
	char buf[128];
	size_t len;
	int res;
	char fsspecial[32];
	char fsnode[32];
	char fstype[32];
	char fsoptions[32];
	unsigned int freq;
	unsigned int passno;

	if(prefs != NULL)
		memcpy(&p, prefs, sizeof(p));
	else
		memset(&p, 0, sizeof(p));
	p.type = fstype;
	p.options = fsoptions;
	if((fp = fopen(fstab, "r")) == NULL)
		return -_mount_error(fstab, 1);
	while(fgets(buf, sizeof(buf), fp) != NULL)
	{
		if((len = strlen(buf)) == 0)
			continue; /* empty line */
		if(buf[len - 1] != '\n')
		{
			errno = E2BIG; /* XXX */
			break; /* line is too long */
		}
		if(buf[0] == '#')
			continue; /* comment */
		freq = 0;
		passno = 0;
		fsoptions[0] = '\0';
		res = sscanf(buf, "%31s %31s %31s %31s %u %u\n", fsspecial,
				fsnode, fstype, fsoptions, &freq, &passno);
		if(res < 3)
		{
			errno = EINVAL;
			break; /* not enough arguments */
		}
		if(node != NULL && strcmp(node, fsnode) != 0)
			continue;
		if(prefs != NULL && prefs->type != NULL
				&& strcmp(prefs->type, fstype) != 0)
			continue;
		fsspecial[sizeof(fsspecial) - 1] = '\0';
		fsnode[sizeof(fsnode) - 1] = '\0';
		fstype[sizeof(fstype) - 1] = '\0';
		fsoptions[sizeof(fsoptions) - 1] = '\0';
		ret |= _mount_do(&p, fsspecial, fsnode);
	}
	if(!feof(fp))
		ret |= -_mount_error(fstab, 1);
	if(fclose(fp) != 0)
		ret |= -_mount_error(fstab, 1);
	return ret;
}

#ifdef ST_WAIT
static void _mount_print_flags(unsigned long flags);

static int _mount_print(void)
{
	int cnt;
	size_t s;
	struct statvfs * f;
	int i;

	if((cnt = getvfsstat(NULL, 0, ST_WAIT)) < 0)
		return _mount_error("getvfsstat", 1);
	s = sizeof(*f) * cnt;
	if((f = malloc(s)) == NULL)
		return _mount_error("malloc", 1);
	/* XXX race condition (the result of getvfsstat() may be different) */
	if((cnt = getvfsstat(f, s, ST_WAIT)) < 0)
	{
		free(f);
		return _mount_error("getvfsstat", 1);
	}
	for(i = 0; i < cnt; i++)
	{
		printf("%s%s%s%s%s%s", f[i].f_mntfromname, " on ",
				f[i].f_mntonname, " type ", f[i].f_fstypename,
				" (");
		_mount_print_flags(f[i].f_flag);
		printf("%s\n", ")");
	}
	free(f);
	return 0;
}

static void _mount_print_flags(unsigned long flags)
{
	size_t i;
	char const * sep = "";

	for(i = 0; i < sizeof(_mount_options) / sizeof(*_mount_options); i++)
	{
		if(_mount_options[i].flags <= 0)
			continue;
		if((flags & _mount_options[i].flags)
				== (unsigned)_mount_options[i].flags)
		{
			printf("%s%s", sep, _mount_options[i].name);
			sep = ",";
			flags -= _mount_options[i].flags;
		}
	}
	if(flags != 0)
		printf("%s%lx", sep, flags);
}
#else /* workaround when getvfsstat() is missing */
static int _mount_print(void)
{
	int ret = 0;
	FILE * fp;
	const char mtab[] = "/etc/mtab";
	const char mounts[] = "/proc/mounts";
	char const * file = mtab;
	size_t res;
	char buf[256];

	if((fp = fopen(file, "r")) == NULL)
	{
		_mount_error(file, 1);
		file = mounts;
		if((fp = fopen(file, "r")) == NULL)
			return -_mount_error(file, 1);
	}
	while((res = fread(buf, 1, sizeof(buf), fp)) > 0)
		fwrite(buf, 1, res, stdout);
	if(!feof(fp))
		ret = -_mount_error(file, 1);
	if(fclose(fp) != 0)
		ret = -_mount_error(file, 1);
	return ret;
}
#endif

static int _mount_do(Prefs * prefs, char const * special, char const * node)
{
	int flags = 0;
	size_t i;

#ifdef MF_FORCE
	if(prefs->flags & PREFS_f)
		flags |= MF_FORCE;
#endif
#ifdef MNT_FORCE
	if(prefs->flags & PREFS_f)
		flags |= MNT_FORCE;
#endif
#ifdef MF_REMOUNT
	if(prefs->flags & PREFS_u)
		flags |= MF_REMOUNT;
#endif
#ifdef MNT_UPDATE
	if(prefs->flags & PREFS_u)
		flags |= MNT_UPDATE;
#endif
#ifdef MS_REMOUNT
	if(prefs->flags & PREFS_u)
		flags |= MS_REMOUNT;
#endif
	_mount_do_options(prefs, &flags);
	if(prefs->type == NULL)
		return _mount_callback_generic(NULL, flags, special, node);
	for(i = 0; _mount_supported[i].name != NULL; i++)
		if(strcmp(_mount_supported[i].name, prefs->type) == 0)
			return _mount_supported[i].callback(
					_mount_supported[i].type, flags,
					special, node);
	fprintf(stderr, "%s: %s: Filesystem not supported\n", PROGNAME,
			prefs->type);
	return -1;
}

static int _mount_do_mount(char const * type, int flags, char const * special,
		char const * node, void * data, size_t datalen)
{
	struct stat st;

#if defined(__NetBSD__) /* NetBSD */
#if !defined(__NetBSD_Version__) || __NetBSD_Version__ >= 499000000
	if(mount(type, node, flags, data, datalen) == 0)
# else
	if(mount(type, node, flags, data) == 0)
# endif
#elif defined(__APPLE__) || defined(__FreeBSD__) /* Darwin, FreeBSD */
	if(mount(type, node, flags, data) == 0)
#else
	struct { char const * fspec; } * d = data;

	if(mount(special, node, type, flags, d->fspec) == 0)
#endif
		return 0;
	switch(errno)
	{
		case ENOENT:
			if(stat(node, &st) == 0)
				return -_mount_error(special, 1);
			return -_mount_error(node, 1);
		case ENODEV:
		case ENXIO:
			return -_mount_error(special, 1);
		default:
			return -_mount_error(node, 1);
	}
}

static void _mount_do_options(Prefs * prefs, int * flags)
{
	char const * o;
	size_t i;
	size_t j;

	if((o = prefs->options) == NULL)
		return;
	for(i = 0;; i++)
	{
		if(o[i] != ',' && o[i] != '\0')
			continue;
		for(j = 0; j < sizeof(_mount_options) / sizeof(*_mount_options);
				j++)
			if(i > 0 && _mount_options[j].len == i
					&& strncmp(_mount_options[j].name, o, i)
					== 0)
			{
				if(_mount_options[j].flags >= 0)
					*flags |= _mount_options[j].flags;
				else
					*flags &= ~(_mount_options[j].flags);
				break;
			}
		o += i;
		i = 0;
		if(o[i] == '\0')
			break;
		o++;
	}
}

#ifdef MT_ADOSFS
static int _mount_callback_adosfs(char const * type, int flags,
		char const * special, char const * node)
{
	int ret;
	struct adosfs_args adosfs;
	void * data = &adosfs;
	struct stat st;

	memset(&adosfs, 0, sizeof(adosfs));
	if((adosfs.fspec = strdup(special)) == NULL)
		return -_mount_error(node, 1);
	if(stat(node, &st) == 0)
	{
		adosfs.uid = st.st_uid;
		adosfs.gid = st.st_gid;
		adosfs.mask = ~st.st_mode & 0777;
	}
	/* FIXME actually parse options */
	adosfs.mask = 0755;
	type = MT_ADOSFS;
	ret = _mount_do_mount(type, flags, special, node, data, sizeof(adosfs));
	free(adosfs.fspec);
	return ret;
}
#endif

#ifdef MT_EXT2FS
static int _mount_callback_ext2fs(char const * type, int flags,
		char const * special, char const * node)
{
	int ret;
	struct ufs_args ffs;
	void * data = &ffs;

	memset(&ffs, 0, sizeof(ffs));
	if((ffs.fspec = strdup(special)) == NULL)
		return -_mount_error(node, 1);
	type = MT_EXT2FS;
	ret = _mount_do_mount(type, flags, special, node, data, sizeof(ffs));
	free(ffs.fspec);
	return ret;
}
#endif

#ifdef MT_FAT
static int _mount_callback_fat(char const * type, int flags,
		char const * special, char const * node)
{
	int ret;
	struct msdosfs_args msdosfs;
	void * data = &msdosfs;
	struct stat st;

	memset(&msdosfs, 0, sizeof(msdosfs));
	if((msdosfs.fspec = strdup(special)) == NULL)
		return -_mount_error(node, 1);
	if(stat(node, &st) == 0)
	{
		msdosfs.uid = st.st_uid;
		msdosfs.gid = st.st_gid;
		msdosfs.mask = ~st.st_mode & 0666;
		msdosfs.dirmask = ~st.st_mode & 0777;
	}
	/* FIXME actually parse options */
	msdosfs.version = MSDOSFSMNT_VERSION;
	type = MT_FAT;
	ret = _mount_do_mount(type, flags, special, node, data,
			sizeof(msdosfs));
	free(msdosfs.fspec);
	return ret;
}
#endif

#ifdef MT_FFS
static int _mount_callback_ffs(char const * type, int flags,
		char const * special, char const * node)
{
	int ret;
	struct ufs_args ffs;
	void * data = &ffs;

	memset(&ffs, 0, sizeof(ffs));
	if((ffs.fspec = strdup(special)) == NULL)
		return -_mount_error(node, 1);
	type = MT_FFS;
	ret = _mount_do_mount(type, flags, special, node, data, sizeof(ffs));
	free(ffs.fspec);
	return ret;
}
#endif

static int _mount_callback_generic(char const * type, int flags,
		char const * special, char const * node)
{
	struct
	{
		char const * fspec;
	} data;

	if(node == NULL)
	{
		errno = EINVAL;
		return -_mount_error("mount", 1);
	}
	if(special == NULL)
		return -_mount_all(NULL, node);
	data.fspec = special;
	return _mount_do_mount(type, flags, special, node, &data,
			sizeof(special));
}

#ifdef MT_HFS
static int _mount_callback_hfs(char const * type, int flags,
		char const * special, char const * node)
{
	int ret;
	struct hfs_args hfs;
	void * data = &hfs;

	memset(&hfs, 0, sizeof(hfs));
	if((hfs.fspec = strdup(special)) == NULL)
		return -_mount_error(node, 1);
	/* XXX does not support options at the moment */
	type = MT_HFS;
	ret = _mount_do_mount(type, flags, special, node, data, sizeof(hfs));
	free(hfs.fspec);
	return ret;
}
#endif

#ifdef MT_ISO9660
static int _mount_callback_iso9660(char const * type, int flags,
		char const * special, char const * node)
{
	struct iso_args iso9660;
	void * data = &iso9660;

	memset(&iso9660, 0, sizeof(iso9660));
	iso9660.fspec = special;
	/* FIXME actually parse options */
	type = MT_ISO9660;
	return _mount_do_mount(type, flags, special, node, data,
			sizeof(iso9660));
}
#endif

#ifdef MT_MFS
static int _mount_callback_mfs(char const * type, int flags,
		char const * special, char const * node)
{
	int ret;
	struct mfs_args mfs;
	void * data = &mfs;

	memset(&mfs, 0, sizeof(mfs));
	if((mfs.fspec = strdup(special)) == NULL)
		return -_mount_error(node, 1);
	/* FIXME actually parse options */
	mfs.size = (2 << 24);
	type = MT_MFS;
	ret = _mount_do_mount(type, flags, special, node, data, sizeof(mfs));
	free(mfs.fspec);
	return ret;
}
#endif

#ifdef MT_NFS
static int _mount_callback_nfs(char const * type, int flags,
		char const * special, char const * node)
{
	int ret;
	struct nfs_args nfs;
	void * data = &nfs;
	char * p;
	char * q;

	memset(&nfs, 0, sizeof(nfs));
	if(special == NULL || strchr(special, ':') == NULL)
	{
		errno = EINVAL;
		return -_mount_error(node, 1);
	}
	if((p = strdup(special)) == NULL)
		return -_mount_error(node, 1);
	q = strchr(p, ':');
	*(q++) = '\0';
	/* FIXME untested */
	nfs.version = NFS_ARGSVERSION;
	nfs.hostname = p;
	nfs.fh = (unsigned char *)q;
	/* FIXME implement the rest */
	type = MT_NFS;
	ret = _mount_do_mount(type, flags, special, node, data, sizeof(nfs));
	free(q);
	return ret;
}
#endif

#ifdef MT_NTFS
static int _mount_callback_ntfs(char const * type, int flags,
		char const * special, char const * node)
{
	int ret;
	struct ntfs_args ntfs;
	void * data = &ntfs;
	struct stat st;

	memset(&ntfs, 0, sizeof(ntfs));
	if((ntfs.fspec = strdup(special)) == NULL)
		return -_mount_error(node, 1);
	if(stat(node, &st) == 0)
	{
		ntfs.uid = st.st_uid;
		ntfs.gid = st.st_gid;
		ntfs.mode = ~st.st_mode & 0777;
	}
	/* FIXME actually parse options */
	type = MT_NTFS;
	ret = _mount_do_mount(type, flags, special, node, data, sizeof(ntfs));
	free(ntfs.fspec);
	return ret;
}
#endif

#ifdef MT_NULLFS
static int _mount_callback_nullfs(char const * type, int flags,
		char const * special, char const * node)
{
	int ret;
	struct null_args nullfs;
	void * data = &nullfs;

	memset(&nullfs, 0, sizeof(nullfs));
	if((nullfs.la.target = strdup(special)) == NULL)
		return -_mount_error(node, 1);
	type = MT_NULLFS;
	ret = _mount_do_mount(type, flags, special, node, data, sizeof(nullfs));
	free(nullfs.la.target);
	return ret;
}
#endif

#ifdef MT_PROCFS
static int _mount_callback_procfs(char const * type, int flags,
		char const * special, char const * node)
{
	struct procfs_args procfs;
	void * data = &procfs;

	memset(&procfs, 0, sizeof(procfs));
	procfs.version = PROCFS_ARGS_VERSION;
	type = MT_PROCFS;
	return _mount_do_mount(type, flags, special, node, data,
			sizeof(procfs));
}
#endif

#ifdef MT_TMPFS
static int _mount_callback_tmpfs(char const * type, int flags,
		char const * special, char const * node)
{
	struct tmpfs_args tmpfs;
	void * data = &tmpfs;
	struct stat st;

	memset(&tmpfs, 0, sizeof(tmpfs));
	tmpfs.ta_version = TMPFS_ARGS_VERSION;
	if(stat(node, &st) == 0)
	{
		tmpfs.ta_root_uid = st.st_uid;
		tmpfs.ta_root_gid = st.st_gid;
		tmpfs.ta_root_mode = st.st_mode;
	}
	/* FIXME actually parse options */
	tmpfs.ta_nodes_max = 1024;
	tmpfs.ta_root_mode = 0755;
	type = MT_TMPFS;
	return _mount_do_mount(type, flags, special, node, data, sizeof(tmpfs));
}
#endif

#ifdef MT_UNIONFS
static int _mount_callback_unionfs(char const * type, int flags,
		char const * special, char const * node)
{
	int ret;
	struct union_args unionfs;
	void * data = &unionfs;

	memset(&unionfs, 0, sizeof(unionfs));
	if((unionfs.target = strdup(special)) == NULL)
		return -_mount_error(node, 1);
	/* FIXME actually parse options */
	type = MT_UNIONFS;
	ret = _mount_do_mount(type, flags, special, node, data,
			sizeof(unionfs));
	free(unionfs.target);
	return ret;
}
#endif


/* mount_error */
static int _mount_error(char const * message, int ret)
{
	fputs(PROGNAME ": ", stderr);
	perror(message);
	return ret;
}


/* mount_usage */
static int _mount_usage(void)
{
	size_t i;
	char const * sep = "";

	fputs("Usage: " PROGNAME " [-a][-t type]\n"
"       " PROGNAME " [-f] special | node\n"
"       " PROGNAME " [-f][-u][-o options] special node\n", stderr);
	fputs("\nFilesystems supported:\n", stderr);
	for(i = 0; _mount_supported[i].name != NULL; i++)
	{
		fprintf(stderr, "%s%s", sep, _mount_supported[i].name);
		sep = ", ";
	}
	fputs("\n\nOptions supported:\n", stderr);
	sep = "";
	for(i = 0; _mount_options[i].name != NULL; i++)
		if(_mount_options[i].flags >= 0)
		{
			fprintf(stderr, "%s%s", sep, _mount_options[i].name);
			sep = ", ";
		}
	fputs("\n\nEach filesystem may not support every option.\n", stderr);
	return 1;
}


/* public */
/* functions */
/* main */
int main(int argc, char * argv[])
{
	Prefs prefs;
	int o;
	char const * special = NULL;
	char const * node = NULL;

	memset(&prefs, 0, sizeof(prefs));
	while((o = getopt(argc, argv, "afo:t:u")) != -1)
		switch(o)
		{
			case 'a':
				prefs.flags |= PREFS_a;
				break;
			case 'f':
				prefs.flags |= PREFS_f;
				break;
			case 'o':
				prefs.options = optarg;
				break;
			case 't':
				prefs.type = optarg;
				break;
			case 'u':
				prefs.flags |= PREFS_u;
				break;
			default:
				return _mount_usage();
		}
	if(optind + 2 == argc)
		special = argv[optind++];
	if(optind + 1 == argc)
		node = argv[optind];
	else if(optind != argc)
		return _mount_usage();
	return (_mount(&prefs, special, node) == 0) ? 0 : 2;
}
