#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/mount.h>
#include <stdbool.h>
#include <sys/mman.h>
#include <wait.h>
#include <cap-ng.h>
#include <getopt.h>

#include "common/module.h"
#include "common/secret.h"
#include "include/addr_parsing.h"
#include "mount.ceph.h"

#ifndef MS_RELATIME
# define MS_RELATIME (1<<21)
#endif

bool verboseflag = false;
bool fakeflag = false;
bool skip_mtab_flag = false;
bool v2_addrs = true;
bool no_fallback = false;
bool ms_mode_specified = false;
bool mon_addr_specified = false;
static const char * const EMPTY_STRING = "";

/* TODO duplicates logic from kernel */
#define CEPH_AUTH_NAME_DEFAULT "guest"

/* path to sysfs for ceph */
#define CEPH_SYS_FS_PATH "/sys/module/ceph/"
#define CEPH_SYS_FS_PARAM_PATH CEPH_SYS_FS_PATH"/parameters"

/*
 * mount support hint from kernel -- we only need to check
 * v2 support for catching bugs.
 */
#define CEPH_V2_MOUNT_SUPPORT_PATH CEPH_SYS_FS_PARAM_PATH"/mount_syntax_v2"

#define CEPH_DEFAULT_V2_MS_MODE "prefer-crc"

#include "mtab.c"

enum mount_dev_format {
  MOUNT_DEV_FORMAT_OLD = 0,
  MOUNT_DEV_FORMAT_NEW = 1,
};

struct ceph_mount_info {
	unsigned long	cmi_flags;
	char		*cmi_name;
	char		*cmi_fsname;
	char		*cmi_fsid;
	char		*cmi_path;
	char		*cmi_mons;
	char		*cmi_conf;
	char		*cmi_opts;
	int		cmi_opts_len;
	char 		cmi_secret[SECRET_BUFSIZE];

	/* mount dev syntax format */
	enum mount_dev_format format;
};

static void mon_addr_as_resolve_param(char *mon_addr)
{
	for (; *mon_addr; ++mon_addr)
		if (*mon_addr == '/')
			*mon_addr = ',';
}

static void resolved_mon_addr_as_mount_opt(char *mon_addr)
{
	for (; *mon_addr; ++mon_addr)
		if (*mon_addr == ',')
			*mon_addr = '/';
}

static void resolved_mon_addr_as_mount_dev(char *mon_addr)
{
	for (; *mon_addr; ++mon_addr)
		if (*mon_addr == '/')
			*mon_addr = ',';
}

static void block_signals (int how)
{
     sigset_t sigs;

     sigfillset (&sigs);
     sigdelset(&sigs, SIGTRAP);
     sigdelset(&sigs, SIGSEGV);
     sigprocmask (how, &sigs, (sigset_t *) 0);
}

void mount_ceph_debug(const char *fmt, ...)
{
	if (verboseflag) {
		va_list args;

		va_start(args, fmt);
		vprintf(fmt, args);
		va_end(args);
	}
}

/*
 * append a key value pair option to option string.
 */
static void append_opt(const char *key, const char *value,
		       struct ceph_mount_info *cmi, int *pos)
{
	if (*pos != 0)
		*pos = safe_cat(&cmi->cmi_opts, &cmi->cmi_opts_len, *pos, ",");

	if (value) {
		*pos = safe_cat(&cmi->cmi_opts, &cmi->cmi_opts_len, *pos, key);
		*pos = safe_cat(&cmi->cmi_opts, &cmi->cmi_opts_len, *pos, "=");
		*pos = safe_cat(&cmi->cmi_opts, &cmi->cmi_opts_len, *pos, value);
	} else {
		*pos = safe_cat(&cmi->cmi_opts, &cmi->cmi_opts_len, *pos, key);
	}
}

/*
 * remove a key value pair from option string. caller should ensure that the
 * key value pair is separated by "=".
 */
