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

John Groves <[email protected]> Mon, 3 Aug 2026 02:30:07 +0000
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 <0100019fc57500f5-06c293c8-393c-44e3-ad42-d4f4de58245d-000000@email.amazonses.com>
From: John Groves <[email protected]>=0D=0A=0D=0AReplace simple_statfs(), w=
hich reports zero blocks (so df omits the mount),=0D=0Awith famfs_statfs(=
) reporting real capacity and usage.=0D=0A=0D=0AAdd dax_fsdev_size() in d=
rivers/dax/fsdev.c, returning the size fsdev=0D=0Acaches at probe (dev_da=
x->cached_size - the sum of the device's ranges,=0D=0Astable while bound)=
, exported. It lives in fsdev.c because cached_size is=0D=0Aset only by t=
he fsdev driver, and famfs only ever holds fsdev-mode daxdevs=0D=0A(fs_da=
x_get() enforces DAXDRV_FSDEV_TYPE); famfs.ko therefore depends on=0D=0Af=
sdev_dax.ko.=0D=0A=0D=0Afamfs tracks two byte counters under a new stats_=
sem:=0D=0A - total_capacity: summed in famfs_install_daxdev() from dax_fs=
dev_size(),=0D=0A   covering the mount primary and every DAXDEV_OPEN seco=
ndary, counted once=0D=0A   per daxdev (on the valid 0->1 transition).=0D=
=0A - used_capacity: summed in famfs_file_init_dax() from the fmap's mapp=
ed=0D=0A   device bytes (superblock + log + data files).=0D=0A=0D=0Afamfs=
_statfs() reports total and free (total - used). Free is an=0D=0Aapproxim=
ation of the userspace allocator's free space (it ignores allocator=0D=0A=
gaps and reserved regions), which is adequate for df.=0D=0A=0D=0A(Side no=
te: I am the maintainer of drivers/dax/fsdev.c)=0D=0A=0D=0ASigned-off-by:=
 John Groves <[email protected]>=0D=0A---=0D=0A drivers/dax/fsdev.c       |=
 19 +++++++++++++++++=0D=0A fs/famfs/famfs_file.c     |  5 +++++=0D=0A fs=
/famfs/famfs_inode.c    | 43 ++++++++++++++++++++++++++++++++++++++-=0D=0A=
 fs/famfs/famfs_internal.h |  8 ++++++++=0D=0A include/linux/dax.h       =
|  1 +=0D=0A 5 files changed, 75 insertions(+), 1 deletion(-)=0D=0A=0D=0A=
diff --git a/drivers/dax/fsdev.c b/drivers/dax/fsdev.c=0D=0Aindex 188b252=
6bee4..a5b4b2d79428 100644=0D=0A--- a/drivers/dax/fsdev.c=0D=0A+++ b/driv=
ers/dax/fsdev.c=0D=0A@@ -104,6 +104,25 @@ static size_t fsdev_dax_recover=
y_write(struct dax_device *dax_dev, pgoff_t pgoff=0D=0A =09return _copy_f=
rom_iter_flushcache(addr, bytes, i);=0D=0A }=0D=0A=20=0D=0A+/**=0D=0A+ * =
dax_fsdev_size() - total size in bytes of an fsdev dax device=0D=0A+ * @d=
ax_dev: the dax device (must be bound to this driver)=0D=0A+ *=0D=0A+ * R=
eturns the size cached at probe time (sum of all ranges); it cannot chang=
e=0D=0A+ * while the driver is bound. Only valid for fsdev dax devices - =
callers=0D=0A+ * ensure that (e.g. fs_dax_get() enforces DAXDRV_FSDEV_TYP=
E). Returns 0 if the=0D=0A+ * device is not alive.=0D=0A+ */=0D=0A+u64 da=
x_fsdev_size(struct dax_device *dax_dev)=0D=0A+{=0D=0A+=09struct dev_dax =
*dev_dax =3D dax_get_private(dax_dev);=0D=0A+=0D=0A+=09if (!dev_dax)=0D=0A=
+=09=09return 0;=0D=0A+=09return dev_dax->cached_size;=0D=0A+}=0D=0A+EXPO=
RT_SYMBOL_GPL(dax_fsdev_size);=0D=0A+=0D=0A static const struct dax_opera=
tions dev_dax_ops =3D {=0D=0A =09.direct_access =3D fsdev_dax_direct_acce=
ss,=0D=0A =09.zero_page_range =3D fsdev_dax_zero_page_range,=0D=0Adiff --=
git a/fs/famfs/famfs_file.c b/fs/famfs/famfs_file.c=0D=0Aindex abf049b32a=
4b..5be39d677089 100644=0D=0A--- a/fs/famfs/famfs_file.c=0D=0A+++ b/fs/fa=
mfs/famfs_file.c=0D=0A@@ -280,6 +280,11 @@ famfs_file_init_dax(struct fil=
e *file, void __user *arg)=0D=0A =09}=0D=0A =09inode_unlock(inode);=0D=0A=
=20=0D=0A+=09/* Account the mapped device bytes for statfs (only on succe=
ss) */=0D=0A+=09if (!rc) {=0D=0A+=09=09scoped_guard(rwsem_write, &fsi->st=
ats_sem)=0D=0A+=09=09=09fsi->used_capacity +=3D extent_total;=0D=0A+=09}=0D=
=0A out:=0D=0A =09kvfree(fmap_buf);=0D=0A =09if (meta)=0D=0Adiff --git a/=
fs/famfs/famfs_inode.c b/fs/famfs/famfs_inode.c=0D=0Aindex 6cbd7d657fd8..=
3c0d1094d653 100644=0D=0A--- a/fs/famfs/famfs_inode.c=0D=0A+++ b/fs/famfs=
/famfs_inode.c=0D=0A@@ -24,6 +24,7 @@=0D=0A #include <linux/iomap.h>=0D=0A=
 #include <linux/path.h>=0D=0A #include <linux/namei.h>=0D=0A+#include <l=
