bool verboseflag = false;
bool skip_mtab_flag = false;
bool v2_addrs = false;
+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;
}
}
-static int parse_src(const char *orig_str, struct ceph_mount_info *cmi)
+/*
+ * 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 */
+ --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(orig_str, ":/");
+ mount_path = strstr(dev_str, ":/");
if (!mount_path) {
fprintf(stderr, "source mount path was not specified\n");
return -EINVAL;
}
- len = mount_path - orig_str;
+ len = mount_path - dev_str;
if (len != 0) {
- cmi->cmi_mons = strndup(orig_str, len);
+ 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;
}
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) {
+ v2_addrs = true;
+ append_opt("ms_mode", CEPH_DEFAULT_V2_MS_MODE, cmi,
+ opt_pos);
+ }
+
+ cmi->format = MOUNT_DEV_FORMAT_NEW;
return 0;
}
-static char *finalize_src(struct ceph_mount_info *cmi)
+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 record in option string */
+static int finalize_src(struct ceph_mount_info *cmi, int *opt_pos)
{
- int pos, len;
char *src;
+ size_t len = strlen(cmi->cmi_mons);
+ char *addr = alloca(len+1);
- src = resolve_addrs(cmi->cmi_mons);
- if (!src)
- return NULL;
+ memcpy(addr, cmi->cmi_mons, len+1);
+ mon_addr_as_resolve_param(addr);
- len = strlen(src);
- pos = safe_cat(&src, &len, len, ":");
- safe_cat(&src, &len, pos, cmi->cmi_path);
+ src = resolve_addrs(addr);
+ if (!src)
+ return -1;
- return src;
+ resolved_mon_addr_as_mount_opt(src);
+ append_opt("mon_addr", src, cmi, opt_pos);
+ free(src);
+ return 0;
}
static int
struct ceph_config_info *cci;
/* Don't do anything if we already have requisite info */
- if (cmi->cmi_secret[0] && cmi->cmi_mons)
+ if (cmi->cmi_secret[0] && cmi->cmi_mons && cmi->cmi_fsid)
return 0;
cci = mmap((void *)0, sizeof(*cci), PROT_READ | PROT_WRITE,
}
if (pid == 0) {
+ char *entity_name = NULL;
+ int name_pos = 0;
+ int name_len = 0;
+
/* child */
ret = drop_capabilities();
if (ret)
exit(1);
- mount_ceph_get_config_info(cmi->cmi_conf, cmi->cmi_name, v2_addrs, cci);
+
+ 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 */
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));
/*
* this one is partially based on parse_options() from cifs.mount.c
*/
-static int parse_options(const char *data, struct ceph_mount_info *cmi)
+static int parse_options(const char *data, struct ceph_mount_info *cmi,
+ int *opt_pos)
{
char * next_keyword = NULL;
- int pos = 0;
char *name = NULL;
- int name_len = 0;
- int name_pos = 0;
if (data == EMPTY_STRING)
goto out;
}
data = "mds_namespace";
skip = false;
+ } else if (strcmp(data, "nofallback") == 0) {
+ no_fallback = true;
} else if (strcmp(data, "secretfile") == 0) {
int ret;
/* 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) {
- if (pos)
- pos = safe_cat(&cmi->cmi_opts, &cmi->cmi_opts_len, pos, ",");
-
- if (value) {
- pos = safe_cat(&cmi->cmi_opts, &cmi->cmi_opts_len, pos, data);
- 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, data);
- }
- }
+ if (!skip)
+ append_opt(data, value, cmi, opt_pos);
data = next_keyword;
} while (data);
out:
- name_pos = safe_cat(&cmi->cmi_name, &name_len, name_pos, "client.");
- name_pos = safe_cat(&cmi->cmi_name, &name_len, name_pos,
- name ? name : CEPH_AUTH_NAME_DEFAULT);
-
+ /*
+ * 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\" will pass to kernel.\n",
- cmi->cmi_opts);
+ mount_ceph_debug("mount.ceph: options \"%s\".\n", cmi->cmi_opts);
if (!cmi->cmi_opts) {
cmi->cmi_opts = strdup(EMPTY_STRING);
return -EINVAL;
}
*opts = argv[i];
- }
- else {
+ } else {
fprintf(stderr, "Can't understand option: '%s'\n\n", argv[i]);
return -EINVAL;
}
{
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 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 = mount(rsrc, node, "ceph", cmi->cmi_flags, cmi->cmi_opts);
+ 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;
+ 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;
+ }
+
+ 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 = mount(rsrc, node, "ceph", cmi->cmi_flags, cmi->cmi_opts);
+ 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 (!cmi->cmi_mons &&
+ !ms_mode_specified &&
+ !mon_addr_specified &&
+ cmi->format == MOUNT_DEV_FORMAT_NEW) {
+ 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);
+ 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 server is up or the cluster is laggy\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);
int main(int argc, char *argv[])
{
- const char *src, *node, *opts;
- char *rsrc = NULL;
+ int opt_pos = 0;
+ const char *dev, *node, *opts;
int retval;
struct ceph_mount_info cmi = { 0 };
- retval = parse_arguments(argc, argv, &src, &node, &opts);
+ 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);
+ 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_src(src, &cmi);
+ retval = parse_dev(dev, &cmi, &opt_pos);
if (retval) {
- fprintf(stderr, "unable to parse mount source: %d\n", 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 */
+ /*
+ * 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);
- if (!cmi.cmi_mons) {
- fprintf(stderr, "unable to determine mon addresses\n");
- retval = EX_USAGE;
- goto out;
- }
-
- rsrc = finalize_src(&cmi);
- if (!rsrc) {
- fprintf(stderr, "failed to resolve source\n");
- retval = EX_USAGE;
- goto out;
- }
-
/* Ensure the ceph key_type is available */
modprobe();
}
block_signals(SIG_BLOCK);
-
- if (mount(rsrc, node, "ceph", cmi.cmi_flags, cmi.cmi_opts)) {
- 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 server is up or the cluster is laggy\n");
- break;
- default:
- fprintf(stderr, "mount error %d = %s\n",errno,strerror(errno));
- }
- } else {
- if (!skip_mtab_flag) {
- update_mtab_entry(rsrc, node, "ceph", cmi.cmi_opts, cmi.cmi_flags, 0, 0);
- }
- }
-
+ retval = do_mount(dev, node, &cmi);
block_signals(SIG_UNBLOCK);
out:
ceph_mount_info_free(&cmi);
- free(rsrc);
return retval;
}