static int remove_opt(struct ceph_mount_info *cmi, const char *key, char **value)
{
	char *key_start = strstr(cmi->cmi_opts, key);
	if (!key_start) {
		return -ENOENT;
	}

	/* key present -- try to split */
	char *key_sep = strstr(key_start, "=");
	if (!key_sep) {
		return -ENOENT;
	}

	if (strncmp(key, key_start, key_sep - key_start) != 0) {
		return -ENOENT;
	}

	++key_sep;
	char *value_end = strstr(key_sep, ",");
	if (!value_end)
		value_end = key_sep + strlen(key_sep);

	if (value_end != key_sep && value) {
		size_t len1 = value_end - key_sep;
		*value = strndup(key_sep, len1+1);
		if (!*value)
			return -ENOMEM;
		(*value)[len1] = '\0';
	}

	/* purge it */
	size_t len2 = strlen(value_end);
	if (len2) {
		++value_end;
		memmove(key_start, value_end, len2);
	} else {
                /* last kv pair - swallow the comma */
		if (*(key_start - 1) == ',') {
			--key_start;
		}
		*key_start = '\0';
	}

	return 0;
}

static void record_name(const char *name, struct ceph_mount_info *cmi)
{
	int name_pos = 0;
	int name_len = 0;

	name_pos = safe_cat(&cmi->cmi_name, &name_len, name_pos, name);
}

/*
 * parse old device string of format: <mon_addr>:/<path>
 */
static int parse_old_dev(const char *dev_str, struct ceph_mount_info *cmi,
			 int *opt_pos)
{
	size_t len;
	char *mount_path;

	mount_path = strstr(dev_str, ":/");
	if (!mount_path) {
		fprintf(stderr, "source mount path was not specified\n");
		return -EINVAL;
	}

	len = mount_path - dev_str;
	if (len != 0) {
		free(cmi->cmi_mons);
		/* overrides mon_addr passed via mount option (if any) */
		cmi->cmi_mons = strndup(dev_str, len);
		if (!cmi->cmi_mons)
			return -ENOMEM;
		mon_addr_specified = true;
	} else {
		/* reset mon_addr=<> mount option */
		mon_addr_specified = false;
	}

	mount_path++;
	cmi->cmi_path = strdup(mount_path);
	if (!cmi->cmi_path)
		return -ENOMEM;
	if (!cmi->cmi_name)
		record_name(CEPH_AUTH_NAME_DEFAULT, cmi);

	cmi->format = MOUNT_DEV_FORMAT_OLD;
	return 0;
}

/*
 * parse new device string of format: name@<fsid>.fs_name=/path
 */
static int parse_new_dev(const char *dev_str, struct ceph_mount_info *cmi,
			 int *opt_pos)
{
	size_t len;
	char *name;
	char *name_end;
	char *dot;
	char *fs_name;

	name_end = strstr(dev_str, "@");
	if (!name_end) {
		mount_ceph_debug("invalid new device string format\n");
		return -ENODEV;
	}

	len = name_end - dev_str;
	if (!len) {
		fprintf(stderr, "missing <name> in device\n");
		return -EINVAL;
	}

	name = (char *)alloca(len+1);
	memcpy(name, dev_str, len);
	name[len] = '\0';

	if (cmi->cmi_name && strcmp(cmi->cmi_name, name)) {
		fprintf(stderr, "mismatching ceph user in mount option and device string\n");
		return -EINVAL;
	}

	/* record name and store in option string */
	if (!cmi->cmi_name) {
		record_name(name, cmi);
		append_opt("name", name, cmi, opt_pos);
	}

	++name_end;
	/* check if an fsid is included in the device string */
	dot = strstr(name_end, ".");
	if (!dot) {
		fprintf(stderr, "invalid device string format\n");
		return -EINVAL;
	}
	len = dot - name_end;
	if (len) {
		/* check if this _looks_ like a UUID */
		if (len != CLUSTER_FSID_LEN - 1) {
			fprintf(stderr, "invalid device string format\n");
			return -EINVAL;
		}

		cmi->cmi_fsid = strndup(name_end, len);
		if (!cmi->cmi_fsid)
			return -ENOMEM;
	}

	++dot;
	fs_name = strstr(dot, "=");
	if (!fs_name) {
		fprintf(stderr, "invalid device string format\n");
		return -EINVAL;
	}
	len = fs_name - dot;
	if (!len) {
		fprintf(stderr, "missing <fs_name> in device\n");
		return -EINVAL;
	}
	cmi->cmi_fsname = strndup(dot, len);
	if (!cmi->cmi_fsname)
		return -ENOMEM;

	++fs_name;
	if (strlen(fs_name)) {
		cmi->cmi_path = strdup(fs_name);
		if (!cmi->cmi_path)
			return -ENOMEM;
	}

	/* new-style dev - force using v2 addrs first */
	if (!ms_mode_specified && !mon_addr_specified)
	  append_opt("ms_mode", CEPH_DEFAULT_V2_MS_MODE, cmi,
		     opt_pos);

	cmi->format = MOUNT_DEV_FORMAT_NEW;
	return 0;
}

