On Fri, Jul 18, 2025 at 1:36 AM Darrick J. Wong <djwong@xxxxxxxxxx> wrote: > > From: Darrick J. Wong <djwong@xxxxxxxxxx> > > Create new fuse_reply_{attr,create,entry}_iflags functions so that we > can send FUSE_ATTR_* flags to the kernel when instantiating an inode. > Servers are expected to send FUSE_IFLAG_* values, which will be > translated into what the kernel can understand. > > Signed-off-by: "Darrick J. Wong" <djwong@xxxxxxxxxx> > --- > include/fuse_common.h | 3 ++ > include/fuse_lowlevel.h | 87 +++++++++++++++++++++++++++++++++++++++++++++-- > lib/fuse_lowlevel.c | 69 ++++++++++++++++++++++++++++++------- > lib/fuse_versionscript | 4 ++ > 4 files changed, 146 insertions(+), 17 deletions(-) > > > diff --git a/include/fuse_common.h b/include/fuse_common.h > index 66c25afe15ec76..11eb22d011896c 100644 > --- a/include/fuse_common.h > +++ b/include/fuse_common.h > @@ -1210,6 +1210,9 @@ struct fuse_iomap { > /* is append ioend */ > #define FUSE_IOMAP_IOEND_APPEND (1U << 15) > > +/* enable fsdax */ > +#define FUSE_IFLAG_DAX (1U << 0) > + > #endif /* FUSE_USE_VERSION >= 318 */ > > /* ----------------------------------------------------------- * > diff --git a/include/fuse_lowlevel.h b/include/fuse_lowlevel.h > index 1b856431de0a60..07748abcf079cf 100644 > --- a/include/fuse_lowlevel.h > +++ b/include/fuse_lowlevel.h > @@ -240,6 +240,7 @@ struct fuse_lowlevel_ops { > * > * Valid replies: > * fuse_reply_entry > + * fuse_reply_entry_iflags > * fuse_reply_err > * > * @param req request handle > @@ -299,6 +300,7 @@ struct fuse_lowlevel_ops { > * > * Valid replies: > * fuse_reply_attr > + * fuse_reply_attr_iflags > * fuse_reply_err > * > * @param req request handle > @@ -334,6 +336,7 @@ struct fuse_lowlevel_ops { > * > * Valid replies: > * fuse_reply_attr > + * fuse_reply_attr_iflags > * fuse_reply_err > * > * @param req request handle > @@ -364,7 +367,7 @@ struct fuse_lowlevel_ops { > * socket node. > * > * Valid replies: > - * fuse_reply_entry > + * fuse_reply_entry_iflags > * fuse_reply_err > * > * @param req request handle > @@ -380,7 +383,7 @@ struct fuse_lowlevel_ops { > * Create a directory > * > * Valid replies: > - * fuse_reply_entry > + * fuse_reply_entry_iflags > * fuse_reply_err > * > * @param req request handle > @@ -429,7 +432,7 @@ struct fuse_lowlevel_ops { > * Create a symbolic link > * > * Valid replies: > - * fuse_reply_entry > + * fuse_reply_entry_iflags > * fuse_reply_err > * > * @param req request handle > @@ -477,7 +480,7 @@ struct fuse_lowlevel_ops { > * Create a hard link > * > * Valid replies: > - * fuse_reply_entry > + * fuse_reply_entry_iflags > * fuse_reply_err > * > * @param req request handle > @@ -969,6 +972,7 @@ struct fuse_lowlevel_ops { > * > * Valid replies: > * fuse_reply_create > + * fuse_reply_create_iflags > * fuse_reply_err > * > * @param req request handle > @@ -1315,6 +1319,7 @@ struct fuse_lowlevel_ops { > * > * Valid replies: > * fuse_reply_create > + * fuse_reply_create_iflags > * fuse_reply_err > * > * @param req request handle > @@ -1435,6 +1440,23 @@ void fuse_reply_none(fuse_req_t req); > */ > int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e); > > +/** > + * Reply with a directory entry and FUSE_IFLAG_* > + * > + * Possible requests: > + * lookup, mknod, mkdir, symlink, link > + * > + * Side effects: > + * increments the lookup count on success > + * > + * @param req request handle > + * @param e the entry parameters > + * @param iflags FUSE_IFLAG_* > + * @return zero for success, -errno for failure to send reply > + */ > +int fuse_reply_entry_iflags(fuse_req_t req, const struct fuse_entry_param *e, > + unsigned int iflags); > + > /** > * Reply with a directory entry and open parameters > * > @@ -1456,6 +1478,29 @@ int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e); > int fuse_reply_create(fuse_req_t req, const struct fuse_entry_param *e, > const struct fuse_file_info *fi); > > +/** > + * Reply with a directory entry, open parameters and FUSE_IFLAG_* > + * > + * currently the following members of 'fi' are used: > + * fh, direct_io, keep_cache, cache_readdir, nonseekable, noflush, > + * parallel_direct_writes > + * > + * Possible requests: > + * create > + * > + * Side effects: > + * increments the lookup count on success > + * > + * @param req request handle > + * @param e the entry parameters > + * @param iflags FUSE_IFLAG_* > + * @param fi file information > + * @return zero for success, -errno for failure to send reply > + */ > +int fuse_reply_create_iflags(fuse_req_t req, const struct fuse_entry_param *e, > + unsigned int iflags, > + const struct fuse_file_info *fi); > + > /** > * Reply with attributes > * > @@ -1470,6 +1515,21 @@ int fuse_reply_create(fuse_req_t req, const struct fuse_entry_param *e, > int fuse_reply_attr(fuse_req_t req, const struct stat *attr, > double attr_timeout); > > +/** > + * Reply with attributes and FUSE_IFLAG_* flags > + * > + * Possible requests: > + * getattr, setattr > + * > + * @param req request handle > + * @param attr the attributes > + * @param attr_timeout validity timeout (in seconds) for the attributes > + * @param iflags set of FUSE_IFLAG_* flags > + * @return zero for success, -errno for failure to send reply > + */ > +int fuse_reply_attr_iflags(fuse_req_t req, const struct stat *attr, > + unsigned int iflags, double attr_timeout); > + > /** > * Reply with the contents of a symbolic link > * > @@ -1697,6 +1757,25 @@ size_t fuse_add_direntry_plus(fuse_req_t req, char *buf, size_t bufsize, > const char *name, > const struct fuse_entry_param *e, off_t off); > > +/** > + * Add a directory entry and FUSE_IFLAG_* to the buffer with the attributes > + * > + * See documentation of `fuse_add_direntry_plus()` for more details. > + * > + * @param req request handle > + * @param buf the point where the new entry will be added to the buffer > + * @param bufsize remaining size of the buffer > + * @param name the name of the entry > + * @param iflags FUSE_IFLAG_* > + * @param e the directory entry > + * @param off the offset of the next entry > + * @return the space needed for the entry > + */ > +size_t fuse_add_direntry_plus_iflags(fuse_req_t req, char *buf, size_t bufsize, > + const char *name, unsigned int iflags, > + const struct fuse_entry_param *e, > + off_t off); > + > /** > * Reply to ask for data fetch and output buffer preparation. ioctl > * will be retried with the specified input data fetched and output > diff --git a/lib/fuse_lowlevel.c b/lib/fuse_lowlevel.c > index d26043fa54c036..568db13502a7d7 100644 > --- a/lib/fuse_lowlevel.c > +++ b/lib/fuse_lowlevel.c > @@ -102,7 +102,8 @@ static void trace_request_reply(uint64_t unique, unsigned int len, > } > #endif > > -static void convert_stat(const struct stat *stbuf, struct fuse_attr *attr) > +static void convert_stat(const struct stat *stbuf, struct fuse_attr *attr, > + unsigned int iflags) > { > attr->ino = stbuf->st_ino; > attr->mode = stbuf->st_mode; > @@ -119,6 +120,10 @@ static void convert_stat(const struct stat *stbuf, struct fuse_attr *attr) > attr->atimensec = ST_ATIM_NSEC(stbuf); > attr->mtimensec = ST_MTIM_NSEC(stbuf); > attr->ctimensec = ST_CTIM_NSEC(stbuf); > + > + attr->flags = 0; > + if (iflags & FUSE_IFLAG_DAX) > + attr->flags |= FUSE_ATTR_DAX; > } > > static void convert_attr(const struct fuse_setattr_in *attr, struct stat *stbuf) > @@ -438,7 +443,8 @@ static unsigned int calc_timeout_nsec(double t) > } > > static void fill_entry(struct fuse_entry_out *arg, > - const struct fuse_entry_param *e) > + const struct fuse_entry_param *e, > + unsigned int iflags) > { > arg->nodeid = e->ino; > arg->generation = e->generation; > @@ -446,14 +452,15 @@ static void fill_entry(struct fuse_entry_out *arg, > arg->entry_valid_nsec = calc_timeout_nsec(e->entry_timeout); > arg->attr_valid = calc_timeout_sec(e->attr_timeout); > arg->attr_valid_nsec = calc_timeout_nsec(e->attr_timeout); > - convert_stat(&e->attr, &arg->attr); > + convert_stat(&e->attr, &arg->attr, iflags); > } > > /* `buf` is allowed to be empty so that the proper size may be > allocated by the caller */ > -size_t fuse_add_direntry_plus(fuse_req_t req, char *buf, size_t bufsize, > - const char *name, > - const struct fuse_entry_param *e, off_t off) > +size_t fuse_add_direntry_plus_iflags(fuse_req_t req, char *buf, size_t bufsize, > + const char *name, unsigned int iflags, > + const struct fuse_entry_param *e, > + off_t off) > { > (void)req; > size_t namelen; > @@ -468,7 +475,7 @@ size_t fuse_add_direntry_plus(fuse_req_t req, char *buf, size_t bufsize, > > struct fuse_direntplus *dp = (struct fuse_direntplus *) buf; > memset(&dp->entry_out, 0, sizeof(dp->entry_out)); > - fill_entry(&dp->entry_out, e); > + fill_entry(&dp->entry_out, e, iflags); > > struct fuse_dirent *dirent = &dp->dirent; > dirent->ino = e->attr.st_ino; > @@ -481,6 +488,14 @@ size_t fuse_add_direntry_plus(fuse_req_t req, char *buf, size_t bufsize, > return entlen_padded; > } > > +size_t fuse_add_direntry_plus(fuse_req_t req, char *buf, size_t bufsize, > + const char *name, > + const struct fuse_entry_param *e, off_t off) > +{ > + return fuse_add_direntry_plus_iflags(req, buf, bufsize, name, 0, e, > + off); > +} > + > static void fill_open(struct fuse_open_out *arg, > const struct fuse_file_info *f) > { > @@ -503,7 +518,8 @@ static void fill_open(struct fuse_open_out *arg, > arg->open_flags |= FOPEN_PARALLEL_DIRECT_WRITES; > } > > -int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e) > +int fuse_reply_entry_iflags(fuse_req_t req, const struct fuse_entry_param *e, > + unsigned int iflags) > { > struct fuse_entry_out arg; > size_t size = req->se->conn.proto_minor < 9 ? > @@ -515,12 +531,18 @@ int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e) > return fuse_reply_err(req, ENOENT); > > memset(&arg, 0, sizeof(arg)); > - fill_entry(&arg, e); > + fill_entry(&arg, e, iflags); > return send_reply_ok(req, &arg, size); > } > > -int fuse_reply_create(fuse_req_t req, const struct fuse_entry_param *e, > - const struct fuse_file_info *f) > +int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e) > +{ > + return fuse_reply_entry_iflags(req, e, 0); > +} > + > +int fuse_reply_create_iflags(fuse_req_t req, const struct fuse_entry_param *e, > + unsigned int iflags, > + const struct fuse_file_info *f) > { > alignas(uint64_t) char buf[sizeof(struct fuse_entry_out) + sizeof(struct fuse_open_out)]; > size_t entrysize = req->se->conn.proto_minor < 9 ? > @@ -529,12 +551,18 @@ int fuse_reply_create(fuse_req_t req, const struct fuse_entry_param *e, > struct fuse_open_out *oarg = (struct fuse_open_out *) (buf + entrysize); > > memset(buf, 0, sizeof(buf)); > - fill_entry(earg, e); > + fill_entry(earg, e, iflags); > fill_open(oarg, f); > return send_reply_ok(req, buf, > entrysize + sizeof(struct fuse_open_out)); > } > > +int fuse_reply_create(fuse_req_t req, const struct fuse_entry_param *e, > + const struct fuse_file_info *f) > +{ > + return fuse_reply_create_iflags(req, e, 0, f); > +} > + > int fuse_reply_attr(fuse_req_t req, const struct stat *attr, > double attr_timeout) > { > @@ -545,7 +573,22 @@ int fuse_reply_attr(fuse_req_t req, const struct stat *attr, > memset(&arg, 0, sizeof(arg)); > arg.attr_valid = calc_timeout_sec(attr_timeout); > arg.attr_valid_nsec = calc_timeout_nsec(attr_timeout); > - convert_stat(attr, &arg.attr); > + convert_stat(attr, &arg.attr, 0); > + > + return send_reply_ok(req, &arg, size); > +} > + > +int fuse_reply_attr_iflags(fuse_req_t req, const struct stat *attr, > + unsigned int iflags, double attr_timeout) > +{ > + struct fuse_attr_out arg; > + size_t size = req->se->conn.proto_minor < 9 ? > + FUSE_COMPAT_ATTR_OUT_SIZE : sizeof(arg); > + > + memset(&arg, 0, sizeof(arg)); > + arg.attr_valid = calc_timeout_sec(attr_timeout); > + arg.attr_valid_nsec = calc_timeout_nsec(attr_timeout); > + convert_stat(attr, &arg.attr, iflags); > > return send_reply_ok(req, &arg, size); > } I wonder why fuse_reply_attr() is not implemented as a wrapper to fuse_reply_attr_iflags()? FWIW, the flags field was added in minor version 23 for FUSE_ATTR_SUBMOUNT, but I guess that doesn't matter here. Thanks, Amir.