All of lore.kernel.org
 help / color / mirror / Atom feed
From: Shachar Sharon <synarete@gmail.com>
To: miklos@szeredi.hu, linux-fsdevel@vger.kernel.org,
	linux-kernel@vger.kernel.or
Subject: [PATCH] fuse: add FUSE_STATX
Date: Tue, 23 Mar 2021 11:45:18 +0200	[thread overview]
Message-ID: <YFm4rjXYXiboLgxv@lpc> (raw)

Since commit a528d35e8bfc ("statx: Add a system call to make enhanced
file info available") kernel has statx(2) system call. This patch
implements FUSE_STATX as a superset of FUSE_GETATTR, which enables
user-space file-systems to report statx information; in particular,
creation (birth) time.

The newly added struct fuse_statx_timestamp has same members as
struct statx_timestamp, where 'sec' is __s64 and padded with reserved
field. Likewise, layout of struct fuse_statx follows struct statx,
including trailing spare place-holders, making it 240 bytes in size,
and struct fuse_statx_out 256 bytes in size. Due to difference in size
(strcut fuse_attr is only 88 bytes), FUSE_GETATTR is preferred over
FUSE_STATX unless request_mask has STATX_BTIME active.

With this patch:
  $ stat --format %w /path/to/fuse/fs/file
  2021-03-19 10:42:07.284912429 +0200

Tested with glibc-2.32 and coreutils-8.32 over 'voluta' user-space
file-system.

Signed-off-by: Shachar Sharon <synarete@gmail.com>
---
 fs/fuse/dir.c             | 161 ++++++++++++++++++++++++++++++++++----
 fs/fuse/fuse_i.h          |   3 +
 include/uapi/linux/fuse.h |  48 +++++++++++-
 3 files changed, 194 insertions(+), 18 deletions(-)

diff --git a/fs/fuse/dir.c b/fs/fuse/dir.c
index 06a18700a845..aa33669b9fda 100644
--- a/fs/fuse/dir.c
+++ b/fs/fuse/dir.c
@@ -111,6 +111,11 @@ static u64 attr_timeout(struct fuse_attr_out *o)
 	return time_to_jiffies(o->attr_valid, o->attr_valid_nsec);
 }
 