static int parse_dev(const char *dev_str, struct ceph_mount_info *cmi,
		     int *opt_pos)
{
	int ret;

	ret = parse_new_dev(dev_str, cmi, opt_pos);
	if (ret < 0 && ret != -ENODEV)
		return -EINVAL;
	if (ret)
		ret = parse_old_dev(dev_str, cmi, opt_pos);
	if (ret < 0)
		fprintf(stderr, "error parsing device string\n");
	return ret;
}

/* resolve monitor host and optionally record in option string.
 * use opt_pos to determine if the caller wants to record the
 * resolved address in mount option (c.f., mount_old_device_format).
 */
static int finalize_src(struct ceph_mount_info *cmi, int *opt_pos,
			char **resolved_addr)
{
	char *src;
        size_t len = strlen(cmi->cmi_mons);
        char *addr = alloca(len+1);

        memcpy(addr, cmi->cmi_mons, len+1);
        mon_addr_as_resolve_param(addr);

	src = resolve_addrs(addr);
	if (!src)
		return -1;

	mount_ceph_debug("mount.ceph: resolved to: \"%s\"\n", src);
	if (opt_pos) {
		resolved_mon_addr_as_mount_opt(src);
		append_opt("mon_addr", src, cmi, opt_pos);
	} else if (resolved_addr) {
                *resolved_addr = strdup(src);
        }
	free(src);
	return 0;
}

static int
drop_capabilities()
{
	capng_setpid(getpid());
	capng_clear(CAPNG_SELECT_BOTH);
	if (capng_update(CAPNG_ADD, CAPNG_PERMITTED, CAP_DAC_READ_SEARCH)) {
		fprintf(stderr, "Unable to update permitted capability set.\n");
		return EX_SYSERR;
	}
	if (capng_update(CAPNG_ADD, CAPNG_EFFECTIVE, CAP_DAC_READ_SEARCH)) {
		fprintf(stderr, "Unable to update effective capability set.\n");
		return EX_SYSERR;
	}
	if (capng_apply(CAPNG_SELECT_BOTH)) {
		fprintf(stderr, "Unable to apply new capability set.\n");
		return EX_SYSERR;
	}
	return 0;
}

/*
 * Attempt to fetch info from the local config file, if one is present. Since
 * this involves activity that may be dangerous for a privileged task, we
 * fork(), have the child drop privileges and do the processing and then hand
 * back the results via memory shared with the parent.
 */
