Browse Source

Merge pull request #1439 from trapexit/statx

Add statx support
pull/1441/head
trapexit 2 weeks ago
committed by GitHub
parent
commit
1f3ebcf418
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 10
      libfuse/include/fuse.h
  2. 6
      libfuse/include/fuse_lowlevel.h
  3. 73
      libfuse/lib/fuse.cpp
  4. 16
      libfuse/lib/fuse_lowlevel.cpp
  5. 26
      src/fs_inode.cpp
  6. 11
      src/fs_inode.hpp
  7. 51
      src/fs_statx.hpp
  8. 186
      src/fuse_statx.cpp
  9. 38
      src/fuse_statx.hpp
  10. 3
      src/mergerfs.cpp
  11. 29
      src/symlinkify.hpp

10
libfuse/include/fuse.h

@ -549,6 +549,16 @@ struct fuse_operations
int (*removemapping)(); int (*removemapping)();
int (*syncfs)(); int (*syncfs)();
int (*tmpfile)(const char *, mode_t, fuse_file_info_t *); int (*tmpfile)(const char *, mode_t, fuse_file_info_t *);
int (*statx)(const char *fusepath,
const uint32_t flags,
const uint32_t mask,
struct fuse_statx *st,
fuse_timeouts_t *timeout);
int (*statx_fh)(const uint64_t fh,
const uint32_t flags,
const uint32_t mask,
struct fuse_statx *st,
fuse_timeouts_t *timeout);
}; };
/** Extra context that may be needed by some filesystems /** Extra context that may be needed by some filesystems

6
libfuse/include/fuse_lowlevel.h

@ -257,6 +257,12 @@ int fuse_reply_attr(fuse_req_t req,
const struct stat *attr, const struct stat *attr,
const uint64_t timeout); const uint64_t timeout);
int fuse_reply_statx(fuse_req_t req,
int flags,
struct fuse_statx *st,
const uint64_t timeout);
/** /**
* Reply with the contents of a symbolic link * Reply with the contents of a symbolic link
* *

73
libfuse/lib/fuse.cpp

@ -1718,6 +1718,78 @@ fuse_lib_getattr(fuse_req_t req,
} }
} }
static
void
fuse_lib_statx_path(fuse_req_t req_,
struct fuse_in_header *hdr_,
struct fuse *f_,
fuse_statx_in *inarg_)
{
int err;
char *fusepath;
struct fuse_statx st{};
fuse_timeouts_t timeouts{};
err = get_path(f_,hdr_->nodeid,&fusepath);
if(err)
{
fuse_reply_err(req_,err);
return;
}
err = f_->fs->op.statx(fusepath,
inarg_->sx_flags,
inarg_->sx_mask,
&st,
&timeouts);
free_path(f_,hdr_->nodeid,fusepath);
if(err)
fuse_reply_err(req_,err);
else
fuse_reply_statx(req_,0,&st,timeouts.attr);
}
static
void
fuse_lib_statx_fh(fuse_req_t req_,
struct fuse_in_header *hdr_,
struct fuse *f_,
fuse_statx_in *inarg_)
{
int err;
struct fuse_statx st{};
fuse_timeouts_t timeouts{};
err = f_->fs->op.statx_fh(inarg_->fh,
inarg_->sx_flags,
inarg_->sx_mask,
&st,
&timeouts);
if(err)
fuse_reply_err(req_,err);
else
fuse_reply_statx(req_,0,&st,timeouts.attr);
}
static
void
fuse_lib_statx(fuse_req_t req_,
struct fuse_in_header *hdr_)
{
struct fuse *f = req_fuse_prepare(req_);
fuse_statx_in *inarg;
inarg = (fuse_statx_in*)fuse_hdr_arg(hdr_);
if(inarg->getattr_flags & FUSE_GETATTR_FH)
fuse_lib_statx_fh(req_,hdr_,f,inarg);
else
fuse_lib_statx_path(req_,hdr_,f,inarg);
}
static static
void void
fuse_lib_setattr(fuse_req_t req, fuse_lib_setattr(fuse_req_t req,
@ -3643,6 +3715,7 @@ static struct fuse_lowlevel_ops fuse_path_ops =
.setupmapping = fuse_lib_setupmapping, .setupmapping = fuse_lib_setupmapping,
.setxattr = fuse_lib_setxattr, .setxattr = fuse_lib_setxattr,
.statfs = fuse_lib_statfs, .statfs = fuse_lib_statfs,
.statx = fuse_lib_statx,
.symlink = fuse_lib_symlink, .symlink = fuse_lib_symlink,
.syncfs = fuse_lib_syncfs, .syncfs = fuse_lib_syncfs,
.tmpfile = fuse_lib_tmpfile, .tmpfile = fuse_lib_tmpfile,

16
libfuse/lib/fuse_lowlevel.cpp

@ -343,6 +343,22 @@ fuse_reply_attr(fuse_req_t req,
return send_reply_ok(req,&arg,size); return send_reply_ok(req,&arg,size);
} }
int
fuse_reply_statx(fuse_req_t req_,
int flags_,
struct fuse_statx *st_,
uint64_t timeout_)
{
struct fuse_statx_out outarg{};
outarg.flags = flags_;
outarg.attr_valid = timeout_;
outarg.attr_valid_nsec = 0;
outarg.stat = *(struct fuse_statx*)st_;
return send_reply_ok(req_,&outarg,sizeof(outarg));
}
int int
fuse_reply_readlink(fuse_req_t req, fuse_reply_readlink(fuse_req_t req,
const char *linkname) const char *linkname)

26
src/fs_inode.cpp

@ -16,9 +16,10 @@
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/ */
#include "fs_inode.hpp"
#include "ef.hpp" #include "ef.hpp"
#include "errno.hpp" #include "errno.hpp"
#include "fs_inode.hpp"
#include "rapidhash.h" #include "rapidhash.h"
#include <cstdint> #include <cstdint>
@ -39,7 +40,9 @@ static
uint32_t uint32_t
h64_to_h32(uint64_t h_) h64_to_h32(uint64_t h_)
{ {
return (h_ - (h_ >> 32));
h_ ^= (h_ >> 32);
h_ *= 0x9E3779B9;
return h_;
} }
static static
@ -227,6 +230,18 @@ namespace fs
st_->st_ino); st_->st_ino);
} }
void
calc(const char *fusepath_,
const uint64_t fusepath_len_,
struct fuse_statx *st_)
{
st_->ino = calc(fusepath_,
fusepath_len_,
st_->mode,
st_->dev_major ^ st_->dev_minor,
st_->ino);
}
void void
calc(const char *fusepath_, calc(const char *fusepath_,
struct stat *st_) struct stat *st_)
@ -234,6 +249,13 @@ namespace fs
calc(fusepath_,strlen(fusepath_),st_); calc(fusepath_,strlen(fusepath_),st_);
} }
void
calc(const char *fusepath_,
struct fuse_statx *st_)
{
calc(fusepath_,strlen(fusepath_),st_);
}
void void
calc(const std::string &fusepath_, calc(const std::string &fusepath_,
struct stat *st_) struct stat *st_)

