mirror of
https://github.com/libfuse/libfuse.git
synced 2026-07-07 14:47:41 +08:00
33996e0511
Fix various cppcheck complaints about pointers that could be const pointers instead. Signed-off-by: Darrick J. Wong <djwong@kernel.org>
993 lines
21 KiB
C
993 lines
21 KiB
C
/*
|
|
* FUSE: Filesystem in Userspace
|
|
* Copyright (C) 2026 Oracle.
|
|
*
|
|
* This program can be distributed under the terms of the GNU GPLv2.
|
|
* See the file GPL2.txt.
|
|
*
|
|
* This file is shared library code for example fuse servers that want to
|
|
* expose a single regular file that wraps another file in a manner that goes
|
|
* beyond simple passthrough. It is not itself a fuse server.
|
|
*/
|
|
#define _GNU_SOURCE
|
|
#include <pthread.h>
|
|
#include <errno.h>
|
|
#include <stdlib.h>
|
|
#include <errno.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
#include <sys/ioctl.h>
|
|
#include <sys/stat.h>
|
|
#ifdef __linux__
|
|
#include <linux/fs.h>
|
|
#include <linux/stat.h>
|
|
#endif
|
|
|
|
#define FUSE_USE_VERSION (FUSE_MAKE_VERSION(3, 19))
|
|
|
|
#include "fuse_lowlevel.h"
|
|
#include "fuse.h"
|
|
#include "fuse_service.h"
|
|
#define USE_SINGLE_FILE_LL_API
|
|
#define USE_SINGLE_FILE_HL_API
|
|
#include "single_file.h"
|
|
|
|
#define min(x, y) ((x) < (y) ? (x) : (y))
|
|
|
|
#if __has_attribute(__fallthrough__)
|
|
#define fallthrough __attribute__((__fallthrough__))
|
|
#else
|
|
#define fallthrough do {} while (0)
|
|
#endif
|
|
|
|
struct dirbuf {
|
|
char *p;
|
|
size_t size;
|
|
};
|
|
|
|
struct single_file_stat {
|
|
struct fuse_entry_param entry;
|
|
};
|
|
|
|
#define SINGLE_FILE_INO (FUSE_ROOT_ID + 1)
|
|
|
|
static const char *single_file_name = "single_file";
|
|
static bool single_file_name_set;
|
|
static struct timespec startup_time;
|
|
|
|
struct single_file single_file = {
|
|
.backing_fd = -1,
|
|
.allow_dio = true,
|
|
.mode = S_IFREG | 0444,
|
|
.lock = PTHREAD_MUTEX_INITIALIZER,
|
|
};
|
|
|
|
static fuse_ino_t single_file_path_to_ino(const char *path)
|
|
{
|
|
if (strcmp(path, "/") == 0)
|
|
return FUSE_ROOT_ID;
|
|
if (strcmp(path + 1, single_file_name) == 0)
|
|
return SINGLE_FILE_INO;
|
|
return 0;
|
|
}
|
|
|
|
static fuse_ino_t single_open_file_path_to_ino(const struct fuse_file_info *fi,
|
|
const char *path)
|
|
{
|
|
if (fi)
|
|
return fi->fh;
|
|
return single_file_path_to_ino(path);
|
|
}
|
|
|
|
static void dirbuf_add(fuse_req_t req, struct dirbuf *b, const char *name,
|
|
fuse_ino_t ino)
|
|
{
|
|
struct stat stbuf;
|
|
size_t oldsize = b->size;
|
|
|
|
b->size += fuse_add_direntry(req, NULL, 0, name, NULL, 0);
|
|
b->p = (char *) realloc(b->p, b->size);
|
|
memset(&stbuf, 0, sizeof(stbuf));
|
|
stbuf.st_ino = ino;
|
|
fuse_add_direntry(req, b->p + oldsize, b->size - oldsize, name, &stbuf,
|
|
b->size);
|
|
}
|
|
|
|
int reply_buf_limited(fuse_req_t req, const char *buf, size_t bufsize,
|
|
off_t off, size_t maxsize)
|
|
{
|
|
if (off < bufsize)
|
|
return fuse_reply_buf(req, buf + off,
|
|
min(bufsize - off, maxsize));
|
|
else
|
|
return fuse_reply_buf(req, NULL, 0);
|
|
}
|
|
|
|
bool is_single_file_child(fuse_ino_t parent, const char *name)
|
|
{
|
|
return parent == FUSE_ROOT_ID &&
|
|
strcmp(name, single_file_name) == 0;
|
|
}
|
|
|
|
bool is_single_file_ino(fuse_ino_t ino)
|
|
{
|
|
return ino == SINGLE_FILE_INO;
|
|
}
|
|
|
|
bool is_single_open_file_path(const struct fuse_file_info *fi, const char *name)
|
|
{
|
|
if (fi)
|
|
return is_single_file_ino(fi->fh);
|
|
return name[0] == '/' && strcmp(name + 1, single_file_name) == 0;
|
|
}
|
|
|
|
void single_file_ll_readdir(fuse_req_t req, fuse_ino_t ino, size_t size,
|
|
off_t off, struct fuse_file_info *fi)
|
|
{
|
|
struct dirbuf b;
|
|
|
|
(void) fi;
|
|
|
|
switch (ino) {
|
|
case FUSE_ROOT_ID:
|
|
break;
|
|
case SINGLE_FILE_INO:
|
|
fuse_reply_err(req, ENOTDIR);
|
|
return;
|
|
default:
|
|
fuse_reply_err(req, ENOENT);
|
|
return;
|
|
}
|
|
|
|
memset(&b, 0, sizeof(b));
|
|
dirbuf_add(req, &b, ".", FUSE_ROOT_ID);
|
|
dirbuf_add(req, &b, "..", FUSE_ROOT_ID);
|
|
dirbuf_add(req, &b, single_file_name, SINGLE_FILE_INO);
|
|
reply_buf_limited(req, b.p, b.size, off, size);
|
|
free(b.p);
|
|
}
|
|
|
|
int single_file_hl_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
|
|
off_t offset, struct fuse_file_info *fi,
|
|
enum fuse_readdir_flags flags)
|
|
{
|
|
struct stat stbuf;
|
|
fuse_ino_t ino = single_open_file_path_to_ino(fi, path);
|
|
|
|
memset(&stbuf, 0, sizeof(stbuf));
|
|
|
|
(void) offset;
|
|
(void) flags;
|
|
|
|
switch (ino) {
|
|
case FUSE_ROOT_ID:
|
|
break;
|
|
case SINGLE_FILE_INO:
|
|
return -ENOTDIR;
|
|
default:
|
|
return -ENOENT;
|
|
}
|
|
|
|
stbuf.st_ino = FUSE_ROOT_ID;
|
|
filler(buf, ".", &stbuf, 0, FUSE_FILL_DIR_DEFAULTS);
|
|
filler(buf, "..", &stbuf, 0, FUSE_FILL_DIR_DEFAULTS);
|
|
|
|
stbuf.st_ino = SINGLE_FILE_INO;
|
|
filler(buf, single_file_name, &stbuf, 0, FUSE_FILL_DIR_DEFAULTS);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static bool sf_stat(fuse_ino_t ino, struct single_file_stat *llstat)
|
|
{
|
|
struct fuse_entry_param *entry = &llstat->entry;
|
|
struct stat *stbuf = &entry->attr;
|
|
|
|
if (ino == FUSE_ROOT_ID) {
|
|
stbuf->st_mode = S_IFDIR | 0555;
|
|
stbuf->st_nlink = 2;
|
|
stbuf->st_atim = startup_time;
|
|
stbuf->st_mtim = startup_time;
|
|
stbuf->st_ctim = startup_time;
|
|
} else if (ino == SINGLE_FILE_INO) {
|
|
stbuf->st_mode = single_file.mode;
|
|
stbuf->st_nlink = 1;
|
|
stbuf->st_size = single_file.isize;
|
|
stbuf->st_blksize = single_file.blocksize;
|
|
stbuf->st_blocks = howmany(single_file.isize, 512);
|
|
stbuf->st_atim = single_file.atime;
|
|
stbuf->st_mtim = single_file.mtime;
|
|
stbuf->st_ctim = single_file.ctime;
|
|
} else {
|
|
return false;
|
|
}
|
|
stbuf->st_ino = ino;
|
|
|
|
entry->generation = ino + 1;
|
|
entry->attr_timeout = 0.0;
|
|
entry->entry_timeout = 0.0;
|
|
entry->ino = ino;
|
|
|
|
return true;
|
|
}
|
|
|
|
#if defined(STATX_BASIC_STATS)
|
|
static inline void sf_set_statx_attr(struct statx *stx,
|
|
uint64_t statx_flag, int set)
|
|
{
|
|
if (set)
|
|
stx->stx_attributes |= statx_flag;
|
|
stx->stx_attributes_mask |= statx_flag;
|
|
}
|
|
|
|
static void sf_statx_directio(struct statx *stx)
|
|
{
|
|
struct statx devx;
|
|
int ret;
|
|
|
|
ret = statx(single_file.backing_fd, "", AT_EMPTY_PATH, STATX_DIOALIGN,
|
|
&devx);
|
|
if (ret)
|
|
return;
|
|
if (!(devx.stx_mask & STATX_DIOALIGN))
|
|
return;
|
|
|
|
stx->stx_mask |= STATX_DIOALIGN;
|
|
stx->stx_dio_mem_align = devx.stx_dio_mem_align;
|
|
stx->stx_dio_offset_align = devx.stx_dio_offset_align;
|
|
}
|
|
|
|
static bool sf_statx(fuse_ino_t ino, int statx_mask, struct statx *stx)
|
|
{
|
|
(void)statx_mask;
|
|
|
|
if (ino == FUSE_ROOT_ID) {
|
|
stx->stx_mask = STATX_BASIC_STATS | STATX_BTIME;
|
|
stx->stx_mode = S_IFDIR | 0555;
|
|
stx->stx_nlink = 2;
|
|
stx->stx_atime.tv_sec = startup_time.tv_sec;
|
|
stx->stx_atime.tv_nsec = startup_time.tv_nsec;
|
|
stx->stx_mtime.tv_sec = startup_time.tv_sec;
|
|
stx->stx_mtime.tv_nsec = startup_time.tv_nsec;
|
|
stx->stx_ctime.tv_sec = startup_time.tv_sec;
|
|
stx->stx_ctime.tv_nsec = startup_time.tv_nsec;
|
|
stx->stx_btime.tv_sec = startup_time.tv_sec;
|
|
stx->stx_btime.tv_nsec = startup_time.tv_nsec;
|
|
} else if (ino == SINGLE_FILE_INO) {
|
|
stx->stx_mask = STATX_BASIC_STATS | STATX_BTIME;
|
|
stx->stx_mode = single_file.mode;
|
|
stx->stx_nlink = 1;
|
|
stx->stx_size = single_file.isize;
|
|
stx->stx_blksize = single_file.blocksize;
|
|
stx->stx_blocks = howmany(single_file.isize, 512);
|
|
stx->stx_atime.tv_sec = single_file.atime.tv_sec;
|
|
stx->stx_atime.tv_nsec = single_file.atime.tv_nsec;
|
|
stx->stx_mtime.tv_sec = single_file.mtime.tv_sec;
|
|
stx->stx_mtime.tv_nsec = single_file.mtime.tv_nsec;
|
|
stx->stx_ctime.tv_sec = single_file.ctime.tv_sec;
|
|
stx->stx_ctime.tv_nsec = single_file.ctime.tv_nsec;
|
|
stx->stx_btime.tv_sec = startup_time.tv_sec;
|
|
stx->stx_btime.tv_nsec = startup_time.tv_nsec;
|
|
} else {
|
|
return false;
|
|
}
|
|
stx->stx_ino = ino;
|
|
|
|
sf_set_statx_attr(stx, STATX_ATTR_IMMUTABLE, single_file.ro);
|
|
sf_statx_directio(stx);
|
|
|
|
return true;
|
|
}
|
|
|
|
void single_file_ll_statx(fuse_req_t req, fuse_ino_t ino, int flags, int mask,
|
|
struct fuse_file_info *fi)
|
|
{
|
|
struct statx stx = { };
|
|
bool filled;
|
|
|
|
(void)fi;
|
|
|
|
if ((flags & AT_STATX_FORCE_SYNC) && is_single_file_ino(ino) &&
|
|
single_file.backing_fd >= 0) {
|
|
int ret = fsync(single_file.backing_fd);
|
|
|
|
if (ret) {
|
|
fuse_reply_err(req, errno);
|
|
return;
|
|
}
|
|
}
|
|
|
|
pthread_mutex_lock(&single_file.lock);
|
|
filled = sf_statx(ino, mask, &stx);
|
|
pthread_mutex_unlock(&single_file.lock);
|
|
if (!filled)
|
|
fuse_reply_err(req, ENOENT);
|
|
else
|
|
fuse_reply_statx(req, 0, &stx, 0.0);
|
|
}
|
|
|
|
int single_file_hl_statx(const char *path, int statx_flags, int statx_mask,
|
|
struct statx *stx, struct fuse_file_info *fi)
|
|
{
|
|
fuse_ino_t ino = single_open_file_path_to_ino(fi, path);
|
|
bool filled;
|
|
|
|
if (!ino)
|
|
return -ENOENT;
|
|
|
|
if ((statx_flags & AT_STATX_FORCE_SYNC) && is_single_file_ino(ino) &&
|
|
single_file.backing_fd >= 0) {
|
|
int ret = fsync(single_file.backing_fd);
|
|
|
|
if (ret)
|
|
return -errno;
|
|
}
|
|
|
|
pthread_mutex_lock(&single_file.lock);
|
|
filled = sf_statx(ino, statx_mask, stx);
|
|
pthread_mutex_unlock(&single_file.lock);
|
|
|
|
return filled ? 0 : -ENOENT;
|
|
}
|
|
#else
|
|
void single_file_ll_statx(fuse_req_t req, fuse_ino_t ino, int flags, int mask,
|
|
struct fuse_file_info *fi)
|
|
{
|
|
fuse_reply_err(req, ENOSYS);
|
|
}
|
|
|
|
int single_file_hl_statx(const char *path, int statx_flags, int statx_mask,
|
|
struct statx *stx, struct fuse_file_info *fi)
|
|
{
|
|
return -ENOSYS;
|
|
}
|
|
#endif /* STATX_BASIC_STATS */
|
|
|
|
static void single_file_statfs(struct statvfs *buf)
|
|
{
|
|
pthread_mutex_lock(&single_file.lock);
|
|
buf->f_bsize = single_file.blocksize;
|
|
buf->f_frsize = 0;
|
|
|
|
buf->f_blocks = single_file.blocks;
|
|
buf->f_bfree = 0;
|
|
buf->f_bavail = 0;
|
|
buf->f_files = 1;
|
|
buf->f_ffree = 0;
|
|
buf->f_favail = 0;
|
|
buf->f_fsid = 0x50C00L;
|
|
buf->f_flag = 0;
|
|
if (single_file.ro)
|
|
buf->f_flag |= ST_RDONLY;
|
|
buf->f_namemax = 255;
|
|
pthread_mutex_unlock(&single_file.lock);
|
|
}
|
|
|
|
void single_file_ll_statfs(fuse_req_t req, fuse_ino_t ino)
|
|
{
|
|
struct statvfs buf;
|
|
|
|
(void)ino;
|
|
|
|
single_file_statfs(&buf);
|
|
fuse_reply_statfs(req, &buf);
|
|
}
|
|
|
|
int single_file_hl_statfs(const char *path, struct statvfs *buf)
|
|
{
|
|
(void)path;
|
|
|
|
single_file_statfs(buf);
|
|
return 0;
|
|
}
|
|
|
|
void single_file_ll_getattr(fuse_req_t req, fuse_ino_t ino,
|
|
struct fuse_file_info *fi)
|
|
{
|
|
struct single_file_stat llstat;
|
|
bool filled;
|
|
|
|
(void) fi;
|
|
|
|
memset(&llstat, 0, sizeof(llstat));
|
|
pthread_mutex_lock(&single_file.lock);
|
|
filled = sf_stat(ino, &llstat);
|
|
pthread_mutex_unlock(&single_file.lock);
|
|
if (!filled)
|
|
fuse_reply_err(req, ENOENT);
|
|
else
|
|
fuse_reply_attr(req, &llstat.entry.attr,
|
|
llstat.entry.attr_timeout);
|
|
}
|
|
|
|
int single_file_hl_getattr(const char *path, struct stat *stbuf,
|
|
struct fuse_file_info *fi)
|
|
{
|
|
struct single_file_stat llstat;
|
|
fuse_ino_t ino = single_open_file_path_to_ino(fi, path);
|
|
bool filled;
|
|
|
|
if (!ino)
|
|
return -ENOENT;
|
|
|
|
memset(&llstat, 0, sizeof(llstat));
|
|
pthread_mutex_lock(&single_file.lock);
|
|
filled = sf_stat(ino, &llstat);
|
|
pthread_mutex_unlock(&single_file.lock);
|
|
|
|
if (!filled)
|
|
return -ENOENT;
|
|
|
|
memcpy(stbuf, &llstat.entry.attr, sizeof(*stbuf));
|
|
return 0;
|
|
}
|
|
|
|
static void get_now(struct timespec *now)
|
|
{
|
|
#ifdef CLOCK_REALTIME
|
|
if (!clock_gettime(CLOCK_REALTIME, now))
|
|
return;
|
|
#endif
|
|
|
|
now->tv_sec = time(NULL);
|
|
now->tv_nsec = 0;
|
|
}
|
|
|
|
void single_file_ll_setattr(fuse_req_t req, fuse_ino_t ino, struct stat *attr,
|
|
int to_set, struct fuse_file_info *fi)
|
|
{
|
|
struct timespec now;
|
|
|
|
if (ino != SINGLE_FILE_INO)
|
|
goto deny;
|
|
if (to_set & (FUSE_SET_ATTR_UID | FUSE_SET_ATTR_GID |
|
|
FUSE_SET_ATTR_SIZE))
|
|
goto deny;
|
|
if (single_file.ro)
|
|
goto deny;
|
|
|
|
get_now(&now);
|
|
|
|
pthread_mutex_lock(&single_file.lock);
|
|
if (to_set & FUSE_SET_ATTR_MODE)
|
|
single_file.mode = (single_file.mode & S_IFMT) |
|
|
(attr->st_mode & ~S_IFMT);
|
|
if (to_set & FUSE_SET_ATTR_ATIME) {
|
|
if (to_set & FUSE_SET_ATTR_ATIME_NOW)
|
|
single_file.atime = now;
|
|
else
|
|
single_file.atime = attr->st_atim;
|
|
}
|
|
if (to_set & FUSE_SET_ATTR_MTIME) {
|
|
if (to_set & FUSE_SET_ATTR_MTIME_NOW)
|
|
single_file.mtime = now;
|
|
else
|
|
single_file.mtime = attr->st_mtim;
|
|
}
|
|
if (to_set & FUSE_SET_ATTR_CTIME)
|
|
single_file.ctime = now;
|
|
pthread_mutex_unlock(&single_file.lock);
|
|
|
|
single_file_ll_getattr(req, ino, fi);
|
|
return;
|
|
deny:
|
|
fuse_reply_err(req, EPERM);
|
|
}
|
|
|
|
int single_file_hl_chmod(const char *path, mode_t mode,
|
|
struct fuse_file_info *fi)
|
|
{
|
|
fuse_ino_t ino = single_open_file_path_to_ino(fi, path);
|
|
|
|
if (!ino)
|
|
return -ENOENT;
|
|
if (ino != SINGLE_FILE_INO)
|
|
return -EPERM;
|
|
if (single_file.ro)
|
|
return -EPERM;
|
|
|
|
pthread_mutex_lock(&single_file.lock);
|
|
single_file.mode = (single_file.mode & S_IFMT) | (mode & ~S_IFMT);
|
|
get_now(&single_file.ctime);
|
|
pthread_mutex_unlock(&single_file.lock);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void set_time(const struct timespec *ctv, struct timespec *tv)
|
|
{
|
|
switch (ctv->tv_nsec) {
|
|
case UTIME_OMIT:
|
|
return;
|
|
case UTIME_NOW:
|
|
get_now(tv);
|
|
break;
|
|
default:
|
|
memcpy(tv, ctv, sizeof(*tv));
|
|
break;
|
|
}
|
|
}
|
|
|
|
int single_file_hl_utimens(const char *path, const struct timespec ctv[2],
|
|
struct fuse_file_info *fi)
|
|
{
|
|
fuse_ino_t ino = single_open_file_path_to_ino(fi, path);
|
|
|
|
if (!ino)
|
|
return -ENOENT;
|
|
if (ino != SINGLE_FILE_INO)
|
|
return -EPERM;
|
|
if (single_file.ro)
|
|
return -EPERM;
|
|
|
|
pthread_mutex_lock(&single_file.lock);
|
|
set_time(&ctv[0], &single_file.atime);
|
|
set_time(&ctv[1], &single_file.mtime);
|
|
get_now(&single_file.ctime);
|
|
pthread_mutex_unlock(&single_file.lock);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int single_file_hl_chown(const char *path, uid_t owner, gid_t group,
|
|
struct fuse_file_info *fi)
|
|
{
|
|
(void)path;
|
|
(void)owner;
|
|
(void)group;
|
|
(void)fi;
|
|
|
|
return -EPERM;
|
|
}
|
|
|
|
int single_file_hl_truncate(const char *path, off_t len,
|
|
struct fuse_file_info *fi)
|
|
{
|
|
(void)path;
|
|
(void)len;
|
|
(void)fi;
|
|
|
|
return -EPERM;
|
|
}
|
|
|
|
void single_file_ll_lookup(fuse_req_t req, fuse_ino_t parent, const char *name)
|
|
{
|
|
struct single_file_stat llstat;
|
|
bool filled;
|
|
|
|
if (!is_single_file_child(parent, name)) {
|
|
fuse_reply_err(req, ENOENT);
|
|
return;
|
|
}
|
|
|
|
memset(&llstat, 0, sizeof(llstat));
|
|
pthread_mutex_lock(&single_file.lock);
|
|
filled = sf_stat(SINGLE_FILE_INO, &llstat);
|
|
pthread_mutex_unlock(&single_file.lock);
|
|
if (!filled)
|
|
fuse_reply_err(req, ENOENT);
|
|
else
|
|
fuse_reply_entry(req, &llstat.entry);
|
|
}
|
|
|
|
void single_file_ll_open(fuse_req_t req, fuse_ino_t ino,
|
|
struct fuse_file_info *fi)
|
|
{
|
|
if (ino != SINGLE_FILE_INO)
|
|
fuse_reply_err(req, EISDIR);
|
|
else if (single_file.ro && (fi->flags & O_ACCMODE) != O_RDONLY)
|
|
fuse_reply_err(req, EROFS);
|
|
else
|
|
fuse_reply_open(req, fi);
|
|
}
|
|
|
|
int single_file_hl_opendir(const char *path, struct fuse_file_info *fi)
|
|
{
|
|
fuse_ino_t ino = single_file_path_to_ino(path);
|
|
|
|
if (!ino)
|
|
return -ENOENT;
|
|
if (ino == SINGLE_FILE_INO)
|
|
return -ENOTDIR;
|
|
|
|
fi->fh = ino;
|
|
return 0;
|
|
}
|
|
|
|
int single_file_hl_open(const char *path, struct fuse_file_info *fi)
|
|
{
|
|
fuse_ino_t ino = single_file_path_to_ino(path);
|
|
|
|
if (!ino)
|
|
return -ENOENT;
|
|
if (ino != SINGLE_FILE_INO)
|
|
return -EISDIR;
|
|
if (single_file.ro && (fi->flags & O_ACCMODE) != O_RDONLY)
|
|
return -EROFS;
|
|
|
|
fi->fh = ino;
|
|
return 0;
|
|
}
|
|
|
|
void single_file_ll_fsync(fuse_req_t req, fuse_ino_t ino, int datasync,
|
|
struct fuse_file_info *fi)
|
|
{
|
|
int ret = 0;
|
|
|
|
(void)datasync;
|
|
(void)fi;
|
|
|
|
if (ino == SINGLE_FILE_INO) {
|
|
ret = fsync(single_file.backing_fd);
|
|
if (ret)
|
|
ret = errno;
|
|
}
|
|
|
|
fuse_reply_err(req, ret);
|
|
}
|
|
|
|
int single_file_hl_fsync(const char *path, int datasync,
|
|
struct fuse_file_info *fi)
|
|
{
|
|
fuse_ino_t ino = single_open_file_path_to_ino(fi, path);
|
|
|
|
(void)datasync;
|
|
|
|
if (!ino)
|
|
return -ENOENT;
|
|
|
|
if (ino == SINGLE_FILE_INO) {
|
|
int ret = fsync(single_file.backing_fd);
|
|
|
|
if (ret)
|
|
return -errno;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
unsigned long long parse_num_blocks(const char *arg, int log_block_size)
|
|
{
|
|
char *p;
|
|
unsigned long long num;
|
|
|
|
num = strtoull(arg, &p, 0);
|
|
|
|
if (p[0] && p[1])
|
|
return 0;
|
|
|
|
switch (*p) {
|
|
case 'T': case 't':
|
|
num <<= 10;
|
|
fallthrough;
|
|
case 'G': case 'g':
|
|
num <<= 10;
|
|
fallthrough;
|
|
case 'M': case 'm':
|
|
num <<= 10;
|
|
fallthrough;
|
|
case 'K': case 'k':
|
|
if (log_block_size < 0)
|
|
num <<= 10;
|
|
else
|
|
num >>= log_block_size;
|
|
break;
|
|
case 's':
|
|
if (log_block_size < 0)
|
|
num <<= 9;
|
|
else
|
|
num >>= (1+log_block_size);
|
|
break;
|
|
case '\0':
|
|
break;
|
|
default:
|
|
return 0;
|
|
}
|
|
return num;
|
|
}
|
|
|
|
static int single_file_set_blocksize(const char *arg)
|
|
{
|
|
unsigned long long l = parse_num_blocks(arg, -1);
|
|
|
|
if (l < 512 || l > INT32_MAX || (l & (l - 1)) != 0) {
|
|
fprintf(stderr, "%s: block size must be power of two between 512 and 2G.\n",
|
|
arg);
|
|
return -1;
|
|
}
|
|
|
|
/* do not pass through to libfuse */
|
|
single_file.blocksize = l;
|
|
return 0;
|
|
}
|
|
|
|
static int single_file_set_size(const char *arg)
|
|
{
|
|
unsigned long long l = parse_num_blocks(arg, -1);
|
|
|
|
if (l < 1 || (l & 511) != 0 || l > INT64_MAX) {
|
|
fprintf(stderr, "%s: size must be multiple of 512 and larger than zero.\n",
|
|
arg);
|
|
return -1;
|
|
}
|
|
|
|
/* do not pass through to libfuse */
|
|
single_file.isize = l;
|
|
return 0;
|
|
}
|
|
|
|
int single_file_opt_proc(void *data, const char *arg, int key,
|
|
struct fuse_args *outargs)
|
|
{
|
|
(void)data;
|
|
(void)outargs;
|
|
|
|
switch (key) {
|
|
case SINGLE_FILE_RO:
|
|
/* pass through to libfuse */
|
|
single_file.ro = true;
|
|
return 1;
|
|
case SINGLE_FILE_RW:
|
|
/* pass through to libfuse */
|
|
single_file.ro = false;
|
|
return 1;
|
|
case SINGLE_FILE_REQUIRE_BDEV:
|
|
single_file.require_bdev = true;
|
|
return 0;
|
|
case SINGLE_FILE_DIO:
|
|
single_file.allow_dio = true;
|
|
return 0;
|
|
case SINGLE_FILE_NODIO:
|
|
single_file.allow_dio = false;
|
|
return 0;
|
|
case SINGLE_FILE_SYNC:
|
|
single_file.sync = true;
|
|
return 0;
|
|
case SINGLE_FILE_NOSYNC:
|
|
single_file.sync = false;
|
|
return 0;
|
|
case SINGLE_FILE_BLOCKSIZE:
|
|
return single_file_set_blocksize(arg + 10);
|
|
case SINGLE_FILE_SIZE:
|
|
return single_file_set_size(arg + 5);
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
int single_file_service_open(const struct fuse_service *sf, const char *path)
|
|
{
|
|
int open_flags = single_file.ro ? O_RDONLY : O_RDWR;
|
|
int fd;
|
|
int ret;
|
|
|
|
again:
|
|
if (single_file.require_bdev)
|
|
ret = fuse_service_request_blockdev(sf, path,
|
|
open_flags | O_EXCL, 0, 0,
|
|
single_file.blocksize);
|
|
else
|
|
ret = fuse_service_request_file(sf, path, open_flags | O_EXCL,
|
|
0, 0);
|
|
if (ret)
|
|
return ret;
|
|
|
|
if (!single_file.ro && open_flags == O_RDONLY)
|
|
single_file.ro = true;
|
|
|
|
ret = fuse_service_receive_file(sf, path, &fd);
|
|
if (ret)
|
|
return ret;
|
|
|
|
/* downgrade from rw to ro if necessary */
|
|
if ((fd == -EPERM || fd == -EACCES || fd == -EROFS) &&
|
|
open_flags == O_RDWR) {
|
|
open_flags = O_RDONLY;
|
|
goto again;
|
|
}
|
|
|
|
if (fd < 0) {
|
|
fprintf(stderr, "%s: opening file: %s.\n",
|
|
path, strerror(-fd));
|
|
return -1;
|
|
}
|
|
|
|
single_file.backing_fd = fd;
|
|
return 0;
|
|
}
|
|
|
|
int single_file_check_write(off_t pos, size_t *count)
|
|
{
|
|
if (pos >= single_file.isize)
|
|
return -EFBIG;
|
|
|
|
if (*count > single_file.isize)
|
|
*count = single_file.isize;
|
|
if (pos >= single_file.isize - *count)
|
|
*count = single_file.isize - pos;
|
|
|
|
return 0;
|
|
}
|
|
|
|
void single_file_check_read(off_t pos, size_t *count)
|
|
{
|
|
int ret = single_file_check_write(pos, count);
|
|
|
|
if (ret)
|
|
*count = 0;
|
|
}
|
|
|
|
ssize_t single_file_pwrite(const char *buf, size_t count, off_t pos)
|
|
{
|
|
ssize_t processed = 0;
|
|
ssize_t got;
|
|
|
|
while ((got = pwrite(single_file.backing_fd, buf, count, pos)) > 0) {
|
|
processed += got;
|
|
pos += got;
|
|
buf += got;
|
|
count -= got;
|
|
}
|
|
|
|
if (processed > 0) {
|
|
struct timespec now;
|
|
|
|
if (single_file.sync) {
|
|
int ret = fsync(single_file.backing_fd);
|
|
|
|
if (ret < 0)
|
|
return -errno;
|
|
}
|
|
|
|
get_now(&now);
|
|
|
|
pthread_mutex_lock(&single_file.lock);
|
|
single_file.mtime = now;
|
|
single_file.ctime = now;
|
|
pthread_mutex_unlock(&single_file.lock);
|
|
|
|
return processed;
|
|
}
|
|
|
|
if (got < 0)
|
|
return -errno;
|
|
return 0;
|
|
}
|
|
|
|
ssize_t single_file_pread(char *buf, size_t count, off_t pos)
|
|
{
|
|
ssize_t processed = 0;
|
|
ssize_t got;
|
|
|
|
while ((got = pread(single_file.backing_fd, buf, count, pos)) > 0) {
|
|
processed += got;
|
|
pos += got;
|
|
buf += got;
|
|
count -= got;
|
|
}
|
|
|
|
if (processed)
|
|
return processed;
|
|
if (got < 0)
|
|
return -errno;
|
|
return 0;
|
|
}
|
|
|
|
int single_file_configure(const char *device, const char *filename)
|
|
{
|
|
struct stat stbuf;
|
|
unsigned long long backing_size;
|
|
unsigned int proposed_blocksize;
|
|
int lbasize;
|
|
int ret;
|
|
|
|
ret = fstat(single_file.backing_fd, &stbuf);
|
|
if (ret) {
|
|
perror(device);
|
|
return -1;
|
|
}
|
|
lbasize = stbuf.st_blksize;
|
|
backing_size = stbuf.st_size;
|
|
|
|
if (S_ISBLK(stbuf.st_mode)) {
|
|
#ifdef BLKSSZGET
|
|
ret = ioctl(single_file.backing_fd, BLKSSZGET, &lbasize);
|
|
if (ret) {
|
|
perror(device);
|
|
return -1;
|
|
}
|
|
#endif
|
|
|
|
#ifdef BLKGETSIZE64
|
|
ret = ioctl(single_file.backing_fd, BLKGETSIZE64, &backing_size);
|
|
if (ret) {
|
|
perror(device);
|
|
return -1;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
if (backing_size == 0) {
|
|
fprintf(stderr, "%s: backing file size zero?\n", device);
|
|
return -1;
|
|
}
|
|
|
|
if (lbasize == 0) {
|
|
fprintf(stderr, "%s: blocksize zero?\n", device);
|
|
return -1;
|
|
}
|
|
|
|
proposed_blocksize = single_file.blocksize ? single_file.blocksize :
|
|
sysconf(_SC_PAGESIZE);
|
|
if (lbasize > proposed_blocksize) {
|
|
fprintf(stderr, "%s: lba size %d smaller than blocksize %u\n",
|
|
device, lbasize, proposed_blocksize);
|
|
return -1;
|
|
}
|
|
|
|
if (single_file.isize % proposed_blocksize > 0) {
|
|
fprintf(stderr, "%s: size parameter %llu not congruent with blocksize %u\n",
|
|
device, (unsigned long long)single_file.isize,
|
|
proposed_blocksize);
|
|
return -1;
|
|
}
|
|
|
|
if (single_file.isize > backing_size) {
|
|
fprintf(stderr, "%s: file size %llu smaller than size param %llu\n",
|
|
device, backing_size,
|
|
(unsigned long long)single_file.isize);
|
|
return -1;
|
|
}
|
|
|
|
if (!single_file.blocksize)
|
|
single_file.blocksize = proposed_blocksize;
|
|
if (!single_file.isize)
|
|
single_file.isize = backing_size;
|
|
|
|
single_file.isize = round_down(single_file.isize, single_file.blocksize);
|
|
single_file.blocks = single_file.isize / single_file.blocksize;
|
|
|
|
return single_file_configure_simple(filename);
|
|
}
|
|
|
|
int single_file_configure_simple(const char *filename)
|
|
{
|
|
if (!single_file.blocksize)
|
|
single_file.blocksize = sysconf(_SC_PAGESIZE);
|
|
|
|
if (filename) {
|
|
char *n = strdup(filename);
|
|
|
|
if (!n) {
|
|
perror(filename);
|
|
return -1;
|
|
}
|
|
|
|
if (single_file_name_set)
|
|
free((void *)single_file_name);
|
|
single_file_name = n;
|
|
single_file_name_set = true;
|
|
}
|
|
|
|
get_now(&startup_time);
|
|
single_file.atime = startup_time;
|
|
single_file.mtime = startup_time;
|
|
single_file.ctime = startup_time;
|
|
|
|
if (!single_file.ro)
|
|
single_file.mode |= 0220;
|
|
|
|
return 0;
|
|
}
|
|
|
|
void single_file_close(void)
|
|
{
|
|
close(single_file.backing_fd);
|
|
single_file.backing_fd = -1;
|
|
|
|
if (single_file_name_set)
|
|
free((void *)single_file_name);
|
|
single_file_name_set = false;
|
|
}
|