static int fetch_config_info(struct ceph_mount_info *cmi)
{
	int ret = 0;
	pid_t pid;
	struct ceph_config_info *cci;

	/* Don't do anything if we already have requisite info */
	if (cmi->cmi_secret[0] && cmi->cmi_mons && cmi->cmi_fsid)
		return 0;

	cci = mmap((void *)0, sizeof(*cci), PROT_READ | PROT_WRITE,
		   MAP_ANONYMOUS | MAP_SHARED, -1, 0);
	if (cci == MAP_FAILED) {
		mount_ceph_debug("Unable to allocate memory: %s\n",
				 strerror(errno));
		return EX_SYSERR;
	}

	pid = fork();
	if (pid < 0) {
		mount_ceph_debug("fork() failure: %s\n", strerror(errno));
		ret = EX_SYSERR;
		goto out;
	}

	if (pid == 0) {
		char *entity_name = NULL;
		int name_pos = 0;
		int name_len = 0;

		/* child */
		ret = drop_capabilities();
		if (ret)
			exit(1);

		name_pos = safe_cat(&entity_name, &name_len, name_pos, "client.");
		name_pos = safe_cat(&entity_name, &name_len, name_pos, cmi->cmi_name);
		mount_ceph_get_config_info(cmi->cmi_conf, entity_name, v2_addrs, cci);
		free(entity_name);
		exit(0);
	} else {
		/* parent */
		pid = wait(&ret);
		if (!WIFEXITED(ret)) {
			mount_ceph_debug("Child process terminated abnormally.\n");
			ret = EX_SYSERR;
			goto out;
		}
		ret = WEXITSTATUS(ret);
		if (ret) {
			mount_ceph_debug("Child exited with status %d\n", ret);
			ret = EX_SYSERR;
			goto out;
		}

		/*
		 * Copy values from MAP_SHARED buffer to cmi if we didn't
		 * already find anything and we got something from the child.
		 */
		size_t len;
		if (!cmi->cmi_secret[0] && cci->cci_secret[0]) {

			len = strnlen(cci->cci_secret, SECRET_BUFSIZE);
			if (len < SECRET_BUFSIZE) {
				memcpy(cmi->cmi_secret, cci->cci_secret, len + 1);
			} else {
				mount_ceph_debug("secret is too long (len=%zu max=%zu)!\n", len, SECRET_BUFSIZE);
			}
		}
		if (!cmi->cmi_mons && cci->cci_mons[0]) {
			len = strnlen(cci->cci_mons, MON_LIST_BUFSIZE);
			if (len < MON_LIST_BUFSIZE)
				cmi->cmi_mons = strndup(cci->cci_mons, len + 1);
		}
		if (!cmi->cmi_fsid) {
			len = strnlen(cci->cci_fsid, CLUSTER_FSID_LEN);
			if (len < CLUSTER_FSID_LEN)
				cmi->cmi_fsid = strndup(cci->cci_fsid, len + 1);
		}
	}
out:
	munmap(cci, sizeof(*cci));
	return ret;
}

/*
 * this one is partially based on parse_options() from cifs.mount.c
 */
