/* FUSE: Filesystem in Userspace Copyright (C) 2001-2007 Miklos Szeredi Copyright (c) 2010 Gluster, Inc. This program can be distributed under the terms of the GNU LGPLv2. See the file COPYING.LIB. */ #include "mount_util.h" #include "mount-gluster-compat.h" #ifdef GF_FUSERMOUNT #define FUSERMOUNT_PROG FUSERMOUNT_DIR "/fusermount-glusterfs" #else #define FUSERMOUNT_PROG "fusermount" #endif #define FUSE_DEVFD_ENV "_FUSE_DEVFD" #ifdef __FreeBSD__ #include #include #include #endif /* __FreeBSD__ */ /* FUSE: function is called fuse_kern_unmount() */ void gf_fuse_unmount (const char *mountpoint, int fd) { int res; int pid; if (!mountpoint) return; if (fd != -1) { struct pollfd pfd; pfd.fd = fd; pfd.events = 0; res = poll (&pfd, 1, 0); /* If file poll returns POLLERR on the device file descriptor, then the filesystem is already unmounted */ if (res == 1 && (pfd.revents & POLLERR)) return; /* Need to close file descriptor, otherwise synchronous umount would recurse into filesystem, and deadlock */ close (fd); } if (geteuid () == 0) { fuse_mnt_umount ("fuse", mountpoint, mountpoint, 1); return; } else { GFFUSE_LOGERR ("fuse: Effective-uid: %d", geteuid()); } res = umount2 (mountpoint, 2); if (res == 0) return; GFFUSE_LOGERR ("fuse: failed to unmount %s: %s", mountpoint, strerror (errno)); pid = fork (); if (pid == -1) return; if (pid == 0) { const char *argv[] = { FUSERMOUNT_PROG, "-u", "-q", "-z", "--", mountpoint, NULL }; execvp (FUSERMOUNT_PROG, (char **)argv); GFFUSE_LOGERR ("fuse: failed to execute fuserumount: %s", strerror (errno)); _exit (1); } waitpid (pid, NULL, 0); } /* gluster-specific routines */ /* Unmounting in a daemon that lurks 'till main process exits */ int gf_fuse_unmount_daemon (const char *mountpoint, int fd) { int ret = -1; pid_t pid = -1; if (fd == -1) return -1; int ump[2] = {0,}; ret = pipe(ump); if (ret == -1) { close (fd); return -1; } pid = fork (); switch (pid) { case 0: { char c = 0; sigset_t sigset; close_fds_except (ump, 1); setsid(); (void)chdir("/"); sigfillset(&sigset); sigprocmask(SIG_BLOCK, &sigset, NULL); read (ump[0], &c, 1); gf_fuse_unmount (mountpoint, fd); exit (0); } case -1: close (fd); fd = -1; ret = -1; close (ump[1]); } close (ump[0]); return ret; } static char * escape (char *s) { size_t len = 0; char *p = NULL; char *q = NULL; char *e = NULL; for (p = s; *p; p++) { if (*p == ',') len++; len++; } e = CALLOC (1, len + 1); if (!e) return NULL; for (p = s, q = e; *p; p++, q++) { if (*p == ',') { *q = '\\'; q++; } *q = *p; } return e; } static int fuse_mount_fusermount (const char *mountpoint, char *fsname, char *mnt_param, int fd) { int pid = -1; int res = 0; int ret = -1; char *fm_mnt_params = NULL; char *efsname = NULL; #ifndef GF_FUSERMOUNT GFFUSE_LOGERR ("Mounting via helper utility " "(unprivileged mounting) is supported " "only if glusterfs is compiled with " "--enable-fusermount"); return -1; #endif efsname = escape (fsname); if (!efsname) { GFFUSE_LOGERR ("Out of memory"); return -1; } ret = asprintf (&fm_mnt_params, "%s,fsname=%s,nonempty,subtype=glusterfs", mnt_param, efsname); FREE (efsname); if (ret == -1) { GFFUSE_LOGERR ("Out of memory"); goto out; } /* fork to exec fusermount */ pid = fork (); if (pid == -1) { GFFUSE_LOGERR ("fork() failed: %s", strerror (errno)); ret = -1; goto out; } if (pid == 0) { char env[10]; const char *argv[32]; int a = 0; argv[a++] = FUSERMOUNT_PROG; argv[a++] = "-o"; argv[a++] = fm_mnt_params; argv[a++] = "--"; argv[a++] = mountpoint; argv[a++] = NULL; snprintf (env, sizeof (env), "%i", fd); setenv (FUSE_DEVFD_ENV, env, 1); execvp (FUSERMOUNT_PROG, (char **)argv); GFFUSE_LOGERR ("failed to exec fusermount: %s", strerror (errno)); _exit (1); } ret = waitpid (pid, &res, 0); ret = (ret == pid && res == 0) ? 0 : -1; out: FREE (fm_mnt_params); return ret; } #if defined(__FreeBSD__) void build_iovec(struct iovec **iov, int *iovlen, const char *name, void *val, size_t len) { int i; if (*iovlen < 0) return; i = *iovlen; *iov = realloc(*iov, sizeof **iov * (i + 2)); if (*iov == NULL) { *iovlen = -1; return; } (*iov)[i].iov_base = strdup(name); (*iov)[i].iov_len = strlen(name) + 1; i++; (*iov)[i].iov_base = val; if (len == (size_t) -1) { if (val != NULL) len = strlen(val) + 1; else len = 0; } (*iov)[i].iov_len = (int)len; *iovlen = ++i; } /* * This function is needed for compatibility with parameters * which used to use the mount_argf() command for the old mount() syscall. */ void build_iovec_argf(struct iovec **iov, int *iovlen, const char *name, const char *fmt, ...) { va_list ap; char val[255] = { 0 }; va_start(ap, fmt); vsnprintf(val, sizeof(val), fmt, ap); va_end(ap); build_iovec(iov, iovlen, name, strdup(val), (size_t)-1); } #endif /* __FreeBSD__ */ struct mount_flags { const char *opt; mount_flag_t flag; int on; } mount_flags[] = { /* We provide best effort cross platform support for mount flags by * defining the ones which are commonly used in Unix-like OS-es. */ {"ro", MS_RDONLY, 1}, {"nosuid", MS_NOSUID, 1}, {"nodev", MS_NODEV, 1}, {"noatime", MS_NOATIME, 1}, {"noexec", MS_NOEXEC, 1}, #ifdef GF_LINUX_HOST_OS {"rw", MS_RDONLY, 0}, {"suid", MS_NOSUID, 0}, {"dev", MS_NODEV, 0}, {"exec", MS_NOEXEC, 0}, {"async", MS_SYNCHRONOUS, 0}, {"sync", MS_SYNCHRONOUS, 1}, {"atime", MS_NOATIME, 0}, {"dirsync", MS_DIRSYNC, 1}, #endif {NULL, 0, 0} }; static int mount_param_to_flag (char *mnt_param, mount_flag_t *mntflags, char **mnt_param_new) { gf_boolean_t found = _gf_false; struct mount_flags *flag = NULL; char *param_tok = NULL; token_iter_t tit = {0,}; gf_boolean_t iter_end = _gf_false; /* Allocate a buffer that will hold the mount parameters remaining * after the ones corresponding to mount flags are processed and * removed.The length of the original params are a good upper bound * of the size needed. */ *mnt_param_new = strdup (mnt_param); if (!*mnt_param_new) return -1; for (param_tok = token_iter_init (*mnt_param_new, ',', &tit) ;;) { iter_end = next_token (¶m_tok, &tit); found = _gf_false; for (flag = mount_flags; flag->opt; flag++) { /* Compare the mount flag name to the param * name at hand. */ if (strcmp (flag->opt, param_tok) == 0) { /* If there is a match, adjust mntflags * accordingly and break. */ if (flag->on) { *mntflags |= flag->flag; } else { *mntflags &= ~flag->flag; } found = _gf_true; break; } } /* Exclude flag names from new parameter list. */ if (found) drop_token (param_tok, &tit); if (iter_end) break; } return 0; } static int fuse_mount_sys (const char *mountpoint, char *fsname, char *mnt_param, int fd) { int ret = -1; unsigned mounted = 0; char *mnt_param_mnt = NULL; char *fstype = "fuse.glusterfs"; char *source = fsname; mount_flag_t mountflags = 0; char *mnt_param_new = NULL; ret = mount_param_to_flag (mnt_param, &mountflags, &mnt_param_new); if (ret == 0) ret = asprintf (&mnt_param_mnt, "%s,fd=%i,rootmode=%o,user_id=%i,group_id=%i", mnt_param_new, fd, S_IFDIR, getuid (), getgid ()); if (ret == -1) { GFFUSE_LOGERR ("Out of memory"); goto out; } #ifdef __FreeBSD__ struct iovec *iov = NULL; int iovlen = 0; char fdstr[15]; sprintf (fdstr, "%d", fd); build_iovec (&iov, &iovlen, "fstype", "fusefs", -1); build_iovec (&iov, &iovlen, "subtype", "glusterfs", -1); build_iovec (&iov, &iovlen, "fspath", __DECONST(void *, mountpoint), -1); build_iovec (&iov, &iovlen, "from", "/dev/fuse", -1); build_iovec (&iov, &iovlen, "volname", source, -1); build_iovec (&iov, &iovlen, "fd", fdstr, -1); build_iovec (&iov, &iovlen, "allow_other", NULL, -1); ret = nmount (iov, iovlen, mountflags); #else ret = mount (source, mountpoint, fstype, mountflags, mnt_param_mnt); #endif /* __FreeBSD__ */ #ifdef GF_LINUX_HOST_OS if (ret == -1 && errno == ENODEV) { /* fs subtype support was added by 79c0b2df aka v2.6.21-3159-g79c0b2d. Probably we have an older kernel ... */ fstype = "fuse"; ret = asprintf (&source, "glusterfs#%s", fsname); if (ret == -1) { GFFUSE_LOGERR ("Out of memory"); goto out; } ret = mount (source, mountpoint, fstype, mountflags, mnt_param_mnt); } #endif /* GF_LINUX_HOST_OS */ if (ret == -1) goto out; else mounted = 1; #ifdef GF_LINUX_HOST_OS if (geteuid () == 0) { char *newmnt = fuse_mnt_resolve_path ("fuse", mountpoint); char *mnt_param_mtab = NULL; if (!newmnt) { ret = -1; goto out; } ret = asprintf (&mnt_param_mtab, "%s%s", mountflags & MS_RDONLY ? "ro," : "", mnt_param_new); if (ret == -1) GFFUSE_LOGERR ("Out of memory"); else { ret = fuse_mnt_add_mount ("fuse", source, newmnt, fstype, mnt_param_mtab); FREE (mnt_param_mtab); } FREE (newmnt); if (ret == -1) { GFFUSE_LOGERR ("failed to add mtab entry"); goto out; } } #endif /* GF_LINUX_HOST_OS */ out: if (ret == -1) { GFFUSE_LOGERR("ret = -1\n"); if (mounted) umount2 (mountpoint, 2); /* lazy umount */ } FREE (mnt_param_mnt); FREE (mnt_param_new); if (source != fsname) FREE (source); return ret; } int gf_fuse_mount (const char *mountpoint, char *fsname, char *mnt_param, pid_t *mnt_pid, int status_fd) { int fd = -1; pid_t pid = -1; int ret = -1; fd = open ("/dev/fuse", O_RDWR); if (fd == -1) { GFFUSE_LOGERR ("cannot open /dev/fuse (%s)", strerror (errno)); return -1; } /* start mount agent */ pid = fork(); switch (pid) { case 0: /* hello it's mount agent */ if (!mnt_pid) { /* daemonize mount agent, caller is * not interested in waiting for it */ pid = fork (); if (pid) exit (pid == -1 ? 1 : 0); } ret = fuse_mount_sys (mountpoint, fsname, mnt_param, fd); if (ret == -1) { gf_log ("glusterfs-fuse", GF_LOG_INFO, "direct mount failed (%s) errno %d", strerror (errno), errno); if (errno == EPERM) { gf_log ("glusterfs-fuse", GF_LOG_INFO, "retry to mount via fusermount"); ret = fuse_mount_fusermount (mountpoint, fsname, mnt_param, fd); } } if (ret == -1) GFFUSE_LOGERR ("mount of %s to %s (%s) failed", fsname, mountpoint, mnt_param); if (status_fd >= 0) (void)write (status_fd, &ret, sizeof (ret)); exit (!!ret); /* bye mount agent */ case -1: close (fd); fd = -1; } if (mnt_pid) *mnt_pid = pid; return fd; }