11
src/fs_inode.hpp

@ -18,6 +18,8 @@
#pragma once #pragma once
#include "fuse_kernel.h"
#include <cstdint> #include <cstdint>
#include <string> #include <string>
@ -42,13 +44,20 @@ namespace fs
mode_t const mode, mode_t const mode,
dev_t const dev, dev_t const dev,
ino_t ino); ino_t ino);
void calc(const char *fusepath, void calc(const char *fusepath,
const uint64_t fusepath_len, const uint64_t fusepath_len,
struct stat *st); struct stat *st);
void calc(const char *fusepath,
const uint64_t fusepath_len,
struct fuse_statx *st);
void calc(const char *fusepath, void calc(const char *fusepath,
struct stat *st); struct stat *st);
void calc(const char *fusepath,
struct fuse_statx *st);
void calc(const std::string &fusepath, void calc(const std::string &fusepath,
struct stat *st); struct stat *st);
} }
} }

51
src/fs_statx.hpp

@ -0,0 +1,51 @@
#pragma once
#include "fuse_kernel.h"
#include <string>
#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif
#include <fcntl.h>
#include <sys/stat.h>
namespace fs
{
static
inline
int
statx(const int dirfd_,
const char *pathname_,
const int flags_,
const unsigned int mask_,
struct fuse_statx *st_)
{
#ifdef STATX_TYPE
int rv;
rv = ::statx(dirfd_,
pathname_,
flags_,
mask_,
(struct statx*)st_);
return ((rv == -1) ? -errno : 0);
#else
return -ENOSYS;
#endif
}
static
inline
int
statx(const int dirfd_,
const std::string &pathname_,
const int flags_,
const unsigned int mask_,
struct fuse_statx *st_)
{
return fs::statx(dirfd_,pathname_.c_str(),flags_,mask_,st_);
}
}

