[PATCH v13 11/12] famfs: Report device capacity via statfs so df works

John Groves <[email protected]>
Newsgroups dev.linux.lists.fuse-devel,dev.linux.lists.nvdimm,org.kernel.vger.linux-cxl,org.kernel.vger.linux-doc,org.kernel.vger.linux-fsdevel,org.kernel.vger.linux-kernel
Message-ID <0100019fed5a7936-f613e396-d14b-455f-bfd0-e2a0c1f4f844-000000@email.amazonses.com>
From: John Groves <[email protected]>

Replace simple_statfs(), which reports zero blocks (so df omits the mount),
with famfs_statfs() reporting real capacity and usage.

Add dax_fsdev_size() in drivers/dax/fsdev.c, returning the size fsdev
caches at probe (dev_dax->cached_size - the sum of the device's ranges,
stable while bound), exported. It lives in fsdev.c because cached_size is
set only by the fsdev driver, and famfs only ever holds fsdev-mode daxdevs
(fs_dax_get() enforces DAXDRV_FSDEV_TYPE); famfs.ko therefore depends on
fsdev_dax.ko.

famfs tracks two byte counters under a new stats_sem:
 - total_capacity: summed in famfs_install_daxdev() from dax_fsdev_size(),
   covering the mount primary and every DAXDEV_OPEN secondary, counted once
   per daxdev (on the valid 0->1 transition).
 - used_capacity: summed in famfs_file_init_dax() from the fmap's mapped
   device bytes (superblock + log + data files).

famfs_statfs() reports total and free (total - used). Free is an
approximation of the userspace allocator's free space (it ignores allocator
gaps and reserved regions), which is adequate for df.

(Side note: I am the maintainer of drivers/dax/fsdev.c)

Signed-off-by: John Groves <[email protected]>
---
 drivers/dax/fsdev.c       | 19 +++++++++++++++
 fs/famfs/famfs_file.c     |  5 ++++
 fs/famfs/famfs_inode.c    | 49 ++++++++++++++++++++++++++++++++++-----
 fs/famfs/famfs_internal.h |  8 +++++++
 include/linux/dax.h       |  1 +
 5 files changed, 76 insertions(+), 6 deletions(-)

diff --git a/drivers/dax/fsdev.c b/drivers/dax/fsdev.c
index 188b2526bee4..a5b4b2d79428 100644
--- a/drivers/dax/fsdev.c
+++ b/drivers/dax/fsdev.c
@@ -104,6 +104,25 @@ static size_t fsdev_dax_recovery_write(struct dax_device *dax_dev, pgoff_t pgoff
 	return _copy_from_iter_flushcache(addr, bytes, i);
 }
 
+/**
+ * dax_fsdev_size() - total size in bytes of an fsdev dax device
+ * @dax_dev: the dax device (must be bound to this driver)
+ *
+ * Returns the size cached at probe time (sum of all ranges); it cannot change
+ * while the driver is bound. Only valid for fsdev dax devices - callers
+ * ensure that (e.g. fs_dax_get() enforces DAXDRV_FSDEV_TYPE). Returns 0 if the
+ * device is not alive.
+ */
+u64 dax_fsdev_size(struct dax_device *dax_dev)
+{
+	struct dev_dax *dev_dax = dax_get_private(dax_dev);
+
+	if (!dev_dax)
+		return 0;
+	return dev_dax->cached_size;
+}
+EXPORT_SYMBOL_GPL(dax_fsdev_size);
+
 static const struct dax_operations dev_dax_ops = {
 	.direct_access = fsdev_dax_direct_access,
 	.zero_page_range = fsdev_dax_zero_page_range,
diff --git a/fs/famfs/famfs_file.c b/fs/famfs/famfs_file.c
index e7fa7324c374..17359fa4007b 100644
--- a/fs/famfs/famfs_file.c
+++ b/fs/famfs/famfs_file.c
@@ -282,6 +282,11 @@ famfs_file_init_dax(struct file *file, void __user *arg)
 	}
 	inode_unlock(inode);
 
+	/* Account the mapped device bytes for statfs (only on success) */
+	if (!rc) {
+		scoped_guard(rwsem_write, &fsi->stats_sem)
+			fsi->used_capacity += extent_total;
+	}
 out:
 	kvfree(fmap_buf);
 	if (meta)
diff --git a/fs/famfs/famfs_inode.c b/fs/famfs/famfs_inode.c
index 2f1938adbfbc..61b668b299ef 100644
--- a/fs/famfs/famfs_inode.c
+++ b/fs/famfs/famfs_inode.c
@@ -24,6 +24,7 @@
 #include <linux/iomap.h>
 #include <linux/path.h>
 #include <linux/namei.h>
+#include <linux/statfs.h>
 
 #include "famfs_internal.h"
 
@@ -348,8 +349,38 @@ famfs_evict_inode(struct inode *inode)
 	clear_inode(inode);
 }
 