static int parse_options(const char *data, struct ceph_mount_info *cmi,
			 int *opt_pos)
{
	char * next_keyword = NULL;
	char *name = NULL;

	if (data == EMPTY_STRING)
		goto out;

	mount_ceph_debug("parsing options: %s\n", data);

	do {
		char * value = NULL;
		bool skip = true;

		/*  check if ends with trailing comma */
		if(*data == 0)
			break;
		next_keyword = strchr(data,',');

		/* temporarily null terminate end of keyword=value pair */
		if(next_keyword)
			*next_keyword++ = 0;

		/* temporarily null terminate keyword to make keyword and value distinct */
		if ((value = strchr(data, '=')) != NULL) {
			*value = '\0';
			value++;
		}

		if (strcmp(data, "ro") == 0) {
			cmi->cmi_flags |= MS_RDONLY;
		} else if (strcmp(data, "rw") == 0) {
			cmi->cmi_flags &= ~MS_RDONLY;
		} else if (strcmp(data, "nosuid") == 0) {
			cmi->cmi_flags |= MS_NOSUID;
		} else if (strcmp(data, "suid") == 0) {
			cmi->cmi_flags &= ~MS_NOSUID;
		} else if (strcmp(data, "dev") == 0) {
			cmi->cmi_flags &= ~MS_NODEV;
		} else if (strcmp(data, "nodev") == 0) {
			cmi->cmi_flags |= MS_NODEV;
		} else if (strcmp(data, "noexec") == 0) {
			cmi->cmi_flags |= MS_NOEXEC;
		} else if (strcmp(data, "exec") == 0) {
			cmi->cmi_flags &= ~MS_NOEXEC;
                } else if (strcmp(data, "sync") == 0) {
                        cmi->cmi_flags |= MS_SYNCHRONOUS;
                } else if (strcmp(data, "remount") == 0) {
                        cmi->cmi_flags |= MS_REMOUNT;
                } else if (strcmp(data, "mandlock") == 0) {
                        cmi->cmi_flags |= MS_MANDLOCK;
		} else if ((strcmp(data, "nobrl") == 0) ||
			   (strcmp(data, "nolock") == 0)) {
			cmi->cmi_flags &= ~MS_MANDLOCK;
		} else if (strcmp(data, "noatime") == 0) {
			cmi->cmi_flags |= MS_NOATIME;
		} else if (strcmp(data, "nodiratime") == 0) {
			cmi->cmi_flags |= MS_NODIRATIME;
		} else if (strcmp(data, "relatime") == 0) {
			cmi->cmi_flags |= MS_RELATIME;
		} else if (strcmp(data, "strictatime") == 0) {
			cmi->cmi_flags |= MS_STRICTATIME;
		} else if (strcmp(data, "noauto") == 0) {
			/* ignore */
		} else if (strcmp(data, "_netdev") == 0) {
			/* ignore */
		} else if (strcmp(data, "nofail") == 0) {
			/* ignore */
		} else if (strcmp(data, "fs") == 0) {
			if (!value || !*value) {
				fprintf(stderr, "mount option fs requires a value.\n");
				return -EINVAL;
			}
			data = "mds_namespace";
			skip = false;
		} else if (strcmp(data, "nofallback") == 0) {
			no_fallback = true;
		} else if (strcmp(data, "secretfile") == 0) {
			int ret;

			if (!value || !*value) {
				fprintf(stderr, "keyword secretfile found, but no secret file specified\n");
				return -EINVAL;
			}
			ret = read_secret_from_file(value, cmi->cmi_secret, sizeof(cmi->cmi_secret));
			if (ret < 0) {
				fprintf(stderr, "error reading secret file: %d\n", ret);
				return ret;
			}
		} else if (strcmp(data, "secret") == 0) {
			size_t len;

			if (!value || !*value) {
				fprintf(stderr, "mount option secret requires a value.\n");
				return -EINVAL;
			}

			len = strnlen(value, sizeof(cmi->cmi_secret)) + 1;
			if (len <= sizeof(cmi->cmi_secret))
				memcpy(cmi->cmi_secret, value, len);
		} else if (strcmp(data, "conf") == 0) {
			if (!value || !*value) {
				fprintf(stderr, "mount option conf requires a value.\n");
				return -EINVAL;
			}
			/* keep pointer to value */
			cmi->cmi_conf = strdup(value);
			if (!cmi->cmi_conf)
				return -ENOMEM;
		} else if (strcmp(data, "name") == 0) {
			if (!value || !*value) {
				fprintf(stderr, "mount option name requires a value.\n");
				return -EINVAL;
			}
			/* keep pointer to value */
			name = value;
			skip = false;
		} else if (strcmp(data, "ms_mode") == 0) {
			if (!value || !*value) {
				fprintf(stderr, "mount option ms_mode requires a value.\n");
				return -EINVAL;
			}
			/* Only legacy ms_mode needs v1 addrs */
			v2_addrs = strcmp(value, "legacy");
			skip = false;
			ms_mode_specified = true;
		} else if (strcmp(data, "mon_addr") == 0) {
			/* monitor address to use for mounting */
			if (!value || !*value) {
				fprintf(stderr, "mount option mon_addr requires a value.\n");
				return -EINVAL;
			}
			cmi->cmi_mons = strdup(value);
			if (!cmi->cmi_mons)
				return -ENOMEM;
			mon_addr_specified = true;
		} else {
			/* unrecognized mount options, passing to kernel */
			skip = false;
		}

		/* Copy (possibly modified) option to out */
		if (!skip)
                        append_opt(data, value, cmi, opt_pos);
		data = next_keyword;
	} while (data);

out:
	/*
	 * set ->cmi_name conditionally -- this gets checked when parsing new
	 * device format. for old device format, ->cmi_name is set to default
	 * user name when name option is not passed in.
	 */
	if (name)
		record_name(name, cmi);
	if (cmi->cmi_opts)
		mount_ceph_debug("mount.ceph: options \"%s\".\n",  cmi->cmi_opts);

	if (!cmi->cmi_opts) {
		cmi->cmi_opts = strdup(EMPTY_STRING);
		if (!cmi->cmi_opts)
			return -ENOMEM;
	}
	return 0;
}


