aboutsummaryrefslogtreecommitdiffhomepage
path: root/cmd/sharefs/fuse-helper.c
diff options
context:
space:
mode:
Diffstat (limited to 'cmd/sharefs/fuse-helper.c')
-rw-r--r--cmd/sharefs/fuse-helper.c316
1 files changed, 316 insertions, 0 deletions
diff --git a/cmd/sharefs/fuse-helper.c b/cmd/sharefs/fuse-helper.c
new file mode 100644
index 00000000..7725b679
--- /dev/null
+++ b/cmd/sharefs/fuse-helper.c
@@ -0,0 +1,316 @@
+#ifndef _GNU_SOURCE
+#define _GNU_SOURCE /* O_DIRECT */
+#endif
+
+#include <dirent.h>
+#include <errno.h>
+#include <unistd.h>
+
+/* TODO(ophestra): remove after 05ce67fea99ca09cd4b6625cff7aec9cc222dd5a reaches a release */
+#include <sys/syscall.h>
+
+#include "fuse-helper.h"
+#define SHAREFS_MEDIA_RW_ID (1 << 10) - 1 /* owning gid presented to userspace */
+#define SHAREFS_PERM_DIR 0700 /* permission bits for directories presented to userspace */
+#define SHAREFS_PERM_REG 0600 /* permission bits for regular files presented to userspace */
+#define SHAREFS_FORBIDDEN_FLAGS O_DIRECT /* these open flags are cleared unconditionally */
+
+/* translate_pathname translates a userspace pathname to a relative pathname;
+ * the returned address is a constant string or part of pathname, it is never heap allocated. */
+static inline const char *translate_pathname(const char *pathname) {
+ if (pathname == NULL) {
+ errno = EINVAL;
+ return NULL;
+ }
+
+ while (*pathname == '/')
+ pathname++;
+ if (*pathname == '\0')
+ pathname = ".";
+ return pathname;
+}
+
+#define MUST_TRANSLATE_PATHNAME(pathname) \
+ do { \
+ pathname = translate_pathname(pathname); \
+ if (pathname == NULL) \
+ return -errno; \
+ } while (0)
+
+/* GET_CONTEXT_PRIV obtains fuse context and private data for the calling thread. */
+#define GET_CONTEXT_PRIV(ctx, priv) \
+ do { \
+ ctx = fuse_get_context(); \
+ priv = ctx->private_data; \
+ } while (0)
+
+/* impl_getattr modifies a struct stat from the kernel to present to userspace;
+ * impl_getattr returns a negative errno style error code. */
+static int impl_getattr(struct fuse_context *ctx, struct stat *statbuf) {
+ /* allowlist of permitted types */
+ if (!S_ISDIR(statbuf->st_mode) && !S_ISREG(statbuf->st_mode) && !S_ISLNK(statbuf->st_mode)) {
+ return -ENOTRECOVERABLE; /* returning an errno causes all operations on the file to return EIO */
+ }
+
+#define OVERRIDE_PERM(v) (statbuf->st_mode & ~0777) | (v & 0777)
+ if (S_ISDIR(statbuf->st_mode))
+ statbuf->st_mode = OVERRIDE_PERM(SHAREFS_PERM_DIR);
+ else if (S_ISREG(statbuf->st_mode))
+ statbuf->st_mode = OVERRIDE_PERM(SHAREFS_PERM_REG);
+ else
+ statbuf->st_mode = 0; /* should always be symlink in this case */
+
+ statbuf->st_uid = ctx->uid;
+ statbuf->st_gid = SHAREFS_MEDIA_RW_ID;
+ statbuf->st_ctim = statbuf->st_mtim;
+ statbuf->st_nlink = 1;
+ return 0;
+}
+
+/* fuse_operations implementation */
+
+int sharefs_getattr(const char *pathname, struct stat *statbuf, struct fuse_file_info *fi) {
+ struct fuse_context *ctx;
+ struct sharefs_private *priv;
+ GET_CONTEXT_PRIV(ctx, priv);
+ MUST_TRANSLATE_PATHNAME(pathname);
+
+ (void)fi;
+
+ if (fstatat(priv->dirfd, pathname, statbuf, AT_SYMLINK_NOFOLLOW) == -1)
+ return -errno;
+ return impl_getattr(ctx, statbuf);
+}
+
+int sharefs_readlink(const char *pathname, char *buf, size_t bufsiz) {
+ int res;
+ struct fuse_context *ctx;
+ struct sharefs_private *priv;
+ GET_CONTEXT_PRIV(ctx, priv);
+ MUST_TRANSLATE_PATHNAME(pathname);
+
+ if ((res = readlinkat(priv->dirfd, pathname, buf, bufsiz - 1)) == -1)
+ return -errno;
+ buf[res] = '\0';
+ return 0;
+}
+
+int sharefs_readdir(const char *pathname, void *buf, fuse_fill_dir_t filler, off_t offset, struct fuse_file_info *fi, enum fuse_readdir_flags flags) {
+ int fd;
+ DIR *dp;
+ struct stat st;
+ int ret = 0;
+ struct dirent *de;
+
+ struct fuse_context *ctx;
+ struct sharefs_private *priv;
+ GET_CONTEXT_PRIV(ctx, priv);
+ MUST_TRANSLATE_PATHNAME(pathname);
+
+ (void)offset;
+ (void)fi;
+
+ if ((fd = openat(priv->dirfd, pathname, O_RDONLY | O_DIRECTORY | O_CLOEXEC)) == -1)
+ return -errno;
+ if ((dp = fdopendir(fd)) == NULL) {
+ close(fd);
+ return -errno;
+ }
+
+ errno = 0; /* for the next readdir call */
+ while ((de = readdir(dp)) != NULL) {
+ if (flags & FUSE_READDIR_PLUS) {
+ if (fstatat(dirfd(dp), de->d_name, &st, AT_SYMLINK_NOFOLLOW) == -1) {
+ ret = -errno;
+ break;
+ }
+
+ if ((ret = impl_getattr(ctx, &st)) < 0)
+ break;
+
+ errno = 0;
+ ret = filler(buf, de->d_name, &st, 0, FUSE_FILL_DIR_PLUS);
+ } else
+ ret = filler(buf, de->d_name, NULL, 0, 0);
+
+ if (ret != 0) {
+ ret = errno != 0 ? -errno : -EIO; /* filler */
+ break;
+ }
+
+ errno = 0; /* for the next readdir call */
+ }
+ if (ret == 0 && errno != 0)
+ ret = -errno; /* readdir */
+
+ closedir(dp);
+ return ret;
+}
+
+int sharefs_mkdir(const char *pathname, mode_t mode) {
+ struct fuse_context *ctx;
+ struct sharefs_private *priv;
+ GET_CONTEXT_PRIV(ctx, priv);
+ MUST_TRANSLATE_PATHNAME(pathname);
+
+ (void)mode;
+
+ if (mkdirat(priv->dirfd, pathname, SHAREFS_PERM_DIR) == -1)
+ return -errno;
+ return 0;
+}
+
+int sharefs_unlink(const char *pathname) {
+ struct fuse_context *ctx;
+ struct sharefs_private *priv;
+ GET_CONTEXT_PRIV(ctx, priv);
+ MUST_TRANSLATE_PATHNAME(pathname);
+
+ if (unlinkat(priv->dirfd, pathname, 0) == -1)
+ return -errno;
+ return 0;
+}
+
+int sharefs_rmdir(const char *pathname) {
+ struct fuse_context *ctx;
+ struct sharefs_private *priv;
+ GET_CONTEXT_PRIV(ctx, priv);
+ MUST_TRANSLATE_PATHNAME(pathname);
+
+ if (unlinkat(priv->dirfd, pathname, AT_REMOVEDIR) == -1)
+ return -errno;
+ return 0;
+}
+
+int sharefs_rename(const char *oldpath, const char *newpath, unsigned int flags) {
+ int res;
+
+ struct fuse_context *ctx;
+ struct sharefs_private *priv;
+ GET_CONTEXT_PRIV(ctx, priv);
+ MUST_TRANSLATE_PATHNAME(oldpath);
+ MUST_TRANSLATE_PATHNAME(newpath);
+
+ /* TODO(ophestra): replace with wrapper after 05ce67fea99ca09cd4b6625cff7aec9cc222dd5a reaches a release */
+ if (syscall(__NR_renameat2, priv->dirfd, oldpath, priv->dirfd, newpath, flags) == -1)
+ return -errno;
+ return 0;
+}
+
+int sharefs_truncate(const char *pathname, off_t length, struct fuse_file_info *fi) {
+ int fd;
+ int ret;
+
+ struct fuse_context *ctx;
+ struct sharefs_private *priv;
+ GET_CONTEXT_PRIV(ctx, priv);
+ MUST_TRANSLATE_PATHNAME(pathname);
+
+ (void)fi;
+
+ if ((fd = openat(priv->dirfd, pathname, O_WRONLY | O_CLOEXEC)) == -1)
+ return -errno;
+ if ((ret = ftruncate(fd, length)) == -1)
+ ret = -errno;
+ close(fd);
+ return ret;
+}
+
+int sharefs_utimens(const char *pathname, const struct timespec times[2], struct fuse_file_info *fi) {
+ int res;
+
+ struct fuse_context *ctx;
+ struct sharefs_private *priv;
+ GET_CONTEXT_PRIV(ctx, priv);
+ MUST_TRANSLATE_PATHNAME(pathname);
+
+ (void)fi;
+
+ if (utimensat(priv->dirfd, pathname, times, AT_SYMLINK_NOFOLLOW) == -1)
+ return -errno;
+ return 0;
+}
+
+int sharefs_create(const char *pathname, mode_t mode, struct fuse_file_info *fi) {
+ int fd;
+
+ struct fuse_context *ctx;
+ struct sharefs_private *priv;
+ GET_CONTEXT_PRIV(ctx, priv);
+ MUST_TRANSLATE_PATHNAME(pathname);
+
+ (void)mode;
+ (void)fi;
+
+ if ((fd = openat(priv->dirfd, pathname, fi->flags & ~SHAREFS_FORBIDDEN_FLAGS, SHAREFS_PERM_REG)) == -1)
+ return -errno;
+ fi->fh = fd;
+ return 0;
+}
+
+int sharefs_open(const char *pathname, struct fuse_file_info *fi) {
+ int fd;
+
+ struct fuse_context *ctx;
+ struct sharefs_private *priv;
+ GET_CONTEXT_PRIV(ctx, priv);
+ MUST_TRANSLATE_PATHNAME(pathname);
+
+ if ((fd = openat(priv->dirfd, pathname, fi->flags & ~SHAREFS_FORBIDDEN_FLAGS)) == -1)
+ return -errno;
+ fi->fh = fd;
+ return 0;
+}
+
+int sharefs_read(const char *pathname, char *buf, size_t count, off_t offset, struct fuse_file_info *fi) {
+ int ret;
+
+ (void)pathname;
+
+ if ((ret = pread(fi->fh, buf, count, offset)) == -1)
+ return -errno;
+ return ret;
+}
+
+int sharefs_write(const char *pathname, const char *buf, size_t count, off_t offset, struct fuse_file_info *fi) {
+ int ret;
+
+ (void)pathname;
+
+ if ((ret = pwrite(fi->fh, buf, count, offset)) == -1)
+ return -errno;
+ return ret;
+}
+
+int sharefs_statfs(const char *pathname, struct statvfs *statbuf) {
+ int fd;
+ int ret;
+
+ struct fuse_context *ctx;
+ struct sharefs_private *priv;
+ GET_CONTEXT_PRIV(ctx, priv);
+ MUST_TRANSLATE_PATHNAME(pathname);
+
+ if ((fd = openat(priv->dirfd, pathname, O_RDONLY | O_CLOEXEC)) == -1)
+ return -errno;
+ if ((ret = fstatvfs(fd, statbuf)) == -1)
+ ret = -errno;
+ close(fd);
+ return ret;
+}
+
+int sharefs_release(const char *pathname, struct fuse_file_info *fi) {
+ (void)pathname;
+
+ return close(fi->fh);
+}
+
+int sharefs_fsync(const char *pathname, int datasync, struct fuse_file_info *fi) {
+ int res;
+
+ (void)pathname;
+
+ if (datasync ? fdatasync(fi->fh) : fsync(fi->fh) == -1)
+ return -errno;
+ return 0;
+}