inux/statfs.h>=0D=0A=20=0D=0A #include "famfs_internal.h"=0D=0A=20=0D=0A@=
@ -307,8 +308,37 @@ static void famfs_evict_inode(struct inode *inode)=0D=
=0A =09clear_inode(inode);=0D=0A }=0D=0A=20=0D=0A+/*=0D=0A+ * famfs_statf=
s() - report device capacity and consumption so 'df' works.=0D=0A+ * @tot=
al_capacity is the sum of installed daxdev sizes; @used_capacity is the=0D=
=0A+ * sum of device bytes mapped by fmaps (superblock + log + data files=
). Free is=0D=0A+ * the difference - an approximation of the userspace al=
locator's free space=0D=0A+ * (it ignores allocator gaps / reserved regio=
ns), which is fine for df.=0D=0A+ */=0D=0A+static int famfs_statfs(struct=
 dentry *dentry, struct kstatfs *buf)=0D=0A+{=0D=0A+=09struct famfs_fs_in=
fo *fsi =3D dentry->d_sb->s_fs_info;=0D=0A+=09u64 total, used, free;=0D=0A=
+=0D=0A+=09scoped_guard(rwsem_read, &fsi->stats_sem) {=0D=0A+=09=09total =
=3D fsi->total_capacity;=0D=0A+=09=09used  =3D fsi->used_capacity;=0D=0A+=
=09}=0D=0A+=09free =3D total > used =3F total - used : 0;=0D=0A+=0D=0A+=09=
buf->f_type    =3D FAMFS_SUPER_MAGIC;=0D=0A+=09buf->f_bsize   =3D PAGE_SI=
ZE;=0D=0A+=09buf->f_frsize  =3D PAGE_SIZE;=0D=0A+=09buf->f_blocks  =3D to=
tal >> PAGE_SHIFT;=0D=0A+=09buf->f_bfree   =3D free  >> PAGE_SHIFT;=0D=0A=
+=09buf->f_bavail  =3D free  >> PAGE_SHIFT;=09/* no root reservation */=0D=
=0A+=09buf->f_namelen =3D NAME_MAX;=0D=0A+=09buf->f_fsid    =3D u64_to_fs=
id(huge_encode_dev(dentry->d_sb->s_dev));=0D=0A+=09return 0;=0D=0A+}=0D=0A=
+=0D=0A static const struct super_operations famfs_super_ops =3D {=0D=0A-=
=09.statfs=09=09=3D simple_statfs,=0D=0A+=09.statfs=09=09=3D famfs_statfs=
,=0D=0A =09.drop_inode=09=3D inode_just_drop,=0D=0A =09.show_options=09=3D=
 famfs_show_options,=0D=0A =09.evict_inode    =3D famfs_evict_inode,=0D=0A=
@@ -399,6 +429,7 @@ int famfs_install_daxdev(=0D=0A =09=09const char *nam=
e)=0D=0A {=0D=0A =09struct famfs_daxdev *daxdev;=0D=0A+=09struct dax_devi=
ce *devp =3D NULL;=0D=0A =09int rc =3D 0;=0D=0A=20=0D=0A =09if (index >=3D=
 fsi->dax_devlist->nslots) {=0D=0A@@ -462,6 +493,15 @@ int famfs_install_=
daxdev(=0D=0A=20=0D=0A =09=09wmb(); /* All other fields must be visible b=
efore valid */=0D=0A =09=09daxdev->valid =3D 1;=0D=0A+=09=09devp =3D daxd=
ev->devp;=0D=0A+=09}=0D=0A+=0D=0A+=09/* Freshly installed: add its capaci=
ty to the statfs accounting */=0D=0A+=09if (devp) {=0D=0A+=09=09u64 sz =3D=
 dax_fsdev_size(devp);=0D=0A+=0D=0A+=09=09scoped_guard(rwsem_write, &fsi-=
>stats_sem)=0D=0A+=09=09=09fsi->total_capacity +=3D sz;=0D=0A =09}=0D=0A=20=
=0D=0A =09return 0;=0D=0A@@ -717,6 +757,7 @@ static int famfs_init_fs_con=
text(struct fs_context *fc)=0D=0A =09=09return -ENOMEM;=0D=0A=20=0D=0A =09=
init_rwsem(&fsi->devlist_sem);=0D=0A+=09init_rwsem(&fsi->stats_sem);=0D=0A=
 =09atomic64_set(&fsi->opts, FAMFS_OPT_DEFAULT);=0D=0A =09fsi->mount_opts=
=2Emode =3D FAMFS_DEFAULT_MODE;=0D=0A =09fc->s_fs_info        =3D fsi;=0D=
=0Adiff --git a/fs/famfs/famfs_internal.h b/fs/famfs/famfs_internal.h=0D=0A=
index 26873162b4a0..0bf50774fa82 100644=0D=0A--- a/fs/famfs/famfs_interna=
l.h=0D=0A+++ b/fs/famfs/famfs_internal.h=0D=0A@@ -123,6 +123,11 @@ struct=
 famfs_dax_devlist {=0D=0A  *               point, or if other "shutdown"=
 conditions exist=0D=0A  * @dax_devlist: Table of backing daxdevs (slot 0=
 is the mount primary)=0D=0A  * @devlist_sem: Serializes installs into, a=
nd teardown of, @dax_devlist=0D=0A+ * @stats_sem:   Protects the statfs a=
ccounting counters below=0D=0A+ * @total_capacity: Sum of installed daxde=
v sizes, in bytes (grows as daxdevs=0D=0A+ *               are added)=0D=0A=
+ * @used_capacity:  Sum of installed fmap sizes, in bytes (grows as MAP_=
CREATE=0D=0A+ *               attaches fmaps; this is device bytes consum=
ed, not file size)=0D=0A  */=0D=0A struct famfs_fs_info {=0D=0A =09struct=
 famfs_mount_opts   mount_opts;=0D=0A@@ -130,6 +135,9 @@ struct famfs_fs_=
info {=0D=0A =09bool                      deverror;=0D=0A =09struct famfs=
_dax_devlist *dax_devlist;=0D=0A =09struct rw_semaphore       devlist_sem=
;=0D=0A+=09struct rw_semaphore       stats_sem;=0D=0A+=09u64             =
          total_capacity;=0D=0A+=09u64                       used_capacit=
y;=0D=0A };=0D=0A=20=0D=0A /*=0D=0Adiff --git a/include/linux/dax.h b/inc=
lude/linux/dax.h=0D=0Aindex 29113eb95e72..c25ef499d04f 100644=0D=0A--- a/=
include/linux/dax.h=0D=0A+++ b/include/linux/dax.h=0D=0A@@ -256,6 +256,7 =
@@ static inline void dax_break_layout_final(struct inode *inode)=0D=0A=20=
=0D=0A bool dax_alive(struct dax_device *dax_dev);=0D=0A void *dax_get_pr=
ivate(struct dax_device *dax_dev);=0D=0A+u64 dax_fsdev_size(struct dax_de=
vice *dax_dev);=0D=0A int dax_set_ops(struct dax_device *dax_dev, const s=
truct dax_operations *ops);=0D=0A long dax_direct_access(struct dax_devic=
e *dax_dev, pgoff_t pgoff, long nr_pages,=0D=0A =09=09enum dax_access_mod=
e mode, void **kaddr, unsigned long *pfn);=0D=0A--=20=0D=0A2.53.0=0D=0A=0D=
=0A