static int parse_arguments(int argc, char *const *const argv,
		const char **src, const char **node, const char **opts)
{
	int opt = 0;
	static struct option long_options[] = {
		{ "help",    no_argument,        0,  'h' },
		{ "no-mtab", no_argument,        0,  'n' },
		{ "verbose", no_argument,        0,  'v' },
		{ "fake",    no_argument,        0,  'f' },
		{ "options", required_argument,  0,  'o' },
		{ 0,         0,                  0,  0   }
	};

	if (argc < 2) {
		// There were no arguments. Just show the usage.
		return 1;
	}
	if ((!strcmp(argv[1], "-h")) || (!strcmp(argv[1], "--help"))) {
		// The user asked for help.
		return 1;
	}

	// The first two arguments are positional
	if (argc < 3)
		return -EINVAL;
	*src = argv[1];
	*node = argv[2];

	// Parse the remaining options
	*opts = EMPTY_STRING;
	while ((opt = getopt_long(argc, argv, "hnvfo:",
					long_options, NULL)) != -1) {
		switch (opt) {
			case 'h' : // -h or --help
				return 1;
			case 'n' : // -n or --no-mtab
				skip_mtab_flag = true;
				break;
			case 'v' : // -v or --verbose
				verboseflag = true;
				break;
			case 'f' : // -f or --fake
				fakeflag = true;
				break;
			case 'o' : // -o or --options
				*opts = optarg;
				break;
			default:
				return -EINVAL;
		}
	}
	return 0;
}

/* modprobe failing doesn't necessarily prevent from working, so this
   returns void */
static void modprobe(void)
{
	int r;

	r = module_load("ceph", NULL);
	if (r)
		printf("failed to load ceph kernel module (%d)\n", r);
}

static void usage(const char *prog_name)
{
	printf("usage: %s [src] [mount-point] [-n] [-v] [-o ceph-options]\n",
		prog_name);
	printf("options:\n");
	printf("\t-h, --help\tPrint this help\n");
	printf("\t-n, --no-mtab\tDo not update /etc/mtab\n");
	printf("\t-v, --verbose\tVerbose\n");
	printf("\t-f, --fake\tFake mount, do not actually mount\n");
	printf("ceph-options: refer to mount.ceph(8)\n");
	printf("\n");
}

/*
 * The structure itself lives on the stack, so don't free it. Just the
 * pointers inside.
 */
static void ceph_mount_info_free(struct ceph_mount_info *cmi)
{
	free(cmi->cmi_opts);
	free(cmi->cmi_name);
	free(cmi->cmi_fsname);
	free(cmi->cmi_fsid);
	free(cmi->cmi_path);
	free(cmi->cmi_mons);
	free(cmi->cmi_conf);
}

static int call_mount_system_call(const char *rsrc, const char *node, struct ceph_mount_info *cmi)
{
	int r = 0;
	if (!fakeflag) {
		r = mount(rsrc, node, "ceph", cmi->cmi_flags, cmi->cmi_opts);
	}
	return r;
}

static int mount_new_device_format(const char *node, struct ceph_mount_info *cmi)
{
	int r;
	char *rsrc = NULL;
	int pos = 0;
	int len = 0;

	if (!cmi->cmi_fsid) {
		fprintf(stderr, "missing ceph cluster-id");
		return -EINVAL;
	}

	pos = safe_cat(&rsrc, &len, pos, cmi->cmi_name);
	pos = safe_cat(&rsrc, &len, pos, "@");
	pos = safe_cat(&rsrc, &len, pos, cmi->cmi_fsid);
	pos = safe_cat(&rsrc, &len, pos, ".");
	pos = safe_cat(&rsrc, &len, pos, cmi->cmi_fsname);
	pos = safe_cat(&rsrc, &len, pos, "=");
	if (cmi->cmi_path)
		safe_cat(&rsrc, &len, pos, cmi->cmi_path);

	mount_ceph_debug("mount.ceph: trying mount with new device syntax: %s\n",
			 rsrc);
	if (cmi->cmi_opts)
		mount_ceph_debug("mount.ceph: options \"%s\" will pass to kernel\n",
				 cmi->cmi_opts);
	r = call_mount_system_call(rsrc, node, cmi);
	if (r)
		r = -errno;
	free(rsrc);
	return r;
}