+/*
+ * famfs_statfs() - report device capacity and consumption so 'df' works.
+ * @total_capacity is the sum of installed daxdev sizes; @used_capacity is the
+ * sum of device bytes mapped by fmaps (superblock + log + data files). Free is
+ * the difference - an approximation of the userspace allocator's free space
+ * (it ignores allocator gaps / reserved regions), which is fine for df.
+ */
+static int
+famfs_statfs(struct dentry *dentry, struct kstatfs *buf)
+{
+	struct famfs_fs_info *fsi = dentry->d_sb->s_fs_info;
+	u64 total, used, free;
+
+	scoped_guard(rwsem_read, &fsi->stats_sem) {
+		total = fsi->total_capacity;
+		used  = fsi->used_capacity;
+	}
+	free = total > used ? total - used : 0;
+
+	buf->f_type    = FAMFS_SUPER_MAGIC;
+	buf->f_bsize   = PAGE_SIZE;
+	buf->f_frsize  = PAGE_SIZE;
+	buf->f_blocks  = total >> PAGE_SHIFT;
+	buf->f_bfree   = free  >> PAGE_SHIFT;
+	buf->f_bavail  = free  >> PAGE_SHIFT;	/* no root reservation */
+	buf->f_namelen = NAME_MAX;
+	buf->f_fsid    = u64_to_fsid(huge_encode_dev(dentry->d_sb->s_dev));
+	return 0;
+}
+
 static const struct super_operations famfs_super_ops = {
-	.statfs		= simple_statfs,
+	.statfs		= famfs_statfs,
 	.drop_inode	= inode_just_drop,
 	.show_options	= famfs_show_options,
 	.evict_inode    = famfs_evict_inode,
@@ -444,6 +475,7 @@ famfs_install_daxdev(
 	const char *name)
 {
 	struct famfs_daxdev *daxdev;
+	struct dax_device *devp = NULL;
 	int rc = 0;
 
 	if (index >= fsi->dax_devlist->nslots) {
@@ -507,6 +539,15 @@ famfs_install_daxdev(
 
 		wmb(); /* All other fields must be visible before valid */
 		daxdev->valid = 1;
+		devp = daxdev->devp;
+	}
+
+	/* Freshly installed: add its capacity to the statfs accounting */
+	if (devp) {
+		u64 sz = dax_fsdev_size(devp);
+
+		scoped_guard(rwsem_write, &fsi->stats_sem)
+			fsi->total_capacity += sz;
 	}
 
 	return 0;
@@ -769,6 +810,7 @@ famfs_init_fs_context(struct fs_context *fc)
 		return -ENOMEM;
 
 	init_rwsem(&fsi->devlist_sem);
+	init_rwsem(&fsi->stats_sem);
 	atomic64_set(&fsi->opts, FAMFS_OPT_DEFAULT);
 	fsi->mount_opts.mode = FAMFS_DEFAULT_MODE;
 	fc->s_fs_info        = fsi;
@@ -802,16 +844,11 @@ static struct file_system_type famfs_fs_type = {
 /******************************************************************************
  * Module stuff
  */
-#define FAMFS_MODULE_INCOMPLETE 1
-
 static int __init
 init_famfs_fs(void)
 {
 	int rc;
 
-	if (FAMFS_MODULE_INCOMPLETE)
-		return -ENODEV;
-
 	rc = register_filesystem(&famfs_fs_type);
 
 	return rc;
diff --git a/fs/famfs/famfs_internal.h b/fs/famfs/famfs_internal.h
index 5e043b5a0ddd..8765be41e4a7 100644
--- a/fs/famfs/famfs_internal.h
+++ b/fs/famfs/famfs_internal.h
@@ -123,6 +123,11 @@ struct famfs_dax_devlist {
  *               point, or if other "shutdown" conditions exist
  * @dax_devlist: Table of backing daxdevs (slot 0 is the mount primary)
  * @devlist_sem: Serializes installs into, and teardown of, @dax_devlist
+ * @stats_sem:   Protects the statfs accounting counters below
+ * @total_capacity: Sum of installed daxdev sizes, in bytes (grows as daxdevs
+ *               are added)
+ * @used_capacity:  Sum of installed fmap sizes, in bytes (grows as MAP_CREATE
+ *               attaches fmaps; this is device bytes consumed, not file size)
  */
 struct famfs_fs_info {
 	struct famfs_mount_opts   mount_opts;
@@ -130,6 +135,9 @@ struct famfs_fs_info {
 	bool                      deverror;
 	struct famfs_dax_devlist *dax_devlist;
 	struct rw_semaphore       devlist_sem;
+	struct rw_semaphore       stats_sem;
+	u64                       total_capacity;
+	u64                       used_capacity;
 };
 
 /*
diff --git a/include/linux/dax.h b/include/linux/dax.h
index 29113eb95e72..c25ef499d04f 100644
--- a/include/linux/dax.h
+++ b/include/linux/dax.h
@@ -256,6 +256,7 @@ static inline void dax_break_layout_final(struct inode *inode)
 
 bool dax_alive(struct dax_device *dax_dev);
 void *dax_get_private(struct dax_device *dax_dev);
+u64 dax_fsdev_size(struct dax_device *dax_dev);
 int dax_set_ops(struct dax_device *dax_dev, const struct dax_operations *ops);
 long dax_direct_access(struct dax_device *dax_dev, pgoff_t pgoff, long nr_pages,
 		enum dax_access_mode mode, void **kaddr, unsigned long *pfn);
-- 
2.53.0
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.