summaryrefslogtreecommitdiffstats
path: root/src/mount/mount.ceph.c
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/mount/mount.ceph.c553
1 files changed, 553 insertions, 0 deletions
diff --git a/src/mount/mount.ceph.c b/src/mount/mount.ceph.c
new file mode 100644
index 00000000..10a76c43
--- /dev/null
+++ b/src/mount/mount.ceph.c
@@ -0,0 +1,553 @@
+#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 "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 skip_mtab_flag = false;
+bool v2_addrs = false;
+static const char * const EMPTY_STRING = "";
+
+/* TODO duplicates logic from kernel */
+#define CEPH_AUTH_NAME_DEFAULT "guest"
+
+#include "mtab.c"
+
+struct ceph_mount_info {
+ unsigned long cmi_flags;
+ char *cmi_name;
+ char *cmi_path;
+ char *cmi_mons;
+ char *cmi_conf;
+ char *cmi_opts;
+ int cmi_opts_len;
+ char cmi_secret[SECRET_BUFSIZE];
+};
+
+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);
+ }
+}
+
+static int parse_src(const char *orig_str, struct ceph_mount_info *cmi)
+{
+ size_t len;
+ char *mount_path;
+
+ mount_path = strstr(orig_str, ":/");
+ if (!mount_path) {
+ fprintf(stderr, "source mount path was not specified\n");
+ return -EINVAL;
+ }
+
+ len = mount_path - orig_str;
+ if (len != 0) {
+ cmi->cmi_mons = strndup(orig_str, len);
+ if (!cmi->cmi_mons)
+ return -ENOMEM;
+ }
+
+ mount_path++;
+ cmi->cmi_path = strdup(mount_path);
+ if (!cmi->cmi_path)
+ return -ENOMEM;
+ return 0;
+}
+
+static char *finalize_src(struct ceph_mount_info *cmi)
+{
+ int pos, len;
+ char *src;
+
+ src = resolve_addrs(cmi->cmi_mons);
+ if (!src)
+ return NULL;
+
+ len = strlen(src);
+ pos = safe_cat(&src, &len, len, ":");
+ safe_cat(&src, &len, pos, cmi->cmi_path);
+
+ return src;
+}
+
+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)
+ 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) {
+ /* child */
+ ret = drop_capabilities();
+ if (ret)
+ exit(1);
+ mount_ceph_get_config_info(cmi->cmi_conf, cmi->cmi_name, v2_addrs, cci);
+ 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);
+ }
+ }
+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)
+{
+ char * next_keyword = NULL;
+ int pos = 0;
+ char *name = NULL;
+ int name_len = 0;
+ int name_pos = 0;
+
+ 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 (strncmp(data, "ro", 2) == 0) {
+ cmi->cmi_flags |= MS_RDONLY;
+ } else if (strncmp(data, "rw", 2) == 0) {
+ cmi->cmi_flags &= ~MS_RDONLY;
+ } else if (strncmp(data, "nosuid", 6) == 0) {
+ cmi->cmi_flags |= MS_NOSUID;
+ } else if (strncmp(data, "suid", 4) == 0) {
+ cmi->cmi_flags &= ~MS_NOSUID;
+ } else if (strncmp(data, "dev", 3) == 0) {
+ cmi->cmi_flags &= ~MS_NODEV;
+ } else if (strncmp(data, "nodev", 5) == 0) {
+ cmi->cmi_flags |= MS_NODEV;
+ } else if (strncmp(data, "noexec", 6) == 0) {
+ cmi->cmi_flags |= MS_NOEXEC;
+ } else if (strncmp(data, "exec", 4) == 0) {
+ cmi->cmi_flags &= ~MS_NOEXEC;
+ } else if (strncmp(data, "sync", 4) == 0) {
+ cmi->cmi_flags |= MS_SYNCHRONOUS;
+ } else if (strncmp(data, "remount", 7) == 0) {
+ cmi->cmi_flags |= MS_REMOUNT;
+ } else if (strncmp(data, "mandlock", 8) == 0) {
+ cmi->cmi_flags |= MS_MANDLOCK;
+ } else if ((strncmp(data, "nobrl", 5) == 0) ||
+ (strncmp(data, "nolock", 6) == 0)) {
+ cmi->cmi_flags &= ~MS_MANDLOCK;
+ } else if (strncmp(data, "noatime", 7) == 0) {
+ cmi->cmi_flags |= MS_NOATIME;
+ } else if (strncmp(data, "nodiratime", 10) == 0) {
+ cmi->cmi_flags |= MS_NODIRATIME;
+ } else if (strncmp(data, "relatime", 8) == 0) {
+ cmi->cmi_flags |= MS_RELATIME;
+ } else if (strncmp(data, "strictatime", 11) == 0) {
+ cmi->cmi_flags |= MS_STRICTATIME;
+ } else if (strncmp(data, "noauto", 6) == 0) {
+ /* ignore */
+ } else if (strncmp(data, "_netdev", 7) == 0) {
+ /* ignore */
+ } else if (strncmp(data, "nofail", 6) == 0) {
+ /* ignore */
+ } else if (strncmp(data, "secretfile", 10) == 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 (strncmp(data, "secret", 6) == 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 (strncmp(data, "conf", 4) == 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 (strncmp(data, "name", 4) == 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;
+ } else {
+ skip = false;
+ mount_ceph_debug("mount.ceph: unrecognized mount option \"%s\", passing to kernel.\n",
+ data);
+ }
+
+ /* 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);
+ }
+
+ }
+ data = next_keyword;
+ } while (data);
+
+ 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);
+
+ 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 i;
+
+ 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;
+ for (i = 3; i < argc; ++i) {
+ if (!strcmp("-h", argv[i]))
+ return 1;
+ else if (!strcmp("-n", argv[i]))
+ skip_mtab_flag = true;
+ else if (!strcmp("-v", argv[i]))
+ verboseflag = true;
+ else if (!strcmp("-o", argv[i])) {
+ ++i;
+ if (i >= argc) {
+ fprintf(stderr, "Option -o requires an argument.\n\n");
+ return -EINVAL;
+ }
+ *opts = argv[i];
+ }
+ else {
+ fprintf(stderr, "Can't understand option: '%s'\n\n", argv[i]);
+ 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: Print this help\n");
+ printf("\t-n: Do not update /etc/mtab\n");
+ printf("\t-v: Verbose\n");
+ printf("\tceph-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_path);
+ free(cmi->cmi_mons);
+ free(cmi->cmi_conf);
+}
+
+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[])
+{
+ const char *src, *node, *opts;
+ char *rsrc = NULL;
+ int retval;
+ struct ceph_mount_info cmi = { 0 };
+
+ retval = parse_arguments(argc, argv, &src, &node, &opts);
+ if (retval) {
+ usage(argv[0]);
+ retval = (retval > 0) ? 0 : EX_USAGE;
+ goto out;
+ }
+
+ retval = parse_options(opts, &cmi);
+ if (retval) {
+ fprintf(stderr, "failed to parse ceph_options: %d\n", retval);
+ retval = EX_USAGE;
+ goto out;
+ }
+
+ retval = parse_src(src, &cmi);
+ if (retval) {
+ fprintf(stderr, "unable to parse mount source: %d\n", retval);
+ retval = EX_USAGE;
+ goto out;
+ }
+
+ /* We don't care if this errors out, since this is best-effort */
+ 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();
+
+ 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);
+
+ 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);
+ }
+ }
+
+ block_signals(SIG_UNBLOCK);
+out:
+ ceph_mount_info_free(&cmi);
+ free(rsrc);
+ return retval;
+}
+