static int mount_old_device_format(const char *node, struct ceph_mount_info *cmi)
{
	int r;
	int len = 0;
	int pos = 0;
	char *mon_addr = NULL;
	char *rsrc = NULL;

	r = remove_opt(cmi, "mon_addr", &mon_addr);
	if (r) {
		fprintf(stderr, "failed to switch using old device format\n");
		return -EINVAL;
	}

	/* if we reach here and still have a v2 addr, we'd need to
	 * refresh with v1 addrs, since we'll be not passing ms_mode
	 * with the old syntax.
	 */
	if (v2_addrs && !ms_mode_specified && !mon_addr_specified) {
		mount_ceph_debug("mount.ceph: switching to using v1 address with old syntax\n");
		v2_addrs = false;
		free(mon_addr);
		free(cmi->cmi_mons);
		mon_addr = NULL;
		cmi->cmi_mons = NULL;
		fetch_config_info(cmi);
		if (!cmi->cmi_mons) {
			fprintf(stderr, "unable to determine (v1) mon addresses\n");
			return -EINVAL;
		}
		r = finalize_src(cmi, NULL, &mon_addr);
		if (r) {
			fprintf(stderr, "failed to resolve (v1) mon addresses\n");
			return -EINVAL;
		}
		remove_opt(cmi, "ms_mode", NULL);
	}

	pos = strlen(cmi->cmi_opts);
        if (cmi->cmi_fsname)
                append_opt("mds_namespace", cmi->cmi_fsname, cmi, &pos);
	if (cmi->cmi_fsid)
		append_opt("fsid", cmi->cmi_fsid, cmi, &pos);

	pos = 0;
	resolved_mon_addr_as_mount_dev(mon_addr);
	pos = safe_cat(&rsrc, &len, pos, mon_addr);
	pos = safe_cat(&rsrc, &len, pos, ":");
	if (cmi->cmi_path)
		safe_cat(&rsrc, &len, pos, cmi->cmi_path);

	mount_ceph_debug("mount.ceph: trying mount with old device syntax: %s\n",
			 rsrc);
	if (cmi->cmi_opts)
		mount_ceph_debug("mount.ceph: options \"%s\" will pass to kernel\n",
				 cmi->cmi_opts);

	r = call_mount_system_call(rsrc, node, cmi);
	free(mon_addr);
	free(rsrc);

	return r;
}

/*
 * check whether to fall-back to using old-style mount syntax (called
 * when new-style mount syntax fails). this is mostly to catch any
 * new-style (v2) implementation bugs in the kernel and is primarly
 * used in teuthology tests.
 */
static bool should_fallback()
{
	int ret;
	struct stat stbuf;

	if (!no_fallback)
		return true;

	ret = stat(CEPH_V2_MOUNT_SUPPORT_PATH, &stbuf);
	if (ret) {
		mount_ceph_debug("mount.ceph: v2 mount support check returned %d\n",
				 errno);
		if (errno == ENOENT)
			mount_ceph_debug("mount.ceph: kernel does not support v2"
					 " syntax\n");
		/* fallback on *all* errors */
		return true;
	}

	fprintf(stderr, "mount.ceph: kernel BUG!\n");
	return false;
}

