All checks were successful
Test / Create distribution (push) Successful in 43s
Test / Sandbox (push) Successful in 2m34s
Test / Hakurei (push) Successful in 3m26s
Test / ShareFS (push) Successful in 3m23s
Test / Hpkg (push) Successful in 4m23s
Test / Sandbox (race detector) (push) Successful in 4m38s
Test / Hakurei (race detector) (push) Successful in 5m33s
Test / Flake checks (push) Successful in 1m45s
This is not really just library wrapper functions, but instead implements the callbacks, so fuse-operations makes more sense. Signed-off-by: Ophestra <cat@gensokyo.uk>
283 lines
7.9 KiB
C
283 lines
7.9 KiB
C
#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-operations.h"
|
|
|
|
/* MUST_TRANSLATE_PATHNAME translates a userspace pathname to a relative pathname;
|
|
* the resulting address points to a constant string or part of pathname, it is never heap allocated. */
|
|
#define MUST_TRANSLATE_PATHNAME(pathname) \
|
|
do { \
|
|
if (pathname == NULL) \
|
|
return -EINVAL; \
|
|
while (*pathname == '/') \
|
|
pathname++; \
|
|
if (*pathname == '\0') \
|
|
pathname = "."; \
|
|
} 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) {
|
|
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) {
|
|
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;
|
|
|
|
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) {
|
|
(void)pathname;
|
|
|
|
if (datasync ? fdatasync(fi->fh) : fsync(fi->fh) == -1)
|
|
return -errno;
|
|
return 0;
|
|
}
|