#ifndef _GNU_SOURCE #define _GNU_SOURCE /* O_DIRECT */ #endif #include #include #include /* TODO(ophestra): remove after 05ce67fea99ca09cd4b6625cff7aec9cc222dd5a reaches a release */ #include #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_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; }