diff options
-rw-r--r-- | ChangeLog.rst | 2 | ||||
-rw-r--r-- | example/memfs_ll.cc | 3 | ||||
-rw-r--r-- | example/passthrough.c | 21 | ||||
-rw-r--r-- | example/passthrough_fh.c | 21 | ||||
-rw-r--r-- | example/passthrough_ll.c | 25 | ||||
-rw-r--r-- | include/fuse.h | 18 | ||||
-rw-r--r-- | include/fuse_lowlevel.h | 35 | ||||
-rw-r--r-- | lib/fuse.c | 108 | ||||
-rw-r--r-- | lib/fuse_lowlevel.c | 66 | ||||
-rw-r--r-- | lib/fuse_versionscript | 3 | ||||
-rw-r--r-- | lib/modules/iconv.c | 19 | ||||
-rw-r--r-- | lib/modules/subdir.c | 19 | ||||
-rw-r--r-- | meson.build | 4 | ||||
-rw-r--r-- | test/test_syscalls.c | 49 |
14 files changed, 391 insertions, 2 deletions
diff --git a/ChangeLog.rst b/ChangeLog.rst index 658f898..505d9db 100644 --- a/ChangeLog.rst +++ b/ChangeLog.rst @@ -1,3 +1,5 @@ +libfuse 3.18 + libfuse 3.17.1-rc0 (2024-02.10) =============================== diff --git a/example/memfs_ll.cc b/example/memfs_ll.cc index b4f0b63..0da7c25 100644 --- a/example/memfs_ll.cc +++ b/example/memfs_ll.cc @@ -1094,6 +1094,9 @@ static const struct fuse_lowlevel_ops memfs_oper = { .copy_file_range = nullptr, .lseek = nullptr, .tmpfile = nullptr, +#ifdef HAVE_STATX + .statx = nullptr, +#endif }; int main(int argc, char *argv[]) diff --git a/example/passthrough.c b/example/passthrough.c index f0e686d..fdaa19e 100644 --- a/example/passthrough.c +++ b/example/passthrough.c @@ -519,6 +519,24 @@ static off_t xmp_lseek(const char *path, off_t off, int whence, struct fuse_file return res; } +#ifdef HAVE_STATX +static int xmp_statx(const char *path, int flags, int mask, struct statx *stxbuf, + struct fuse_file_info *fi) +{ + int fd = -1; + int res; + + if (fi) + fd = fi->fh; + + res = statx(fd, path, flags | AT_SYMLINK_NOFOLLOW, mask, stxbuf); + if (res == -1) + return -errno; + + return 0; +} +#endif + static const struct fuse_operations xmp_oper = { .init = xmp_init, .getattr = xmp_getattr, @@ -556,6 +574,9 @@ static const struct fuse_operations xmp_oper = { .copy_file_range = xmp_copy_file_range, #endif .lseek = xmp_lseek, +#ifdef HAVE_STATX + .statx = xmp_statx, +#endif }; int main(int argc, char *argv[]) diff --git a/example/passthrough_fh.c b/example/passthrough_fh.c index 6764554..0d4fb5b 100644 --- a/example/passthrough_fh.c +++ b/example/passthrough_fh.c @@ -616,6 +616,24 @@ static off_t xmp_lseek(const char *path, off_t off, int whence, struct fuse_file return res; } +#ifdef HAVE_STATX +static int xmp_statx(const char *path, int flags, int mask, struct statx *stxbuf, + struct fuse_file_info *fi) +{ + int fd = -1; + int res; + + if (fi) + fd = fi->fh; + + res = statx(fd, path, flags | AT_SYMLINK_NOFOLLOW, mask, stxbuf); + if (res == -1) + return -errno; + + return 0; +} +#endif + static const struct fuse_operations xmp_oper = { .init = xmp_init, .getattr = xmp_getattr, @@ -662,6 +680,9 @@ static const struct fuse_operations xmp_oper = { .copy_file_range = xmp_copy_file_range, #endif .lseek = xmp_lseek, +#ifdef HAVE_STATX + .statx = xmp_statx, +#endif }; int main(int argc, char *argv[]) diff --git a/example/passthrough_ll.c b/example/passthrough_ll.c index 9e027b4..5ca6efa 100644 --- a/example/passthrough_ll.c +++ b/example/passthrough_ll.c @@ -1215,6 +1215,28 @@ static void lo_lseek(fuse_req_t req, fuse_ino_t ino, off_t off, int whence, fuse_reply_err(req, errno); } +#ifdef HAVE_STATX +static void lo_statx(fuse_req_t req, fuse_ino_t ino, int flags, int mask, + struct fuse_file_info *fi) +{ + struct lo_data *lo = lo_data(req); + struct statx buf; + int res; + int fd; + + if (fi) + fd = fi->fh; + else + fd = lo_fd(req, ino); + + res = statx(fd, "", flags | AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW, mask, &buf); + if (res == -1) + fuse_reply_err(req, errno); + else + fuse_reply_statx(req, 0, &buf, lo->timeout); +} +#endif + static const struct fuse_lowlevel_ops lo_oper = { .init = lo_init, .destroy = lo_destroy, @@ -1255,6 +1277,9 @@ static const struct fuse_lowlevel_ops lo_oper = { .copy_file_range = lo_copy_file_range, #endif .lseek = lo_lseek, +#ifdef HAVE_STATX + .statx = lo_statx, +#endif }; int main(int argc, char *argv[]) diff --git a/include/fuse.h b/include/fuse.h index b990043..06feacb 100644 --- a/include/fuse.h +++ b/include/fuse.h @@ -33,6 +33,9 @@ extern "C" { * Basic FUSE API * * ----------------------------------------------------------- */ +/* Forward declaration */ +struct statx; + /** Handle for a FUSE filesystem */ struct fuse; @@ -850,6 +853,19 @@ struct fuse_operations { * Find next data or hole after the specified offset */ off_t (*lseek) (const char *, off_t off, int whence, struct fuse_file_info *); + +#ifdef HAVE_STATX + /** + * Get extended file attributes. + * + * fi may be NULL. + * + * If path is NULL, then the AT_EMPTY_PATH bit in flags will be + * already set. + */ + int (*statx)(const char *path, int flags, int mask, struct statx *stxbuf, + struct fuse_file_info *fi); +#endif }; /** Extra context that may be needed by some filesystems @@ -1344,6 +1360,8 @@ ssize_t fuse_fs_copy_file_range(struct fuse_fs *fs, const char *path_in, size_t len, int flags); off_t fuse_fs_lseek(struct fuse_fs *fs, const char *path, off_t off, int whence, struct fuse_file_info *fi); +int fuse_fs_statx(struct fuse_fs *fs, const char *path, int flags, int mask, + struct statx *stxbuf, struct fuse_file_info *fi); void fuse_fs_init(struct fuse_fs *fs, struct fuse_conn_info *conn, struct fuse_config *cfg); void fuse_fs_destroy(struct fuse_fs *fs); diff --git a/include/fuse_lowlevel.h b/include/fuse_lowlevel.h index 75e084d..844ee71 100644 --- a/include/fuse_lowlevel.h +++ b/include/fuse_lowlevel.h @@ -49,6 +49,9 @@ typedef uint64_t fuse_ino_t; /** Request pointer type */ typedef struct fuse_req *fuse_req_t; +/* Forward declaration */ +struct statx; + /** * Session * @@ -1303,7 +1306,6 @@ struct fuse_lowlevel_ops { void (*lseek) (fuse_req_t req, fuse_ino_t ino, off_t off, int whence, struct fuse_file_info *fi); - /** * Create a tempfile * @@ -1325,6 +1327,23 @@ struct fuse_lowlevel_ops { void (*tmpfile) (fuse_req_t req, fuse_ino_t parent, mode_t mode, struct fuse_file_info *fi); +#ifdef HAVE_STATX + /** + * Get extended file attributes. + * + * Valid replies: + * fuse_reply_statx + * fuse_reply_err + * + * @param req request handle + * @param ino the inode number + * @param flags bitmask of requested flags + * @param mask bitmask of requested fields + * @param fi file information (may be NULL) + */ + void (*statx)(fuse_req_t req, fuse_ino_t ino, int flags, int mask, + struct fuse_file_info *fi); +#endif }; /** @@ -1705,6 +1724,20 @@ int fuse_reply_poll(fuse_req_t req, unsigned revents); */ int fuse_reply_lseek(fuse_req_t req, off_t off); +/** + * Reply with extended file attributes. + * + * Possible requests: + * statx + * + * @param req request handle + * @param flags statx flags + * @param statx the attributes + * @param attr_timeout validity timeout (in seconds) for the attributes + * @return zero for success, -errno for failure to send reply + */ +int fuse_reply_statx(fuse_req_t req, int flags, struct statx *statx, double attr_timeout); + /* ----------------------------------------------------------- * * Notification * * ----------------------------------------------------------- */ @@ -1507,6 +1507,29 @@ static void set_stat(struct fuse *f, fuse_ino_t nodeid, struct stat *stbuf) stbuf->st_gid = f->conf.gid; } +#ifdef HAVE_STATX +static void set_statx(struct fuse *f, fuse_ino_t nodeid, struct statx *stxbuf) +{ + if (!f->conf.use_ino) + stxbuf->stx_ino = nodeid; + if (f->conf.set_mode) { + if (f->conf.dmask && S_ISDIR(stxbuf->stx_mode)) + stxbuf->stx_mode = (stxbuf->stx_mode & S_IFMT) | + (0777 & ~f->conf.dmask); + else if (f->conf.fmask) + stxbuf->stx_mode = (stxbuf->stx_mode & S_IFMT) | + (0777 & ~f->conf.fmask); + else + stxbuf->stx_mode = (stxbuf->stx_mode & S_IFMT) | + (0777 & ~f->conf.umask); + } + if (f->conf.set_uid) + stxbuf->stx_uid = f->conf.uid; + if (f->conf.set_gid) + stxbuf->stx_gid = f->conf.gid; +} +#endif + static struct fuse *req_fuse(fuse_req_t req) { return (struct fuse *) fuse_req_userdata(req); @@ -2312,6 +2335,39 @@ off_t fuse_fs_lseek(struct fuse_fs *fs, const char *path, off_t off, int whence, return fs->op.lseek(path, off, whence, fi); } +#ifdef HAVE_STATX +int fuse_fs_statx(struct fuse_fs *fs, const char *path, int flags, int mask, + struct statx *stxbuf, struct fuse_file_info *fi) +{ + fuse_get_context()->private_data = fs->user_data; + if (fs->op.statx) { + if (fs->debug) { + char buf[10]; + + fuse_log(FUSE_LOG_DEBUG, "statx[%s] %s %d %d\n", + file_info_string(fi, buf, sizeof(buf)), path, + flags, mask); + } + return fs->op.statx(path, flags, mask, stxbuf, fi); + } + + return -ENOSYS; +} +#else +int fuse_fs_statx(struct fuse_fs *fs, const char *path, int flags, int mask, + struct statx *stxbuf, struct fuse_file_info *fi) +{ + (void)fs; + (void)path; + (void)flags; + (void)mask; + (void)stxbuf; + (void)fi; + + return -ENOSYS; +} +#endif + static int is_open(struct fuse *f, fuse_ino_t dir, const char *name) { struct node *node; @@ -4361,6 +4417,55 @@ static void fuse_lib_lseek(fuse_req_t req, fuse_ino_t ino, off_t off, int whence reply_err(req, res); } +#ifdef HAVE_STATX +static void fuse_lib_statx(fuse_req_t req, fuse_ino_t ino, int flags, int mask, + struct fuse_file_info *fi) +{ + struct fuse *f = req_fuse_prepare(req); + struct statx stxbuf; + char *path; + int err; + + memset(&stxbuf, 0, sizeof(stxbuf)); + + if (fi != NULL) + err = get_path_nullok(f, ino, &path); + else + err = get_path(f, ino, &path); + + if (!err) { + struct fuse_intr_data d; + + if (!path) + flags |= AT_EMPTY_PATH; + fuse_prepare_interrupt(f, req, &d); + err = fuse_fs_statx(f->fs, path, flags, mask, &stxbuf, fi); + fuse_finish_interrupt(f, req, &d); + free_path(f, ino, path); + } + if (!err) { + struct node *node; + + pthread_mutex_lock(&f->lock); + node = get_node(f, ino); + if (node->is_hidden && stxbuf.stx_nlink > 0) + stxbuf.stx_nlink--; + if (f->conf.auto_cache) { + struct stat stbuf; + + stbuf.st_mtime = stxbuf.stx_mtime.tv_nsec; + ST_MTIM_NSEC(&stbuf) = stxbuf.stx_mtime.tv_nsec; + stbuf.st_size = stxbuf.stx_size; + update_stat(node, &stbuf); + } + pthread_mutex_unlock(&f->lock); + set_statx(f, ino, &stxbuf); + fuse_reply_statx(req, 0, &stxbuf, f->conf.attr_timeout); + } else + reply_err(req, err); +} +#endif + static int clean_delay(struct fuse *f) { /* @@ -4459,6 +4564,9 @@ static struct fuse_lowlevel_ops fuse_path_ops = { .fallocate = fuse_lib_fallocate, .copy_file_range = fuse_lib_copy_file_range, .lseek = fuse_lib_lseek, +#ifdef HAVE_STATX + .statx = fuse_lib_statx, +#endif }; int fuse_notify_poll(struct fuse_pollhandle *ph) diff --git a/lib/fuse_lowlevel.c b/lib/fuse_lowlevel.c index 91f4244..1ae3473 100644 --- a/lib/fuse_lowlevel.c +++ b/lib/fuse_lowlevel.c @@ -1216,6 +1216,33 @@ int fuse_reply_lseek(fuse_req_t req, off_t off) return send_reply_ok(req, &arg, sizeof(arg)); } +#ifdef HAVE_STATX +int fuse_reply_statx(fuse_req_t req, int flags, struct statx *statx, + double attr_timeout) +{ + struct fuse_statx_out arg; + + memset(&arg, 0, sizeof(arg)); + arg.flags = flags; + arg.attr_valid = calc_timeout_sec(attr_timeout); + arg.attr_valid_nsec = calc_timeout_nsec(attr_timeout); + memcpy(&arg.stat, statx, sizeof(arg.stat)); + + return send_reply_ok(req, &arg, sizeof(arg)); +} +#else +int fuse_reply_statx(fuse_req_t req, int flags, struct statx *statx, + double attr_timeout) +{ + (void)req; + (void)flags; + (void)statx; + (void)attr_timeout; + + return -ENOSYS; +} +#endif + static void _do_lookup(fuse_req_t req, const fuse_ino_t nodeid, const void *op_in, const void *in_payload) { @@ -2428,6 +2455,42 @@ static void do_lseek(fuse_req_t req, const fuse_ino_t nodeid, const void *inarg) _do_lseek(req, nodeid, inarg, NULL); } +#ifdef HAVE_STATX +static void _do_statx(fuse_req_t req, const fuse_ino_t nodeid, + const void *op_in, const void *in_payload) +{ + (void)in_payload; + const struct fuse_statx_in *arg = op_in; + struct fuse_file_info *fip = NULL; + struct fuse_file_info fi; + + if (arg->getattr_flags & FUSE_GETATTR_FH) { + memset(&fi, 0, sizeof(fi)); + fi.fh = arg->fh; + fip = &fi; + } + + if (req->se->op.statx) + req->se->op.statx(req, nodeid, arg->sx_flags, arg->sx_mask, fip); + else + fuse_reply_err(req, ENOSYS); +} +#else +static void _do_statx(fuse_req_t req, const fuse_ino_t nodeid, + const void *op_in, const void *in_payload) +{ + (void)in_payload; + (void)req; + (void)nodeid; + (void)op_in; +} +#endif + +static void do_statx(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) +{ + _do_statx(req, nodeid, inarg, NULL); +} + static bool want_flags_valid(uint64_t capable, uint64_t want) { uint64_t unknown_flags = want & (~capable); @@ -2510,7 +2573,6 @@ bool fuse_get_feature_flag(struct fuse_conn_info *conn, return conn->capable_ext & flag ? true : false; } - /* Prevent bogus data races (bogus since "init" is called before * multi-threading becomes relevant */ static __attribute__((no_sanitize("thread"))) void @@ -3297,6 +3359,7 @@ static struct { [FUSE_RENAME2] = { do_rename2, "RENAME2" }, [FUSE_COPY_FILE_RANGE] = { do_copy_file_range, "COPY_FILE_RANGE" }, [FUSE_LSEEK] = { do_lseek, "LSEEK" }, + [FUSE_STATX] = { do_statx, "STATX" }, [CUSE_INIT] = { cuse_lowlevel_init, "CUSE_INIT" }, }; @@ -3351,6 +3414,7 @@ static struct { [FUSE_RENAME2] = { _do_rename2, "RENAME2" }, [FUSE_COPY_FILE_RANGE] = { _do_copy_file_range, "COPY_FILE_RANGE" }, [FUSE_LSEEK] = { _do_lseek, "LSEEK" }, + [FUSE_STATX] = { _do_statx, "STATX" }, [CUSE_INIT] = { _cuse_lowlevel_init, "CUSE_INIT" }, }; diff --git a/lib/fuse_versionscript b/lib/fuse_versionscript index 2d8884d..0e581f1 100644 --- a/lib/fuse_versionscript +++ b/lib/fuse_versionscript @@ -212,6 +212,9 @@ FUSE_3.18 { # Not part of public API, for internal test use only fuse_convert_to_conn_want_ext; + + fuse_reply_statx; + fuse_fs_statx; } FUSE_3.17; # Local Variables: diff --git a/lib/modules/iconv.c b/lib/modules/iconv.c index 599b8df..417c904 100644 --- a/lib/modules/iconv.c +++ b/lib/modules/iconv.c @@ -568,6 +568,22 @@ static off_t iconv_lseek(const char *path, off_t off, int whence, return res; } +#ifdef HAVE_STATX +static int iconv_statx(const char *path, int flags, int mask, struct statx *stxbuf, + struct fuse_file_info *fi) +{ + struct iconv *ic = iconv_get(); + char *newpath; + int res = iconv_convpath(ic, path, &newpath, 0); + + if (!res) { + res = fuse_fs_statx(ic->next, newpath, flags, mask, stxbuf, fi); + free(newpath); + } + return res; +} +#endif + static void *iconv_init(struct fuse_conn_info *conn, struct fuse_config *cfg) { @@ -627,6 +643,9 @@ static const struct fuse_operations iconv_oper = { .flock = iconv_flock, .bmap = iconv_bmap, .lseek = iconv_lseek, +#ifdef HAVE_STATX + .statx = iconv_statx, +#endif }; static const struct fuse_opt iconv_opts[] = { diff --git a/lib/modules/subdir.c b/lib/modules/subdir.c index dd2d49d..67c4697 100644 --- a/lib/modules/subdir.c +++ b/lib/modules/subdir.c @@ -553,6 +553,22 @@ static off_t subdir_lseek(const char *path, off_t off, int whence, return res; } +#ifdef HAVE_STATX +static int subdir_statx(const char *path, int flags, int mask, struct statx *stxbuf, + struct fuse_file_info *fi) +{ + struct subdir *ic = subdir_get(); + char *newpath; + int res = subdir_addpath(ic, path, &newpath); + + if (!res) { + res = fuse_fs_statx(ic->next, newpath, flags, mask, stxbuf, fi); + free(newpath); + } + return res; +} +#endif + static void *subdir_init(struct fuse_conn_info *conn, struct fuse_config *cfg) { @@ -608,6 +624,9 @@ static const struct fuse_operations subdir_oper = { .flock = subdir_flock, .bmap = subdir_bmap, .lseek = subdir_lseek, +#ifdef HAVE_STATX + .statx = subdir_statx, +#endif }; static const struct fuse_opt subdir_opts[] = { diff --git a/meson.build b/meson.build index 7091d5f..64ff334 100644 --- a/meson.build +++ b/meson.build @@ -126,6 +126,10 @@ private_cfg.set('HAVE_ICONV', cc.has_function('iconv', prefix: '#include <iconv.h>')) private_cfg.set('HAVE_BACKTRACE', cc.has_function('backtrace', prefix: '#include <execinfo.h>')) +public_cfg.set('HAVE_STATX', + cc.has_function('statx', prefix : '#define _GNU_SOURCE\n#include <sys/stat.h>')) +private_cfg.set('HAVE_STATX', + cc.has_function('statx', prefix : '#define _GNU_SOURCE\n#include <sys/stat.h>')) # Struct member checks private_cfg.set('HAVE_STRUCT_STAT_ST_ATIM', diff --git a/test/test_syscalls.c b/test/test_syscalls.c index 4bbe973..61ee953 100644 --- a/test/test_syscalls.c +++ b/test/test_syscalls.c @@ -11,6 +11,7 @@ #include <utime.h> #include <errno.h> #include <assert.h> +#include <time.h> #include <sys/socket.h> #include <sys/types.h> #include <sys/stat.h> @@ -921,6 +922,53 @@ static int test_copy_file_range(void) } #endif +#ifdef HAVE_STATX +static int test_statx(void) +{ + struct statx sb; + char msg[] = "hi"; + size_t msg_size = sizeof(msg); + struct timespec tp; + int res; + + memset(&sb, 0, sizeof(sb)); + unlink(testfile); + + start_test("statx"); + + res = create_testfile(testfile, msg, msg_size); + if (res == -1) + return -1; + + res = statx(-1, testfile, AT_EMPTY_PATH, + STATX_BASIC_STATS | STATX_BTIME, &sb); + if (res == -1) + return -1; + + if (sb.stx_size != msg_size) + return -1; + + clock_gettime(CLOCK_REALTIME, &tp); + + if (sb.stx_btime.tv_sec > tp.tv_sec) + return -1; + + if (sb.stx_btime.tv_sec == tp.tv_sec && + sb.stx_btime.tv_nsec >= tp.tv_nsec) + return -1; + + unlink(testfile); + + success(); + return 0; +} +#else +static int test_statx(void) +{ + return 0; +} +#endif + static int test_utime(void) { struct utimbuf utm; @@ -2179,6 +2227,7 @@ int main(int argc, char *argv[]) err += test_create_ro_dir(O_CREAT | O_WRONLY); err += test_create_ro_dir(O_CREAT | O_TRUNC); err += test_copy_file_range(); + err += test_statx(); #ifndef __FreeBSD__ err += test_create_tmpfile(); err += test_create_and_link_tmpfile(); |