186
src/fuse_statx.cpp

@ -0,0 +1,186 @@
#include "fuse_statx.hpp"
#include "config.hpp"
#include "errno.hpp"
#include "fileinfo.hpp"
#include "fs_inode.hpp"
#include "fs_path.hpp"
#include "fs_statx.hpp"
#include "symlinkify.hpp"
#include "ugid.hpp"
#include "fmt/core.h"
#include "fuse.h"
#include <string>
static
void
_set_stat_if_leads_to_dir(const std::string &path_,
struct fuse_statx *st_)
{
int rv;
struct fuse_statx st;
rv = fs::statx(AT_FDCWD,path_,AT_SYMLINK_FOLLOW,STATX_TYPE,st_);
if(rv < 0)
return;
if(S_ISDIR(st.mode))
*st_ = st;
return;
}
static
void
_set_stat_if_leads_to_reg(const std::string &path_,
struct fuse_statx *st_)
{
int rv;
struct fuse_statx st;
rv = fs::statx(AT_FDCWD,path_,AT_SYMLINK_FOLLOW,STATX_TYPE,st_);
if(rv < 0)
return;
if(S_ISREG(st.mode))
*st_ = st;
return;
}
static
int
_statx_controlfile(struct fuse_statx *st_)
{
static const uid_t uid = ::getuid();
static const gid_t gid = ::getgid();
static const time_t now = ::time(NULL);
st_->ino = fs::inode::MAGIC;
st_->mode = (S_IFREG|S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH);
st_->nlink = 1;
st_->uid = uid;
st_->gid = gid;
st_->size = 0;
st_->blocks = 0;
st_->atime.tv_sec = now;
st_->mtime.tv_sec = now;
st_->ctime.tv_sec = now;
return 0;
}
static
int
_statx(const Policy::Search &searchFunc_,
const Branches &branches_,
const char *fusepath_,
const uint32_t flags_,
const uint32_t mask_,
struct fuse_statx *st_,
const bool symlinkify_,
const time_t symlinkify_timeout_,
FollowSymlinks followsymlinks_)
{
int rv;
std::string fullpath;
StrVec basepaths;
rv = searchFunc_(branches_,fusepath_,&basepaths);
if(rv == -1)
return -errno;
fullpath = fs::path::make(basepaths[0],fusepath_);
switch(followsymlinks_)
{
case FollowSymlinks::ENUM::NEVER:
rv = fs::statx(AT_FDCWD,fullpath,flags_|AT_SYMLINK_NOFOLLOW,mask_,st_);
break;
case FollowSymlinks::ENUM::DIRECTORY:
rv = fs::statx(AT_FDCWD,fullpath,flags_|AT_SYMLINK_NOFOLLOW,mask_,st_);
if((rv >= 0) && S_ISLNK(st_->mode))
::_set_stat_if_leads_to_dir(fullpath,st_);
break;
case FollowSymlinks::ENUM::REGULAR:
rv = fs::statx(AT_FDCWD,fullpath,flags_|AT_SYMLINK_NOFOLLOW,mask_,st_);
if((rv >= 0) && S_ISLNK(st_->mode))
::_set_stat_if_leads_to_reg(fullpath,st_);
break;
case FollowSymlinks::ENUM::ALL:
rv = fs::statx(AT_FDCWD,fullpath,flags_|AT_SYMLINK_FOLLOW,mask_,st_);
if(rv < 0)
rv = fs::statx(AT_FDCWD,fullpath,flags_|AT_SYMLINK_NOFOLLOW,mask_,st_);
break;
}
if(rv < 0)
return rv;
if(symlinkify_ && symlinkify::can_be_symlink(*st_,symlinkify_timeout_))
symlinkify::convert(fullpath,st_);
fs::inode::calc(fusepath_,st_);
return 0;
}
static
int
_statx(const char *fusepath_,
const uint32_t flags_,
const uint32_t mask_,
struct fuse_statx *st_,
fuse_timeouts_t *timeout_)
{
int rv;
Config::Read cfg;
const fuse_context *fc = fuse_get_context();
const ugid::Set ugid(fc->uid,fc->gid);
rv = ::_statx(cfg->func.getattr.policy,
cfg->branches,
fusepath_,
flags_,
mask_,
st_,
cfg->symlinkify,
cfg->symlinkify_timeout,
cfg->follow_symlinks);
timeout_->entry = ((rv >= 0) ?
cfg->cache_entry :
cfg->cache_negative_entry);
timeout_->attr = cfg->cache_attr;
return rv;
}
int
FUSE::statx(const char *fusepath_,
const uint32_t flags_,
const uint32_t mask_,
struct fuse_statx *st_,
fuse_timeouts_t *timeout_)
{
if(fusepath_ == CONTROLFILE)
return ::_statx_controlfile(st_);
return ::_statx(fusepath_,flags_|AT_STATX_DONT_SYNC,mask_,st_,timeout_);
}
int
FUSE::statx_fh(const uint64_t fh_,
const uint32_t flags_,
const uint32_t mask_,
struct fuse_statx *st_,
fuse_timeouts_t *timeout_)
{
FileInfo *fi = reinterpret_cast<FileInfo*>(fh_);
return ::_statx(fi->fusepath.c_str(),flags_,mask_,st_,timeout_);
}