static int do_mount(const char *dev, const char *node,
		    struct ceph_mount_info *cmi) {
	int pos = 0;
	int retval= -EINVAL;
	bool fallback = true;

        /* no v2 addresses available via config - try v1 addresses */
	if (v2_addrs &&
	    !cmi->cmi_mons &&
	    !ms_mode_specified &&
	    !mon_addr_specified) {
		mount_ceph_debug("mount.ceph: switching to using v1 address\n");
		v2_addrs = false;
		fetch_config_info(cmi);
		remove_opt(cmi, "ms_mode", NULL);
	}

	if (!cmi->cmi_mons) {
		fprintf(stderr, "unable to determine mon addresses\n");
		return -EINVAL;
	}

	pos = strlen(cmi->cmi_opts);
	retval = finalize_src(cmi, &pos, NULL);
	if (retval) {
		fprintf(stderr, "failed to resolve source\n");
		return -EINVAL;
	}

	retval = -1;
	if (cmi->format == MOUNT_DEV_FORMAT_NEW) {
		retval = mount_new_device_format(node, cmi);
		if (retval)
			fallback = (should_fallback() && retval == -EINVAL && cmi->cmi_fsid);
	}

	/* pass-through or fallback to old-style mount device */
	if (retval && fallback)
		retval = mount_old_device_format(node, cmi);
	if (retval) {
		retval = EX_FAIL;
		switch (errno) {
		case ENODEV:
			fprintf(stderr, "mount error: ceph filesystem not supported by the system\n");
			break;
		case EHOSTUNREACH:
			fprintf(stderr, "mount error: no mds (Metadata Server) is up. "
			"The cluster might be laggy, or you may not be authorized\n");
			break;
		default:
			fprintf(stderr, "mount error %d = %s\n", errno, strerror(errno));
		}
	}

	if (!retval && !skip_mtab_flag) {
		update_mtab_entry(dev, node, "ceph", cmi->cmi_opts, cmi->cmi_flags, 0, 0);
	}

	return retval;
}

static int append_key_or_secret_option(struct ceph_mount_info *cmi)
{
	int pos = strlen(cmi->cmi_opts);

	if (!cmi->cmi_secret[0] && !is_kernel_secret(cmi->cmi_name))
		return 0;

	if (pos)
		pos = safe_cat(&cmi->cmi_opts, &cmi->cmi_opts_len, pos, ",");

	/* when parsing kernel options (-o remount) we get '<hidden>' as the secret */
	if (cmi->cmi_secret[0] && (strcmp(cmi->cmi_secret, "<hidden>") != 0)) {
		int ret = set_kernel_secret(cmi->cmi_secret, cmi->cmi_name);
		if (ret < 0) {
			if (ret == -ENODEV || ret == -ENOSYS) {
				/* old kernel; fall back to secret= in options */
				pos = safe_cat(&cmi->cmi_opts,
					       &cmi->cmi_opts_len, pos,
					       "secret=");
				pos = safe_cat(&cmi->cmi_opts,
					       &cmi->cmi_opts_len, pos,
					       cmi->cmi_secret);
				return 0;
			}
			fprintf(stderr, "adding ceph secret key to kernel failed: %s\n",
				strerror(-ret));
			return ret;
		}
	}

	pos = safe_cat(&cmi->cmi_opts, &cmi->cmi_opts_len, pos, "key=");
	pos = safe_cat(&cmi->cmi_opts, &cmi->cmi_opts_len, pos, cmi->cmi_name);

	return 0;
}

int main(int argc, char *argv[])
{
	int opt_pos = 0;
	const char *dev, *node, *opts;
	int retval;
	struct ceph_mount_info cmi = { 0 };

	retval = parse_arguments(argc, argv, &dev, &node, &opts);
	if (retval) {
		usage(argv[0]);
		retval = (retval > 0) ? 0 : EX_USAGE;
		goto out;
	}

	retval = parse_options(opts, &cmi, &opt_pos);
	if (retval) {
		fprintf(stderr, "failed to parse ceph_options: %d\n", retval);
		retval = EX_USAGE;
		goto out;
	}

	retval = parse_dev(dev, &cmi, &opt_pos);
	if (retval) {
		fprintf(stderr, "unable to parse mount device string: %d\n", retval);
		retval = EX_USAGE;
		goto out;
	}

	/*
	 * We don't care if this errors out, since this is best-effort.
	 * note that this fetches v1 or v2 addr depending on @v2_addr
	 * flag.
	 */
	fetch_config_info(&cmi);

	/* Ensure the ceph key_type is available */
	modprobe();

	retval = append_key_or_secret_option(&cmi);
	if (retval) {
		fprintf(stderr, "couldn't append secret option: %d\n", retval);
		retval = EX_USAGE;
		goto out;
	}

	block_signals(SIG_BLOCK);
	retval = do_mount(dev, node, &cmi);
	block_signals(SIG_UNBLOCK);
out:
	ceph_mount_info_free(&cmi);
	return retval;
}

