Add a change counter on each mount object so that the user can easily check to see if a mount has changed its attributes or its children. Future patches will: (1) Provide this value through fsinfo() attributes. (2) Implement a watch_mount() system call to provide a notification interface for userspace monitoring. Signed-off-by: David Howells <dhowells@xxxxxxxxxx> --- fs/mount.h | 22 ++++++++++++++++++++++ fs/namespace.c | 11 +++++++++++ 2 files changed, 33 insertions(+) diff --git a/fs/mount.h b/fs/mount.h index 711a4093e475..a1625924fe81 100644 --- a/fs/mount.h +++ b/fs/mount.h @@ -72,6 +72,7 @@ struct mount { int mnt_expiry_mark; /* true if marked for expiry */ struct hlist_head mnt_pins; struct hlist_head mnt_stuck_children; + atomic_t mnt_change_counter; /* Number of changed applied */ } __randomize_layout; #define MNT_NS_INTERNAL ERR_PTR(-EINVAL) /* distinct from any mnt_namespace */ @@ -153,3 +154,24 @@ static inline bool is_anon_ns(struct mnt_namespace *ns) { return ns->seq == 0; } + +/* + * Type of mount topology change notification. + */ +enum mount_notification_subtype { + NOTIFY_MOUNT_NEW_MOUNT = 0, /* New mount added */ + NOTIFY_MOUNT_UNMOUNT = 1, /* Mount removed manually */ + NOTIFY_MOUNT_EXPIRY = 2, /* Automount expired */ + NOTIFY_MOUNT_READONLY = 3, /* Mount R/O state changed */ + NOTIFY_MOUNT_SETATTR = 4, /* Mount attributes changed */ + NOTIFY_MOUNT_MOVE_FROM = 5, /* Mount moved from here */ + NOTIFY_MOUNT_MOVE_TO = 6, /* Mount moved to here (compare op_id) */ +}; + +static inline void notify_mount(struct mount *changed, + struct mount *aux, + enum mount_notification_subtype subtype, + u32 info_flags) +{ + atomic_inc(&changed->mnt_change_counter); +} diff --git a/fs/namespace.c b/fs/namespace.c index 85b5f7bea82e..5c84aadb6aa1 100644 --- a/fs/namespace.c +++ b/fs/namespace.c @@ -498,6 +498,8 @@ static int mnt_make_readonly(struct mount *mnt) smp_wmb(); mnt->mnt.mnt_flags &= ~MNT_WRITE_HOLD; unlock_mount_hash(); + if (ret == 0) + notify_mount(mnt, NULL, NOTIFY_MOUNT_READONLY, 0x10000); return ret; } @@ -506,6 +508,7 @@ static int __mnt_unmake_readonly(struct mount *mnt) lock_mount_hash(); mnt->mnt.mnt_flags &= ~MNT_READONLY; unlock_mount_hash(); + notify_mount(mnt, NULL, NOTIFY_MOUNT_READONLY, 0); return 0; } @@ -819,6 +822,7 @@ static struct mountpoint *unhash_mnt(struct mount *mnt) */ static void umount_mnt(struct mount *mnt) { + notify_mount(mnt->mnt_parent, mnt, NOTIFY_MOUNT_UNMOUNT, 0); put_mountpoint(unhash_mnt(mnt)); } @@ -1453,6 +1457,7 @@ static void umount_tree(struct mount *mnt, enum umount_tree_flags how) p = list_first_entry(&tmp_list, struct mount, mnt_list); list_del_init(&p->mnt_expire); list_del_init(&p->mnt_list); + ns = p->mnt_ns; if (ns) { ns->mounts--; @@ -2078,7 +2083,10 @@ static int attach_recursive_mnt(struct mount *source_mnt, lock_mount_hash(); } if (moving) { + notify_mount(source_mnt->mnt_parent, source_mnt, + NOTIFY_MOUNT_MOVE_FROM, 0); unhash_mnt(source_mnt); + notify_mount(dest_mnt, source_mnt, NOTIFY_MOUNT_MOVE_TO, 0); attach_mnt(source_mnt, dest_mnt, dest_mp); touch_mnt_namespace(source_mnt->mnt_ns); } else { @@ -2087,6 +2095,7 @@ static int attach_recursive_mnt(struct mount *source_mnt, list_del_init(&source_mnt->mnt_ns->list); } mnt_set_mountpoint(dest_mnt, dest_mp, source_mnt); + notify_mount(dest_mnt, source_mnt, NOTIFY_MOUNT_NEW_MOUNT, 0); commit_tree(source_mnt); } @@ -2464,6 +2473,7 @@ static void set_mount_attributes(struct mount *mnt, unsigned int mnt_flags) mnt->mnt.mnt_flags = mnt_flags; touch_mnt_namespace(mnt->mnt_ns); unlock_mount_hash(); + notify_mount(mnt, NULL, NOTIFY_MOUNT_SETATTR, 0); } static void mnt_warn_timestamp_expiry(struct path *mountpoint, struct vfsmount *mnt) @@ -2898,6 +2908,7 @@ void mark_mounts_for_expiry(struct list_head *mounts) if (!xchg(&mnt->mnt_expiry_mark, 1) || propagate_mount_busy(mnt, 1)) continue; + notify_mount(mnt, NULL, NOTIFY_MOUNT_EXPIRY, 0); list_move(&mnt->mnt_expire, &graveyard); } while (!list_empty(&graveyard)) {