38
src/fuse_statx.hpp

@ -0,0 +1,38 @@
/*
ISC License
Copyright (c) 2025, Antonio SJ Musumeci <trapexit@spawn.link>
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#pragma once
#include "fuse.h"
#include "fuse_kernel.h"
#include <cstdint>
namespace FUSE
{
int statx(const char *fusepath,
const uint32_t flags,
const uint32_t mask,
struct fuse_statx *st,
fuse_timeouts_t *timeout);
int statx_fh(const uint64_t fh,
const uint32_t flags,
const uint32_t mask,
struct fuse_statx *st,
fuse_timeouts_t *timeout);
}

3
src/mergerfs.cpp

@ -71,6 +71,7 @@
#include "fuse_setupmapping.hpp" #include "fuse_setupmapping.hpp"
#include "fuse_setxattr.hpp" #include "fuse_setxattr.hpp"
#include "fuse_statfs.hpp" #include "fuse_statfs.hpp"
#include "fuse_statx.hpp"
#include "fuse_symlink.hpp" #include "fuse_symlink.hpp"
#include "fuse_syncfs.hpp" #include "fuse_syncfs.hpp"
#include "fuse_tmpfile.hpp" #include "fuse_tmpfile.hpp"
@ -139,6 +140,8 @@ namespace l
ops_.setupmapping = FUSE::setupmapping; ops_.setupmapping = FUSE::setupmapping;
ops_.setxattr = FUSE::setxattr; ops_.setxattr = FUSE::setxattr;
ops_.statfs = FUSE::statfs; ops_.statfs = FUSE::statfs;
ops_.statx = FUSE::statx;
ops_.statx_fh = FUSE::statx_fh;
ops_.symlink = FUSE::symlink; ops_.symlink = FUSE::symlink;
ops_.syncfs = FUSE::syncfs; ops_.syncfs = FUSE::syncfs;
ops_.tmpfile = FUSE::tmpfile; ops_.tmpfile = FUSE::tmpfile;

29
src/symlinkify.hpp

@ -18,6 +18,8 @@
#pragma once #pragma once
#include "fuse_kernel.h"
#include <sys/stat.h> #include <sys/stat.h>
#include <time.h> #include <time.h>
@ -42,6 +44,22 @@ namespace symlinkify
((now - st_.st_ctime) > timeout_)); ((now - st_.st_ctime) > timeout_));
} }
static
inline
bool
can_be_symlink(const struct fuse_statx &st_,
const time_t timeout_)
{
if(S_ISDIR(st_.mode) ||
(st_.mode & (S_IWUSR|S_IWGRP|S_IWOTH)))
return false;
const time_t now = ::time(NULL);
return (((now - st_.mtime.tv_sec) > timeout_) &&
((now - st_.ctime.tv_sec) > timeout_));
}
static static
inline inline
void void
@ -52,4 +70,15 @@ namespace symlinkify
st_->st_size = target_.size(); st_->st_size = target_.size();
st_->st_blocks = 0; st_->st_blocks = 0;
} }
static
inline
void
convert(const std::string &target_,
struct fuse_statx *st_)
{
st_->mode = (((st_->mode & ~S_IFMT) | S_IFLNK) | 0777);
st_->size = target_.size();
st_->blocks = 0;
}
} }
Loading…
Cancel
Save