Provide support for the handling of an overrun in a watch queue. In the event that an overrun occurs, the watcher needs to be able to find out what it was that they missed. To this end, previous patches added event counters to the superblock and mount object structures. To make them accessible, they can be accessed using fsinfo() and the FSINFO_ATTR_MOUNT_INFO attribute. struct fsinfo_mount_info { __u64 mnt_unique_id; __u32 sb_changes; __u32 sb_notifications; __u32 mnt_attr_changes; __u32 mnt_topology_changes; __u32 mnt_subtree_notifications; ... }; There's a uniquifier and five event counters: (1) mnt_unique_id - This is an effectively non-repeating ID given to each mount object on creation. This allows the caller to check that the mount ID didn't get reused (the 32-bit mount ID is more efficient to look up). (2) sb_changes - Count of superblock configuration changes. (3) sb_notifications - Count of other superblock notifications (errors, quota overruns, etc.). (4) mnt_attr_changes - Count of attribute changes on a mount object. (5) mnt_topology_changes - Count of alterations to the mount tree that affected this node. (6) mnt_subtree_notifications - Count of mount object event notifications that were generated in the subtree rooted at this node. This excludes events generated on this node itself and does not include superblock events. The counters are also accessible through the FSINFO_ATTR_MOUNT_CHILDREN attribute, where a list of all the children of a mount can be scanned. The record returned for each child includes the sum of the above five counters for that child. An additional record is added at the end for the queried object and that also includes the sum of its five counters The mnt_topology_changes counter is also included in FSINFO_ATTR_MOUNT_TOPOLOGY. Signed-off-by: David Howells <dhowells@xxxxxxxxxx> --- fs/mount_notify.c | 2 ++ fs/namespace.c | 38 +++++++++++++++++++++++++++++++++----- include/uapi/linux/fsinfo.h | 11 ++++++++++- samples/vfs/test-fsinfo.c | 8 ++++++-- 4 files changed, 51 insertions(+), 8 deletions(-) diff --git a/fs/mount_notify.c b/fs/mount_notify.c index 403d79785807..9ca6888e53c2 100644 --- a/fs/mount_notify.c +++ b/fs/mount_notify.c @@ -93,6 +93,8 @@ void notify_mount(struct mount *trigger, n.watch.info = info_flags | watch_sizeof(n); n.triggered_on = trigger->mnt_id; + smp_wmb(); /* See fsinfo_generic_mount_info(). */ + switch (subtype) { case NOTIFY_MOUNT_EXPIRY: case NOTIFY_MOUNT_READONLY: diff --git a/fs/namespace.c b/fs/namespace.c index 61b110149fc5..5427e732c1bf 100644 --- a/fs/namespace.c +++ b/fs/namespace.c @@ -4166,6 +4166,21 @@ int fsinfo_generic_mount_info(struct path *path, struct fsinfo_context *ctx) p->mnt_unique_id = m->mnt_unique_id; p->mnt_id = m->mnt_id; +#ifdef CONFIG_SB_NOTIFICATIONS + p->sb_changes = atomic_read(&sb->s_change_counter); + p->sb_notifications = atomic_read(&sb->s_notify_counter); +#endif +#ifdef CONFIG_MOUNT_NOTIFICATIONS + p->mnt_subtree_notifications = atomic_read(&m->mnt_subtree_notifications); + p->mnt_topology_changes = atomic_read(&m->mnt_topology_changes); + p->mnt_attr_changes = atomic_read(&m->mnt_attr_changes); +#endif + + /* Record the counters before reading the attributes as we're not + * holding a lock. Paired with a write barrier in notify_mount(). + */ + smp_rmb(); + flags = READ_ONCE(m->mnt.mnt_flags); if (flags & MNT_READONLY) p->attr |= MOUNT_ATTR_RDONLY; @@ -4203,6 +4218,7 @@ int fsinfo_generic_mount_topology(struct path *path, struct fsinfo_context *ctx) m = real_mount(path->mnt); + p->mnt_topology_changes = atomic_read(&m->mnt_topology_changes); p->parent_id = m->mnt_parent->mnt_id; if (path->mnt == root.mnt) { @@ -4313,17 +4329,29 @@ int fsinfo_generic_mount_point_full(struct path *path, struct fsinfo_context *ct static void fsinfo_store_mount(struct fsinfo_context *ctx, const struct mount *p) { struct fsinfo_mount_child record = {}; + const struct super_block *sb = p->mnt.mnt_sb; unsigned int usage = ctx->usage; if (ctx->usage >= INT_MAX) return; ctx->usage = usage + sizeof(record); + if (!ctx->buffer || ctx->usage > ctx->buf_size) + return; - if (ctx->buffer && ctx->usage <= ctx->buf_size) { - record.mnt_unique_id = p->mnt_unique_id; - record.mnt_id = p->mnt_id; - memcpy(ctx->buffer + usage, &record, sizeof(record)); - } + record.mnt_unique_id = p->mnt_unique_id; + record.mnt_id = p->mnt_id; + record.notify_sum = 0; +#ifdef CONFIG_SB_NOTIFICATIONS + record.notify_sum += (atomic_read(&sb->s_change_counter) + + atomic_read(&sb->s_notify_counter)); +#endif +#ifdef CONFIG_MOUNT_NOTIFICATIONS + record.notify_sum += (atomic_read(&p->mnt_attr_changes) + + atomic_read(&p->mnt_topology_changes) + + atomic_read(&p->mnt_subtree_notifications)); +#endif + + memcpy(ctx->buffer + usage, &record, sizeof(record)); } /* diff --git a/include/uapi/linux/fsinfo.h b/include/uapi/linux/fsinfo.h index 9410e320d824..85edc3ef2e51 100644 --- a/include/uapi/linux/fsinfo.h +++ b/include/uapi/linux/fsinfo.h @@ -100,6 +100,12 @@ struct fsinfo_mount_info { __u64 mnt_unique_id; /* Kernel-lifetime unique mount ID */ __u32 mnt_id; /* Mount identifier (use with AT_FSINFO_MOUNTID_PATH) */ __u32 attr; /* MOUNT_ATTR_* flags */ + __u32 sb_changes; /* Number of sb configuration changes */ + __u32 sb_notifications; /* Number of other sb notifications */ + __u32 mnt_attr_changes; /* Number of attribute changes to this mount. */ + __u32 mnt_topology_changes; /* Number of topology changes to this mount. */ + __u32 mnt_subtree_notifications; /* Number of notifications in mount subtree */ + __u32 padding[1]; }; #define FSINFO_ATTR_MOUNT_INFO__STRUCT struct fsinfo_mount_info @@ -113,6 +119,7 @@ struct fsinfo_mount_topology { __u32 master_id; /* Slave master group ID */ __u32 from_id; /* Slave propagated from ID */ __u32 propagation; /* MOUNT_PROPAGATION_* flags */ + __u32 mnt_topology_changes; /* Number of topology changes to this mount. */ }; #define FSINFO_ATTR_MOUNT_TOPOLOGY__STRUCT struct fsinfo_mount_topology @@ -124,7 +131,9 @@ struct fsinfo_mount_topology { struct fsinfo_mount_child { __u64 mnt_unique_id; /* Kernel-lifetime unique mount ID */ __u32 mnt_id; /* Mount identifier (use with AT_FSINFO_MOUNTID_PATH) */ - __u32 __padding[1]; + __u32 notify_sum; /* Sum of sb_changes, sb_notifications, mnt_attr_changes, + * mnt_topology_changes and mnt_subtree_notifications. + */ }; #define FSINFO_ATTR_MOUNT_CHILDREN__STRUCT struct fsinfo_mount_child diff --git a/samples/vfs/test-fsinfo.c b/samples/vfs/test-fsinfo.c index 762ab4517cd9..7b2676e1b7b0 100644 --- a/samples/vfs/test-fsinfo.c +++ b/samples/vfs/test-fsinfo.c @@ -297,6 +297,9 @@ static void dump_fsinfo_generic_mount_info(void *reply, unsigned int size) printf("\tmnt_uniq: %llx\n", (unsigned long long)r->mnt_unique_id); printf("\tmnt_id : %x\n", r->mnt_id); printf("\tattr : %x\n", r->attr); + printf("\tsb_nfy : changes=%u other=%u\n", r->sb_changes, r->sb_notifications); + printf("\tmnt_nfy : attr=%u topology=%u subtree=%u\n", + r->mnt_attr_changes, r->mnt_topology_changes, r->mnt_subtree_notifications); } static void dump_fsinfo_generic_mount_topology(void *reply, unsigned int size) @@ -309,6 +312,7 @@ static void dump_fsinfo_generic_mount_topology(void *reply, unsigned int size) printf("\tmaster : %x\n", r->master_id); printf("\tfrom : %x\n", r->from_id); printf("\tpropag : %x\n", r->propagation); + printf("\tmnt_nfy : topology=%u\n", r->mnt_topology_changes); } static void dump_fsinfo_generic_mount_children(void *reply, unsigned int size) @@ -331,8 +335,8 @@ static void dump_fsinfo_generic_mount_children(void *reply, unsigned int size) mp = "<this>"; } - printf("%8x %16llx %s\n", - r->mnt_id, (unsigned long long)r->mnt_unique_id, mp); + printf("%8x %16llx %10u %s\n", + r->mnt_id, (unsigned long long)r->mnt_unique_id, r->notify_sum, mp); } static void dump_string(void *reply, unsigned int size)