+static u64 statx_timeout(struct fuse_statx_out *o)
+{
+	return time_to_jiffies(o->attr_valid, o->attr_valid_nsec);
+}
+
 u64 entry_attr_timeout(struct fuse_entry_out *o)
 {
 	return time_to_jiffies(o->attr_valid, o->attr_valid_nsec);
@@ -1022,8 +1027,116 @@ static void fuse_fillattr(struct inode *inode, struct fuse_attr *attr,
 	stat->blksize = 1 << blkbits;
 }
 
-static int fuse_do_getattr(struct inode *inode, struct kstat *stat,
-			   struct file *file)
+static void fuse_fillattr2(struct inode *inode, struct fuse_attr *attr,
+			   struct fuse_statx *stx, struct kstat *stat)
+{
+	fuse_fillattr(inode, attr, stat);
+	stat->result_mask = stx->mask;
+	stat->attributes = stx->attributes;
+	stat->attributes_mask = stx->attributes_mask;
+	if (stx->mask & STATX_BTIME) {
+		stat->btime.tv_sec = stx->btime.sec;
+		stat->btime.tv_nsec = stx->btime.nsec;
+	}
+}
+
+static void fuse_statx_to_attr(struct fuse_statx *stx, struct fuse_attr *attr)
+{
+	attr->ino = stx->ino;
+	attr->size = stx->size;
+	attr->blocks = stx->blocks;
+	attr->atime = stx->atime.sec;
+	attr->mtime = stx->mtime.sec;
+	attr->ctime = stx->ctime.sec;
+	attr->atimensec = stx->atime.nsec;
+	attr->mtimensec = stx->mtime.nsec;
+	attr->ctimensec = stx->ctime.nsec;
+	attr->mode = stx->mode;
+	attr->nlink = stx->nlink;
+	attr->uid = stx->uid;
+	attr->gid = stx->gid;
+	attr->rdev = MKDEV(stx->rdev_major, stx->rdev_minor);
+	attr->blksize = stx->blksize;
+	attr->flags = stx->flags;
+}
+
+static int fuse_require_valid_attr(struct inode *inode, struct fuse_attr *attr)
+{
+	int ret = 0;
+
+	if (fuse_invalid_attr(attr) || (inode->i_mode ^ attr->mode) & S_IFMT) {
+		fuse_make_bad(inode);
+		ret = -EIO;
+	}
+	return ret;
+}
+
+static void fuse_getattr_inargs(struct inode *inode, struct file *file,
+				u32 *getattr_flags, u64 *fh)
+{
+	/* Directories have separate file-handle space */
+	if (file && S_ISREG(inode->i_mode)) {
+		struct fuse_file *ff = file->private_data;
+
+		*getattr_flags |= FUSE_GETATTR_FH;
+		*fh = ff->fh;
+	}
+}
+
+static int __fuse_do_statx(struct inode *inode, struct kstat *stat,
+			   struct file *file, u32 request_mask)
+{
+	int err;
+	struct fuse_attr attr;
+	struct fuse_statx_in inarg;
+	struct fuse_statx_out outarg;
+	struct fuse_mount *fm = get_fuse_mount(inode);
+	FUSE_ARGS(args);
+	u64 attr_version;
+
+	attr_version = fuse_get_attr_version(fm->fc);
+
+	memset(&inarg, 0, sizeof(inarg));
+	memset(&outarg, 0, sizeof(outarg));
+	fuse_getattr_inargs(inode, file, &inarg.getattr_flags, &inarg.fh);
+	inarg.mask = request_mask | STATX_BASIC_STATS;
+	args.opcode = FUSE_STATX;
+	args.nodeid = get_node_id(inode);
+	args.in_numargs = 1;
+	args.in_args[0].size = sizeof(inarg);
+	args.in_args[0].value = &inarg;
+	args.out_numargs = 1;
+	args.out_args[0].size = sizeof(outarg);
+	args.out_args[0].value = &outarg;
+	err = fuse_simple_request(fm, &args);
+	if (err == -ENOSYS)
+		goto no_statx;
+
+	if (err)
+		return err;
+
+	if ((outarg.attr.mask & STATX_BASIC_STATS) != STATX_BASIC_STATS)
+		goto no_statx;
+
+	fuse_statx_to_attr(&outarg.attr, &attr);
+	err = fuse_require_valid_attr(inode, &attr);
+	if (err)
+		return err;
+
+	fuse_change_attributes(inode, &attr,
+				statx_timeout(&outarg), attr_version);
+	if (stat)
+		fuse_fillattr2(inode, &attr, &outarg.attr, stat);
+
+	return err;
+
+no_statx:
+	fm->fc->no_statx = 1;
+	return -EINVAL;
+}
+
+static int __fuse_do_getattr(struct inode *inode, struct kstat *stat,
+			     struct file *file)
 {
 	int err;
 	struct fuse_getattr_in inarg;
@@ -1036,13 +1149,7 @@ static int fuse_do_getattr(struct inode *inode, struct kstat *stat,
 
 	memset(&inarg, 0, sizeof(inarg));
 	memset(&outarg, 0, sizeof(outarg));
-	/* Directories have separate file-handle space */
-	if (file && S_ISREG(inode->i_mode)) {
-		struct fuse_file *ff = file->private_data;
-
-		inarg.getattr_flags |= FUSE_GETATTR_FH;
-		inarg.fh = ff->fh;
-	}
+	fuse_getattr_inargs(inode, file, &inarg.getattr_flags, &inarg.fh);
 	args.opcode = FUSE_GETATTR;
 	args.nodeid = get_node_id(inode);
 	args.in_numargs = 1;
@@ -1053,11 +1160,8 @@ static int fuse_do_getattr(struct inode *inode, struct kstat *stat,
 	args.out_args[0].value = &outarg;
 	err = fuse_simple_request(fm, &args);
 	if (!err) {
-		if (fuse_invalid_attr(&outarg.attr) ||
-		    (inode->i_mode ^ outarg.attr.mode) & S_IFMT) {
-			fuse_make_bad(inode);
-			err = -EIO;
-		} else {
+		err = fuse_require_valid_attr(inode, &outarg.attr);
+		if (!err) {
 			fuse_change_attributes(inode, &outarg.attr,
 					       attr_timeout(&outarg),
 					       attr_version);
@@ -1068,11 +1172,31 @@ static int fuse_do_getattr(struct inode *inode, struct kstat *stat,
 	return err;
 }
 
+static int fuse_do_getattr(struct inode *inode, struct kstat *stat,
+			   struct file *file, u32 request_mask)
+{
+	struct fuse_conn *fc = get_fuse_conn(inode);
+	int err;
+
+	if (fc->no_statx || fc->minor < 34 || !(request_mask & STATX_BTIME))
+		goto getattr;
+
+	err = __fuse_do_statx(inode, stat, file, request_mask);
+	if (err && fc->no_statx)
+		goto getattr;
+
+	return err;
+
+getattr:
+	return __fuse_do_getattr(inode, stat, file);
+}
+
 static int fuse_update_get_attr(struct inode *inode, struct file *file,
 				struct kstat *stat, u32 request_mask,
 				unsigned int flags)
 {
 	struct fuse_inode *fi = get_fuse_inode(inode);
+	struct fuse_conn *fc = get_fuse_conn(inode);
 	int err = 0;
 	bool sync;
 
@@ -1082,12 +1206,14 @@ static int fuse_update_get_attr(struct inode *inode, struct file *file,
 		sync = false;
 	else if (request_mask & READ_ONCE(fi->inval_mask))
 		sync = true;
+	else if (!fc->no_statx && (request_mask & STATX_BTIME))
+		sync = true;
 	else
 		sync = time_before64(fi->i_time, get_jiffies_64());
 
 	if (sync) {
 		forget_all_cached_acls(inode);
-		err = fuse_do_getattr(inode, stat, file);
+		err = fuse_do_getattr(inode, stat, file, request_mask);
 	} else if (stat) {
 		generic_fillattr(&init_user_ns, inode, stat);
 		stat->mode = fi->orig_i_mode;
@@ -1235,7 +1361,7 @@ static int fuse_perm_getattr(struct inode *inode, int mask)
 		return -ECHILD;
 
 	forget_all_cached_acls(inode);
-	return fuse_do_getattr(inode, NULL, NULL);
+	return fuse_do_getattr(inode, NULL, NULL, STATX_BASIC_STATS);
 }
 
 /*
@@ -1789,7 +1915,8 @@ static int fuse_setattr(struct user_namespace *mnt_userns, struct dentry *entry,
 			 * ia_mode calculation may have used stale i_mode.
 			 * Refresh and recalculate.
 			 */
-			ret = fuse_do_getattr(inode, NULL, file);
+			ret = fuse_do_getattr(inode, NULL, file,
+					      STATX_BASIC_STATS);
 			if (ret)
 				return ret;
 
diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h
index 63d97a15ffde..15cd1968aac9 100644
--- a/fs/fuse/fuse_i.h
+++ b/fs/fuse/fuse_i.h
@@ -755,6 +755,9 @@ struct fuse_conn {
 	/* Auto-mount submounts announced by the server */
 	unsigned int auto_submounts:1;
 
+	/** Is statx not implemented by fs? */
+	unsigned int no_statx:1;
+
 	/** The number of requests waiting for completion */
 	atomic_t num_waiting;
 
diff --git a/include/uapi/linux/fuse.h b/include/uapi/linux/fuse.h
index 54442612c48b..36ac3c40b922 100644
--- a/include/uapi/linux/fuse.h
+++ b/include/uapi/linux/fuse.h
@@ -179,6 +179,9 @@
  *  7.33
  *  - add FUSE_HANDLE_KILLPRIV_V2, FUSE_WRITE_KILL_SUIDGID, FATTR_KILL_SUIDGID
  *  - add FUSE_OPEN_KILL_SUIDGID
+ *
+ *  7.34
+ *  - add FUSE_STATX
  */
 
 #ifndef _LINUX_FUSE_H
@@ -214,7 +217,7 @@
 #define FUSE_KERNEL_VERSION 7
 
 /** Minor version number of this interface */
-#define FUSE_KERNEL_MINOR_VERSION 33
+#define FUSE_KERNEL_MINOR_VERSION 34
 
 /** The node ID of the root inode */
 #define FUSE_ROOT_ID 1
@@ -499,6 +502,7 @@ enum fuse_opcode {
 	FUSE_COPY_FILE_RANGE	= 47,
 	FUSE_SETUPMAPPING	= 48,
 	FUSE_REMOVEMAPPING	= 49,
+	FUSE_STATX		= 50,
 
 	/* CUSE specific operations */
 	CUSE_INIT		= 4096,
@@ -957,4 +961,46 @@ struct fuse_removemapping_one {
 #define FUSE_REMOVEMAPPING_MAX_ENTRY   \
 		(PAGE_SIZE / sizeof(struct fuse_removemapping_one))
 
+struct fuse_statx_timestamp {
+	int64_t		sec;
+	uint32_t	nsec;
+	int32_t		reserved;
+};
+
+struct fuse_statx {
+	uint32_t	mask;
+	uint32_t	blksize;
+	uint64_t	attributes;
+	uint32_t	nlink;
+	uint32_t	uid;
+	uint32_t	gid;
+	uint32_t	mode;
+	uint64_t	ino;
+	uint64_t	size;
+	uint64_t	blocks;
+	uint64_t	attributes_mask;
+	struct fuse_statx_timestamp atime;
+	struct fuse_statx_timestamp btime;
+	struct fuse_statx_timestamp ctime;
+	struct fuse_statx_timestamp mtime;
+	uint32_t	rdev_major;
+	uint32_t	rdev_minor;
+	uint32_t	flags;
+	uint32_t	padding;
+	uint64_t	spare[12];
+};
+
+struct fuse_statx_in {
+	uint32_t	getattr_flags;
+	uint32_t	mask;
+	uint64_t	fh;
+};
+
+struct fuse_statx_out {
+	uint64_t	attr_valid;
+	uint32_t	attr_valid_nsec;
+	uint32_t	dummy;
+	struct fuse_statx attr;
+};
+
 #endif /* _LINUX_FUSE_H */
-- 
2.26.3


                 reply	other threads:[~2021-03-23  9:46 UTC|newest]

Thread overview: [no followups] expand[flat|nested]  mbox.gz  Atom feed

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=YFm4rjXYXiboLgxv@lpc \
    --to=synarete@gmail.com \
    --cc=linux-fsdevel@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.or \
    --cc=miklos@szeredi.hu \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.