* removers may invoke this function concurrently on @kn and all will
* return after draining is complete.
*/
-static void kernfs_drain(struct kernfs_node *kn)
+static void kernfs_drain(struct kernfs_node *kn, bool drop_supers)
__releases(&kernfs_root(kn)->kernfs_rwsem)
__acquires(&kernfs_root(kn)->kernfs_rwsem)
{
return;
up_write(&root->kernfs_rwsem);
+ if (drop_supers)
+ up_read(&root->kernfs_supers_rwsem);
if (kernfs_lockdep(kn)) {
rwsem_acquire(&kn->dep_map, 0, 0, _RET_IP_);
if (kernfs_should_drain_open_files(kn))
kernfs_drain_open_files(kn);
+ if (drop_supers)
+ down_read(&root->kernfs_supers_rwsem);
down_write(&root->kernfs_rwsem);
}
kn->flags |= KERNFS_HIDDEN;
if (kernfs_active(kn))
atomic_add(KN_DEACTIVATED_BIAS, &kn->active);
- kernfs_drain(kn);
+ kernfs_drain(kn, false);
}
up_write(&root->kernfs_rwsem);
}
+/*
+ * This function enables VFS to send fsnotify events for deletions.
+ * There is gap in this implementation for certain file removals due their
+ * unique nature in kernfs. Directory removals that trigger file removals occur
+ * through vfs_rmdir, which shrinks the dcache and emits fsnotify events after
+ * the rmdir operation; there is no issue here. However kernfs writes to
+ * particular files (e.g. cgroup.subtree_control) can also cause file removal,
+ * but vfs_write does not attempt to emit fsnotify events after the write
+ * operation, even if i_nlink counts are 0. As a usecase for monitoring this
+ * category of file removals is not known, they are left without having
+ * IN_DELETE or IN_DELETE_SELF events generated.
+ * Fanotify recursive monitoring also does not work for kernfs nodes that do not
+ * have inodes attached, as they are created on-demand in kernfs.
+ */
+static void kernfs_clear_inode_nlink(struct kernfs_node *kn)
+{
+ struct kernfs_root *root = kernfs_root(kn);
+ struct kernfs_super_info *info;
+
+ lockdep_assert_held_read(&root->kernfs_supers_rwsem);
+
+ list_for_each_entry(info, &root->supers, node) {
+ struct inode *inode = ilookup(info->sb, kernfs_ino(kn));
+
+ if (inode) {
+ clear_nlink(inode);
+ iput(inode);
+ }
+ }
+}
+
static void __kernfs_remove(struct kernfs_node *kn)
{
struct kernfs_node *pos, *parent;
if (!kn)
return;
+ lockdep_assert_held_read(&kernfs_root(kn)->kernfs_supers_rwsem);
lockdep_assert_held_write(&kernfs_root(kn)->kernfs_rwsem);
/*
*/
kernfs_get(pos);
- kernfs_drain(pos);
+ kernfs_drain(pos, true);
parent = kernfs_parent(pos);
/*
* kernfs_unlink_sibling() succeeds once per node. Use it
struct kernfs_iattrs *ps_iattr =
parent ? parent->iattr : NULL;
- /* update timestamps on the parent */
down_write(&kernfs_root(kn)->kernfs_iattr_rwsem);
+ kernfs_clear_inode_nlink(pos);
+
+ /* update timestamps on the parent */
if (ps_iattr) {
ktime_get_real_ts64(&ps_iattr->ia_ctime);
ps_iattr->ia_mtime = ps_iattr->ia_ctime;
root = kernfs_root(kn);
+ down_read(&root->kernfs_supers_rwsem);
down_write(&root->kernfs_rwsem);
__kernfs_remove(kn);
up_write(&root->kernfs_rwsem);
+ up_read(&root->kernfs_supers_rwsem);
}
/**
bool ret;
struct kernfs_root *root = kernfs_root(kn);
+ down_read(&root->kernfs_supers_rwsem);
down_write(&root->kernfs_rwsem);
kernfs_break_active_protection(kn);
break;
up_write(&root->kernfs_rwsem);
+ up_read(&root->kernfs_supers_rwsem);
schedule();
+ down_read(&root->kernfs_supers_rwsem);
down_write(&root->kernfs_rwsem);
}
finish_wait(waitq, &wait);
kernfs_unbreak_active_protection(kn);
up_write(&root->kernfs_rwsem);
+ up_read(&root->kernfs_supers_rwsem);
return ret;
}
}
root = kernfs_root(parent);
+ down_read(&root->kernfs_supers_rwsem);
down_write(&root->kernfs_rwsem);
kn = kernfs_find_ns(parent, name, ns);
}
up_write(&root->kernfs_rwsem);
+ up_read(&root->kernfs_supers_rwsem);
if (kn)
return 0;