From 8606f5839d5cc9979237eb4a2dbad06658881e70 Mon Sep 17 00:00:00 2001 From: Venky Shankar Date: Thu, 6 May 2021 00:59:27 -0400 Subject: [PATCH] mount: introduce new mount syntax Old mount device (source) have the following problems: - mounts to the same cluster but with different fsnames and/or creds have identical device string which can confuse xfstests. - device shown in /proc/mounts is different that what is used to mount. New proposed syntax is as follows: # mount -t ceph cephuser@.mycephfs2=/path The cluster-id is optional but is always passed to the kernel via mount() syscall (by fetching it from cluster configuration file if available). The mount helper tries the mounting the file system with the new device syntax, falling back to using the old syntax if required. Signed-off-by: Venky Shankar --- src/mount/mount.ceph.c | 430 ++++++++++++++++++++++++++++++++++------- 1 file changed, 365 insertions(+), 65 deletions(-) diff --git a/src/mount/mount.ceph.c b/src/mount/mount.ceph.c index 8672920bc7439..8ff58d5e87987 100644 --- a/src/mount/mount.ceph.c +++ b/src/mount/mount.ceph.c @@ -27,17 +27,48 @@ static const char * const EMPTY_STRING = ""; #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; @@ -59,20 +90,101 @@ void mount_ceph_debug(const char *fmt, ...) } } -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, "client."); + name_pos = safe_cat(&cmi->cmi_name, &name_len, name_pos, name); +} + +/* + * parse old device string of format: :/ + */ +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; } @@ -81,23 +193,131 @@ static int parse_src(const char *orig_str, struct ceph_mount_info *cmi) 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@.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 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 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; + } + + 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 @@ -206,13 +426,11 @@ out: /* * 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; @@ -332,10 +550,10 @@ static int parse_options(const char *data, struct ceph_mount_info *cmi) /* Only legacy ms_mode needs v1 addrs */ v2_addrs = strcmp(value, "legacy"); skip = false; - } else if (strcmp(data, "mon_host") == 0) { + } else if (strcmp(data, "mon_addr") == 0) { /* monitor address to use for mounting */ if (!value || !*value) { - fprintf(stderr, "mount option mon_host requires a value.\n"); + fprintf(stderr, "mount option mon_addr requires a value.\n"); return -EINVAL; } cmi->cmi_mons = strdup(value); @@ -347,29 +565,21 @@ static int parse_options(const char *data, struct ceph_mount_info *cmi) } /* 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); @@ -456,11 +666,120 @@ 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 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; +} + +static int do_mount(const char *dev, const char *node, + struct ceph_mount_info *cmi) { + int retval= -EINVAL; + bool fallback = true; + + if (cmi->format == MOUNT_DEV_FORMAT_NEW) { + retval = mount_new_device_format(node, cmi); + if (retval) + 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); @@ -499,28 +818,28 @@ static int append_key_or_secret_option(struct ceph_mount_info *cmi) 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; } @@ -534,8 +853,8 @@ int main(int argc, char *argv[]) goto out; } - rsrc = finalize_src(&cmi); - if (!rsrc) { + retval = finalize_src(&cmi, &opt_pos); + if (retval) { fprintf(stderr, "failed to resolve source\n"); retval = EX_USAGE; goto out; @@ -552,29 +871,10 @@ int main(int argc, char *argv[]) } 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; } -- 2.39.5