Skip to contentSkip to conversation

mount-related stuff

RFC Superseded Al Viro 11 participants 330 messages Aug 25, 2025 – Sep 14, 2025 Page 1 of 2

Subsystems (5)
lore
Share & tools
lore

Al Viro

author maintainer
#0 lore raw

Most of this pile is basically an attempt to see how well do
cleanup.h-style mechanisms apply in mount handling. That stuff lives in
git://git.kernel.org/pub/scm/linux/kernel/git/viro/vfs.git #work.mount Rebased to -rc3 (used to be a bit past -rc2, branched at mount fixes merge) Individual patches in followups.

Please, help with review and testing. It seems to survive the
local beating and code generation seems to be OK, but more testing would be a good thing and I would really like to see comments on that stuff.

This is not all I've got around mount handling, but I'd rather
get that thing out for review before starting to sort out other local mount-related branches.

Series overview:

Part 1: guards.

This part starts with infrastructure, followed by one-by-one
conversions to the guard/scoped_guard in some of the places that fit that well enough. Note that one of those places turned out to be taking mount_lock for no reason whatsoever; I already see places where we do write_seqlock when read_seqlock_excl would suffice, etc.

Folks, _please_ don't do any bulk conversions in that area.
IMO one area where RAII becomes dangerous is locking; usually it's not a big deal to delay freeing some object a bit, but delay dropping a lock and you risk introducing deadlocks that will be bloody hard to spot. It _has_ to be done carefully; we had trouble in that area several times over the last year or so in fs/namespace.c alone. Another fun problem is that quite a few comments regarding the locking in there are stale. We still have the comments that talk about mount lock as if it had been an rwlock-like thing. It hadn't been that for more than a decade now. It needs to be documented sanely; so do the access rules to the data structures involved. I hope to get some of that into the tree this cycle, but it's still in progress.

1/52) fs/namespace.c: fix the namespace_sem guard mess
New guards: namespace_excl and namespace_shared. The former implies
the latter, as for anything rwsem-like. No inode locks, no dropping the final references, no opening files, etc. in scope of those.
2/52) introduced guards for mount_lock
New guards: mount_writer, mount_locked_reader. That's write_seqlock
and read_seqlock_excl on mount_lock; obviously, nothing blocking should be done in scope of those.
3/52) fs/namespace.c: allow to drop vfsmount references via __free(mntput)
Missing DEFINE_FREE (for mntput()); local in fs/namespace.c, to be
used only for keeping shit out of namespace_... and mount_... scopes.

4/52)  __detach_mounts(): use guards
5/52)  __is_local_mountpoint(): use guards
6/52)  do_change_type(): use guards
7/52)  do_set_group(): use guards
8/52)  mark_mounts_for_expiry(): use guards
9/52)  put_mnt_ns(): use guards

10/52) mnt_already_visible(): use guards
a bunch of clear-cut conversions, with explanations of the reasons
why this or that guard is needed.
11/52) check_for_nsfs_mounts(): no need to take locks
... and here we have one where it turns out that locking had been
excessive. Iterating through a subtree in mount_locked_reader scope is safe, all right, but (1) mount_writer is not needed here at all and (2) namespace_shared + a reference held to the root of subtree is also enough. All callers had (2) already. Documented the locking requirements for function, removed {,un}lock_mount_hash() in it...
12/52) propagate_mnt(): use scoped_guard(mount_locked_reader) for mnt_set_mountpoint()
This one is interesting - existing code had been equivalent to
scoped_guard(mount_locked_reader), and it's right for that call. However, mnt_set_mountpoint() generally requires mount_writer - the only reason we get away with that here is that the mount in question never had been reachable from the mounts visible to other threads.

13/52)  has_locked_children(): use guards
14/52)  mnt_set_expiry(): use guards
15/52)  path_is_under(): use guards

more clear-cut conversions with explanations.
16/52) current_chrooted(): don't bother with follow_down_one() 17/52) current_chrooted(): use guards
this pair might be better off with #16 taken to the beginning
of the series (or to a separate branch merge into this one); no better reason to do as I had than wanting to keep the guard infrastructure in the very beginning.

Part 2: turning unlock_mount() into __cleanup.

Environment for mounting something on given location consists of:
1) namespace_excl scope
2) parent mount - the one we'll be attaching things to.
3) mountpoint to be, protected from disappearing under us.
4) inode of that mountpoint's dentry held exclusive. Unfortunately, we can't take inode locks in namespace_excl scopes.
And we want to cope with the possibility that somebody has managed to mount something on that place while we'd been taking locks. "Cope" part is simple for finish_automount() ("drop our mount and go away quietly; somebody triggered it before we did"), but for everything else it's trickier - "use whatever's overmounting that place now (with the right locks, please)".
lock_mount() does all of that (do_lock_mount(), actually), with
unlock_mount() closing the scope. And it's definitely a good candidate for __cleanup()-based approach, except that
* the damn thing can return an error and conditional variants of that infrastructure are too revolting.
* parent mount is returned in a fucking awful way - we modify the struct path passed to us as location to mount on and then its ->mnt is the parent to be... except for the "beneath" variant where we play convoluted games with "no, here we want the parent of that". Implementation is also vulnerable to umount propagtion races.
* the structure we set up (everything except the parent) is inserted into a linked list by lock_mount(). That excludes DEFINE_CLASS() - it wants the value formed and then copied to the variable we are defining.
* it contains an implicit namespace_excl scope, so path_put() and its ilk *must* be done after the unlock_mount(). And most of the users have gotos past that.
The first two problems are solved by adding an explicit pointer
to parent mount into struct pinned_mountpoint. Having lock_mount()
failure reported by setting it to ERR_PTR(-E...) allows to avoid the problem with expressing the constructor failure. The third one is dealt with by defining local macros to be used instead of CLASS - I went with LOCK_MOUNT(mp, path) which defines struct pinned_mountpoint mp with __cleanup(unlock_mount) and sets it up. If anybody has better suggestions, I'll be glad to hear those.
The last one is dealt with by massaging the users to form that
would have all post-unlock_mount() stuff done by __free().

First, several trivial cleanups:

18/52)  do_move_mount(): trim local variables
19/52)  do_move_mount(): deal with the checks on old_path early
20/52)  move_mount(2): take sanity checks in 'beneath' case into do_lock_mount()
21/52)  finish_automount(): simplify the ELOOP check

Getting rid of post-unlock_mount() stuff:

22/52)  do_loopback(): use __free(path_put) to deal with old_path
23/52)  pivot_root(2): use __free() to deal with struct path in it
24/52)  finish_automount(): take the lock_mount() analogue into a helper

this one turns the open-coded logics into lock_mount_exact() with
the same kind of calling conventions as lock_mount() and do_lock_mount() 25/52) do_new_mount_rc(): use __free() to deal with dropping mnt on failure 26/52) finish_automount(): use __free() to deal with dropping mnt on failure

This is the main part:
27/52) change calling conventions for lock_mount() et.al.

Followups, cleaning up the games with parent mount in the user:

28/52)  do_move_mount(): use the parent mount returned by do_lock_mount()
29/52)  do_add_mount(): switch to passing pinned_mountpoint instead of mountpoint + path
30/52)  graft_tree(), attach_recursive_mnt() - pass pinned_mountpoint

Part 3: getting rid of mutating struct path there.

do_lock_mount() is still playing silly buggers with struct path it
had been given - the logics in that thing hadn't changed. It's not a pretty function and it's racy as well; the thing is, by this point its users have almost no use for the changed contents of struct path - dentry can be derived from struct mountpoint, parent mount to use is provided directly and we want that a lot more than modified path->mnt. There's only one place (in can_move_mount_beneath()) where we still want that and it's not hard to reconstruct the value by *original* path->mnt value + parent mount to be used.

	Getting rid of ->dentry uses.

31/52) pivot_root(2): use old_mp.mp->m_dentry instead of old.dentry 32/52) don't bother passing new_path->dentry to can_move_mount_beneath()

A helper, already open-coded in a couple of places; carved out of
the next patch to keep it reasonably small
33/52) new helper: topmost_overmount()

Rewrite of do_lock_mount() to keep path constant + trivial change
in do_move_mount() to adjust the argument it passes to can_move_mount_beneath():
34/52) do_lock_mount(): don't modify path.

Part 5: a bunch of trivial cleanups (mostly constifications)

35/52)  constify check_mnt()
36/52)  do_mount_setattr(): constify path argument
37/52)  do_set_group(): constify path arguments
38/52)  drop_collected_paths(): constify arguments
39/52)  collect_paths(): constify the return value
40/52)  do_move_mount(), vfs_move_mount(), do_move_mount_old(): constify struct path argument(s)
41/52)  mnt_warn_timestamp_expiry(): constify struct path argument
42/52)  do_new_mount{,_fc}(): constify struct path argument
43/52)  do_{loopback,change_type,remount,reconfigure_mnt}(): constify struct path argument
44/52)  path_mount(): constify struct path argument
45/52)  may_copy_tree(), __do_loopback(): constify struct path argument
46/52)  path_umount(): constify struct path argument
47/52)  constify can_move_mount_beneath() arguments
48/52)  do_move_mount_old(): use __free(path_put)
49/52)  do_mount(): use __free(path_put)

Part 6: assorted stuff, will grow.

50/52) umount_tree(): take all victims out of propagation graph at once [had been earlier]
For each removed mount we need to calculate where the slaves
will end up. To avoid duplicating that work, do it for all mounts to be removed at once, taking the mounts themselves out of propagation graph as we go, then do all transfers; the duplicate work on finding destinations is avoided since if we run into a mount that already had destination found, we don't need to trace the rest of the way. That's guaranteed O(removed mounts) for finding destinations and removing from propagation graph and O(surviving mounts that have master removed) for transfers.

51/52) ecryptfs: get rid of pointless mount references in ecryptfs dentries

	->lower_path.mnt has the same value for all dentries on given

ecryptfs instance and if somebody goes for mountpoint-crossing variant where that would not be true, we can deal with that when it happens (and _not_ with duplicating these reference into each dentry).
As it is, we are better off just sticking a reference into
ecryptfs-private part of superblock and keeping it pinned until ->kill_sb().
That way we can stick a reference to underlying dentry right into
->d_fsdata of ecryptfs one, getting rid of indirection through struct ecryptfs_dentry_info, along with the entire struct ecryptfs_dentry_info machinery.

52/52) fs/namespace.c: sanitize descriptions for {__,}lookup_mnt()
Comments regarding "shadow mounts" were stale - no such thing
anymore. Document the locking requirements for __lookup_mnt()...

FWIW, the current diffstat:

 fs/ecryptfs/dentry.c          |  14 +-
 fs/ecryptfs/ecryptfs_kernel.h |  27 +-
 fs/ecryptfs/file.c            |  15 +-
 fs/ecryptfs/inode.c           |  19 +-
 fs/ecryptfs/main.c            |  24 +-
 fs/internal.h                 |   4 +-
 fs/mount.h                    |  12 +
 fs/namespace.c                | 775 +++++++++++++++++++-----------------------
 fs/pnode.c                    |  75 ++--
 fs/pnode.h                    |   1 +
 include/linux/mount.h         |   4 +-
 kernel/audit_tree.c           |  12 +-
 12 files changed, 464 insertions(+), 518 deletions(-)
author maintainer
#1 lore raw

PATCH 1/52 fs/namespace.c: fix the namespace_sem guard mess

If anything, namespace_lock should be DEFINE_LOCK_GUARD_0, not DEFINE_GUARD. That way we
* do not need to feed it a bogus argument
* do not get gcc trying to compare an address of static in
file variable with -4097 - and, if we are unlucky, trying to keep it in a register, with spills and all such.

The same problems apply to grabbing namespace_sem shared.

Rename it to namespace_excl, add namespace_shared, convert the existing users:

guard(namespace_lock, &namespace_sem) => guard(namespace_excl)() guard(rwsem_read, &namespace_sem) => guard(namespace_shared)() scoped_guard(namespace_lock, &namespace_sem) => scoped_guard(namespace_excl) scoped_guard(rwsem_read, &namespace_sem) => scoped_guard(namespace_shared)

---
 fs/namespace.c | 18 +++++++++++-------
 1 file changed, 11 insertions(+), 7 deletions(-)
fs/namespace.c +11 -7
index ae6d1312b184..fcea65587ff9 100644
@@ -82,6 +82,12 @@ static LIST_HEAD(ex_mountpoints); /* protected by namespace_sem */
 static struct mnt_namespace *emptied_ns; /* protected by namespace_sem */
 static DEFINE_SEQLOCK(mnt_ns_tree_lock);
 
+static inline void namespace_lock(void);
+static void namespace_unlock(void);
+DEFINE_LOCK_GUARD_0(namespace_excl, namespace_lock(), namespace_unlock())
+DEFINE_LOCK_GUARD_0(namespace_shared, down_read(&namespace_sem),
+				      up_read(&namespace_sem))
+
 #ifdef CONFIG_FSNOTIFY
 LIST_HEAD(notify_list); /* protected by namespace_sem */
 #endif
@@ -1776,8 +1782,6 @@ static inline void namespace_lock(void)
 	down_write(&namespace_sem);
 }
 
-DEFINE_GUARD(namespace_lock, struct rw_semaphore *, namespace_lock(), namespace_unlock())
-
 enum umount_tree_flags {
 	UMOUNT_SYNC = 1,
 	UMOUNT_PROPAGATE = 2,
@@ -2306,7 +2310,7 @@ struct path *collect_paths(const struct path *path,
 	struct path *res = prealloc, *to_free = NULL;
 	unsigned n = 0;
 
-	guard(rwsem_read)(&namespace_sem);
+	guard(namespace_shared)();
 
 	if (!check_mnt(root))
 		return ERR_PTR(-EINVAL);
@@ -2361,7 +2365,7 @@ void dissolve_on_fput(struct vfsmount *mnt)
 			return;
 	}
 
-	scoped_guard(namespace_lock, &namespace_sem) {
+	scoped_guard(namespace_excl) {
 		if (!anon_ns_root(m))
 			return;
 
@@ -2435,7 +2439,7 @@ struct vfsmount *clone_private_mount(const struct path *path)
 	struct mount *old_mnt = real_mount(path->mnt);
 	struct mount *new_mnt;
 
-	guard(rwsem_read)(&namespace_sem);
+	guard(namespace_shared)();
 
 	if (IS_MNT_UNBINDABLE(old_mnt))
 		return ERR_PTR(-EINVAL);
@@ -5957,7 +5961,7 @@ SYSCALL_DEFINE4(statmount, const struct mnt_id_req __user *, req,
 	if (ret)
 		return ret;
 
-	scoped_guard(rwsem_read, &namespace_sem)
+	scoped_guard(namespace_shared)
 		ret = do_statmount(ks, kreq.mnt_id, kreq.mnt_ns_id, ns);
 
 	if (!ret)
@@ -6079,7 +6083,7 @@ SYSCALL_DEFINE4(listmount, const struct mnt_id_req __user *, req,
 	 * We only need to guard against mount topology changes as
 	 * listmount() doesn't care about any mount properties.
 	 */
-	scoped_guard(rwsem_read, &namespace_sem)
+	scoped_guard(namespace_shared)
 		ret = do_listmount(ns, kreq.mnt_id, last_mnt_id, kmnt_ids,
 				   nr_mnt_ids, (flags & LISTMOUNT_REVERSE));
 	if (ret <= 0)
Signature · 1 line
2.47.2
author maintainer
#2 lore raw

PATCH 2/52 introduced guards for mount_lock

mount_writer: write_seqlock; that's an equivalent of {un,}lock_mount_hash()
mount_locked_reader: read_seqlock_excl; these tend to be open-coded.

No bulk conversions, please - if nothing else, quite a few places take use mount_writer form when mount_locked_reader is sufficent. It needs to be dealt with carefully.

---
 fs/mount.h | 5 +++++
 1 file changed, 5 insertions(+)
fs/mount.h +5 -0
index 97737051a8b9..ed8c83ba836a 100644
@@ -154,6 +154,11 @@ static inline void get_mnt_ns(struct mnt_namespace *ns)
 
 extern seqlock_t mount_lock;
 
+DEFINE_LOCK_GUARD_0(mount_writer, write_seqlock(&mount_lock),
+		    write_sequnlock(&mount_lock))
+DEFINE_LOCK_GUARD_0(mount_locked_reader, read_seqlock_excl(&mount_lock),
+		    read_sequnlock_excl(&mount_lock))
+
 struct proc_mounts {
 	struct mnt_namespace *ns;
 	struct path root;
Signature · 1 line
2.47.2
#3 lore raw

Re: [PATCH 02/52] introduced guards for mount_lock

Quoted 2 lines · Al Viro

mount_writer: write_seqlock; that's an equivalent of {un,}lock_mount_hash()
mount_locked_reader: read_seqlock_excl; these tend to be open-coded.

Do we really need the "locked" midfix in there? Doesn't seem to buy any clarity. I'd drop it so the naming is nicely consistent.

Quoted 29 lines

No bulk conversions, please - if nothing else, quite a few places take use mount_writer form when mount_locked_reader is sufficent. It needs to be dealt with carefully.

---
 fs/mount.h | 5 +++++
 1 file changed, 5 insertions(+)
fs/mount.h +5 -0
index 97737051a8b9..ed8c83ba836a 100644
@@ -154,6 +154,11 @@ static inline void get_mnt_ns(struct mnt_namespace *ns)
 
 extern seqlock_t mount_lock;
 
+DEFINE_LOCK_GUARD_0(mount_writer, write_seqlock(&mount_lock),
+		    write_sequnlock(&mount_lock))
+DEFINE_LOCK_GUARD_0(mount_locked_reader, read_seqlock_excl(&mount_lock),
+		    read_sequnlock_excl(&mount_lock))
+
 struct proc_mounts {
 	struct mnt_namespace *ns;
 	struct path root;
Signature · 1 line
2.47.2
author maintainer
#4 lore raw

Re: [PATCH 02/52] introduced guards for mount_lock

Quoted 6 lines · Christian Brauner
Quoted 2 lines · Al Viro

mount_writer: write_seqlock; that's an equivalent of {un,}lock_mount_hash()
mount_locked_reader: read_seqlock_excl; these tend to be open-coded.

Do we really need the "locked" midfix in there? Doesn't seem to buy any clarity. I'd drop it so the naming is nicely consistent.

It's a seqlock. "Readers" is this context are lockless ones - sample/retry under rcu_read_lock() kind. The only difference between writer and locked reader is that locked reader does not disrupt those sample/retry loops.

Note that for something that is never traversed locklessly (expiry lists, lists of children, etc.) locked reader is fine for all accesses, including modifications.

If you have better suggestions re terminology, I'd love to hear those, but simply "writer"/"reader" is misleadingly similar to rw-semaphors/links/whatnot.

Basically, there are 3 kinds of contexts here:
1) lockless, must be under RCU, fairly limited in which pointers they
can traverse, read-only access to structures in question. Must sample the seqcount side of mount_lock first, then verifying that it has not changed after everything.

2) hold the spinlock side of mount_lock, _without_ bumping the seqcount
one. Can be used for reads and writes, as long as the stuff being modified is not among the things that is traversed locklessly. Do not disrupt the previous class, have full exclusion with calles 2 and 3

3) hold the spinlock side of mount_lock, and bump the seqcount one on
entry and leave. Any reads and writes. Full exclusion with classes 2 and 3, invalidates the checks for class 1 (i.e. will push it into retries/fallbacks/ whatnot).

I'm used to "lockless reader" for 1, "writer" for 3. "locked reader" kinda works for 2 - that's what it is wrt things that can be accessed by lockless readers, but for the things that are *not* traversed without a lock it can be actually used as a less disruptive form of 3. Is used that way in mount locking for some of the data structures.

author maintainer
#5 lore raw

Re: [PATCH 02/52] introduced guards for mount_lock

Quoted 11 lines · Al Viro

Basically, there are 3 kinds of contexts here:
1) lockless, must be under RCU, fairly limited in which pointers they
can traverse, read-only access to structures in question. Must sample the seqcount side of mount_lock first, then verifying that it has not changed after everything.

2) hold the spinlock side of mount_lock, _without_ bumping the seqcount
one. Can be used for reads and writes, as long as the stuff being modified is not among the things that is traversed locklessly. Do not disrupt the previous class, have full exclusion with calles 2 and 3

Quoted 4 lines

3) hold the spinlock side of mount_lock, and bump the seqcount one on
entry and leave. Any reads and writes. Full exclusion with classes 2 and 3, invalidates the checks for class 1 (i.e. will push it into retries/fallbacks/ whatnot).

FWIW, partial dump from what I hope to push out as docs:

* all modifications of mount hash chains must be mount_writer.
* only one function is allowed to traverse hash chains - __lookup_mnt().
Important part here is reachability - hash is a shared data structure, but a struct mount instance can be reached that way only if it has parent equal to the argument you've been able to pass to __lookup_mnt().
* callers of __lookup_mnt() must either be at least mount_locked_reader
OR hold rcu_read_lock through the entire thing, sample the seqcount side of mount_lock before the call, validate it afterwards and discard the attempt entirely if validation fails. Note that __legitimize_mnt() contains validation.
* being hashed contributes 1 to refcount.

* (sub)tree topology (encoded in ->mnt_parent, ->mnt_mounts/->mnt_child,
->mnt_mp, ->mnt_mountpoint and ->overmount) is stabilize by either mount_locked_reader OR by namespace_shared + positive refcount for root of subtree.
namespace_shared by itself is *NOT* enough. When the last reference to
mount past the umount_tree() (i.e. already with NULL ->mnt_ns) goes away, anything subtree stuck to it will be detached from it and have its root unhashed and dropped. In other words, such tree (e.g. result of umount -l) decays from root to leaves - once all references to root are gone, it's cut off and all pieces are left to decay. That is done with mount_writer (has to be - there are mount hash changes and for those mount_writer is a hard requirement) and only after the final reference to root has been dropped.
All other topology changes happen with namespace_excl and, at least,
mount_locked_reader. Normally - with mount_writer; the only exception is that setting parent for a newly allocated subtree is fine with mount_locked_reader; we are not hashing it yet (that's done only in commit_tree()), so there's no need to disrupt the lockless readers; note that RCU pathwalk *is* such, so blind use of mount_writer has an effect on performance.

	->mnt_mounts/->mnt_child is never traversed unless the tree is stabilized

by either lock (note that list modifications there are not with ..._rcu() primitives). ->overmount, ->mnt_parent and ->mnt_mountpoint can be; those need sample/validate on the seqcount side; it *would* require mount_write from those who modify them, except that for the ones that had never been reachable yet we don't need to bother. In practice, ->overmount is changed along with the mount hash, so we need mount_writer anyway; ->mnt_parent/->mnt_mountpoint/->mnt_mp need it only for reachable mounts. [[
FWIW, I'm considering the possibility of having copy_tree() delay
hashing all nodes in the copy and having them hashed all at once; fewer disruptions for lockless readers that way. All nodes in the copy are reachable only for the caller; we do need mount_locked_reader for attaching a new node to copy (it has to be inserted into the per-mountpoint lists of mounts), but we don't need to bump the seqcount every time - and we can't hold a spinlock over allocations. It's not even that hard; all we'd need is a bit of a change in commit_tree() and in a couple of places where we create a namespace with more than one node - we have the loops in those places already where we insert the mounts into per-namespace rbtrees; same loops could handle hashing them.
]]

* propagation graph (->mnt_share, ->mnt_slave/->mnt_slave_list,
->mnt_master, ->mnt_group_id, IS_MNT_SHARED()) is modified only under namespace_excl; all accesses are under at least namespace_shared. Only mounts that belong to a namespace may be reached via those; umount_tree() removed all victims from the graph before it returns and it's impossible to include something that isn't a part of some namespace into the graph afterwards.

* ->mnt_expire is accessed (both traversals and modifications)
under mount_locked_reader. No lockless traversals there.

* per-namespace rbtree (->mnt_node linkage) is modified only
under namespace_excl and all traversals are at least namespace_shared. Mount leaving a namespace is removed from that before the end of namespace_excl scope.

* ->mnt_root and ->mnt_sb are assign-once; never changed. So's
->mnt_devname, ->mnt_id and ->mnt_id_unique.

* per-mountpoint mount lists (->mnt_mp_list) are mount_locked_reader
for all accesses (modification and traversal along).

* ->prev_ns is a fucking mess.

* ->mnt_umount has only transient uses; umount_tree() uses it
to link the victims to be dropped at namespace_unlock(), final mntput links the stuck children into a list stashed into ->mnt_stuch_children, also for eventual dropping (by cleanup_mnt()). mount_writer for gathering them into those, nothing for "dissolve and drop everything on the list" - in both cases the lists are visible only to a single thread by that point.

author maintainer
#6 lore raw

Re: [PATCH 02/52] introduced guards for mount_lock

Quoted 10 lines · Al Viro

FWIW, I'm considering the possibility of having copy_tree() delay
hashing all nodes in the copy and having them hashed all at once; fewer disruptions for lockless readers that way. All nodes in the copy are reachable only for the caller; we do need mount_locked_reader for attaching a new node to copy (it has to be inserted into the per-mountpoint lists of mounts), but we don't need to bump the seqcount every time - and we can't hold a spinlock over allocations. It's not even that hard; all we'd need is a bit of a change in commit_tree() and in a couple of places where we create a namespace with more than one node - we have the loops in those places already where we insert the mounts into per-namespace rbtrees; same loops could handle hashing them.

The main issue I'm having with that is that currently "in list of children" implies "hashed"; equivalent, even, except for a transient state seen only in mount_writer. OTOH, having that not true for unreachable mounts... I'm trying to find anything that might care, but I don't see any candidates.

It would be nice to have regardless of doing fewer mount_lock seqcount bumps - better isolation from shared data structures until we glue them in place would make for simpler correctness proofs...

Anyway,
copy_tree() call chains:
1. copy_tree() <- propagate_mnt() <- attach_recursive_mnt(), with the call chain prior to that point being one the

		<- graft_tree() <- do_loopback()
		<- graft_tree() <- do_add_mount() <- do_new_mount_fc()
		<- graft_tree() <- do_add_mount() <- finish_automount()
		<- do_move_mount().

All of those start inside a lock_mount scope.
Result gets passed (prior to return from attach_recursive_mnt(), within an mnt_writer scope there) either to commit_tree() or to umount_tree(), without having been visible to others prior to that.
That's creation of secondary copies from mount propagation, for
various pathways to mounting stuff.

2. copy_tree() <- __do_loopback() <- do_loopback(). Inside a lock_mount scope. Result gets passed into graft_tree() -> attach_recursive_mnt(). In the latter either it gets passed to commit_tree() (within mount_writer scope, without having been visible to others prior to that), in which case success is reported, or it is left alone and error gets reported; in that case back in do_loopback() it gets passed to umount_tree(), again in mount_writer scope and without having been visible to others prior to that.

	That's MS_BIND|MS_REC mount(2).

3. copy_tree() <- __do_loopback() <- open_detached_copy(). In namespace_excl scope. Result is fed through a loop that inserts those mounts into rbtree of new namespace (in mount_writer scope) and its root is stored as ->root of that new namespace. Once out of namespace_excl scope, the tree becomes visible (and an extra reference is attached to the file we are opening). That's open_tree(2)/open_tree_attr(2) with OPEN_TREE_CLONE. BTW, a bit of mystery there: insertions into rbtree don't need to be in
mount_writer - we do have places where it's done without that, all readers are in namespace_shared scopes *and* the namespace, along with its rbtree, is not visible to anyone yet to start with. If we delay hashing until there it will need mount_writer, though.

4. copy_tree() <- copy_mnt_ns(). In namespace_excl scope. Somewhat similar to the previous, but the namespace is not an anonymous one and we have a couple of extra passes - one might do lock_mnt_tree() (under mount_writer, almost certainly excessive - mount_locked_reader would do just fine) and another (combined with rbtree insertions) finds the counterparts of root and pwd of the caller and flips over to those. Old ones get dropped after we leave the scope.

Looks like we should be able to unify quite a bit of logics in populating a new namespace and yes, delaying hash insertions past copy_tree() looks plausible...

Incidentally, destruction of new namespace on copy_tree() failure
is another mystery: here we do

                ns_free_inum(&new_ns->ns);
		dec_mnt_namespaces(new_ns->ucounts);
		mnt_ns_release(new_ns);
and in open_detached_copy() it's
	free_mnt_ns(ns);

They are similar - free_mnt_ns() is

	if (!is_anon_ns(ns))
		ns_free_inum(&ns->ns);
	dec_mnt_namespaces(ns->ucounts);
	mnt_ns_tree_remove(ns);

and mnt_ns_tree_remove() is a bunch of !is_anon_ns() code, followed by an rcu-delayed mnt_ns_release(). So in case of open_detached_copy(), where the namespace is anonymous, it boils down to an RCU-delayed call of mnt_ns_release()...

AFAICS the only possible reasons not to use free_mnt_ns() here are
1) avoiding an RCU-delayed call and
2) conditional removal of ns from mnt_ns_tree.

As for the second, couldn't we simply use !list_empty(&ns->mnt_ns_list) as a condition? And avoiding an RCU delay... nice, in principle, but the case when that would've saved us anything is CLONE_NEWNS clone(2) or unshare(2) failing due to severe OOM. Do we give a damn about one extra call_rcu() for each of such failures?

mnt_ns_tree handling is your code; do you see any problems with

static void mnt_ns_tree_remove(struct mnt_namespace *ns)

{
	/* remove from global mount namespace list */
	if (!list_empty(&ns->mnt_ns_list)) {
		mnt_ns_tree_write_lock();
		rb_erase(&ns->mnt_ns_tree_node, &mnt_ns_tree);
		list_bidir_del_rcu(&ns->mnt_ns_list);
		mnt_ns_tree_write_unlock();
	}

	call_rcu(&ns->mnt_ns_rcu, mnt_ns_release_rcu);
}
and
	mnt = __do_loopback(path, recursive);
	if (IS_ERR(mnt)) {
		emptied_ns = ns;
		namespace_unlock();
		return ERR_CAST(mnt);
	}
in open_detached_copy() and
	new = copy_tree(old, old->mnt.mnt_root, copy_flags);
	if (IS_ERR(new)) {
		emptied_ns = new_ns;
		namespace_unlock();
		return ERR_CAST(new);
	}

in copy_mnt_ns()?

author maintainer
#7 lore raw

Re: [PATCH 02/52] introduced guards for mount_lock

Quoted 7 lines · Al Viro

As for the second, couldn't we simply use !list_empty(&ns->mnt_ns_list) as a condition? And avoiding an RCU delay... nice, in principle, but the case when that would've saved us anything is CLONE_NEWNS clone(2) or unshare(2) failing due to severe OOM. Do we give a damn about one extra call_rcu() for each of such failures?

mnt_ns_tree handling is your code; do you see any problems with

... this (on top of the posted series, needs to be carved into several parts - dropping pointless lock_mount_hash() in open_detached_copy(), making mnt_ns_tree_remove() and thus free_mnt_ns() safe to use on ns not in mnt_ns_tree yet, then dealing with open_detached_copy() and copy_mnt_ns() separately):

fs/namespace.c +23 -27
index 63b74d7384fd..b77469789f82 100644
@@ -195,7 +195,7 @@ static void mnt_ns_release_rcu(struct rcu_head *rcu)
 static void mnt_ns_tree_remove(struct mnt_namespace *ns)
 {
 	/* remove from global mount namespace list */
-	if (!is_anon_ns(ns)) {
+	if (!list_empty(&ns->mnt_ns_list)) {
 		mnt_ns_tree_write_lock();
 		rb_erase(&ns->mnt_ns_tree_node, &mnt_ns_tree);
 		list_bidir_del_rcu(&ns->mnt_ns_list);
@@ -3053,18 +3053,17 @@ static int do_loopback(const struct path *path, const char *old_name,
 	return err;
 }
 
-static struct file *open_detached_copy(struct path *path, bool recursive)
+static struct mnt_namespace *get_detached_copy(const struct path *path, bool recursive)
 {
 	struct mnt_namespace *ns, *mnt_ns = current->nsproxy->mnt_ns, *src_mnt_ns;
 	struct user_namespace *user_ns = mnt_ns->user_ns;
 	struct mount *mnt, *p;
-	struct file *file;
 
 	ns = alloc_mnt_ns(user_ns, true);
 	if (IS_ERR(ns))
-		return ERR_CAST(ns);
+		return ns;
 
-	namespace_lock();
+	guard(namespace_excl)();
 
 	/*
 	 * Record the sequence number of the source mount namespace.
@@ -3081,23 +3080,28 @@ static struct file *open_detached_copy(struct path *path, bool recursive)
 
 	mnt = __do_loopback(path, recursive);
 	if (IS_ERR(mnt)) {
-		namespace_unlock();
-		free_mnt_ns(ns);
+		emptied_ns = ns;
 		return ERR_CAST(mnt);
 	}
 
-	lock_mount_hash();
 	for (p = mnt; p; p = next_mnt(p, mnt)) {
 		mnt_add_to_ns(ns, p);
 		ns->nr_mounts++;
 	}
 	ns->root = mnt;
-	mntget(&mnt->mnt);
-	unlock_mount_hash();
-	namespace_unlock();
+	return ns;
+}
+
+static struct file *open_detached_copy(struct path *path, bool recursive)
+{
+	struct mnt_namespace *ns = get_detached_copy(path, recursive);
+	struct file *file;
+
+	if (IS_ERR(ns))
+		return ERR_CAST(ns);
 
 	mntput(path->mnt);
-	path->mnt = &mnt->mnt;
+	path->mnt = mntget(&ns->root->mnt);
 	file = dentry_open(path, O_PATH, current_cred());
 	if (IS_ERR(file))
 		dissolve_on_fput(path->mnt);
@@ -4165,7 +4169,8 @@ struct mnt_namespace *copy_mnt_ns(unsigned long flags, struct mnt_namespace *ns,
 		struct user_namespace *user_ns, struct fs_struct *new_fs)
 {
 	struct mnt_namespace *new_ns;
-	struct vfsmount *rootmnt = NULL, *pwdmnt = NULL;
+	struct vfsmount *rootmnt __free(mntput)= NULL;
+	struct vfsmount *pwdmnt __free(mntput) = NULL;
 	struct mount *p, *q;
 	struct mount *old;
 	struct mount *new;
@@ -4184,23 +4189,20 @@ struct mnt_namespace *copy_mnt_ns(unsigned long flags, struct mnt_namespace *ns,
 	if (IS_ERR(new_ns))
 		return new_ns;
 
-	namespace_lock();
+	guard(namespace_excl)();
 	/* First pass: copy the tree topology */
 	copy_flags = CL_COPY_UNBINDABLE | CL_EXPIRE;
 	if (user_ns != ns->user_ns)
 		copy_flags |= CL_SLAVE;
 	new = copy_tree(old, old->mnt.mnt_root, copy_flags);
 	if (IS_ERR(new)) {
-		namespace_unlock();
-		ns_free_inum(&new_ns->ns);
-		dec_mnt_namespaces(new_ns->ucounts);
-		mnt_ns_release(new_ns);
+		emptied_ns = new_ns;
 		return ERR_CAST(new);
 	}
+
 	if (user_ns != ns->user_ns) {
-		lock_mount_hash();
-		lock_mnt_tree(new);
-		unlock_mount_hash();
+		scoped_guard(mount_writer)
+			lock_mnt_tree(new);
 	}
 	new_ns->root = new;
 
@@ -4232,12 +4234,6 @@ struct mnt_namespace *copy_mnt_ns(unsigned long flags, struct mnt_namespace *ns,
 		while (p->mnt.mnt_root != q->mnt.mnt_root)
 			p = next_mnt(skip_mnt_tree(p), old);
 	}
-	namespace_unlock();
-
-	if (rootmnt)
-		mntput(rootmnt);
-	if (pwdmnt)
-		mntput(pwdmnt);
 
 	mnt_ns_tree_add(new_ns);
 	return new_ns;
#8 lore raw

Re: [PATCH 02/52] introduced guards for mount_lock

In reply to Al Viro

Al Viro <[email protected]>:

Quoted 8 lines

When the last reference to
mount past the umount_tree() (i.e. already with NULL ->mnt_ns) goes away, anything subtree stuck to it will be detached from it and have its root unhashed and dropped. In other words, such tree (e.g. result of umount -l) decays from root to leaves - once all references to root are gone, it's cut off and all pieces are left to decay. That is done with mount_writer (has to be - there are mount hash changes and for those mount_writer is a hard requirement) and only after the final reference to root has been dropped.

I'm unable to understand this.

As well as I understand your text, when you unmount some directory /a using "umount -l /a", then /a and all its children will stay as long as there are references to /a . This contradicts to reality.

Consider this:

# mount -t tmpfs tmpfs /a
# mkdir /a/b
# mount -t tmpfs tmpfs /a/b
# mkdir /a/b/c
# cd /a
# umount -l /a

According to your text, both /a and /a/b will stay, because we have reference to /a (via our cwd).

But in reality /a/b disappears immidiately (i. e. "ls b" shows nothing, as opposed to "c").

This happens even if I test with your patches applied.

So, your explanation seems to be wrong.

Signature · 1 line
Askar Safin
author maintainer
#9 lore raw

Re: [PATCH 02/52] introduced guards for mount_lock

Quoted 24 lines · Askar Safin

Al Viro <[email protected]>:

Quoted 8 lines

When the last reference to
mount past the umount_tree() (i.e. already with NULL ->mnt_ns) goes away, anything subtree stuck to it will be detached from it and have its root unhashed and dropped. In other words, such tree (e.g. result of umount -l) decays from root to leaves - once all references to root are gone, it's cut off and all pieces are left to decay. That is done with mount_writer (has to be - there are mount hash changes and for those mount_writer is a hard requirement) and only after the final reference to root has been dropped.

I'm unable to understand this.

As well as I understand your text, when you unmount some directory /a using "umount -l /a", then /a and all its children will stay as long as there are references to /a . This contradicts to reality.

Consider this:

# mount -t tmpfs tmpfs /a
# mkdir /a/b
# mount -t tmpfs tmpfs /a/b
# mkdir /a/b/c
# cd /a
# umount -l /a
Quoted 7 lines

According to your text, both /a and /a/b will stay, because we have reference to /a (via our cwd).

But in reality /a/b disappears immidiately (i. e. "ls b" shows nothing, as opposed to "c").

This happens even if I test with your patches applied.

So, your explanation seems to be wrong.

Take a look at disconnect_mount(). For example, if mount is locked (== propagated across the userns boundary), it will remain stuck to its parent.

author maintainer
#10 lore raw

PATCH 3/52 fs/namespace.c: allow to drop vfsmount references via __free(mntput)

In reply to Al Viro

Note that just as path_put, it should never be done in scope of namespace_sem, be it shared or exclusive.

---
 fs/namespace.c | 2 ++
 1 file changed, 2 insertions(+)
fs/namespace.c +2 -0
index fcea65587ff9..767ab751ee2a 100644
@@ -88,6 +88,8 @@ DEFINE_LOCK_GUARD_0(namespace_excl, namespace_lock(), namespace_unlock())
 DEFINE_LOCK_GUARD_0(namespace_shared, down_read(&namespace_sem),
 				      up_read(&namespace_sem))
 
+DEFINE_FREE(mntput, struct vfsmount *, if (!IS_ERR(_T)) mntput(_T))
+
 #ifdef CONFIG_FSNOTIFY
 LIST_HEAD(notify_list); /* protected by namespace_sem */
 #endif
Signature · 1 line
2.47.2
author maintainer
#12 lore raw

PATCH 4/52 __detach_mounts(): use guards

In reply to Al Viro

Clean fit for guards use; guards can't be weaker due to umount_tree() calls.

---
 fs/namespace.c | 10 ++++------
 1 file changed, 4 insertions(+), 6 deletions(-)
fs/namespace.c +4 -6
index 767ab751ee2a..1ae1ab8815c9 100644
@@ -2032,10 +2032,11 @@ void __detach_mounts(struct dentry *dentry)
 	struct pinned_mountpoint mp = {};
 	struct mount *mnt;
 
-	namespace_lock();
-	lock_mount_hash();
+	guard(namespace_excl)();
+	guard(mount_writer)();
+
 	if (!lookup_mountpoint(dentry, &mp))
-		goto out_unlock;
+		return;
 
 	event++;
 	while (mp.node.next) {
@@ -2047,9 +2048,6 @@ void __detach_mounts(struct dentry *dentry)
 		else umount_tree(mnt, UMOUNT_CONNECTED);
 	}
 	unpin_mountpoint(&mp);
-out_unlock:
-	unlock_mount_hash();
-	namespace_unlock();
 }
 
 /*
Signature · 1 line
2.47.2
author maintainer
#14 lore raw

PATCH 5/52 __is_local_mountpoint(): use guards

In reply to Al Viro

clean fit; namespace_shared due to iterating through ns->mounts.

---
 fs/namespace.c | 15 ++++++---------
 1 file changed, 6 insertions(+), 9 deletions(-)
fs/namespace.c +6 -9
index 1ae1ab8815c9..f1460ddd1486 100644
@@ -906,17 +906,14 @@ bool __is_local_mountpoint(const struct dentry *dentry)
 {
 	struct mnt_namespace *ns = current->nsproxy->mnt_ns;
 	struct mount *mnt, *n;
-	bool is_covered = false;
 
-	down_read(&namespace_sem);
-	rbtree_postorder_for_each_entry_safe(mnt, n, &ns->mounts, mnt_node) {
-		is_covered = (mnt->mnt_mountpoint == dentry);
-		if (is_covered)
-			break;
-	}
-	up_read(&namespace_sem);
+	guard(namespace_shared)();
+
+	rbtree_postorder_for_each_entry_safe(mnt, n, &ns->mounts, mnt_node)
+		if (mnt->mnt_mountpoint == dentry)
+			return true;
 
-	return is_covered;
+	return false;
 }
 
 struct pinned_mountpoint {
Signature · 1 line
2.47.2
author maintainer
#16 lore raw

PATCH 6/52 do_change_type(): use guards

In reply to Al Viro

clean fit; namespace_excl to modify propagation graph

---
 fs/namespace.c | 13 ++++++-------
 1 file changed, 6 insertions(+), 7 deletions(-)
fs/namespace.c +6 -7
index f1460ddd1486..a6a7b068770a 100644
@@ -2899,7 +2899,7 @@ static int do_change_type(struct path *path, int ms_flags)
 	struct mount *mnt = real_mount(path->mnt);
 	int recurse = ms_flags & MS_REC;
 	int type;
-	int err = 0;
+	int err;
 
 	if (!path_mounted(path))
 		return -EINVAL;
@@ -2908,23 +2908,22 @@ static int do_change_type(struct path *path, int ms_flags)
 	if (!type)
 		return -EINVAL;
 
-	namespace_lock();
+	guard(namespace_excl)();
+
 	err = may_change_propagation(mnt);
 	if (err)
-		goto out_unlock;
+		return err;
 
 	if (type == MS_SHARED) {
 		err = invent_group_ids(mnt, recurse);
 		if (err)
-			goto out_unlock;
+			return err;
 	}
 
 	for (m = mnt; m; m = (recurse ? next_mnt(m, mnt) : NULL))
 		change_mnt_propagation(m, type);
 
- out_unlock:
-	namespace_unlock();
-	return err;
+	return 0;
 }
 
 /* may_copy_tree() - check if a mount tree can be copied
Signature · 1 line
2.47.2
author maintainer
#18 lore raw

PATCH 7/52 do_set_group(): use guards

In reply to Al Viro

clean fit; namespace_excl to modify propagation graph

---
 fs/namespace.c | 33 +++++++++++++--------------------
 1 file changed, 13 insertions(+), 20 deletions(-)
fs/namespace.c +13 -20
index a6a7b068770a..13e2f3837a26 100644
@@ -3349,47 +3349,44 @@ static inline int tree_contains_unbindable(struct mount *mnt)
 
 static int do_set_group(struct path *from_path, struct path *to_path)
 {
-	struct mount *from, *to;
+	struct mount *from = real_mount(from_path->mnt);
+	struct mount *to = real_mount(to_path->mnt);
 	int err;
 
-	from = real_mount(from_path->mnt);
-	to = real_mount(to_path->mnt);
-
-	namespace_lock();
+	guard(namespace_excl)();
 
 	err = may_change_propagation(from);
 	if (err)
-		goto out;
+		return err;
 	err = may_change_propagation(to);
 	if (err)
-		goto out;
+		return err;
 
-	err = -EINVAL;
 	/* To and From paths should be mount roots */
 	if (!path_mounted(from_path))
-		goto out;
+		return -EINVAL;
 	if (!path_mounted(to_path))
-		goto out;
+		return -EINVAL;
 
 	/* Setting sharing groups is only allowed across same superblock */
 	if (from->mnt.mnt_sb != to->mnt.mnt_sb)
-		goto out;
+		return -EINVAL;
 
 	/* From mount root should be wider than To mount root */
 	if (!is_subdir(to->mnt.mnt_root, from->mnt.mnt_root))
-		goto out;
+		return -EINVAL;
 
 	/* From mount should not have locked children in place of To's root */
 	if (__has_locked_children(from, to->mnt.mnt_root))
-		goto out;
+		return -EINVAL;
 
 	/* Setting sharing groups is only allowed on private mounts */
 	if (IS_MNT_SHARED(to) || IS_MNT_SLAVE(to))
-		goto out;
+		return -EINVAL;
 
 	/* From should not be private */
 	if (!IS_MNT_SHARED(from) && !IS_MNT_SLAVE(from))
-		goto out;
+		return -EINVAL;
 
 	if (IS_MNT_SLAVE(from)) {
 		hlist_add_behind(&to->mnt_slave, &from->mnt_slave);
@@ -3401,11 +3398,7 @@ static int do_set_group(struct path *from_path, struct path *to_path)
 		list_add(&to->mnt_share, &from->mnt_share);
 		set_mnt_shared(to);
 	}
-
-	err = 0;
-out:
-	namespace_unlock();
-	return err;
+	return 0;
 }
 
 /**
Signature · 1 line
2.47.2
author maintainer
#20 lore raw

PATCH 8/52 mark_mounts_for_expiry(): use guards

In reply to Al Viro

Clean fit; guards can't be weaker due to umount_tree() calls.

---
 fs/namespace.c | 6 ++----
 1 file changed, 2 insertions(+), 4 deletions(-)
fs/namespace.c +2 -4
index 13e2f3837a26..898a6b7307e4 100644
@@ -3886,8 +3886,8 @@ void mark_mounts_for_expiry(struct list_head *mounts)
 	if (list_empty(mounts))
 		return;
 
-	namespace_lock();
-	lock_mount_hash();
+	guard(namespace_excl)();
+	guard(mount_writer)();
 
 	/* extract from the expiration list every vfsmount that matches the
 	 * following criteria:
@@ -3909,8 +3909,6 @@ void mark_mounts_for_expiry(struct list_head *mounts)
 		touch_mnt_namespace(mnt->mnt_ns);
 		umount_tree(mnt, UMOUNT_PROPAGATE|UMOUNT_SYNC);
 	}
-	unlock_mount_hash();
-	namespace_unlock();
 }
 
 EXPORT_SYMBOL_GPL(mark_mounts_for_expiry);
Signature · 1 line
2.47.2
author maintainer
#22 lore raw

PATCH 9/52 put_mnt_ns(): use guards

In reply to Al Viro

clean fit; guards can't be weaker due to umount_tree() call. Setting emptied_ns requires namespace_excl, but not anything mount_lock-related.

---
 fs/namespace.c | 6 ++----
 1 file changed, 2 insertions(+), 4 deletions(-)
fs/namespace.c +2 -4
index 898a6b7307e4..86a86be2b0ef 100644
@@ -6153,12 +6153,10 @@ void put_mnt_ns(struct mnt_namespace *ns)
 {
 	if (!refcount_dec_and_test(&ns->ns.count))
 		return;
-	namespace_lock();
+	guard(namespace_excl)();
 	emptied_ns = ns;
-	lock_mount_hash();
+	guard(mount_writer)();
 	umount_tree(ns->root, 0);
-	unlock_mount_hash();
-	namespace_unlock();
 }
 
 struct vfsmount *kern_mount(struct file_system_type *type)
Signature · 1 line
2.47.2
#24 lore raw

Re: [PATCH 09/52] put_mnt_ns(): use guards

In reply to Al Viro
Quoted 14 lines · Al Viro

clean fit; guards can't be weaker due to umount_tree() call. Setting emptied_ns requires namespace_excl, but not anything mount_lock-related.

---
 fs/namespace.c | 6 ++----
 1 file changed, 2 insertions(+), 4 deletions(-)
fs/namespace.c +0 -0
index 898a6b7307e4..86a86be2b0ef 100644

fs/namespace.c void put_mnt_ns(struct mnt_namespace *ns) · line 6157

Quoted 6 lines
fs/namespace.c +1 -1
@@ -6153,12 +6153,10 @@ void put_mnt_ns(struct mnt_namespace *ns)
 {
 	if (!refcount_dec_and_test(&ns->ns.count))
 		return;
-	namespace_lock();
+	guard(namespace_excl)();
 	emptied_ns = ns;

Another thing, did I miss

commit aab771f34e63ef89e195b63d121abcb55eebfde6

Author:     Al Viro <[email protected]>
AuthorDate: Wed Jun 18 18:23:41 2025 -0400
Commit:     Al Viro <[email protected]>
CommitDate: Sun Jun 29 19:03:46 2025 -0400

take freeing of emptied mnt_namespace to namespace_unlock()

on the list somehow? I just saw that "emptied_ns" thing for the first time and was very confused where that came from. I don't see any lore link attached to the commit message.

author maintainer
#25 lore raw

Re: [PATCH 09/52] put_mnt_ns(): use guards

Quoted 8 lines · Christian Brauner

Another thing, did I miss

commit aab771f34e63ef89e195b63d121abcb55eebfde6

Author:     Al Viro <[email protected]>
AuthorDate: Wed Jun 18 18:23:41 2025 -0400
Commit:     Al Viro <[email protected]>
CommitDate: Sun Jun 29 19:03:46 2025 -0400
Quoted 5 lines

take freeing of emptied mnt_namespace to namespace_unlock()

on the list somehow? I just saw that "emptied_ns" thing for the first time and was very confused where that came from. I don't see any lore link attached to the commit message.

https://lore.kernel.org/all/[email protected]/

and

https://lore.kernel.org/all/[email protected]/

in the next iteration of the same patchset, both Cc'd to you.

As for the reasons, there are nasty hidden constraints caused by mount notifications; even though all mounts are out of that namespace, we can't free it until the calls of mnt_notify(), which come from notify_mnt_list(), from namespace_unlock().

Better handle it that way than have a recurring headache; besides, it helps with cleaning post-unlock_mount() stuff.

author maintainer
#26 lore raw

PATCH 10/52 mnt_already_visible(): use guards

In reply to Al Viro

clean fit; namespace_shared due to iterating through ns->mounts.

---
 fs/namespace.c | 10 +++-------
 1 file changed, 3 insertions(+), 7 deletions(-)
fs/namespace.c +3 -7
index 86a86be2b0ef..a5d37b97088f 100644
@@ -6232,9 +6232,8 @@ static bool mnt_already_visible(struct mnt_namespace *ns,
 {
 	int new_flags = *new_mnt_flags;
 	struct mount *mnt, *n;
-	bool visible = false;
 
-	down_read(&namespace_sem);
+	guard(namespace_shared)();
 	rbtree_postorder_for_each_entry_safe(mnt, n, &ns->mounts, mnt_node) {
 		struct mount *child;
 		int mnt_flags;
@@ -6281,13 +6280,10 @@ static bool mnt_already_visible(struct mnt_namespace *ns,
 		/* Preserve the locked attributes */
 		*new_mnt_flags |= mnt_flags & (MNT_LOCK_READONLY | \
 					       MNT_LOCK_ATIME);
-		visible = true;
-		goto found;
+		return true;
 	next:	;
 	}
-found:
-	up_read(&namespace_sem);
-	return visible;
+	return false;
 }
 
 static bool mount_too_revealing(const struct super_block *sb, int *new_mnt_flags)
Signature · 1 line
2.47.2
author maintainer
#28 lore raw

PATCH 11/52 check_for_nsfs_mounts(): no need to take locks

In reply to Al Viro

Currently we are taking mount_writer; what that function needs is either mount_locked_reader (we are not changing anything, we just want to iterate through the subtree) or namespace_shared and a reference held by caller on the root of subtree - that's also enough to stabilize the topology.

The thing is, all callers are already holding at least namespace_shared as well as a reference to the root of subtree.

Let's make the callers provide locking warranties - don't mess with mount_lock in check_for_nsfs_mounts() itself and document the locking requirements.

---
 fs/namespace.c | 16 +++++-----------
 1 file changed, 5 insertions(+), 11 deletions(-)
fs/namespace.c +5 -11
index a5d37b97088f..59948cbf9c47 100644
@@ -2402,21 +2402,15 @@ bool has_locked_children(struct mount *mnt, struct dentry *dentry)
  * specified subtree.  Such references can act as pins for mount namespaces
  * that aren't checked by the mount-cycle checking code, thereby allowing
  * cycles to be made.
+ *
+ * locks: mount_locked_reader || namespace_shared && pinned(subtree)
  */
 static bool check_for_nsfs_mounts(struct mount *subtree)
 {
-	struct mount *p;
-	bool ret = false;
-
-	lock_mount_hash();
-	for (p = subtree; p; p = next_mnt(p, subtree))
+	for (struct mount *p = subtree; p; p = next_mnt(p, subtree))
 		if (mnt_ns_loop(p->mnt.mnt_root))
-			goto out;
-
-	ret = true;
-out:
-	unlock_mount_hash();
-	return ret;
+			return false;
+	return true;
 }
 
 /**
Signature · 1 line
2.47.2
maintainer Reviewed-by on v1 11/52
#29 lore raw

Re: [PATCH 11/52] check_for_nsfs_mounts(): no need to take locks

Quoted 9 lines · Al Viro

Currently we are taking mount_writer; what that function needs is either mount_locked_reader (we are not changing anything, we just want to iterate through the subtree) or namespace_shared and a reference held by caller on the root of subtree - that's also enough to stabilize the topology.

The thing is, all callers are already holding at least namespace_shared as well as a reference to the root of subtree.

Quoted 6 lines

Let's make the callers provide locking warranties - don't mess with mount_lock in check_for_nsfs_mounts() itself and document the locking requirements.

---

author maintainer
#30 lore raw

PATCH 12/52 propagate_mnt(): use scoped_guard(mount_locked_reader) for mnt_set_mountpoint()

In reply to Al Viro
---
 fs/pnode.c | 5 ++---
 1 file changed, 2 insertions(+), 3 deletions(-)
fs/pnode.c +2 -3
index 6f7d02f3fa98..0702d45d856d 100644
@@ -304,9 +304,8 @@ int propagate_mnt(struct mount *dest_mnt, struct mountpoint *dest_mp,
 				err = PTR_ERR(this);
 				break;
 			}
-			read_seqlock_excl(&mount_lock);
-			mnt_set_mountpoint(n, dest_mp, this);
-			read_sequnlock_excl(&mount_lock);
+			scoped_guard(mount_locked_reader)
+				mnt_set_mountpoint(n, dest_mp, this);
 			if (n->mnt_master)
 				SET_MNT_MARK(n->mnt_master);
 			copy = this;
Signature · 1 line
2.47.2
author maintainer
#32 lore raw

PATCH 13/52 has_locked_children(): use guards

In reply to Al Viro

... and document the locking requirements of __has_locked_children()

---
 fs/namespace.c | 9 +++------
 1 file changed, 3 insertions(+), 6 deletions(-)
fs/namespace.c +3 -6
index 59948cbf9c47..eabb0d996c6a 100644
@@ -2373,6 +2373,7 @@ void dissolve_on_fput(struct vfsmount *mnt)
 	}
 }
 
+/* locks: namespace_shared && pinned(mnt) || mount_locked_reader */
 static bool __has_locked_children(struct mount *mnt, struct dentry *dentry)
 {
 	struct mount *child;
@@ -2389,12 +2390,8 @@ static bool __has_locked_children(struct mount *mnt, struct dentry *dentry)
 
 bool has_locked_children(struct mount *mnt, struct dentry *dentry)
 {
-	bool res;
-
-	read_seqlock_excl(&mount_lock);
-	res = __has_locked_children(mnt, dentry);
-	read_sequnlock_excl(&mount_lock);
-	return res;
+	scoped_guard(mount_locked_reader)
+		return __has_locked_children(mnt, dentry);
 }
 
 /*
Signature · 1 line
2.47.2
#33 lore raw

Re: [PATCH 13/52] has_locked_children(): use guards

[ diff edited to be just the end result ]

fs/namespace.c static bool __has_locked_children(struct mount *mnt, struct dentry *dentry) · line 2395

Quoted 6 lines · Al Viro

bool has_locked_children(struct mount *mnt, struct dentry *dentry) {

fs/namespace.c +2 -0
+       scoped_guard(mount_locked_reader)
+               return __has_locked_children(mnt, dentry);
 }

So the use of scoped_guard() looks a bit odd to me. Why create a new scope for when the existing scope is identical? It would seem to be more straightforward to just do

        guard(mount_locked_reader);
        return __has_locked_children(mnt, dentry);

instead. Was there some code generation issue or other thing that made you go the 'scoped' way?

There was at least one other patch that did the same pattern (but I haven't gone through the whole series, maybe there are explanations later).

Linus

author maintainer
#34 lore raw

Re: [PATCH 13/52] has_locked_children(): use guards

Quoted 17 lines · Linus Torvalds

[ diff edited to be just the end result ]

fs/namespace.c static bool __has_locked_children(struct mount *mnt, struct dentry *dentry) · line 2395

Quoted 6 lines · Al Viro

bool has_locked_children(struct mount *mnt, struct dentry *dentry) {

fs/namespace.c +2 -0
+       scoped_guard(mount_locked_reader)
+               return __has_locked_children(mnt, dentry);
 }

So the use of scoped_guard() looks a bit odd to me. Why create a new scope for when the existing scope is identical? It would seem to be more straightforward to just do

        guard(mount_locked_reader);
        return __has_locked_children(mnt, dentry);
Quoted 6 lines

instead. Was there some code generation issue or other thing that made you go the 'scoped' way?

There was at least one other patch that did the same pattern (but I haven't gone through the whole series, maybe there are explanations later).

TBH, the main reason is that my mental model for that is

	with_lock: lock -> m X -> m X

pardon the pseudo-Haskell. IOW, "wrap that sequence of operations into this lock".

Oh, well - I can live with open-ended scope in a function that small and that unlikely to grow more stuff in it...

#35 lore raw

Re: [PATCH 13/52] has_locked_children(): use guards

In reply to Al Viro
Quoted 21 lines · Al Viro

... and document the locking requirements of __has_locked_children()

---
 fs/namespace.c | 9 +++------
 1 file changed, 3 insertions(+), 6 deletions(-)
fs/namespace.c +1 -0
index 59948cbf9c47..eabb0d996c6a 100644
@@ -2373,6 +2373,7 @@ void dissolve_on_fput(struct vfsmount *mnt)
 	}
 }
 
+/* locks: namespace_shared && pinned(mnt) || mount_locked_reader */
 static bool __has_locked_children(struct mount *mnt, struct dentry *dentry)
 {
 	struct mount *child;
@@ -2389,12 +2390,8 @@ static bool __has_locked_children(struct mount *mnt, struct dentry *dentry)
 

fs/namespace.c static bool __has_locked_children(struct mount *mnt, struct dentry *dentry) · line 2394

Quoted 10 lines
fs/namespace.c +2 -6
 bool has_locked_children(struct mount *mnt, struct dentry *dentry)
 {
-	bool res;
-
-	read_seqlock_excl(&mount_lock);
-	res = __has_locked_children(mnt, dentry);
-	read_sequnlock_excl(&mount_lock);
-	return res;
+	scoped_guard(mount_locked_reader)
+		return __has_locked_children(mnt, dentry);

Agree with Linus, this should just use a plain guard().

author maintainer
#36 lore raw

PATCH 14/52 mnt_set_expiry(): use guards

In reply to Al Viro

The reason why it needs only mount_locked_reader is that there's no lockless accesses of expiry lists.

---
 fs/namespace.c | 5 ++---
 1 file changed, 2 insertions(+), 3 deletions(-)
fs/namespace.c +2 -3
index eabb0d996c6a..acacfe767a7c 100644
@@ -3858,9 +3858,8 @@ int finish_automount(struct vfsmount *m, const struct path *path)
  */
 void mnt_set_expiry(struct vfsmount *mnt, struct list_head *expiry_list)
 {
-	read_seqlock_excl(&mount_lock);
-	list_add_tail(&real_mount(mnt)->mnt_expire, expiry_list);
-	read_sequnlock_excl(&mount_lock);
+	scoped_guard(mount_locked_reader)
+		list_add_tail(&real_mount(mnt)->mnt_expire, expiry_list);
 }
 EXPORT_SYMBOL(mnt_set_expiry);
 
Signature · 1 line
2.47.2
#37 lore raw

Re: [PATCH 14/52] mnt_set_expiry(): use guards

Quoted 13 lines · Al Viro

The reason why it needs only mount_locked_reader is that there's no lockless accesses of expiry lists.

---
 fs/namespace.c | 5 ++---
 1 file changed, 2 insertions(+), 3 deletions(-)
fs/namespace.c +0 -0
index eabb0d996c6a..acacfe767a7c 100644

fs/namespace.c int finish_automount(struct vfsmount *m, const struct path *path) · line 3862

Quoted 8 lines
fs/namespace.c +2 -3
@@ -3858,9 +3858,8 @@ int finish_automount(struct vfsmount *m, const struct path *path)
  */
 void mnt_set_expiry(struct vfsmount *mnt, struct list_head *expiry_list)
 {
-	read_seqlock_excl(&mount_lock);
-	list_add_tail(&real_mount(mnt)->mnt_expire, expiry_list);
-	read_sequnlock_excl(&mount_lock);
+	scoped_guard(mount_locked_reader)
+		list_add_tail(&real_mount(mnt)->mnt_expire, expiry_list);

Should also just use a guard(). I don't think religiously sticking to scoped_guard() out of conceptual aversion to guard() buys us anything. It's cleaner to read in such short functions very clearly.

author maintainer
#38 lore raw

PATCH 15/52 path_is_under(): use guards

In reply to Al Viro

... and document that locking requirements for is_path_reachable(). There is one questionable caller in do_listmount() where we are not holding mount_lock *and* might not have the first argument mounted. However, in that case it will immediately return true without having to look at the ancestors. Might be cleaner to move the check into non-LSTM_ROOT case which it really belongs in - there the check is not always true and is_mounted() is guaranteed.

Document the locking environments for is_path_reachable() callers:
get_peer_under_root()
get_dominating_id()
do_statmount()
do_listmount()

---
 fs/namespace.c | 12 ++++++------
 fs/pnode.c     |  3 ++-
 2 files changed, 8 insertions(+), 7 deletions(-)
fs/namespace.c +6 -6
index acacfe767a7c..bf9a3a644faa 100644
@@ -4592,7 +4592,7 @@ SYSCALL_DEFINE5(move_mount,
 /*
  * Return true if path is reachable from root
  *
- * namespace_sem or mount_lock is held
+ * locks: mount_locked_reader || namespace_shared && is_mounted(mnt)
  */
 bool is_path_reachable(struct mount *mnt, struct dentry *dentry,
 			 const struct path *root)
@@ -4606,11 +4606,9 @@ bool is_path_reachable(struct mount *mnt, struct dentry *dentry,
 
 bool path_is_under(const struct path *path1, const struct path *path2)
 {
-	bool res;
-	read_seqlock_excl(&mount_lock);
-	res = is_path_reachable(real_mount(path1->mnt), path1->dentry, path2);
-	read_sequnlock_excl(&mount_lock);
-	return res;
+	scoped_guard(mount_locked_reader)
+		return is_path_reachable(real_mount(path1->mnt), path1->dentry,
+					 path2);
 }
 EXPORT_SYMBOL(path_is_under);
 
@@ -5689,6 +5687,7 @@ static int grab_requested_root(struct mnt_namespace *ns, struct path *root)
 			     STATMOUNT_MNT_UIDMAP | \
 			     STATMOUNT_MNT_GIDMAP)
 
+/* locks: namespace_shared */
 static int do_statmount(struct kstatmount *s, u64 mnt_id, u64 mnt_ns_id,
 			struct mnt_namespace *ns)
 {
@@ -5949,6 +5948,7 @@ SYSCALL_DEFINE4(statmount, const struct mnt_id_req __user *, req,
 	return ret;
 }
 
+/* locks: namespace_shared */
 static ssize_t do_listmount(struct mnt_namespace *ns, u64 mnt_parent_id,
 			    u64 last_mnt_id, u64 *mnt_ids, size_t nr_mnt_ids,
 			    bool reverse)
fs/pnode.c +2 -1
index 0702d45d856d..edaf9d9d0eaf 100644
@@ -29,6 +29,7 @@ static inline struct mount *next_slave(struct mount *p)
 	return hlist_entry(p->mnt_slave.next, struct mount, mnt_slave);
 }
 
+/* locks: namespace_shared && is_mounted(mnt) */
 static struct mount *get_peer_under_root(struct mount *mnt,
 					 struct mnt_namespace *ns,
 					 const struct path *root)
@@ -50,7 +51,7 @@ static struct mount *get_peer_under_root(struct mount *mnt,
  * Get ID of closest dominating peer group having a representative
  * under the given root.
  *
- * Caller must hold namespace_sem
+ * locks: namespace_shared
  */
 int get_dominating_id(struct mount *mnt, const struct path *root)
 {
Signature · 1 line
2.47.2
maintainer Reviewed-by on v1 15/52
#39 lore raw

Re: [PATCH 15/52] path_is_under(): use guards

Quoted 8 lines · Al Viro

... and document that locking requirements for is_path_reachable(). There is one questionable caller in do_listmount() where we are not holding mount_lock *and* might not have the first argument mounted. However, in that case it will immediately return true without having to look at the ancestors. Might be cleaner to move the check into non-LSTM_ROOT case which it really belongs in - there the check is not always true and is_mounted() is guaranteed.

Quoted 8 lines

Document the locking environments for is_path_reachable() callers:
get_peer_under_root()
get_dominating_id()
do_statmount()
do_listmount()

---

Quoted 19 lines
 fs/namespace.c | 12 ++++++------
 fs/pnode.c     |  3 ++-
 2 files changed, 8 insertions(+), 7 deletions(-)
fs/namespace.c +1 -1
index acacfe767a7c..bf9a3a644faa 100644
@@ -4592,7 +4592,7 @@ SYSCALL_DEFINE5(move_mount,
 /*
  * Return true if path is reachable from root
  *
- * namespace_sem or mount_lock is held
+ * locks: mount_locked_reader || namespace_shared && is_mounted(mnt)
  */
 bool is_path_reachable(struct mount *mnt, struct dentry *dentry,
 			 const struct path *root)
@@ -4606,11 +4606,9 @@ bool is_path_reachable(struct mount *mnt, struct dentry *dentry,
 

fs/namespace.c bool is_path_reachable(struct mount *mnt, struct dentry *dentry, · line 4611

Quoted 10 lines
fs/namespace.c +3 -5
 bool path_is_under(const struct path *path1, const struct path *path2)
 {
-	bool res;
-	read_seqlock_excl(&mount_lock);
-	res = is_path_reachable(real_mount(path1->mnt), path1->dentry, path2);
-	read_sequnlock_excl(&mount_lock);
-	return res;
+	scoped_guard(mount_locked_reader)
+		return is_path_reachable(real_mount(path1->mnt), path1->dentry,
+					 path2);

Same thing, no need for this scoped guard eyesore.

author maintainer
#40 lore raw

PATCH 16/52 current_chrooted(): don't bother with follow_down_one()

In reply to Al Viro

All we need here is to follow ->overmount on root mount of namespace...

---
 fs/namespace.c | 18 ++++++++----------
 1 file changed, 8 insertions(+), 10 deletions(-)
fs/namespace.c +8 -10
index bf9a3a644faa..107da30b408c 100644
@@ -6195,24 +6195,22 @@ bool our_mnt(struct vfsmount *mnt)
 bool current_chrooted(void)
 {
 	/* Does the current process have a non-standard root */
-	struct path ns_root;
+	struct mount *root = current->nsproxy->mnt_ns->root;
 	struct path fs_root;
 	bool chrooted;
 
+	get_fs_root(current->fs, &fs_root);
+
 	/* Find the namespace root */
-	ns_root.mnt = &current->nsproxy->mnt_ns->root->mnt;
-	ns_root.dentry = ns_root.mnt->mnt_root;
-	path_get(&ns_root);
-	while (d_mountpoint(ns_root.dentry) && follow_down_one(&ns_root))
-		;
+	read_seqlock_excl(&mount_lock);
 
-	get_fs_root(current->fs, &fs_root);
+	while (unlikely(root->overmount))
+		root = root->overmount;
 
-	chrooted = !path_equal(&fs_root, &ns_root);
+	chrooted = fs_root.mnt != &root->mnt || !path_mounted(&fs_root);
 
+	read_sequnlock_excl(&mount_lock);
 	path_put(&fs_root);
-	path_put(&ns_root);
-
 	return chrooted;
 }
 
Signature · 1 line
2.47.2
author maintainer
#42 lore raw

PATCH 17/52 current_chrooted(): use guards

In reply to Al Viro

here a use of __free(path_put) for dropping fs_root is enough to make guard(mount_locked_reader) fit...

---
 fs/namespace.c | 15 ++++++---------
 1 file changed, 6 insertions(+), 9 deletions(-)
fs/namespace.c +6 -9
index 107da30b408c..a8b586e635d8 100644
@@ -6195,23 +6195,20 @@ bool our_mnt(struct vfsmount *mnt)
 bool current_chrooted(void)
 {
 	/* Does the current process have a non-standard root */
-	struct mount *root = current->nsproxy->mnt_ns->root;
-	struct path fs_root;
-	bool chrooted;
+	struct path fs_root __free(path_put) = {};
+	struct mount *root;
 
 	get_fs_root(current->fs, &fs_root);
 
 	/* Find the namespace root */
-	read_seqlock_excl(&mount_lock);
 
+	guard(mount_locked_reader)();
+
+	root = current->nsproxy->mnt_ns->root;
 	while (unlikely(root->overmount))
 		root = root->overmount;
 
-	chrooted = fs_root.mnt != &root->mnt || !path_mounted(&fs_root);
-
-	read_sequnlock_excl(&mount_lock);
-	path_put(&fs_root);
-	return chrooted;
+	return fs_root.mnt != &root->mnt || !path_mounted(&fs_root);
 }
 
 static bool mnt_already_visible(struct mnt_namespace *ns,
Signature · 1 line
2.47.2
author maintainer
#44 lore raw

PATCH 18/52 do_move_mount(): trim local variables

In reply to Al Viro

Both 'parent' and 'ns' are used at most once, no point precalculating those...

---
 fs/namespace.c | 12 ++++--------
 1 file changed, 4 insertions(+), 8 deletions(-)
fs/namespace.c +4 -8
index a8b586e635d8..1a076aac5d73 100644
@@ -3564,10 +3564,8 @@ static inline bool may_use_mount(struct mount *mnt)
 static int do_move_mount(struct path *old_path,
 			 struct path *new_path, enum mnt_tree_flags_t flags)
 {
-	struct mnt_namespace *ns;
 	struct mount *p;
 	struct mount *old;
-	struct mount *parent;
 	struct pinned_mountpoint mp;
 	int err;
 	bool beneath = flags & MNT_TREE_BENEATH;
@@ -3578,8 +3576,6 @@ static int do_move_mount(struct path *old_path,
 
 	old = real_mount(old_path->mnt);
 	p = real_mount(new_path->mnt);
-	parent = old->mnt_parent;
-	ns = old->mnt_ns;
 
 	err = -EINVAL;
 
@@ -3588,12 +3584,12 @@ static int do_move_mount(struct path *old_path,
 		/* ... it should be detachable from parent */
 		if (!mnt_has_parent(old) || IS_MNT_LOCKED(old))
 			goto out;
+		/* ... which should not be shared */
+		if (IS_MNT_SHARED(old->mnt_parent))
+			goto out;
 		/* ... and the target should be in our namespace */
 		if (!check_mnt(p))
 			goto out;
-		/* parent of the source should not be shared */
-		if (IS_MNT_SHARED(parent))
-			goto out;
 	} else {
 		/*
 		 * otherwise the source must be the root of some anon namespace.
@@ -3605,7 +3601,7 @@ static int do_move_mount(struct path *old_path,
 		 * subsequent checks would've rejected that, but they lose
 		 * some corner cases if we check it early.
 		 */
-		if (ns == p->mnt_ns)
+		if (old->mnt_ns == p->mnt_ns)
 			goto out;
 		/*
 		 * Target should be either in our namespace or in an acceptable
Signature · 1 line
2.47.2
maintainer Reviewed-by on v1 18/52
#45 lore raw

Re: [PATCH 18/52] do_move_mount(): trim local variables

Quoted 4 lines · Al Viro

Both 'parent' and 'ns' are used at most once, no point precalculating those...

---

Quoted 55 lines
 fs/namespace.c | 12 ++++--------
 1 file changed, 4 insertions(+), 8 deletions(-)
fs/namespace.c +4 -8
index a8b586e635d8..1a076aac5d73 100644
@@ -3564,10 +3564,8 @@ static inline bool may_use_mount(struct mount *mnt)
 static int do_move_mount(struct path *old_path,
 			 struct path *new_path, enum mnt_tree_flags_t flags)
 {
-	struct mnt_namespace *ns;
 	struct mount *p;
 	struct mount *old;
-	struct mount *parent;
 	struct pinned_mountpoint mp;
 	int err;
 	bool beneath = flags & MNT_TREE_BENEATH;
@@ -3578,8 +3576,6 @@ static int do_move_mount(struct path *old_path,
 
 	old = real_mount(old_path->mnt);
 	p = real_mount(new_path->mnt);
-	parent = old->mnt_parent;
-	ns = old->mnt_ns;
 
 	err = -EINVAL;
 
@@ -3588,12 +3584,12 @@ static int do_move_mount(struct path *old_path,
 		/* ... it should be detachable from parent */
 		if (!mnt_has_parent(old) || IS_MNT_LOCKED(old))
 			goto out;
+		/* ... which should not be shared */
+		if (IS_MNT_SHARED(old->mnt_parent))
+			goto out;
 		/* ... and the target should be in our namespace */
 		if (!check_mnt(p))
 			goto out;
-		/* parent of the source should not be shared */
-		if (IS_MNT_SHARED(parent))
-			goto out;
 	} else {
 		/*
 		 * otherwise the source must be the root of some anon namespace.
@@ -3605,7 +3601,7 @@ static int do_move_mount(struct path *old_path,
 		 * subsequent checks would've rejected that, but they lose
 		 * some corner cases if we check it early.
 		 */
-		if (ns == p->mnt_ns)
+		if (old->mnt_ns == p->mnt_ns)
 			goto out;
 		/*
 		 * Target should be either in our namespace or in an acceptable
Signature · 1 line
2.47.2
author maintainer
#46 lore raw

PATCH 19/52 do_move_mount(): deal with the checks on old_path early

In reply to Al Viro

1) checking that location we want to move does point to root of some mount can be done before anything else; that property is not going to change and having it already verified simplifies the analysis.

2) checking the type agreement between what we are trying to move and what we are trying to move it onto also belongs in the very beginning - do_lock_mount() might end up switching new_path to something that overmounts the original location, but... the same type agreement applies to overmounts, so we could just as well check against the original location.

3) since we know that old_path->dentry is the root of old_path->mnt, there's no point bothering with path_is_overmounted() in can_move_mount_beneath(); it's simply a check for the mount we are trying to move having non-NULL ->overmount. And with that, we can switch can_move_mount_beneath() to taking old instead of old_path, leaving no uses of old_path past the original checks.

---
 fs/namespace.c | 29 +++++++++++++----------------
 1 file changed, 13 insertions(+), 16 deletions(-)
fs/namespace.c +13 -16
index 1a076aac5d73..42ef0d0c3d40 100644
@@ -3433,7 +3433,7 @@ static bool mount_is_ancestor(const struct mount *p1, const struct mount *p2)
 
 /**
  * can_move_mount_beneath - check that we can mount beneath the top mount
- * @from: mount to mount beneath
+ * @mnt_from: mount we are trying to move
  * @to:   mount under which to mount
  * @mp:   mountpoint of @to
  *
@@ -3443,7 +3443,7 @@ static bool mount_is_ancestor(const struct mount *p1, const struct mount *p2)
  *   root or the rootfs of the namespace.
  * - Make sure that the caller can unmount the topmost mount ensuring
  *   that the caller could reveal the underlying mountpoint.
- * - Ensure that nothing has been mounted on top of @from before we
+ * - Ensure that nothing has been mounted on top of @mnt_from before we
  *   grabbed @namespace_sem to avoid creating pointless shadow mounts.
  * - Prevent mounting beneath a mount if the propagation relationship
  *   between the source mount, parent mount, and top mount would lead to
@@ -3452,12 +3452,11 @@ static bool mount_is_ancestor(const struct mount *p1, const struct mount *p2)
  * Context: This function expects namespace_lock() to be held.
  * Return: On success 0, and on error a negative error code is returned.
  */
-static int can_move_mount_beneath(const struct path *from,
+static int can_move_mount_beneath(struct mount *mnt_from,
 				  const struct path *to,
 				  const struct mountpoint *mp)
 {
-	struct mount *mnt_from = real_mount(from->mnt),
-		     *mnt_to = real_mount(to->mnt),
+	struct mount *mnt_to = real_mount(to->mnt),
 		     *parent_mnt_to = mnt_to->mnt_parent;
 
 	if (!mnt_has_parent(mnt_to))
@@ -3470,7 +3469,7 @@ static int can_move_mount_beneath(const struct path *from,
 		return -EINVAL;
 
 	/* Avoid creating shadow mounts during mount propagation. */
-	if (path_overmounted(from))
+	if (mnt_from->overmount)
 		return -EINVAL;
 
 	/*
@@ -3565,16 +3564,21 @@ static int do_move_mount(struct path *old_path,
 			 struct path *new_path, enum mnt_tree_flags_t flags)
 {
 	struct mount *p;
-	struct mount *old;
+	struct mount *old = real_mount(old_path->mnt);
 	struct pinned_mountpoint mp;
 	int err;
 	bool beneath = flags & MNT_TREE_BENEATH;
 
+	if (!path_mounted(old_path))
+		return -EINVAL;
+
+	if (d_is_dir(new_path->dentry) != d_is_dir(old_path->dentry))
+		return -EINVAL;
+
 	err = do_lock_mount(new_path, &mp, beneath);
 	if (err)
 		return err;
 
-	old = real_mount(old_path->mnt);
 	p = real_mount(new_path->mnt);
 
 	err = -EINVAL;
@@ -3611,15 +3615,8 @@ static int do_move_mount(struct path *old_path,
 			goto out;
 	}
 
-	if (!path_mounted(old_path))
-		goto out;
-
-	if (d_is_dir(new_path->dentry) !=
-	    d_is_dir(old_path->dentry))
-		goto out;
-
 	if (beneath) {
-		err = can_move_mount_beneath(old_path, new_path, mp.mp);
+		err = can_move_mount_beneath(old, new_path, mp.mp);
 		if (err)
 			goto out;
 
Signature · 1 line
2.47.2
maintainer Reviewed-by on v1 19/52
#47 lore raw

Re: [PATCH 19/52] do_move_mount(): deal with the checks on old_path early

Quoted 17 lines · Al Viro

1) checking that location we want to move does point to root of some mount can be done before anything else; that property is not going to change and having it already verified simplifies the analysis.

2) checking the type agreement between what we are trying to move and what we are trying to move it onto also belongs in the very beginning - do_lock_mount() might end up switching new_path to something that overmounts the original location, but... the same type agreement applies to overmounts, so we could just as well check against the original location.

3) since we know that old_path->dentry is the root of old_path->mnt, there's no point bothering with path_is_overmounted() in can_move_mount_beneath(); it's simply a check for the mount we are trying to move having non-NULL ->overmount. And with that, we can switch can_move_mount_beneath() to taking old instead of old_path, leaving no uses of old_path past the original checks.

Quoted 2 lines

---

author maintainer
#48 lore raw

PATCH 20/52 move_mount(2): take sanity checks in 'beneath' case into do_lock_mount()

In reply to Al Viro

We want to mount beneath the given location. For that operation to make sense, location must be the root of some mount that has something under it. Currently we let it proceed if those requirements are not met, with rather meaningless results, and have that bogosity caught further down the road; let's fail early instead - do_lock_mount() doesn't make sense unless those conditions hold, and checking them there makes things simpler.

---
 fs/namespace.c | 15 +++++++--------
 1 file changed, 7 insertions(+), 8 deletions(-)
fs/namespace.c +7 -8
index 42ef0d0c3d40..9e04133d81dd 100644
@@ -2768,12 +2768,19 @@ static int do_lock_mount(struct path *path, struct pinned_mountpoint *pinned, bo
 	struct path under = {};
 	int err = -ENOENT;
 
+	if (unlikely(beneath) && !path_mounted(path))
+		return -EINVAL;
+
 	for (;;) {
 		struct mount *m = real_mount(mnt);
 
 		if (beneath) {
 			path_put(&under);
 			read_seqlock_excl(&mount_lock);
+			if (unlikely(!mnt_has_parent(m))) {
+				read_sequnlock_excl(&mount_lock);
+				return -EINVAL;
+			}
 			under.mnt = mntget(&m->mnt_parent->mnt);
 			under.dentry = dget(m->mnt_mountpoint);
 			read_sequnlock_excl(&mount_lock);
@@ -3437,8 +3444,6 @@ static bool mount_is_ancestor(const struct mount *p1, const struct mount *p2)
  * @to:   mount under which to mount
  * @mp:   mountpoint of @to
  *
- * - Make sure that @to->dentry is actually the root of a mount under
- *   which we can mount another mount.
  * - Make sure that nothing can be mounted beneath the caller's current
  *   root or the rootfs of the namespace.
  * - Make sure that the caller can unmount the topmost mount ensuring
@@ -3459,12 +3464,6 @@ static int can_move_mount_beneath(struct mount *mnt_from,
 	struct mount *mnt_to = real_mount(to->mnt),
 		     *parent_mnt_to = mnt_to->mnt_parent;
 
-	if (!mnt_has_parent(mnt_to))
-		return -EINVAL;
-
-	if (!path_mounted(to))
-		return -EINVAL;
-
 	if (IS_MNT_LOCKED(mnt_to))
 		return -EINVAL;
 
Signature · 1 line
2.47.2
#49 lore raw

Re: [PATCH 20/52] move_mount(2): take sanity checks in 'beneath' case into do_lock_mount()

fs/namespace.c static int do_lock_mount(struct path *path, struct pinned_mountpoint *pinned, bo · line 2786

Quoted 11 lines · Al Viro
                if (beneath) {
                        path_put(&under);
                        read_seqlock_excl(&mount_lock);
fs/namespace.c +4 -0
+                       if (unlikely(!mnt_has_parent(m))) {
+                               read_sequnlock_excl(&mount_lock);
+                               return -EINVAL;
+                       }
                        under.mnt = mntget(&m->mnt_parent->mnt);
                        under.dentry = dget(m->mnt_mountpoint);
                        read_sequnlock_excl(&mount_lock);

Well, *this* would look a lot cleaner with a "scoped_guard(mount_locked_reader)", but you didn't do that for some reason. Am I missing something?

Linus

#50 lore raw

Re: [PATCH 20/52] move_mount(2): take sanity checks in 'beneath' case into do_lock_mount()

Quoted 4 lines · Linus Torvalds

Well, *this* would look a lot cleaner with a "scoped_guard(mount_locked_reader)", but you didn't do that for some reason. Am I missing something?

Ahh. You rewrite it to look very different in 34/52.

Linus

maintainer Reviewed-by on v1 20/52
#51 lore raw

Re: [PATCH 20/52] move_mount(2): take sanity checks in 'beneath' case into do_lock_mount()

In reply to Al Viro
Quoted 10 lines · Al Viro

We want to mount beneath the given location. For that operation to make sense, location must be the root of some mount that has something under it. Currently we let it proceed if those requirements are not met, with rather meaningless results, and have that bogosity caught further down the road; let's fail early instead - do_lock_mount() doesn't make sense unless those conditions hold, and checking them there makes things simpler.

---

Well, do_lock_mount() was already convoluted enough that didn't want that in there as well. But I don't care,

author maintainer
#52 lore raw

Re: [PATCH 20/52] move_mount(2): take sanity checks in 'beneath' case into do_lock_mount()

Quoted 9 lines · Christian Brauner
Quoted 8 lines · Al Viro

We want to mount beneath the given location. For that operation to make sense, location must be the root of some mount that has something under it. Currently we let it proceed if those requirements are not met, with rather meaningless results, and have that bogosity caught further down the road; let's fail early instead - do_lock_mount() doesn't make sense unless those conditions hold, and checking them there makes things simpler.

Quoted 5 lines
Quoted 2 lines

---

Well, do_lock_mount() was already convoluted enough that didn't want that in there as well. But I don't care,

It helps when it comes to cleaning it up - look at the condition it's in after 34/52...

author maintainer
#53 lore raw

PATCH 21/52 finish_automount(): simplify the ELOOP check

In reply to Al Viro

It's enough to check that dentries match; if path->dentry is equal to m->mnt_root, superblocks will match as well.

---
 fs/namespace.c | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)
fs/namespace.c +1 -2
index 9e04133d81dd..5c4b4f25b5f8 100644
@@ -3803,8 +3803,7 @@ int finish_automount(struct vfsmount *m, const struct path *path)
 
 	mnt = real_mount(m);
 
-	if (m->mnt_sb == path->mnt->mnt_sb &&
-	    m->mnt_root == dentry) {
+	if (m->mnt_root == path->dentry) {
 		err = -ELOOP;
 		goto discard;
 	}
Signature · 1 line
2.47.2
author maintainer
#55 lore raw

PATCH 22/52 do_loopback(): use __free(path_put) to deal with old_path

In reply to Al Viro

preparations for making unlock_mount() a __cleanup(); can't have path_put() inside mount_lock scope.

---
 fs/namespace.c | 9 +++------
 1 file changed, 3 insertions(+), 6 deletions(-)
fs/namespace.c +3 -6
index 5c4b4f25b5f8..602612cbd095 100644
@@ -3014,7 +3014,7 @@ static struct mount *__do_loopback(struct path *old_path, int recurse)
 static int do_loopback(struct path *path, const char *old_name,
 				int recurse)
 {
-	struct path old_path;
+	struct path old_path __free(path_put) = {};
 	struct mount *mnt = NULL, *parent;
 	struct pinned_mountpoint mp = {};
 	int err;
@@ -3024,13 +3024,12 @@ static int do_loopback(struct path *path, const char *old_name,
 	if (err)
 		return err;
 
-	err = -EINVAL;
 	if (mnt_ns_loop(old_path.dentry))
-		goto out;
+		return -EINVAL;
 
 	err = lock_mount(path, &mp);
 	if (err)
-		goto out;
+		return err;
 
 	parent = real_mount(path->mnt);
 	if (!check_mnt(parent))
@@ -3050,8 +3049,6 @@ static int do_loopback(struct path *path, const char *old_name,
 	}
 out2:
 	unlock_mount(&mp);
-out:
-	path_put(&old_path);
 	return err;
 }
 
Signature · 1 line
2.47.2
author maintainer
#57 lore raw

PATCH 23/52 pivot_root(2): use __free() to deal with struct path in it

In reply to Al Viro

preparations for making unlock_mount() a __cleanup(); can't have path_put() inside mount_lock scope.

---
 fs/namespace.c | 19 +++++++------------
 1 file changed, 7 insertions(+), 12 deletions(-)
fs/namespace.c +7 -12
index 602612cbd095..892251663419 100644
@@ -4628,7 +4628,9 @@ EXPORT_SYMBOL(path_is_under);
 SYSCALL_DEFINE2(pivot_root, const char __user *, new_root,
 		const char __user *, put_old)
 {
-	struct path new, old, root;
+	struct path new __free(path_put) = {};
+	struct path old __free(path_put) = {};
+	struct path root __free(path_put) = {};
 	struct mount *new_mnt, *root_mnt, *old_mnt, *root_parent, *ex_parent;
 	struct pinned_mountpoint old_mp = {};
 	int error;
@@ -4639,21 +4641,21 @@ SYSCALL_DEFINE2(pivot_root, const char __user *, new_root,
 	error = user_path_at(AT_FDCWD, new_root,
 			     LOOKUP_FOLLOW | LOOKUP_DIRECTORY, &new);
 	if (error)
-		goto out0;
+		return error;
 
 	error = user_path_at(AT_FDCWD, put_old,
 			     LOOKUP_FOLLOW | LOOKUP_DIRECTORY, &old);
 	if (error)
-		goto out1;
+		return error;
 
 	error = security_sb_pivotroot(&old, &new);
 	if (error)
-		goto out2;
+		return error;
 
 	get_fs_root(current->fs, &root);
 	error = lock_mount(&old, &old_mp);
 	if (error)
-		goto out3;
+		return error;
 
 	error = -EINVAL;
 	new_mnt = real_mount(new.mnt);
@@ -4711,13 +4713,6 @@ SYSCALL_DEFINE2(pivot_root, const char __user *, new_root,
 	error = 0;
 out4:
 	unlock_mount(&old_mp);
-out3:
-	path_put(&root);
-out2:
-	path_put(&old);
-out1:
-	path_put(&new);
-out0:
 	return error;
 }
 
Signature · 1 line
2.47.2
author maintainer
#59 lore raw

PATCH 24/52 finish_automount(): take the lock_mount() analogue into a helper

In reply to Al Viro

finish_automount() can't use lock_mount() - it treats finding something already mounted as "quitely drop our mount and return 0", not as "mount on top of whatever mounted there". It's been open-coded; let's take it into a helper similar to lock_mount(). "something's already mounted" => -EBUSY, finish_automount() needs to distinguish it from the normal case and it can't happen in other failure cases.

---
 fs/namespace.c | 42 +++++++++++++++++++++++++-----------------
 1 file changed, 25 insertions(+), 17 deletions(-)
fs/namespace.c +25 -17
index 892251663419..99757040a39a 100644
@@ -3786,9 +3786,29 @@ static int do_new_mount(struct path *path, const char *fstype, int sb_flags,
 	return err;
 }
 
-int finish_automount(struct vfsmount *m, const struct path *path)
+static int lock_mount_exact(const struct path *path,
+			    struct pinned_mountpoint *mp)
 {
 	struct dentry *dentry = path->dentry;
+	int err;
+
+	inode_lock(dentry->d_inode);
+	namespace_lock();
+	if (unlikely(cant_mount(dentry)))
+		err = -ENOENT;
+	else if (path_overmounted(path))
+		err = -EBUSY;
+	else
+		err = get_mountpoint(dentry, mp);
+	if (unlikely(err)) {
+		namespace_unlock();
+		inode_unlock(dentry->d_inode);
+	}
+	return err;
+}
+
+int finish_automount(struct vfsmount *m, const struct path *path)
+{
 	struct pinned_mountpoint mp = {};
 	struct mount *mnt;
 	int err;
@@ -3810,20 +3830,11 @@ int finish_automount(struct vfsmount *m, const struct path *path)
 	 * that overmounts our mountpoint to be means "quitely drop what we've
 	 * got", not "try to mount it on top".
 	 */
-	inode_lock(dentry->d_inode);
-	namespace_lock();
-	if (unlikely(cant_mount(dentry))) {
-		err = -ENOENT;
-		goto discard_locked;
-	}
-	if (path_overmounted(path)) {
-		err = 0;
-		goto discard_locked;
+	err = lock_mount_exact(path, &mp);
+	if (unlikely(err)) {
+		mntput(m);
+		return err == -EBUSY ? 0 : err;
 	}
-	err = get_mountpoint(dentry, &mp);
-	if (err)
-		goto discard_locked;
-
 	err = do_add_mount(mnt, mp.mp, path,
 			   path->mnt->mnt_flags | MNT_SHRINKABLE);
 	unlock_mount(&mp);
@@ -3831,9 +3842,6 @@ int finish_automount(struct vfsmount *m, const struct path *path)
 		goto discard;
 	return 0;
 
-discard_locked:
-	namespace_unlock();
-	inode_unlock(dentry->d_inode);
 discard:
 	mntput(m);
 	return err;
Signature · 1 line
2.47.2
maintainer Reviewed-by on v1 24/52
#60 lore raw

Re: [PATCH 24/52] finish_automount(): take the lock_mount() analogue into a helper

Quoted 9 lines · Al Viro

finish_automount() can't use lock_mount() - it treats finding something already mounted as "quitely drop our mount and return 0", not as "mount on top of whatever mounted there". It's been open-coded; let's take it into a helper similar to lock_mount(). "something's already mounted" => -EBUSY, finish_automount() needs to distinguish it from the normal case and it can't happen in other failure cases.

---

Quoted 64 lines
 fs/namespace.c | 42 +++++++++++++++++++++++++-----------------
 1 file changed, 25 insertions(+), 17 deletions(-)
fs/namespace.c +25 -14
index 892251663419..99757040a39a 100644
@@ -3786,9 +3786,29 @@ static int do_new_mount(struct path *path, const char *fstype, int sb_flags,
 	return err;
 }
 
-int finish_automount(struct vfsmount *m, const struct path *path)
+static int lock_mount_exact(const struct path *path,
+			    struct pinned_mountpoint *mp)
 {
 	struct dentry *dentry = path->dentry;
+	int err;
+
+	inode_lock(dentry->d_inode);
+	namespace_lock();
+	if (unlikely(cant_mount(dentry)))
+		err = -ENOENT;
+	else if (path_overmounted(path))
+		err = -EBUSY;
+	else
+		err = get_mountpoint(dentry, mp);
+	if (unlikely(err)) {
+		namespace_unlock();
+		inode_unlock(dentry->d_inode);
+	}
+	return err;
+}
+
+int finish_automount(struct vfsmount *m, const struct path *path)
+{
 	struct pinned_mountpoint mp = {};
 	struct mount *mnt;
 	int err;
@@ -3810,20 +3830,11 @@ int finish_automount(struct vfsmount *m, const struct path *path)
 	 * that overmounts our mountpoint to be means "quitely drop what we've
 	 * got", not "try to mount it on top".
 	 */
-	inode_lock(dentry->d_inode);
-	namespace_lock();
-	if (unlikely(cant_mount(dentry))) {
-		err = -ENOENT;
-		goto discard_locked;
-	}
-	if (path_overmounted(path)) {
-		err = 0;
-		goto discard_locked;
+	err = lock_mount_exact(path, &mp);
+	if (unlikely(err)) {
+		mntput(m);
+		return err == -EBUSY ? 0 : err;
 	}
-	err = get_mountpoint(dentry, &mp);
-	if (err)
-		goto discard_locked;
-
 	err = do_add_mount(mnt, mp.mp, path,
 			   path->mnt->mnt_flags | MNT_SHRINKABLE);
 	unlock_mount(&mp);

fs/namespace.c int finish_automount(struct vfsmount *m, const struct path *path) · line 3846

Quoted 8 lines
fs/namespace.c +0 -3
@@ -3831,9 +3842,6 @@ int finish_automount(struct vfsmount *m, const struct path *path)
 		goto discard;
 	return 0;
 
-discard_locked:
-	namespace_unlock();
-	inode_unlock(dentry->d_inode);
 discard:
 	mntput(m);

Can use direct returns if you do:

        struct mount *mnt __free(mntput) = NULL;

and then in the success condition:

        retain_and_null_ptr(mnt);
author maintainer
#61 lore raw

PATCH 25/52 do_new_mount_rc(): use __free() to deal with dropping mnt on failure

In reply to Al Viro

do_add_mount() consumes vfsmount on success; just follow it with conditional retain_and_null_ptr() on success and we can switch to __free() for mnt and be done with that - unlock_mount() is in the very end.

---
 fs/namespace.c | 7 +++----
 1 file changed, 3 insertions(+), 4 deletions(-)
fs/namespace.c +3 -4
index 99757040a39a..79c87937a7dd 100644
@@ -3694,7 +3694,6 @@ static bool mount_too_revealing(const struct super_block *sb, int *new_mnt_flags
 static int do_new_mount_fc(struct fs_context *fc, struct path *mountpoint,
 			   unsigned int mnt_flags)
 {
-	struct vfsmount *mnt;
 	struct pinned_mountpoint mp = {};
 	struct super_block *sb = fc->root->d_sb;
 	int error;
@@ -3710,7 +3709,7 @@ static int do_new_mount_fc(struct fs_context *fc, struct path *mountpoint,
 
 	up_write(&sb->s_umount);
 
-	mnt = vfs_create_mount(fc);
+	struct vfsmount *mnt __free(mntput) = vfs_create_mount(fc);
 	if (IS_ERR(mnt))
 		return PTR_ERR(mnt);
 
@@ -3720,10 +3719,10 @@ static int do_new_mount_fc(struct fs_context *fc, struct path *mountpoint,
 	if (!error) {
 		error = do_add_mount(real_mount(mnt), mp.mp,
 				     mountpoint, mnt_flags);
+		if (!error)
+			retain_and_null_ptr(mnt); // consumed on success
 		unlock_mount(&mp);
 	}
-	if (error < 0)
-		mntput(mnt);
 	return error;
 }
 
Signature · 1 line
2.47.2
#62 lore raw

Re: [PATCH 25/52] do_new_mount_rc(): use __free() to deal with dropping mnt on failure

Quoted 25 lines · Al Viro

do_add_mount() consumes vfsmount on success; just follow it with conditional retain_and_null_ptr() on success and we can switch to __free() for mnt and be done with that - unlock_mount() is in the very end.

---

 fs/namespace.c | 7 +++----
 1 file changed, 3 insertions(+), 4 deletions(-)
fs/namespace.c +0 -1
index 99757040a39a..79c87937a7dd 100644
@@ -3694,7 +3694,6 @@ static bool mount_too_revealing(const struct super_block *sb, int *new_mnt_flags
 static int do_new_mount_fc(struct fs_context *fc, struct path *mountpoint,
 			   unsigned int mnt_flags)
 {
-	struct vfsmount *mnt;
 	struct pinned_mountpoint mp = {};
 	struct super_block *sb = fc->root->d_sb;
 	int error;

fs/namespace.c static int do_new_mount_fc(struct fs_context *fc, struct path *mountpoint, · line 3712

Quoted 5 lines
fs/namespace.c +1 -1
@@ -3710,7 +3709,7 @@ static int do_new_mount_fc(struct fs_context *fc, struct path *mountpoint,
 
 	up_write(&sb->s_umount);
 
-	mnt = vfs_create_mount(fc);
+	struct vfsmount *mnt __free(mntput) = vfs_create_mount(fc);

Ugh, can we please not start declaring variables in the middle of a scope.

Quoted 19 lines
fs/namespace.c +2 -2
 	if (IS_ERR(mnt))
 		return PTR_ERR(mnt);
 
@@ -3720,10 +3719,10 @@ static int do_new_mount_fc(struct fs_context *fc, struct path *mountpoint,
 	if (!error) {
 		error = do_add_mount(real_mount(mnt), mp.mp,
 				     mountpoint, mnt_flags);
+		if (!error)
+			retain_and_null_ptr(mnt); // consumed on success
 		unlock_mount(&mp);
 	}
-	if (error < 0)
-		mntput(mnt);
 	return error;
 }
 
Signature · 1 line
2.47.2
author maintainer
#63 lore raw

Re: [PATCH 25/52] do_new_mount_rc(): use __free() to deal with dropping mnt on failure

Quoted 5 lines · Christian Brauner

fs/namespace.c static int do_new_mount_fc(struct fs_context *fc, struct path *mountpoint, · line 3712

Quoted 2 lines
fs/namespace.c +1 -1
-	mnt = vfs_create_mount(fc);
+	struct vfsmount *mnt __free(mntput) = vfs_create_mount(fc);

Ugh, can we please not start declaring variables in the middle of a scope.

Seeing that it *is* the beginning of its scope, what do you suggest? Declaring it above, initializing with NULL and reassigning here? That's actually just as wrong, if not more so - any assignment added to it at earlier point and you've got a silent leak, so verifying correctness would be harder that way.

#64 lore raw

Re: [PATCH 25/52] do_new_mount_rc(): use __free() to deal with dropping mnt on failure

Quoted 8 lines · Al Viro
Quoted 5 lines · Christian Brauner

fs/namespace.c static int do_new_mount_fc(struct fs_context *fc, struct path *mountpoint, · line 3712

Quoted 2 lines
fs/namespace.c +1 -1
-	mnt = vfs_create_mount(fc);
+	struct vfsmount *mnt __free(mntput) = vfs_create_mount(fc);

Ugh, can we please not start declaring variables in the middle of a scope.

Seeing that it *is* the beginning of its scope, what do you suggest?

What? Did I miss earlier or later changes because:

static int do_new_mount_fc(struct fs_context *fc, struct path *mountpoint,
unsigned int mnt_flags)

{
	struct vfsmount *mnt;
	struct pinned_mountpoint mp = {};
	struct super_block *sb = fc->root->d_sb;
	int error;

	error = security_sb_kern_mount(sb);
	if (!error && mount_too_revealing(sb, &mnt_flags))
		error = -EPERM;

	if (unlikely(error)) {
		fc_drop_locked(fc);
		return error;
	}

	up_write(&sb->s_umount);

	mnt = vfs_create_mount(fc);
	if (IS_ERR(mnt))
		return PTR_ERR(mnt);

How does up_write() create a new scope?

	mnt_warn_timestamp_expiry(mountpoint, mnt);

	error = lock_mount(mountpoint, &mp);
	if (!error) {
		error = do_add_mount(real_mount(mnt), mp.mp,
				     mountpoint, mnt_flags);
		unlock_mount(&mp);
	}
	if (error < 0)
		mntput(mnt);
	return error;
}
Quoted 2 lines

Declaring it above, initializing with NULL and reassigning here? That's actually just as wrong, if not more so - any assignment added

I disagree. I do very much prefer having cleanups at the top of the function or e.g.,:

if (foo) {
	struct vfsmount *mnt __free(mntput) = vfs_create_mount(fc);
}

Because it is really easy to figure out visually. But just doing it somewhere in the middle is just confusing.

static int do_new_mount_fc(struct fs_context *fc, struct path *mountpoint,
unsigned int mnt_flags)

{
	struct pinned_mountpoint mp = {};
	struct super_block *sb = fc->root->d_sb;
	int error;

	error = security_sb_kern_mount(sb);
	if (!error && mount_too_revealing(sb, &mnt_flags))
		error = -eperm;

	if (unlikely(error)) {
		fc_drop_locked(fc);
		return error;
	}

	up_write(&sb->s_umount);

	struct vfsmount *mnt __free(mntput) = vfs_create_mount(fc);
	if (is_err(mnt))
		return ptr_err(mnt);
author maintainer
#65 lore raw

Re: [PATCH 25/52] do_new_mount_rc(): use __free() to deal with dropping mnt on failure

Quoted 6 lines · Christian Brauner
Quoted 2 lines

Declaring it above, initializing with NULL and reassigning here? That's actually just as wrong, if not more so - any assignment added

I disagree. I do very much prefer having cleanups at the top of the function or e.g.,:

Quoted 6 lines
if (foo) {
	struct vfsmount *mnt __free(mntput) = vfs_create_mount(fc);
}

Because it is really easy to figure out visually. But just doing it somewhere in the middle is just confusing.

So basically you treat __free() simply as a syntax sugar for "call this on exits from this block", rather than an approximation for "here's an auto object we've created, this should be called to destroy it at the end of its scope/lifetime"?

IMO it's a bad practice - it makes life much harder when you are tracing callchains, etc.

FWIW, I wonder if the things would be cleaner if we did security_sb_kern_mount() and mount_too_revealing() *after* unlocking the superblock and getting a vfsmount. The latter definitely doesn't give a damn about superblock being locked and AFAICS neither does the only in-tree instance of ->sb_kern_mount(). That way we have the real initialization reasonably close to __free() and control flow is easier to follow...

Folks, how about something like the delta below (on top of the posted queue)?

fs/namespace.c +9 -11
index 63b74d7384fd..191e7f776de5 100644
@@ -3689,24 +3689,22 @@ static bool mount_too_revealing(const struct super_block *sb, int *new_mnt_flags
 static int do_new_mount_fc(struct fs_context *fc, const struct path *mountpoint,
 			   unsigned int mnt_flags)
 {
+	struct vfsmount *mnt __free(mntput) = NULL;
 	struct super_block *sb = fc->root->d_sb;
 	int error;
 
-	error = security_sb_kern_mount(sb);
-	if (!error && mount_too_revealing(sb, &mnt_flags))
-		error = -EPERM;
-
-	if (unlikely(error)) {
-		fc_drop_locked(fc);
-		return error;
-	}
-
 	up_write(&sb->s_umount);
-
-	struct vfsmount *mnt __free(mntput) = vfs_create_mount(fc);
+	mnt = vfs_create_mount(fc);
 	if (IS_ERR(mnt))
 		return PTR_ERR(mnt);
 
+	error = security_sb_kern_mount(sb);
+	if (unlikely(error))
+		return error;
+
+	if (mount_too_revealing(sb, &mnt_flags))
+		return -EPERM;
+
 	mnt_warn_timestamp_expiry(mountpoint, mnt);
 
 	LOCK_MOUNT(mp, mountpoint);
author maintainer
#66 lore raw

Re: [PATCH 25/52] do_new_mount_rc(): use __free() to deal with dropping mnt on failure

Quoted 6 lines · Al Viro

FWIW, I wonder if the things would be cleaner if we did security_sb_kern_mount() and mount_too_revealing() *after* unlocking the superblock and getting a vfsmount. The latter definitely doesn't give a damn about superblock being locked and AFAICS neither does the only in-tree instance of ->sb_kern_mount(). That way we have the real initialization reasonably close to __free() and control flow is easier to follow...

Or, better yet, take vfs_get_tree() from do_new_mount() to do_new_mount_fc() and collapse it with "unlock ->s_umount and call vfs_create_mount()" into a call of fc_mount(), like the delta below (on top of posted queue, would get reordered ealier in it and pick the bits of #25 along the way).

Does anyone have objections here? The only real change is that security_sb_kern_mount() gets called outside of ->s_umount exclusive scope; no in-tree instances care, but I'd Cc that to LSM list...

fs/namespace.c +7 -13
index 63b74d7384fd..6f062dc7f9bf 100644
@@ -3690,22 +3690,18 @@ static int do_new_mount_fc(struct fs_context *fc, const struct path *mountpoint,
 			   unsigned int mnt_flags)
 {
 	struct super_block *sb = fc->root->d_sb;
+	struct vfsmount *mnt __free(mntput) = fc_mount(fc);
 	int error;
 
-	error = security_sb_kern_mount(sb);
-	if (!error && mount_too_revealing(sb, &mnt_flags))
-		error = -EPERM;
+	if (IS_ERR(mnt))
+		return PTR_ERR(mnt);
 
-	if (unlikely(error)) {
-		fc_drop_locked(fc);
+	error = security_sb_kern_mount(sb);
+	if (unlikely(error))
 		return error;
-	}
 
-	up_write(&sb->s_umount);
-
-	struct vfsmount *mnt __free(mntput) = vfs_create_mount(fc);
-	if (IS_ERR(mnt))
-		return PTR_ERR(mnt);
+	if (mount_too_revealing(sb, &mnt_flags))
+		return -EPERM;
 
 	mnt_warn_timestamp_expiry(mountpoint, mnt);
 
@@ -3767,8 +3763,6 @@ static int do_new_mount(const struct path *path, const char *fstype,
 		err = parse_monolithic_mount_data(fc, data);
 	if (!err && !mount_capable(fc))
 		err = -EPERM;
-	if (!err)
-		err = vfs_get_tree(fc);
 	if (!err)
 		err = do_new_mount_fc(fc, path, mnt_flags);
 
author maintainer
#67 lore raw

RFC PATCH 1/1 switch do_new_mount_fc() to using fc_mount()

[
This is on top of -rc3; if nobody objects, I'll insert that early in series in viro/vfs.git#work.mount. It has an impact for LSM folks - ->sb_kern_mount() would be called without ->s_umount; nothing in-tree cares, but if you have objections, yell now.
]

Prior to the call of do_new_mount_fc() the caller has just done successful vfs_get_tree(). Then do_new_mount_fc() does several checks on resulting superblock, and either does fc_drop_locked() and returns an error or proceeds to unlock the superblock and call vfs_create_mount().

The thing is, there's no reason to delay that unlock + vfs_create_mount() - the tests do not rely upon the state of ->s_umount and
fc_drop_locked()
put_fs_context()
is equivalent to

        unlock ->s_umount
        put_fs_context()

Doing vfs_create_mount() before the checks allows us to move vfs_get_tree() from caller to do_new_mount_fc() and collapse it with vfs_create_mount() into an fc_mount() call.

---

fs/namespace.c +10 -16
index ae6d1312b184..9e1b7319532c 100644
@@ -3721,25 +3721,19 @@ static bool mount_too_revealing(const struct super_block *sb, int *new_mnt_flags
 static int do_new_mount_fc(struct fs_context *fc, struct path *mountpoint,
 			   unsigned int mnt_flags)
 {
-	struct vfsmount *mnt;
 	struct pinned_mountpoint mp = {};
 	struct super_block *sb = fc->root->d_sb;
+	struct vfsmount *mnt = fc_mount(fc);
 	int error;
 
+	if (IS_ERR(mnt))
+		return PTR_ERR(mnt);
+
 	error = security_sb_kern_mount(sb);
 	if (!error && mount_too_revealing(sb, &mnt_flags))
 		error = -EPERM;
-
-	if (unlikely(error)) {
-		fc_drop_locked(fc);
-		return error;
-	}
-
-	up_write(&sb->s_umount);
-
-	mnt = vfs_create_mount(fc);
-	if (IS_ERR(mnt))
-		return PTR_ERR(mnt);
+	if (unlikely(error))
+		goto out;
 
 	mnt_warn_timestamp_expiry(mountpoint, mnt);
 
@@ -3747,10 +3741,12 @@ static int do_new_mount_fc(struct fs_context *fc, struct path *mountpoint,
 	if (!error) {
 		error = do_add_mount(real_mount(mnt), mp.mp,
 				     mountpoint, mnt_flags);
+		if (!error)
+			mnt = NULL;	// consumed on success
 		unlock_mount(&mp);
 	}
-	if (error < 0)
-		mntput(mnt);
+out:
+	mntput(mnt);
 	return error;
 }
 
@@ -3804,8 +3800,6 @@ static int do_new_mount(struct path *path, const char *fstype, int sb_flags,
 		err = parse_monolithic_mount_data(fc, data);
 	if (!err && !mount_capable(fc))
 		err = -EPERM;
-	if (!err)
-		err = vfs_get_tree(fc);
 	if (!err)
 		err = do_new_mount_fc(fc, path, mnt_flags);
 
maintainer
#68 lore raw

Re: [RFC][PATCH] switch do_new_mount_fc() to using fc_mount()

Quoted 7 lines · Al Viro

[
This is on top of -rc3; if nobody objects, I'll insert that early in series in viro/vfs.git#work.mount. It has an impact for LSM folks - ->sb_kern_mount() would be called without ->s_umount; nothing in-tree cares, but if you have objections, yell now.
]

Thanks for the heads-up, I'm not aware of anyone currently posting/working-on patches that would be dependent on this.

Quoted 18 lines

Prior to the call of do_new_mount_fc() the caller has just done successful vfs_get_tree(). Then do_new_mount_fc() does several checks on resulting superblock, and either does fc_drop_locked() and returns an error or proceeds to unlock the superblock and call vfs_create_mount().

The thing is, there's no reason to delay that unlock + vfs_create_mount() - the tests do not rely upon the state of ->s_umount and
fc_drop_locked()
put_fs_context()
is equivalent to

        unlock ->s_umount
        put_fs_context()

Doing vfs_create_mount() before the checks allows us to move vfs_get_tree() from caller to do_new_mount_fc() and collapse it with vfs_create_mount() into an fc_mount() call.

Signature · 1 line
paul-moore.com
author maintainer
#69 lore raw

PATCH 26/52 finish_automount(): use __free() to deal with dropping mnt on failure

In reply to Al Viro

same story as with do_new_mount_fc().

---
 fs/namespace.c | 22 ++++++++--------------
 1 file changed, 8 insertions(+), 14 deletions(-)
fs/namespace.c +8 -14
index 79c87937a7dd..5819a50d7d67 100644
@@ -3806,8 +3806,9 @@ static int lock_mount_exact(const struct path *path,
 	return err;
 }
 
-int finish_automount(struct vfsmount *m, const struct path *path)
+int finish_automount(struct vfsmount *__m, const struct path *path)
 {
+	struct vfsmount *m __free(mntput) = __m;
 	struct pinned_mountpoint mp = {};
 	struct mount *mnt;
 	int err;
@@ -3819,10 +3820,8 @@ int finish_automount(struct vfsmount *m, const struct path *path)
 
 	mnt = real_mount(m);
 
-	if (m->mnt_root == path->dentry) {
-		err = -ELOOP;
-		goto discard;
-	}
+	if (m->mnt_root == path->dentry)
+		return -ELOOP;
 
 	/*
 	 * we don't want to use lock_mount() - in this case finding something
@@ -3830,19 +3829,14 @@ int finish_automount(struct vfsmount *m, const struct path *path)
 	 * got", not "try to mount it on top".
 	 */
 	err = lock_mount_exact(path, &mp);
-	if (unlikely(err)) {
-		mntput(m);
+	if (unlikely(err))
 		return err == -EBUSY ? 0 : err;
-	}
+
 	err = do_add_mount(mnt, mp.mp, path,
 			   path->mnt->mnt_flags | MNT_SHRINKABLE);
+	if (likely(!err))
+		retain_and_null_ptr(m);
 	unlock_mount(&mp);
-	if (unlikely(err))
-		goto discard;
-	return 0;
-
-discard:
-	mntput(m);
 	return err;
 }
 
Signature · 1 line
2.47.2
author maintainer
#71 lore raw

PATCH 27/52 change calling conventions for lock_mount() et.al.

In reply to Al Viro

1) pinned_mountpoint gets a new member - struct mount *parent. Set only if we locked the sucker; ERR_PTR() - on failed attempt.

2) do_lock_mount() et.al. return void and set ->parent to
* on success with !beneath - mount corresponding to path->mnt
* on success with beneath - the parent of mount corresponding
to path->mnt
* in case of error - ERR_PTR(-E...).
IOW, we get the mount we will be actually mounting upon or ERR_PTR().

3) we can't use CLASS, since the pinned_mountpoint is placed on hlist during initialization, so we define local macros:
LOCK_MOUNT(mp, path) LOCK_MOUNT_MAYBE_BENEATH(mp, path, beneath) LOCK_MOUNT_EXACT(mp, path)
All of them declare and initialize struct pinned_mountpoint mp, with unlock_mount done via __cleanup().

Users converted.

---
 fs/namespace.c | 219 ++++++++++++++++++++++++-------------------------
 1 file changed, 108 insertions(+), 111 deletions(-)
fs/namespace.c +108 -111
index 5819a50d7d67..8d6e26e2c97a 100644
@@ -919,6 +919,7 @@ bool __is_local_mountpoint(const struct dentry *dentry)
 struct pinned_mountpoint {
 	struct hlist_node node;
 	struct mountpoint *mp;
+	struct mount *parent;
 };
 
 static bool lookup_mountpoint(struct dentry *dentry, struct pinned_mountpoint *m)
@@ -2728,48 +2729,47 @@ static int attach_recursive_mnt(struct mount *source_mnt,
 }
 
 /**
- * do_lock_mount - lock mount and mountpoint
- * @path:    target path
- * @beneath: whether the intention is to mount beneath @path
+ * do_lock_mount - acquire environment for mounting
+ * @path:	target path
+ * @res:	context to set up
+ * @beneath:	whether the intention is to mount beneath @path
  *
- * Follow the mount stack on @path until the top mount @mnt is found. If
- * the initial @path->{mnt,dentry} is a mountpoint lookup the first
- * mount stacked on top of it. Then simply follow @{mnt,mnt->mnt_root}
- * until nothing is stacked on top of it anymore.
+ * To mount something at given location, we need
+ *	namespace_sem locked exclusive
+ *	inode of dentry we are mounting on locked exclusive
+ *	struct mountpoint for that dentry
+ *	struct mount we are mounting on
  *
- * Acquire the inode_lock() on the top mount's ->mnt_root to protect
- * against concurrent removal of the new mountpoint from another mount
- * namespace.
+ * Results are stored in caller-supplied context (pinned_mountpoint);
+ * on success we have res->parent and res->mp pointing to parent and
+ * mountpoint respectively and res->node inserted into the ->m_list
+ * of the mountpoint, making sure the mountpoint won't disappear.
+ * On failure we have res->parent set to ERR_PTR(-E...), res->mp
+ * left NULL, res->node - empty.
+ * In case of success do_lock_mount returns with locks acquired (in
+ * proper order - inode lock nests outside of namespace_sem).
  *
- * If @beneath is requested, acquire inode_lock() on @mnt's mountpoint
- * @mp on @mnt->mnt_parent must be acquired. This protects against a
- * concurrent unlink of @mp->mnt_dentry from another mount namespace
- * where @mnt doesn't have a child mount mounted @mp. A concurrent
- * removal of @mnt->mnt_root doesn't matter as nothing will be mounted
- * on top of it for @beneath.
+ * Request to mount on overmounted location is treated as "mount on
+ * top of whatever's overmounting it"; request to mount beneath
+ * a location - "mount immediately beneath the topmost mount at that
+ * place".
  *
- * In addition, @beneath needs to make sure that @mnt hasn't been
- * unmounted or moved from its current mountpoint in between dropping
- * @mount_lock and acquiring @namespace_sem. For the !@beneath case @mnt
- * being unmounted would be detected later by e.g., calling
- * check_mnt(mnt) in the function it's called from. For the @beneath
- * case however, it's useful to detect it directly in do_lock_mount().
- * If @mnt hasn't been unmounted then @mnt->mnt_mountpoint still points
- * to @mnt->mnt_mp->m_dentry. But if @mnt has been unmounted it will
- * point to @mnt->mnt_root and @mnt->mnt_mp will be NULL.
- *
- * Return: Either the target mountpoint on the top mount or the top
- *         mount's mountpoint.
+ * In all cases the location must not have been unmounted and the
+ * chosen mountpoint must be allowed to be mounted on.  For "beneath"
+ * case we also require the location to be at the root of a mount
+ * that has a parent (i.e. is not a root of some namespace).
  */
-static int do_lock_mount(struct path *path, struct pinned_mountpoint *pinned, bool beneath)
+static void do_lock_mount(struct path *path, struct pinned_mountpoint *res, bool beneath)
 {
 	struct vfsmount *mnt = path->mnt;
 	struct dentry *dentry;
 	struct path under = {};
 	int err = -ENOENT;
 
-	if (unlikely(beneath) && !path_mounted(path))
-		return -EINVAL;
+	if (unlikely(beneath) && !path_mounted(path)) {
+		res->parent = ERR_PTR(-EINVAL);
+		return;
+	}
 
 	for (;;) {
 		struct mount *m = real_mount(mnt);
@@ -2779,7 +2779,8 @@ static int do_lock_mount(struct path *path, struct pinned_mountpoint *pinned, bo
 			read_seqlock_excl(&mount_lock);
 			if (unlikely(!mnt_has_parent(m))) {
 				read_sequnlock_excl(&mount_lock);
-				return -EINVAL;
+				res->parent = ERR_PTR(-EINVAL);
+				return;
 			}
 			under.mnt = mntget(&m->mnt_parent->mnt);
 			under.dentry = dget(m->mnt_mountpoint);
@@ -2811,7 +2812,7 @@ static int do_lock_mount(struct path *path, struct pinned_mountpoint *pinned, bo
 			path->dentry = dget(mnt->mnt_root);
 			continue;	// got overmounted
 		}
-		err = get_mountpoint(dentry, pinned);
+		err = get_mountpoint(dentry, res);
 		if (err)
 			break;
 		if (beneath) {
@@ -2822,22 +2823,25 @@ static int do_lock_mount(struct path *path, struct pinned_mountpoint *pinned, bo
 			 * we are not dropping the final references here).
 			 */
 			path_put(&under);
+			res->parent = real_mount(path->mnt)->mnt_parent;
+			return;
 		}
-		return 0;
+		res->parent = real_mount(path->mnt);
+		return;
 	}
 	namespace_unlock();
 	inode_unlock(dentry->d_inode);
 	if (beneath)
 		path_put(&under);
-	return err;
+	res->parent = ERR_PTR(err);
 }
 
-static inline int lock_mount(struct path *path, struct pinned_mountpoint *m)
+static inline void lock_mount(struct path *path, struct pinned_mountpoint *m)
 {
-	return do_lock_mount(path, m, false);
+	do_lock_mount(path, m, false);
 }
 
-static void unlock_mount(struct pinned_mountpoint *m)
+static void __unlock_mount(struct pinned_mountpoint *m)
 {
 	inode_unlock(m->mp->m_dentry->d_inode);
 	read_seqlock_excl(&mount_lock);
@@ -2846,6 +2850,20 @@ static void unlock_mount(struct pinned_mountpoint *m)
 	namespace_unlock();
 }
 
+static inline void unlock_mount(struct pinned_mountpoint *m)
+{
+	if (!IS_ERR(m->parent))
+		__unlock_mount(m);
+}
+
+#define LOCK_MOUNT_MAYBE_BENEATH(mp, path, beneath) \
+	struct pinned_mountpoint mp __cleanup(unlock_mount) = {}; \
+	do_lock_mount((path), &mp, (beneath))
+#define LOCK_MOUNT(mp, path) LOCK_MOUNT_MAYBE_BENEATH(mp, (path), false)
+#define LOCK_MOUNT_EXACT(mp, path) \
+	struct pinned_mountpoint mp __cleanup(unlock_mount) = {}; \
+	lock_mount_exact((path), &mp)
+
 static int graft_tree(struct mount *mnt, struct mount *p, struct mountpoint *mp)
 {
 	if (mnt->mnt.mnt_sb->s_flags & SB_NOUSER)
@@ -3015,8 +3033,7 @@ static int do_loopback(struct path *path, const char *old_name,
 				int recurse)
 {
 	struct path old_path __free(path_put) = {};
-	struct mount *mnt = NULL, *parent;
-	struct pinned_mountpoint mp = {};
+	struct mount *mnt = NULL;
 	int err;
 	if (!old_name || !*old_name)
 		return -EINVAL;
@@ -3027,28 +3044,23 @@ static int do_loopback(struct path *path, const char *old_name,
 	if (mnt_ns_loop(old_path.dentry))
 		return -EINVAL;
 
-	err = lock_mount(path, &mp);
-	if (err)
-		return err;
+	LOCK_MOUNT(mp, path);
+	if (IS_ERR(mp.parent))
+		return PTR_ERR(mp.parent);
 
-	parent = real_mount(path->mnt);
-	if (!check_mnt(parent))
-		goto out2;
+	if (!check_mnt(mp.parent))
+		return -EINVAL;
 
 	mnt = __do_loopback(&old_path, recurse);
-	if (IS_ERR(mnt)) {
-		err = PTR_ERR(mnt);
-		goto out2;
-	}
+	if (IS_ERR(mnt))
+		return PTR_ERR(mnt);
 
-	err = graft_tree(mnt, parent, mp.mp);
+	err = graft_tree(mnt, mp.parent, mp.mp);
 	if (err) {
 		lock_mount_hash();
 		umount_tree(mnt, UMOUNT_SYNC);
 		unlock_mount_hash();
 	}
-out2:
-	unlock_mount(&mp);
 	return err;
 }
 
@@ -3561,7 +3573,6 @@ static int do_move_mount(struct path *old_path,
 {
 	struct mount *p;
 	struct mount *old = real_mount(old_path->mnt);
-	struct pinned_mountpoint mp;
 	int err;
 	bool beneath = flags & MNT_TREE_BENEATH;
 
@@ -3571,52 +3582,49 @@ static int do_move_mount(struct path *old_path,
 	if (d_is_dir(new_path->dentry) != d_is_dir(old_path->dentry))
 		return -EINVAL;
 
-	err = do_lock_mount(new_path, &mp, beneath);
-	if (err)
-		return err;
+	LOCK_MOUNT_MAYBE_BENEATH(mp, new_path, beneath);
+	if (IS_ERR(mp.parent))
+		return PTR_ERR(mp.parent);
 
 	p = real_mount(new_path->mnt);
 
-	err = -EINVAL;
-
 	if (check_mnt(old)) {
 		/* if the source is in our namespace... */
 		/* ... it should be detachable from parent */
 		if (!mnt_has_parent(old) || IS_MNT_LOCKED(old))
-			goto out;
+			return -EINVAL;
 		/* ... which should not be shared */
 		if (IS_MNT_SHARED(old->mnt_parent))
-			goto out;
+			return -EINVAL;
 		/* ... and the target should be in our namespace */
 		if (!check_mnt(p))
-			goto out;
+			return -EINVAL;
 	} else {
 		/*
 		 * otherwise the source must be the root of some anon namespace.
 		 */
 		if (!anon_ns_root(old))
-			goto out;
+			return -EINVAL;
 		/*
 		 * Bail out early if the target is within the same namespace -
 		 * subsequent checks would've rejected that, but they lose
 		 * some corner cases if we check it early.
 		 */
 		if (old->mnt_ns == p->mnt_ns)
-			goto out;
+			return -EINVAL;
 		/*
 		 * Target should be either in our namespace or in an acceptable
 		 * anon namespace, sensu check_anonymous_mnt().
 		 */
 		if (!may_use_mount(p))
-			goto out;
+			return -EINVAL;
 	}
 
 	if (beneath) {
 		err = can_move_mount_beneath(old, new_path, mp.mp);
 		if (err)
-			goto out;
+			return err;
 
-		err = -EINVAL;
 		p = p->mnt_parent;
 	}
 
@@ -3625,17 +3633,13 @@ static int do_move_mount(struct path *old_path,
 	 * mount which is shared.
 	 */
 	if (IS_MNT_SHARED(p) && tree_contains_unbindable(old))
-		goto out;
-	err = -ELOOP;
+		return -EINVAL;
 	if (!check_for_nsfs_mounts(old))
-		goto out;
+		return -ELOOP;
 	if (mount_is_ancestor(old, p))
-		goto out;
+		return -ELOOP;
 
-	err = attach_recursive_mnt(old, p, mp.mp);
-out:
-	unlock_mount(&mp);
-	return err;
+	return attach_recursive_mnt(old, p, mp.mp);
 }
 
 static int do_move_mount_old(struct path *path, const char *old_name)
@@ -3694,7 +3698,6 @@ static bool mount_too_revealing(const struct super_block *sb, int *new_mnt_flags
 static int do_new_mount_fc(struct fs_context *fc, struct path *mountpoint,
 			   unsigned int mnt_flags)
 {
-	struct pinned_mountpoint mp = {};
 	struct super_block *sb = fc->root->d_sb;
 	int error;
 
@@ -3715,13 +3718,14 @@ static int do_new_mount_fc(struct fs_context *fc, struct path *mountpoint,
 
 	mnt_warn_timestamp_expiry(mountpoint, mnt);
 
-	error = lock_mount(mountpoint, &mp);
-	if (!error) {
+	LOCK_MOUNT(mp, mountpoint);
+	if (IS_ERR(mp.parent)) {
+		return PTR_ERR(mp.parent);
+	} else {
 		error = do_add_mount(real_mount(mnt), mp.mp,
 				     mountpoint, mnt_flags);
 		if (!error)
 			retain_and_null_ptr(mnt); // consumed on success
-		unlock_mount(&mp);
 	}
 	return error;
 }
@@ -3785,8 +3789,8 @@ static int do_new_mount(struct path *path, const char *fstype, int sb_flags,
 	return err;
 }
 
-static int lock_mount_exact(const struct path *path,
-			    struct pinned_mountpoint *mp)
+static void lock_mount_exact(const struct path *path,
+			     struct pinned_mountpoint *mp)
 {
 	struct dentry *dentry = path->dentry;
 	int err;
@@ -3802,14 +3806,15 @@ static int lock_mount_exact(const struct path *path,
 	if (unlikely(err)) {
 		namespace_unlock();
 		inode_unlock(dentry->d_inode);
+		mp->parent = ERR_PTR(err);
+	} else {
+		mp->parent = real_mount(path->mnt);
 	}
-	return err;
 }
 
 int finish_automount(struct vfsmount *__m, const struct path *path)
 {
 	struct vfsmount *m __free(mntput) = __m;
-	struct pinned_mountpoint mp = {};
 	struct mount *mnt;
 	int err;
 
@@ -3828,15 +3833,14 @@ int finish_automount(struct vfsmount *__m, const struct path *path)
 	 * that overmounts our mountpoint to be means "quitely drop what we've
 	 * got", not "try to mount it on top".
 	 */
-	err = lock_mount_exact(path, &mp);
-	if (unlikely(err))
-		return err == -EBUSY ? 0 : err;
+	LOCK_MOUNT_EXACT(mp, path);
+	if (IS_ERR(mp.parent))
+		return mp.parent == ERR_PTR(-EBUSY) ? 0 : PTR_ERR(mp.parent);
 
 	err = do_add_mount(mnt, mp.mp, path,
 			   path->mnt->mnt_flags | MNT_SHRINKABLE);
 	if (likely(!err))
 		retain_and_null_ptr(m);
-	unlock_mount(&mp);
 	return err;
 }
 
@@ -4633,7 +4637,6 @@ SYSCALL_DEFINE2(pivot_root, const char __user *, new_root,
 	struct path old __free(path_put) = {};
 	struct path root __free(path_put) = {};
 	struct mount *new_mnt, *root_mnt, *old_mnt, *root_parent, *ex_parent;
-	struct pinned_mountpoint old_mp = {};
 	int error;
 
 	if (!may_mount())
@@ -4654,45 +4657,42 @@ SYSCALL_DEFINE2(pivot_root, const char __user *, new_root,
 		return error;
 
 	get_fs_root(current->fs, &root);
-	error = lock_mount(&old, &old_mp);
-	if (error)
-		return error;
 
-	error = -EINVAL;
+	LOCK_MOUNT(old_mp, &old);
+	old_mnt = old_mp.parent;
+	if (IS_ERR(old_mnt))
+		return PTR_ERR(old_mnt);
+
 	new_mnt = real_mount(new.mnt);
 	root_mnt = real_mount(root.mnt);
-	old_mnt = real_mount(old.mnt);
 	ex_parent = new_mnt->mnt_parent;
 	root_parent = root_mnt->mnt_parent;
 	if (IS_MNT_SHARED(old_mnt) ||
 		IS_MNT_SHARED(ex_parent) ||
 		IS_MNT_SHARED(root_parent))
-		goto out4;
+		return -EINVAL;
 	if (!check_mnt(root_mnt) || !check_mnt(new_mnt))
-		goto out4;
+		return -EINVAL;
 	if (new_mnt->mnt.mnt_flags & MNT_LOCKED)
-		goto out4;
-	error = -ENOENT;
+		return -EINVAL;
 	if (d_unlinked(new.dentry))
-		goto out4;
-	error = -EBUSY;
+		return -ENOENT;
 	if (new_mnt == root_mnt || old_mnt == root_mnt)
-		goto out4; /* loop, on the same file system  */
-	error = -EINVAL;
+		return -EBUSY; /* loop, on the same file system  */
 	if (!path_mounted(&root))
-		goto out4; /* not a mountpoint */
+		return -EINVAL; /* not a mountpoint */
 	if (!mnt_has_parent(root_mnt))
-		goto out4; /* absolute root */
+		return -EINVAL; /* absolute root */
 	if (!path_mounted(&new))
-		goto out4; /* not a mountpoint */
+		return -EINVAL; /* not a mountpoint */
 	if (!mnt_has_parent(new_mnt))
-		goto out4; /* absolute root */
+		return -EINVAL; /* absolute root */
 	/* make sure we can reach put_old from new_root */
 	if (!is_path_reachable(old_mnt, old.dentry, &new))
-		goto out4;
+		return -EINVAL;
 	/* make certain new is below the root */
 	if (!is_path_reachable(new_mnt, new.dentry, &root))
-		goto out4;
+		return -EINVAL;
 	lock_mount_hash();
 	umount_mnt(new_mnt);
 	if (root_mnt->mnt.mnt_flags & MNT_LOCKED) {
@@ -4711,10 +4711,7 @@ SYSCALL_DEFINE2(pivot_root, const char __user *, new_root,
 	mnt_notify_add(root_mnt);
 	mnt_notify_add(new_mnt);
 	chroot_fs_refs(&root, &new);
-	error = 0;
-out4:
-	unlock_mount(&old_mp);
-	return error;
+	return 0;
 }
 
 static unsigned int recalc_flags(struct mount_kattr *kattr, struct mount *mnt)
Signature · 1 line
2.47.2
author maintainer
#72 lore raw

PATCH 28/52 do_move_mount(): use the parent mount returned by do_lock_mount()

In reply to Al Viro

After successful do_lock_mount() call, mp.parent is set to either real_mount(path->mnt) (for !beneath case) or to ->mnt_parent of that (for beneath). p is set to real_mount(path->mnt) and after several uses it's made equal to mp.parent. All uses prior to that care only about p->mnt_ns and since p->mnt_ns == parent->mnt_ns, we might as well use mp.parent all along.

---
 fs/namespace.c | 17 ++++++-----------
 1 file changed, 6 insertions(+), 11 deletions(-)
fs/namespace.c +6 -11
index 8d6e26e2c97a..05019dde25a0 100644
@@ -3571,7 +3571,6 @@ static inline bool may_use_mount(struct mount *mnt)
 static int do_move_mount(struct path *old_path,
 			 struct path *new_path, enum mnt_tree_flags_t flags)
 {
-	struct mount *p;
 	struct mount *old = real_mount(old_path->mnt);
 	int err;
 	bool beneath = flags & MNT_TREE_BENEATH;
@@ -3586,8 +3585,6 @@ static int do_move_mount(struct path *old_path,
 	if (IS_ERR(mp.parent))
 		return PTR_ERR(mp.parent);
 
-	p = real_mount(new_path->mnt);
-
 	if (check_mnt(old)) {
 		/* if the source is in our namespace... */
 		/* ... it should be detachable from parent */
@@ -3597,7 +3594,7 @@ static int do_move_mount(struct path *old_path,
 		if (IS_MNT_SHARED(old->mnt_parent))
 			return -EINVAL;
 		/* ... and the target should be in our namespace */
-		if (!check_mnt(p))
+		if (!check_mnt(mp.parent))
 			return -EINVAL;
 	} else {
 		/*
@@ -3610,13 +3607,13 @@ static int do_move_mount(struct path *old_path,
 		 * subsequent checks would've rejected that, but they lose
 		 * some corner cases if we check it early.
 		 */
-		if (old->mnt_ns == p->mnt_ns)
+		if (old->mnt_ns == mp.parent->mnt_ns)
 			return -EINVAL;
 		/*
 		 * Target should be either in our namespace or in an acceptable
 		 * anon namespace, sensu check_anonymous_mnt().
 		 */
-		if (!may_use_mount(p))
+		if (!may_use_mount(mp.parent))
 			return -EINVAL;
 	}
 
@@ -3624,22 +3621,20 @@ static int do_move_mount(struct path *old_path,
 		err = can_move_mount_beneath(old, new_path, mp.mp);
 		if (err)
 			return err;
-
-		p = p->mnt_parent;
 	}
 
 	/*
 	 * Don't move a mount tree containing unbindable mounts to a destination
 	 * mount which is shared.
 	 */
-	if (IS_MNT_SHARED(p) && tree_contains_unbindable(old))
+	if (IS_MNT_SHARED(mp.parent) && tree_contains_unbindable(old))
 		return -EINVAL;
 	if (!check_for_nsfs_mounts(old))
 		return -ELOOP;
-	if (mount_is_ancestor(old, p))
+	if (mount_is_ancestor(old, mp.parent))
 		return -ELOOP;
 
-	return attach_recursive_mnt(old, p, mp.mp);
+	return attach_recursive_mnt(old, mp.parent, mp.mp);
 }
 
 static int do_move_mount_old(struct path *path, const char *old_name)
Signature · 1 line
2.47.2
author maintainer
#73 lore raw

PATCH 29/52 do_add_mount(): switch to passing pinned_mountpoint instead of mountpoint + path

In reply to Al Viro

Both callers pass it a mountpoint reference picked from pinned_mountpoint and path it corresponds to.

First of all, path->dentry is equal to mp.mp->m_dentry. Furthermore, path->mnt is &mp.parent->mnt, making struct path contents redundant.

Pass it the address of that pinned_mountpoint instead; what's more, if we teach it to treat ERR_PTR(error) in ->parent as "bail out with that error" we can simplify the callers even more - do_add_mount() will do the right thing even when called after lock_mount() failure.

---
 fs/namespace.c | 32 +++++++++++++++-----------------
 1 file changed, 15 insertions(+), 17 deletions(-)
fs/namespace.c +15 -17
index 05019dde25a0..06c672127aee 100644
@@ -3657,10 +3657,13 @@ static int do_move_mount_old(struct path *path, const char *old_name)
 /*
  * add a mount into a namespace's mount tree
  */
-static int do_add_mount(struct mount *newmnt, struct mountpoint *mp,
-			const struct path *path, int mnt_flags)
+static int do_add_mount(struct mount *newmnt, const struct pinned_mountpoint *mp,
+			int mnt_flags)
 {
-	struct mount *parent = real_mount(path->mnt);
+	struct mount *parent = mp->parent;
+
+	if (IS_ERR(parent))
+		return PTR_ERR(parent);
 
 	mnt_flags &= ~MNT_INTERNAL_FLAGS;
 
@@ -3674,14 +3677,15 @@ static int do_add_mount(struct mount *newmnt, struct mountpoint *mp,
 	}
 
 	/* Refuse the same filesystem on the same mount point */
-	if (path->mnt->mnt_sb == newmnt->mnt.mnt_sb && path_mounted(path))
+	if (parent->mnt.mnt_sb == newmnt->mnt.mnt_sb &&
+	    parent->mnt.mnt_root == mp->mp->m_dentry)
 		return -EBUSY;
 
 	if (d_is_symlink(newmnt->mnt.mnt_root))
 		return -EINVAL;
 
 	newmnt->mnt.mnt_flags = mnt_flags;
-	return graft_tree(newmnt, parent, mp);
+	return graft_tree(newmnt, parent, mp->mp);
 }
 
 static bool mount_too_revealing(const struct super_block *sb, int *new_mnt_flags);
@@ -3714,14 +3718,9 @@ static int do_new_mount_fc(struct fs_context *fc, struct path *mountpoint,
 	mnt_warn_timestamp_expiry(mountpoint, mnt);
 
 	LOCK_MOUNT(mp, mountpoint);
-	if (IS_ERR(mp.parent)) {
-		return PTR_ERR(mp.parent);
-	} else {
-		error = do_add_mount(real_mount(mnt), mp.mp,
-				     mountpoint, mnt_flags);
-		if (!error)
-			retain_and_null_ptr(mnt); // consumed on success
-	}
+	error = do_add_mount(real_mount(mnt), &mp, mnt_flags);
+	if (!error)
+		retain_and_null_ptr(mnt); // consumed on success
 	return error;
 }
 
@@ -3829,11 +3828,10 @@ int finish_automount(struct vfsmount *__m, const struct path *path)
 	 * got", not "try to mount it on top".
 	 */
 	LOCK_MOUNT_EXACT(mp, path);
-	if (IS_ERR(mp.parent))
-		return mp.parent == ERR_PTR(-EBUSY) ? 0 : PTR_ERR(mp.parent);
+	if (mp.parent == ERR_PTR(-EBUSY))
+		return 0;
 
-	err = do_add_mount(mnt, mp.mp, path,
-			   path->mnt->mnt_flags | MNT_SHRINKABLE);
+	err = do_add_mount(mnt, &mp, path->mnt->mnt_flags | MNT_SHRINKABLE);
 	if (likely(!err))
 		retain_and_null_ptr(m);
 	return err;
Signature · 1 line
2.47.2
author maintainer
#74 lore raw

PATCH 30/52 graft_tree(), attach_recursive_mnt() - pass pinned_mountpoint

In reply to Al Viro

parent and mountpoint always come from the same struct pinned_mountpoint now.

---
 fs/namespace.c | 17 +++++++++--------
 1 file changed, 9 insertions(+), 8 deletions(-)
fs/namespace.c +9 -8
index 06c672127aee..9ffdbb093f57 100644
@@ -2613,10 +2613,11 @@ enum mnt_tree_flags_t {
  *         Otherwise a negative error code is returned.
  */
 static int attach_recursive_mnt(struct mount *source_mnt,
-				struct mount *dest_mnt,
-				struct mountpoint *dest_mp)
+				const struct pinned_mountpoint *dest)
 {
 	struct user_namespace *user_ns = current->nsproxy->mnt_ns->user_ns;
+	struct mount *dest_mnt = dest->parent;
+	struct mountpoint *dest_mp = dest->mp;
 	HLIST_HEAD(tree_list);
 	struct mnt_namespace *ns = dest_mnt->mnt_ns;
 	struct pinned_mountpoint root = {};
@@ -2864,16 +2865,16 @@ static inline void unlock_mount(struct pinned_mountpoint *m)
 	struct pinned_mountpoint mp __cleanup(unlock_mount) = {}; \
 	lock_mount_exact((path), &mp)
 
-static int graft_tree(struct mount *mnt, struct mount *p, struct mountpoint *mp)
+static int graft_tree(struct mount *mnt, const struct pinned_mountpoint *mp)
 {
 	if (mnt->mnt.mnt_sb->s_flags & SB_NOUSER)
 		return -EINVAL;
 
-	if (d_is_dir(mp->m_dentry) !=
+	if (d_is_dir(mp->mp->m_dentry) !=
 	      d_is_dir(mnt->mnt.mnt_root))
 		return -ENOTDIR;
 
-	return attach_recursive_mnt(mnt, p, mp);
+	return attach_recursive_mnt(mnt, mp);
 }
 
 static int may_change_propagation(const struct mount *m)
@@ -3055,7 +3056,7 @@ static int do_loopback(struct path *path, const char *old_name,
 	if (IS_ERR(mnt))
 		return PTR_ERR(mnt);
 
-	err = graft_tree(mnt, mp.parent, mp.mp);
+	err = graft_tree(mnt, &mp);
 	if (err) {
 		lock_mount_hash();
 		umount_tree(mnt, UMOUNT_SYNC);
@@ -3634,7 +3635,7 @@ static int do_move_mount(struct path *old_path,
 	if (mount_is_ancestor(old, mp.parent))
 		return -ELOOP;
 
-	return attach_recursive_mnt(old, mp.parent, mp.mp);
+	return attach_recursive_mnt(old, &mp);
 }
 
 static int do_move_mount_old(struct path *path, const char *old_name)
@@ -3685,7 +3686,7 @@ static int do_add_mount(struct mount *newmnt, const struct pinned_mountpoint *mp
 		return -EINVAL;
 
 	newmnt->mnt.mnt_flags = mnt_flags;
-	return graft_tree(newmnt, parent, mp->mp);
+	return graft_tree(newmnt, mp);
 }
 
 static bool mount_too_revealing(const struct super_block *sb, int *new_mnt_flags);
Signature · 1 line
2.47.2
author maintainer
#75 lore raw

PATCH 31/52 pivot_root(2): use old_mp.mp->m_dentry instead of old.dentry

In reply to Al Viro

That kills the last place where callers of lock_mount(path, &mp) used path->dentry.

---
 fs/namespace.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
fs/namespace.c +1 -1
index 9ffdbb093f57..494433d2e04b 100644
@@ -4682,7 +4682,7 @@ SYSCALL_DEFINE2(pivot_root, const char __user *, new_root,
 	if (!mnt_has_parent(new_mnt))
 		return -EINVAL; /* absolute root */
 	/* make sure we can reach put_old from new_root */
-	if (!is_path_reachable(old_mnt, old.dentry, &new))
+	if (!is_path_reachable(old_mnt, old_mp.mp->m_dentry, &new))
 		return -EINVAL;
 	/* make certain new is below the root */
 	if (!is_path_reachable(new_mnt, new.dentry, &root))
Signature · 1 line
2.47.2
author maintainer
#77 lore raw

PATCH 32/52 don't bother passing new_path->dentry to can_move_mount_beneath()

In reply to Al Viro
---
 fs/namespace.c | 11 +++++------
 1 file changed, 5 insertions(+), 6 deletions(-)
fs/namespace.c +5 -6
index 494433d2e04b..7d51763fc76c 100644
@@ -3451,8 +3451,8 @@ static bool mount_is_ancestor(const struct mount *p1, const struct mount *p2)
 /**
  * can_move_mount_beneath - check that we can mount beneath the top mount
  * @mnt_from: mount we are trying to move
- * @to:   mount under which to mount
- * @mp:   mountpoint of @to
+ * @mnt_to:   mount under which to mount
+ * @mp:   mountpoint of @mnt_to
  *
  * - Make sure that nothing can be mounted beneath the caller's current
  *   root or the rootfs of the namespace.
@@ -3468,11 +3468,10 @@ static bool mount_is_ancestor(const struct mount *p1, const struct mount *p2)
  * Return: On success 0, and on error a negative error code is returned.
  */
 static int can_move_mount_beneath(struct mount *mnt_from,
-				  const struct path *to,
+				  struct mount *mnt_to,
 				  const struct mountpoint *mp)
 {
-	struct mount *mnt_to = real_mount(to->mnt),
-		     *parent_mnt_to = mnt_to->mnt_parent;
+	struct mount *parent_mnt_to = mnt_to->mnt_parent;
 
 	if (IS_MNT_LOCKED(mnt_to))
 		return -EINVAL;
@@ -3619,7 +3618,7 @@ static int do_move_mount(struct path *old_path,
 	}
 
 	if (beneath) {
-		err = can_move_mount_beneath(old, new_path, mp.mp);
+		err = can_move_mount_beneath(old, real_mount(new_path->mnt), mp.mp);
 		if (err)
 			return err;
 	}
Signature · 1 line
2.47.2
author maintainer
#78 lore raw

PATCH 33/52 new helper: topmost_overmount()

In reply to Al Viro

Returns the final (topmost) mount in the chain of overmounts starting at given mount. Same locking rules as for any mount tree traversal - either the spinlock side of mount_lock, or rcu + sample the seqcount side of mount_lock before the call and recheck afterwards.

---
 fs/mount.h     | 7 +++++++
 fs/namespace.c | 9 +++------
 2 files changed, 10 insertions(+), 6 deletions(-)
fs/mount.h +7 -0
index ed8c83ba836a..04d0eadc4c10 100644
@@ -235,4 +235,11 @@ static inline void mnt_notify_add(struct mount *m)
 }
 #endif
 
+static inline struct mount *topmost_overmount(struct mount *m)
+{
+	while (m->overmount)
+		m = m->overmount;
+	return m;
+}
+
 struct mnt_namespace *mnt_ns_from_dentry(struct dentry *dentry);
fs/namespace.c +3 -6
index 7d51763fc76c..93eba16e42b6 100644
@@ -2697,10 +2697,9 @@ static int attach_recursive_mnt(struct mount *source_mnt,
 				 child->mnt_mountpoint);
 		commit_tree(child);
 		if (q) {
+			struct mount *r = topmost_overmount(child);
 			struct mountpoint *mp = root.mp;
-			struct mount *r = child;
-			while (unlikely(r->overmount))
-				r = r->overmount;
+
 			if (unlikely(shorter) && child != source_mnt)
 				mp = shorter;
 			mnt_change_mountpoint(r, mp, q);
@@ -6178,9 +6177,7 @@ bool current_chrooted(void)
 
 	guard(mount_locked_reader)();
 
-	root = current->nsproxy->mnt_ns->root;
-	while (unlikely(root->overmount))
-		root = root->overmount;
+	root = topmost_overmount(current->nsproxy->mnt_ns->root);
 
 	return fs_root.mnt != &root->mnt || !path_mounted(&fs_root);
 }
Signature · 1 line
2.47.2
maintainer Reviewed-by on v1 33/52
#79 lore raw

Re: [PATCH 33/52] new helper: topmost_overmount()

Quoted 8 lines · Al Viro

Returns the final (topmost) mount in the chain of overmounts starting at given mount. Same locking rules as for any mount tree traversal - either the spinlock side of mount_lock, or rcu + sample the seqcount side of mount_lock before the call and recheck afterwards.

---

author maintainer
#80 lore raw

PATCH 34/52 do_lock_mount(): don't modify path.

In reply to Al Viro

Currently do_lock_mount() has the target path switched to whatever might be overmounting it. We _do_ want to have the parent mount/mountpoint chosen on top of the overmounting pile; however, the way it's done has unpleasant races - if umount propagation removes the overmount while we'd been trying to set the environment up, we might end up failing if our target path strays into that overmount just before the overmount gets kicked out.

Users of do_lock_mount() do not need the target path changed - they have all information in res->{parent,mp}; only one place (in do_move_mount()) currently uses the resulting path->mnt, and that value is trivial to reconstruct by the original value of path->mnt + chosen parent mount.

Let's keep the target path unchanged; it avoids a bunch of subtle races and it's not hard to do:
do
as mount_locked_reader
find the prospective parent mount/mountpoint dentry grab references if it's not the original target
lock the prospective mountpoint dentry
take namespace_sem exclusive
if prospective parent/mountpoint would be different now
err = -EAGAIN
else if location has been unmounted
err = -ENOENT
else if mountpoint dentry is not allowed to be mounted on
err = -ENOENT
else if beneath and the top of the pile was the absolute root
err = -EINVAL
else
try to get struct mountpoint (by dentry), set err to 0 on success and -ENO{MEM,ENT} on failure
if err != 0
res->parent = ERR_PTR(err)
drop locks
else
res->parent = prospective parent
drop temporary references
while err == -EAGAIN

A somewhat subtle part is that dropping temporary references is allowed. Neither mounts nor dentries should be evicted by a thread that holds namespace_sem. On success we are dropping those references under namespace_sem, so we need to be sure that these are not the last references remaining. However, on success we'd already verified (under namespace_sem) that original target is still mounted and that mount and dentry we are about to drop are still reachable from it via the mount tree. That guarantees that we are not about to drop the last remaining references.

---
 fs/namespace.c | 126 ++++++++++++++++++++++++++-----------------------
 1 file changed, 68 insertions(+), 58 deletions(-)
fs/namespace.c +68 -58
index 93eba16e42b6..f95e12ab6c9a 100644
@@ -2728,6 +2728,27 @@ static int attach_recursive_mnt(struct mount *source_mnt,
 	return err;
 }
 
+static inline struct mount *where_to_mount(const struct path *path,
+					   struct dentry **dentry,
+					   bool beneath)
+{
+	struct mount *m;
+
+	if (unlikely(beneath)) {
+		m = topmost_overmount(real_mount(path->mnt));
+		*dentry = m->mnt_mountpoint;
+		return m->mnt_parent;
+	} else {
+		m = __lookup_mnt(path->mnt, *dentry = path->dentry);
+		if (unlikely(m)) {
+			m = topmost_overmount(m);
+			*dentry = m->mnt.mnt_root;
+			return m;
+		}
+		return real_mount(path->mnt);
+	}
+}
+
 /**
  * do_lock_mount - acquire environment for mounting
  * @path:	target path
@@ -2759,84 +2780,69 @@ static int attach_recursive_mnt(struct mount *source_mnt,
  * case we also require the location to be at the root of a mount
  * that has a parent (i.e. is not a root of some namespace).
  */
-static void do_lock_mount(struct path *path, struct pinned_mountpoint *res, bool beneath)
+static void do_lock_mount(const struct path *path,
+			  struct pinned_mountpoint *res,
+			  bool beneath)
 {
-	struct vfsmount *mnt = path->mnt;
-	struct dentry *dentry;
-	struct path under = {};
-	int err = -ENOENT;
+	int err;
 
 	if (unlikely(beneath) && !path_mounted(path)) {
 		res->parent = ERR_PTR(-EINVAL);
 		return;
 	}
 
-	for (;;) {
-		struct mount *m = real_mount(mnt);
-
-		if (beneath) {
-			path_put(&under);
-			read_seqlock_excl(&mount_lock);
-			if (unlikely(!mnt_has_parent(m))) {
-				read_sequnlock_excl(&mount_lock);
-				res->parent = ERR_PTR(-EINVAL);
-				return;
+	do {
+		struct dentry *dentry, *d;
+		struct mount *m, *n;
+
+		scoped_guard(mount_locked_reader) {
+			m = where_to_mount(path, &dentry, beneath);
+			if (&m->mnt != path->mnt) {
+				mntget(&m->mnt);
+				dget(dentry);
 			}
-			under.mnt = mntget(&m->mnt_parent->mnt);
-			under.dentry = dget(m->mnt_mountpoint);
-			read_sequnlock_excl(&mount_lock);
-			dentry = under.dentry;
-		} else {
-			dentry = path->dentry;
 		}
 
 		inode_lock(dentry->d_inode);
 		namespace_lock();
 
-		if (unlikely(cant_mount(dentry) || !is_mounted(mnt)))
-			break;		// not to be mounted on
+		// check if the chain of mounts (if any) has changed.
+		scoped_guard(mount_locked_reader)
+			n = where_to_mount(path, &d, beneath);
 
-		if (beneath && unlikely(m->mnt_mountpoint != dentry ||
-				        &m->mnt_parent->mnt != under.mnt)) {
-			namespace_unlock();
-			inode_unlock(dentry->d_inode);
-			continue;	// got moved
-		}
+		if (unlikely(n != m || dentry != d))
+			err = -EAGAIN;		// something moved, retry
+		else if (unlikely(cant_mount(dentry) || !is_mounted(path->mnt)))
+			err = -ENOENT;		// not to be mounted on
+		else if (beneath && &m->mnt == path->mnt && !m->overmount)
+			err = -EINVAL;
+		else
+			err = get_mountpoint(dentry, res);
 
-		mnt = lookup_mnt(path);
-		if (unlikely(mnt)) {
+		if (unlikely(err)) {
+			res->parent = ERR_PTR(err);
 			namespace_unlock();
 			inode_unlock(dentry->d_inode);
-			path_put(path);
-			path->mnt = mnt;
-			path->dentry = dget(mnt->mnt_root);
-			continue;	// got overmounted
+		} else {
+			res->parent = m;
 		}
-		err = get_mountpoint(dentry, res);
-		if (err)
-			break;
-		if (beneath) {
-			/*
-			 * @under duplicates the references that will stay
-			 * at least until namespace_unlock(), so the path_put()
-			 * below is safe (and OK to do under namespace_lock -
-			 * we are not dropping the final references here).
-			 */
-			path_put(&under);
-			res->parent = real_mount(path->mnt)->mnt_parent;
-			return;
+		/*
+		 * Drop the temporary references.  This is subtle - on success
+		 * we are doing that under namespace_sem, which would normally
+		 * be forbidden.  However, in that case we are guaranteed that
+		 * refcounts won't reach zero, since we know that path->mnt
+		 * is mounted and thus all mounts reachable from it are pinned
+		 * and stable, along with their mountpoints and roots.
+		 */
+		if (&m->mnt != path->mnt) {
+			dput(dentry);
+			mntput(&m->mnt);
 		}
-		res->parent = real_mount(path->mnt);
-		return;
-	}
-	namespace_unlock();
-	inode_unlock(dentry->d_inode);
-	if (beneath)
-		path_put(&under);
-	res->parent = ERR_PTR(err);
+	} while (err == -EAGAIN);
 }
 
-static inline void lock_mount(struct path *path, struct pinned_mountpoint *m)
+static inline void lock_mount(const struct path *path,
+			      struct pinned_mountpoint *m)
 {
 	do_lock_mount(path, m, false);
 }
@@ -3617,7 +3623,11 @@ static int do_move_mount(struct path *old_path,
 	}
 
 	if (beneath) {
-		err = can_move_mount_beneath(old, real_mount(new_path->mnt), mp.mp);
+		struct mount *over = real_mount(new_path->mnt);
+
+		if (mp.parent != over->mnt_parent)
+			over = mp.parent->overmount;
+		err = can_move_mount_beneath(old, over, mp.mp);
 		if (err)
 			return err;
 	}
Signature · 1 line
2.47.2
#81 lore raw

Re: [PATCH 34/52] do_lock_mount(): don't modify path.

fs/namespace.c static int attach_recursive_mnt(struct mount *source_mnt, · line 2742

Quoted 1 line
fs/namespace.c +1 -0
+		m = __lookup_mnt(path->mnt, *dentry = path->dentry);

I don't like this.

Someone may think you meant "*dentry == path->dentry" here.

Please, write this:

  *dentry = path->dentry;
  m = __lookup_mnt(path->mnt, *dentry);
Signature · 1 line
Askar Safin
author maintainer
#82 lore raw

PATCH 35/52 constify check_mnt()

In reply to Al Viro
---
 fs/namespace.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
fs/namespace.c +1 -1
index f95e12ab6c9a..458bef569816 100644
@@ -1010,7 +1010,7 @@ static void unpin_mountpoint(struct pinned_mountpoint *m)
 	}
 }
 
-static inline int check_mnt(struct mount *mnt)
+static inline int check_mnt(const struct mount *mnt)
 {
 	return mnt->mnt_ns == current->nsproxy->mnt_ns;
 }
Signature · 1 line
2.47.2
author maintainer
#84 lore raw

PATCH 36/52 do_mount_setattr(): constify path argument

In reply to Al Viro
---
 fs/namespace.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
fs/namespace.c +1 -1
index 458bef569816..2db9b006e37e 100644
@@ -4872,7 +4872,7 @@ static void mount_setattr_commit(struct mount_kattr *kattr, struct mount *mnt)
 	touch_mnt_namespace(mnt->mnt_ns);
 }
 
-static int do_mount_setattr(struct path *path, struct mount_kattr *kattr)
+static int do_mount_setattr(const struct path *path, struct mount_kattr *kattr)
 {
 	struct mount *mnt = real_mount(path->mnt);
 	int err = 0;
Signature · 1 line
2.47.2
author maintainer
#86 lore raw

PATCH 37/52 do_set_group(): constify path arguments

In reply to Al Viro
---
 fs/namespace.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
fs/namespace.c +1 -1
index 2db9b006e37e..d61601fc97ca 100644
@@ -3360,7 +3360,7 @@ static inline int tree_contains_unbindable(struct mount *mnt)
 	return 0;
 }
 
-static int do_set_group(struct path *from_path, struct path *to_path)
+static int do_set_group(const struct path *from_path, const struct path *to_path)
 {
 	struct mount *from = real_mount(from_path->mnt);
 	struct mount *to = real_mount(to_path->mnt);
Signature · 1 line
2.47.2
author maintainer
#88 lore raw

PATCH 38/52 drop_collected_paths(): constify arguments

In reply to Al Viro

... and use that to constify the pointers in callers

---
 fs/namespace.c        |  4 ++--
 include/linux/mount.h |  2 +-
 kernel/audit_tree.c   | 12 ++++++------
 3 files changed, 9 insertions(+), 9 deletions(-)
fs/namespace.c +2 -2
index d61601fc97ca..d29d7c948ec1 100644
@@ -2334,9 +2334,9 @@ struct path *collect_paths(const struct path *path,
 	return res;
 }
 
-void drop_collected_paths(struct path *paths, struct path *prealloc)
+void drop_collected_paths(const struct path *paths, struct path *prealloc)
 {
-	for (struct path *p = paths; p->mnt; p++)
+	for (const struct path *p = paths; p->mnt; p++)
 		path_put(p);
 	if (paths != prealloc)
 		kfree(paths);
include/linux/mount.h +1 -1
index 5f9c053b0897..c09032463b36 100644
@@ -105,7 +105,7 @@ extern int may_umount(struct vfsmount *);
 int do_mount(const char *, const char __user *,
 		     const char *, unsigned long, void *);
 extern struct path *collect_paths(const struct path *, struct path *, unsigned);
-extern void drop_collected_paths(struct path *, struct path *);
+extern void drop_collected_paths(const struct path *, struct path *);
 extern void kern_unmount_array(struct vfsmount *mnt[], unsigned int num);
 
 extern int cifs_root_data(char **dev, char **opts);
kernel/audit_tree.c +6 -6
index b0eae2a3c895..32007edf0e55 100644
@@ -678,7 +678,7 @@ void audit_trim_trees(void)
 		struct audit_tree *tree;
 		struct path path;
 		struct audit_node *node;
-		struct path *paths;
+		const struct path *paths;
 		struct path array[16];
 		int err;
 
@@ -701,7 +701,7 @@ void audit_trim_trees(void)
 			struct audit_chunk *chunk = find_chunk(node);
 			/* this could be NULL if the watch is dying else where... */
 			node->index |= 1U<<31;
-			for (struct path *p = paths; p->dentry; p++) {
+			for (const struct path *p = paths; p->dentry; p++) {
 				struct inode *inode = p->dentry->d_inode;
 				if (inode_to_key(inode) == chunk->key) {
 					node->index &= ~(1U<<31);
@@ -740,9 +740,9 @@ void audit_put_tree(struct audit_tree *tree)
 	put_tree(tree);
 }
 
-static int tag_mounts(struct path *paths, struct audit_tree *tree)
+static int tag_mounts(const struct path *paths, struct audit_tree *tree)
 {
-	for (struct path *p = paths; p->dentry; p++) {
+	for (const struct path *p = paths; p->dentry; p++) {
 		int err = tag_chunk(p->dentry->d_inode, tree);
 		if (err)
 			return err;
@@ -805,7 +805,7 @@ int audit_add_tree_rule(struct audit_krule *rule)
 	struct audit_tree *seed = rule->tree, *tree;
 	struct path path;
 	struct path array[16];
-	struct path *paths;
+	const struct path *paths;
 	int err;
 
 	rule->tree = NULL;
@@ -877,7 +877,7 @@ int audit_tag_tree(char *old, char *new)
 	int failed = 0;
 	struct path path1, path2;
 	struct path array[16];
-	struct path *paths;
+	const struct path *paths;
 	int err;
 
 	err = kern_path(new, 0, &path2);
Signature · 1 line
2.47.2
author maintainer
#90 lore raw

PATCH 39/52 collect_paths(): constify the return value

In reply to Al Viro

callers have no business modifying the paths they get

---
 fs/namespace.c        | 4 ++--
 include/linux/mount.h | 4 ++--
 2 files changed, 4 insertions(+), 4 deletions(-)
fs/namespace.c +2 -2
index d29d7c948ec1..cc4e18040506 100644
@@ -2300,7 +2300,7 @@ static inline bool extend_array(struct path **res, struct path **to_free,
 	return p;
 }
 
-struct path *collect_paths(const struct path *path,
+const struct path *collect_paths(const struct path *path,
 			      struct path *prealloc, unsigned count)
 {
 	struct mount *root = real_mount(path->mnt);
@@ -2334,7 +2334,7 @@ struct path *collect_paths(const struct path *path,
 	return res;
 }
 
-void drop_collected_paths(const struct path *paths, struct path *prealloc)
+void drop_collected_paths(const struct path *paths, const struct path *prealloc)
 {
 	for (const struct path *p = paths; p->mnt; p++)
 		path_put(p);
include/linux/mount.h +2 -2
index c09032463b36..18e4b97f8a98 100644
@@ -104,8 +104,8 @@ extern int may_umount_tree(struct vfsmount *);
 extern int may_umount(struct vfsmount *);
 int do_mount(const char *, const char __user *,
 		     const char *, unsigned long, void *);
-extern struct path *collect_paths(const struct path *, struct path *, unsigned);
-extern void drop_collected_paths(const struct path *, struct path *);
+extern const struct path *collect_paths(const struct path *, struct path *, unsigned);
+extern void drop_collected_paths(const struct path *, const struct path *);
 extern void kern_unmount_array(struct vfsmount *mnt[], unsigned int num);
 
 extern int cifs_root_data(char **dev, char **opts);
Signature · 1 line
2.47.2
author maintainer
#92 lore raw

PATCH 40/52 do_move_mount(), vfs_move_mount(), do_move_mount_old(): constify struct path argument(s)

In reply to Al Viro
---
 fs/namespace.c | 10 ++++++----
 1 file changed, 6 insertions(+), 4 deletions(-)
fs/namespace.c +6 -4
index cc4e18040506..4704630847af 100644
@@ -3573,8 +3573,9 @@ static inline bool may_use_mount(struct mount *mnt)
 	return check_anonymous_mnt(mnt);
 }
 
-static int do_move_mount(struct path *old_path,
-			 struct path *new_path, enum mnt_tree_flags_t flags)
+static int do_move_mount(const struct path *old_path,
+			 const struct path *new_path,
+			 enum mnt_tree_flags_t flags)
 {
 	struct mount *old = real_mount(old_path->mnt);
 	int err;
@@ -3646,7 +3647,7 @@ static int do_move_mount(struct path *old_path,
 	return attach_recursive_mnt(old, &mp);
 }
 
-static int do_move_mount_old(struct path *path, const char *old_name)
+static int do_move_mount_old(const struct path *path, const char *old_name)
 {
 	struct path old_path;
 	int err;
@@ -4481,7 +4482,8 @@ SYSCALL_DEFINE3(fsmount, int, fs_fd, unsigned int, flags,
 	return ret;
 }
 
-static inline int vfs_move_mount(struct path *from_path, struct path *to_path,
+static inline int vfs_move_mount(const struct path *from_path,
+				 const struct path *to_path,
 				 enum mnt_tree_flags_t mflags)
 {
 	int ret;
Signature · 1 line
2.47.2
author maintainer
#94 lore raw

PATCH 41/52 mnt_warn_timestamp_expiry(): constify struct path argument

In reply to Al Viro
---
 fs/namespace.c | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)
fs/namespace.c +2 -1
index 4704630847af..70636922310c 100644
@@ -3231,7 +3231,8 @@ static void set_mount_attributes(struct mount *mnt, unsigned int mnt_flags)
 	touch_mnt_namespace(mnt->mnt_ns);
 }
 
-static void mnt_warn_timestamp_expiry(struct path *mountpoint, struct vfsmount *mnt)
+static void mnt_warn_timestamp_expiry(const struct path *mountpoint,
+				      struct vfsmount *mnt)
 {
 	struct super_block *sb = mnt->mnt_sb;
 
Signature · 1 line
2.47.2
author maintainer
#96 lore raw

PATCH 42/52 do_new_mount{,_fc}(): constify struct path argument

In reply to Al Viro
---
 fs/namespace.c | 7 ++++---
 1 file changed, 4 insertions(+), 3 deletions(-)
fs/namespace.c +4 -3
index 70636922310c..bf1a6efd335e 100644
@@ -3705,7 +3705,7 @@ static bool mount_too_revealing(const struct super_block *sb, int *new_mnt_flags
  * Create a new mount using a superblock configuration and request it
  * be added to the namespace tree.
  */
-static int do_new_mount_fc(struct fs_context *fc, struct path *mountpoint,
+static int do_new_mount_fc(struct fs_context *fc, const struct path *mountpoint,
 			   unsigned int mnt_flags)
 {
 	struct super_block *sb = fc->root->d_sb;
@@ -3739,8 +3739,9 @@ static int do_new_mount_fc(struct fs_context *fc, struct path *mountpoint,
  * create a new mount for userspace and request it to be added into the
  * namespace's tree
  */
-static int do_new_mount(struct path *path, const char *fstype, int sb_flags,
-			int mnt_flags, const char *name, void *data)
+static int do_new_mount(const struct path *path, const char *fstype,
+			int sb_flags, int mnt_flags,
+			const char *name, void *data)
 {
 	struct file_system_type *type;
 	struct fs_context *fc;
Signature · 1 line
2.47.2
author maintainer
#98 lore raw

PATCH 43/52 do_{loopback,change_type,remount,reconfigure_mnt}(): constify struct path argument

In reply to Al Viro
---
 fs/namespace.c | 10 +++++-----
 1 file changed, 5 insertions(+), 5 deletions(-)
fs/namespace.c +5 -5
index bf1a6efd335e..68c12866205c 100644
@@ -2915,7 +2915,7 @@ static int flags_to_propagation_type(int ms_flags)
 /*
  * recursively change the type of the mountpoint.
  */
-static int do_change_type(struct path *path, int ms_flags)
+static int do_change_type(const struct path *path, int ms_flags)
 {
 	struct mount *m;
 	struct mount *mnt = real_mount(path->mnt);
@@ -3035,8 +3035,8 @@ static struct mount *__do_loopback(struct path *old_path, int recurse)
 /*
  * do loopback mount.
  */
-static int do_loopback(struct path *path, const char *old_name,
-				int recurse)
+static int do_loopback(const struct path *path, const char *old_name,
+		       int recurse)
 {
 	struct path old_path __free(path_put) = {};
 	struct mount *mnt = NULL;
@@ -3266,7 +3266,7 @@ static void mnt_warn_timestamp_expiry(const struct path *mountpoint,
  * superblock it refers to.  This is triggered by specifying MS_REMOUNT|MS_BIND
  * to mount(2).
  */
-static int do_reconfigure_mnt(struct path *path, unsigned int mnt_flags)
+static int do_reconfigure_mnt(const struct path *path, unsigned int mnt_flags)
 {
 	struct super_block *sb = path->mnt->mnt_sb;
 	struct mount *mnt = real_mount(path->mnt);
@@ -3303,7 +3303,7 @@ static int do_reconfigure_mnt(struct path *path, unsigned int mnt_flags)
  * If you've mounted a non-root directory somewhere and want to do remount
  * on it - tough luck.
  */
-static int do_remount(struct path *path, int ms_flags, int sb_flags,
+static int do_remount(const struct path *path, int ms_flags, int sb_flags,
 		      int mnt_flags, void *data)
 {
 	int err;
Signature · 1 line
2.47.2
author maintainer
#100 lore raw

PATCH 44/52 path_mount(): constify struct path argument

In reply to Al Viro

now it finally can be done.

---
 fs/internal.h  | 2 +-
 fs/namespace.c | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)
fs/internal.h +1 -1
index 38e8aab27bbd..fe88563b4822 100644
@@ -84,7 +84,7 @@ void mnt_put_write_access_file(struct file *file);
 extern void dissolve_on_fput(struct vfsmount *);
 extern bool may_mount(void);
 
-int path_mount(const char *dev_name, struct path *path,
+int path_mount(const char *dev_name, const struct path *path,
 		const char *type_page, unsigned long flags, void *data_page);
 int path_umount(struct path *path, int flags);
 
fs/namespace.c +1 -1
index 68c12866205c..94eec417cc61 100644
@@ -4024,7 +4024,7 @@ static char *copy_mount_string(const void __user *data)
  * Therefore, if this magic number is present, it carries no information
  * and must be discarded.
  */
-int path_mount(const char *dev_name, struct path *path,
+int path_mount(const char *dev_name, const struct path *path,
 		const char *type_page, unsigned long flags, void *data_page)
 {
 	unsigned int mnt_flags = 0, sb_flags;
Signature · 1 line
2.47.2
author maintainer
#102 lore raw

PATCH 45/52 may_copy_tree(), __do_loopback(): constify struct path argument

In reply to Al Viro
---
 fs/namespace.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)
fs/namespace.c +2 -2
index 94eec417cc61..a94aa249cedb 100644
@@ -2991,7 +2991,7 @@ static int do_change_type(const struct path *path, int ms_flags)
  *
  * Returns true if the mount tree can be copied, false otherwise.
  */
-static inline bool may_copy_tree(struct path *path)
+static inline bool may_copy_tree(const struct path *path)
 {
 	struct mount *mnt = real_mount(path->mnt);
 	const struct dentry_operations *d_op;
@@ -3013,7 +3013,7 @@ static inline bool may_copy_tree(struct path *path)
 }
 
 
-static struct mount *__do_loopback(struct path *old_path, int recurse)
+static struct mount *__do_loopback(const struct path *old_path, int recurse)
 {
 	struct mount *old = real_mount(old_path->mnt);
 
Signature · 1 line
2.47.2
author maintainer
#104 lore raw

PATCH 46/52 path_umount(): constify struct path argument

In reply to Al Viro
---
 fs/internal.h  | 2 +-
 fs/namespace.c | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)
fs/internal.h +1 -1
index fe88563b4822..549e6bd453b0 100644
@@ -86,7 +86,7 @@ extern bool may_mount(void);
 
 int path_mount(const char *dev_name, const struct path *path,
 		const char *type_page, unsigned long flags, void *data_page);
-int path_umount(struct path *path, int flags);
+int path_umount(const struct path *path, int flags);
 
 int show_path(struct seq_file *m, struct dentry *root);
 
fs/namespace.c +1 -1
index a94aa249cedb..76f0dde2ff62 100644
@@ -2084,7 +2084,7 @@ static int can_umount(const struct path *path, int flags)
 }
 
 // caller is responsible for flags being sane
-int path_umount(struct path *path, int flags)
+int path_umount(const struct path *path, int flags)
 {
 	struct mount *mnt = real_mount(path->mnt);
 	int ret;
Signature · 1 line
2.47.2
author maintainer
#107 lore raw

Re: [PATCH 46/52] path_umount(): constify struct path argument

Quoted 9 lines · Askar Safin

Please, drop this patch.

I plan to inline path_umount into the only caller in v2 version of my initrd removal patchset
https://lore.kernel.org/lkml/[email protected]/
.

(path_umount is called in one place only
after initrd is removed.)

Um... That'll conflict with more than that commit - take a look at "do_mount(): use __free(path_put)" in there. So let's do that inlining later...

author maintainer
#108 lore raw

PATCH 47/52 constify can_move_mount_beneath() arguments

In reply to Al Viro
---
 fs/namespace.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)
fs/namespace.c +2 -2
index 76f0dde2ff62..c6fd5d4d7947 100644
@@ -3473,8 +3473,8 @@ static bool mount_is_ancestor(const struct mount *p1, const struct mount *p2)
  * Context: This function expects namespace_lock() to be held.
  * Return: On success 0, and on error a negative error code is returned.
  */
-static int can_move_mount_beneath(struct mount *mnt_from,
-				  struct mount *mnt_to,
+static int can_move_mount_beneath(const struct mount *mnt_from,
+				  const struct mount *mnt_to,
 				  const struct mountpoint *mp)
 {
 	struct mount *parent_mnt_to = mnt_to->mnt_parent;
Signature · 1 line
2.47.2
author maintainer
#110 lore raw

PATCH 48/52 do_move_mount_old(): use __free(path_put)

In reply to Al Viro
---
 fs/namespace.c | 6 ++----
 1 file changed, 2 insertions(+), 4 deletions(-)
fs/namespace.c +2 -4
index c6fd5d4d7947..da30c7b757d3 100644
@@ -3650,7 +3650,7 @@ static int do_move_mount(const struct path *old_path,
 
 static int do_move_mount_old(const struct path *path, const char *old_name)
 {
-	struct path old_path;
+	struct path old_path __free(path_put) = {};
 	int err;
 
 	if (!old_name || !*old_name)
@@ -3660,9 +3660,7 @@ static int do_move_mount_old(const struct path *path, const char *old_name)
 	if (err)
 		return err;
 
-	err = do_move_mount(&old_path, path, 0);
-	path_put(&old_path);
-	return err;
+	return do_move_mount(&old_path, path, 0);
 }
 
 /*
Signature · 1 line
2.47.2
author maintainer
#112 lore raw

PATCH 49/52 do_mount(): use __free(path_put)

In reply to Al Viro
---
 fs/namespace.c | 6 ++----
 1 file changed, 2 insertions(+), 4 deletions(-)
fs/namespace.c +2 -4
index da30c7b757d3..d8554742b1c0 100644
@@ -4104,15 +4104,13 @@ int path_mount(const char *dev_name, const struct path *path,
 int do_mount(const char *dev_name, const char __user *dir_name,
 		const char *type_page, unsigned long flags, void *data_page)
 {
-	struct path path;
+	struct path path __free(path_put) = {};
 	int ret;
 
 	ret = user_path_at(AT_FDCWD, dir_name, LOOKUP_FOLLOW, &path);
 	if (ret)
 		return ret;
-	ret = path_mount(dev_name, &path, type_page, flags, data_page);
-	path_put(&path);
-	return ret;
+	return path_mount(dev_name, &path, type_page, flags, data_page);
 }
 
 static struct ucounts *inc_mnt_namespaces(struct user_namespace *ns)
Signature · 1 line
2.47.2
author maintainer
#114 lore raw

PATCH 50/52 umount_tree(): take all victims out of propagation graph at once

In reply to Al Viro

For each removed mount we need to calculate where the slaves will end up. To avoid duplicating that work, do it for all mounts to be removed at once, taking the mounts themselves out of propagation graph as we go, then do all transfers; the duplicate work on finding destinations is avoided since if we run into a mount that already had destination found, we don't need to trace the rest of the way. That's guaranteed O(removed mounts) for finding destinations and removing from propagation graph and O(surviving mounts that have master removed) for transfers.

---
 fs/namespace.c |  3 ++-
 fs/pnode.c     | 67 +++++++++++++++++++++++++++++++++++++++-----------
 fs/pnode.h     |  1 +
 3 files changed, 55 insertions(+), 16 deletions(-)
fs/namespace.c +2 -1
index d8554742b1c0..82cab5459ec7 100644
@@ -1846,6 +1846,8 @@ static void umount_tree(struct mount *mnt, enum umount_tree_flags how)
 	if (how & UMOUNT_PROPAGATE)
 		propagate_umount(&tmp_list);
 
+	bulk_make_private(&tmp_list);
+
 	while (!list_empty(&tmp_list)) {
 		struct mnt_namespace *ns;
 		bool disconnect;
@@ -1870,7 +1872,6 @@ static void umount_tree(struct mount *mnt, enum umount_tree_flags how)
 				umount_mnt(p);
 			}
 		}
-		change_mnt_propagation(p, MS_PRIVATE);
 		if (disconnect)
 			hlist_add_head(&p->mnt_umount, &unmounted);
 
fs/pnode.c +52 -15

88 lines not on this page · raw message

fs/pnode.h +1 -0
index 00ab153e3e9d..b029db225f33 100644
@@ -42,6 +42,7 @@ static inline bool peers(const struct mount *m1, const struct mount *m2)
 }
 
 void change_mnt_propagation(struct mount *, int);
+void bulk_make_private(struct list_head *);
 int propagate_mnt(struct mount *, struct mountpoint *, struct mount *,
 		struct hlist_head *);
 void propagate_umount(struct list_head *);
Signature · 1 line
2.47.2
author maintainer
#115 lore raw

PATCH 51/52 ecryptfs: get rid of pointless mount references in ecryptfs dentries

In reply to Al Viro

->lower_path.mnt has the same value for all dentries on given ecryptfs instance and if somebody goes for mountpoint-crossing variant where that would not be true, we can deal with that when it happens (and _not_ with duplicating these reference into each dentry).

As it is, we are better off just sticking a reference into ecryptfs-private part of superblock and keeping it pinned until ->kill_sb().

That way we can stick a reference to underlying dentry right into ->d_fsdata of ecryptfs one, getting rid of indirection through struct ecryptfs_dentry_info, along with the entire struct ecryptfs_dentry_info machinery.

---
 fs/ecryptfs/dentry.c          | 14 +-------------
 fs/ecryptfs/ecryptfs_kernel.h | 27 +++++++++++----------------
 fs/ecryptfs/file.c            | 15 +++++++--------
 fs/ecryptfs/inode.c           | 19 +++++--------------
 fs/ecryptfs/main.c            | 24 ++++++------------------
 5 files changed, 30 insertions(+), 69 deletions(-)
fs/ecryptfs/dentry.c +1 -13
index 1dfd5b81d831..6648a924e31a 100644
@@ -59,14 +59,6 @@ static int ecryptfs_d_revalidate(struct inode *dir, const struct qstr *name,
 	return rc;
 }
 
-struct kmem_cache *ecryptfs_dentry_info_cache;
-
-static void ecryptfs_dentry_free_rcu(struct rcu_head *head)
-{
-	kmem_cache_free(ecryptfs_dentry_info_cache,
-		container_of(head, struct ecryptfs_dentry_info, rcu));
-}
-
 /**
  * ecryptfs_d_release
  * @dentry: The ecryptfs dentry
@@ -75,11 +67,7 @@ static void ecryptfs_dentry_free_rcu(struct rcu_head *head)
  */
 static void ecryptfs_d_release(struct dentry *dentry)
 {
-	struct ecryptfs_dentry_info *p = dentry->d_fsdata;
-	if (p) {
-		path_put(&p->lower_path);
-		call_rcu(&p->rcu, ecryptfs_dentry_free_rcu);
-	}
+	dput(dentry->d_fsdata);
 }
 
 const struct dentry_operations ecryptfs_dops = {
fs/ecryptfs/ecryptfs_kernel.h +11 -16
index 1f562e75d0e4..9e6ab0b41337 100644
@@ -258,13 +258,6 @@ struct ecryptfs_inode_info {
 	struct ecryptfs_crypt_stat crypt_stat;
 };
 
-/* dentry private data. Each dentry must keep track of a lower
- * vfsmount too. */
-struct ecryptfs_dentry_info {
-	struct path lower_path;
-	struct rcu_head rcu;
-};
-
 /**
  * ecryptfs_global_auth_tok - A key used to encrypt all new files under the mountpoint
  * @flags: Status flags
@@ -348,6 +341,7 @@ struct ecryptfs_mount_crypt_stat {
 /* superblock private data. */
 struct ecryptfs_sb_info {
 	struct super_block *wsi_sb;
+	struct vfsmount *lower_mnt;
 	struct ecryptfs_mount_crypt_stat mount_crypt_stat;
 };
 
@@ -494,22 +488,25 @@ ecryptfs_set_superblock_lower(struct super_block *sb,
 }
 
 static inline void
-ecryptfs_set_dentry_private(struct dentry *dentry,
-			    struct ecryptfs_dentry_info *dentry_info)
+ecryptfs_set_dentry_lower(struct dentry *dentry,
+			  struct dentry *lower_dentry)
 {
-	dentry->d_fsdata = dentry_info;
+	dentry->d_fsdata = lower_dentry;
 }
 
 static inline struct dentry *
 ecryptfs_dentry_to_lower(struct dentry *dentry)
 {
-	return ((struct ecryptfs_dentry_info *)dentry->d_fsdata)->lower_path.dentry;
+	return dentry->d_fsdata;
 }
 
-static inline const struct path *
-ecryptfs_dentry_to_lower_path(struct dentry *dentry)
+static inline struct path
+ecryptfs_lower_path(struct dentry *dentry)
 {
-	return &((struct ecryptfs_dentry_info *)dentry->d_fsdata)->lower_path;
+	return (struct path){
+		.mnt = ecryptfs_superblock_to_private(dentry->d_sb)->lower_mnt,
+		.dentry = ecryptfs_dentry_to_lower(dentry)
+	};
 }
 
 #define ecryptfs_printk(type, fmt, arg...) \
@@ -532,7 +529,6 @@ extern unsigned int ecryptfs_number_of_users;
 
 extern struct kmem_cache *ecryptfs_auth_tok_list_item_cache;
 extern struct kmem_cache *ecryptfs_file_info_cache;
-extern struct kmem_cache *ecryptfs_dentry_info_cache;
 extern struct kmem_cache *ecryptfs_inode_info_cache;
 extern struct kmem_cache *ecryptfs_sb_info_cache;
 extern struct kmem_cache *ecryptfs_header_cache;
@@ -557,7 +553,6 @@ int ecryptfs_encrypt_and_encode_filename(
 	size_t *encoded_name_size,
 	struct ecryptfs_mount_crypt_stat *mount_crypt_stat,
 	const char *name, size_t name_size);
-struct dentry *ecryptfs_lower_dentry(struct dentry *this_dentry);
 void ecryptfs_dump_hex(char *data, int bytes);
 int virt_to_scatterlist(const void *addr, int size, struct scatterlist *sg,
 			int sg_size);
fs/ecryptfs/file.c +7 -8
index 5f8f96da09fe..7929411837cf 100644
@@ -33,13 +33,12 @@ static ssize_t ecryptfs_read_update_atime(struct kiocb *iocb,
 				struct iov_iter *to)
 {
 	ssize_t rc;
-	const struct path *path;
 	struct file *file = iocb->ki_filp;
 
 	rc = generic_file_read_iter(iocb, to);
 	if (rc >= 0) {
-		path = ecryptfs_dentry_to_lower_path(file->f_path.dentry);
-		touch_atime(path);
+		struct path path = ecryptfs_lower_path(file->f_path.dentry);
+		touch_atime(&path);
 	}
 	return rc;
 }
@@ -59,12 +58,11 @@ static ssize_t ecryptfs_splice_read_update_atime(struct file *in, loff_t *ppos,
 						 size_t len, unsigned int flags)
 {
 	ssize_t rc;
-	const struct path *path;
 
 	rc = filemap_splice_read(in, ppos, pipe, len, flags);
 	if (rc >= 0) {
-		path = ecryptfs_dentry_to_lower_path(in->f_path.dentry);
-		touch_atime(path);
+		struct path path = ecryptfs_lower_path(in->f_path.dentry);
+		touch_atime(&path);
 	}
 	return rc;
 }
@@ -283,6 +281,7 @@ static int ecryptfs_dir_open(struct inode *inode, struct file *file)
 	 * ecryptfs_lookup() */
 	struct ecryptfs_file_info *file_info;
 	struct file *lower_file;
+	struct path path;
 
 	/* Released in ecryptfs_release or end of function if failure */
 	file_info = kmem_cache_zalloc(ecryptfs_file_info_cache, GFP_KERNEL);
@@ -292,8 +291,8 @@ static int ecryptfs_dir_open(struct inode *inode, struct file *file)
 				"Error attempting to allocate memory\n");
 		return -ENOMEM;
 	}
-	lower_file = dentry_open(ecryptfs_dentry_to_lower_path(ecryptfs_dentry),
-				 file->f_flags, current_cred());
+	path = ecryptfs_lower_path(ecryptfs_dentry);
+	lower_file = dentry_open(&path, file->f_flags, current_cred());
 	if (IS_ERR(lower_file)) {
 		printk(KERN_ERR "%s: Error attempting to initialize "
 			"the lower file for the dentry with name "
fs/ecryptfs/inode.c +5 -14
index 72fbe1316ab8..d2b262dc485d 100644
@@ -327,24 +327,15 @@ static int ecryptfs_i_size_read(struct dentry *dentry, struct inode *inode)
 static struct dentry *ecryptfs_lookup_interpose(struct dentry *dentry,
 				     struct dentry *lower_dentry)
 {
-	const struct path *path = ecryptfs_dentry_to_lower_path(dentry->d_parent);
+	struct dentry *lower_parent = ecryptfs_dentry_to_lower(dentry->d_parent);
 	struct inode *inode, *lower_inode;
-	struct ecryptfs_dentry_info *dentry_info;
 	int rc = 0;
 
-	dentry_info = kmem_cache_alloc(ecryptfs_dentry_info_cache, GFP_KERNEL);
-	if (!dentry_info) {
-		dput(lower_dentry);
-		return ERR_PTR(-ENOMEM);
-	}
-
 	fsstack_copy_attr_atime(d_inode(dentry->d_parent),
-				d_inode(path->dentry));
+				d_inode(lower_parent));
 	BUG_ON(!d_count(lower_dentry));
 
-	ecryptfs_set_dentry_private(dentry, dentry_info);
-	dentry_info->lower_path.mnt = mntget(path->mnt);
-	dentry_info->lower_path.dentry = lower_dentry;
+	ecryptfs_set_dentry_lower(dentry, lower_dentry);
 
 	/*
 	 * negative dentry can go positive under us here - its parent is not
@@ -1022,10 +1013,10 @@ static int ecryptfs_getattr(struct mnt_idmap *idmap,
 {
 	struct dentry *dentry = path->dentry;
 	struct kstat lower_stat;
+	struct path lower_path = ecryptfs_lower_path(dentry);
 	int rc;
 
-	rc = vfs_getattr_nosec(ecryptfs_dentry_to_lower_path(dentry),
-			       &lower_stat, request_mask, flags);
+	rc = vfs_getattr_nosec(&lower_path, &lower_stat, request_mask, flags);
 	if (!rc) {
 		fsstack_copy_attr_all(d_inode(dentry),
 				      ecryptfs_inode_to_lower(d_inode(dentry)));
fs/ecryptfs/main.c +6 -18
index eab1beb846d3..2afbcbbd9546 100644
@@ -106,15 +106,14 @@ static int ecryptfs_init_lower_file(struct dentry *dentry,
 				    struct file **lower_file)
 {
 	const struct cred *cred = current_cred();
-	const struct path *path = ecryptfs_dentry_to_lower_path(dentry);
+	struct path path = ecryptfs_lower_path(dentry);
 	int rc;
 
-	rc = ecryptfs_privileged_open(lower_file, path->dentry, path->mnt,
-				      cred);
+	rc = ecryptfs_privileged_open(lower_file, path.dentry, path.mnt, cred);
 	if (rc) {
 		printk(KERN_ERR "Error opening lower file "
 		       "for lower_dentry [0x%p] and lower_mnt [0x%p]; "
-		       "rc = [%d]\n", path->dentry, path->mnt, rc);
+		       "rc = [%d]\n", path.dentry, path.mnt, rc);
 		(*lower_file) = NULL;
 	}
 	return rc;
@@ -437,7 +436,6 @@ static int ecryptfs_get_tree(struct fs_context *fc)
 	struct ecryptfs_fs_context *ctx = fc->fs_private;
 	struct ecryptfs_sb_info *sbi = fc->s_fs_info;
 	struct ecryptfs_mount_crypt_stat *mount_crypt_stat;
-	struct ecryptfs_dentry_info *root_info;
 	const char *err = "Getting sb failed";
 	struct inode *inode;
 	struct path path;
@@ -543,14 +541,8 @@ static int ecryptfs_get_tree(struct fs_context *fc)
 		goto out_free;
 	}
 
-	rc = -ENOMEM;
-	root_info = kmem_cache_zalloc(ecryptfs_dentry_info_cache, GFP_KERNEL);
-	if (!root_info)
-		goto out_free;
-
-	/* ->kill_sb() will take care of root_info */
-	ecryptfs_set_dentry_private(s->s_root, root_info);
-	root_info->lower_path = path;
+	ecryptfs_set_dentry_lower(s->s_root, path.dentry);
+	sbi->lower_mnt = path.mnt;
 
 	s->s_flags |= SB_ACTIVE;
 	fc->root = dget(s->s_root);
@@ -580,6 +572,7 @@ static void ecryptfs_kill_block_super(struct super_block *sb)
 	kill_anon_super(sb);
 	if (!sb_info)
 		return;
+	mntput(sb_info->lower_mnt);
 	ecryptfs_destroy_mount_crypt_stat(&sb_info->mount_crypt_stat);
 	kmem_cache_free(ecryptfs_sb_info_cache, sb_info);
 }
@@ -667,11 +660,6 @@ static struct ecryptfs_cache_info {
 		.name = "ecryptfs_file_cache",
 		.size = sizeof(struct ecryptfs_file_info),
 	},
-	{
-		.cache = &ecryptfs_dentry_info_cache,
-		.name = "ecryptfs_dentry_info_cache",
-		.size = sizeof(struct ecryptfs_dentry_info),
-	},
 	{
 		.cache = &ecryptfs_inode_info_cache,
 		.name = "ecryptfs_inode_cache",
Signature · 1 line
2.47.2
maintainer Reviewed-by on v1 51/52
#116 lore raw

Re: [PATCH 51/52] ecryptfs: get rid of pointless mount references in ecryptfs dentries

Quoted 7 lines · Al Viro

->lower_path.mnt has the same value for all dentries on given ecryptfs instance and if somebody goes for mountpoint-crossing variant where that would not be true, we can deal with that when it happens (and _not_ with duplicating these reference into each dentry).

As it is, we are better off just sticking a reference into ecryptfs-private part of superblock and keeping it pinned until ->kill_sb().

The overlayfs model.

Quoted 7 lines

That way we can stick a reference to underlying dentry right into ->d_fsdata of ecryptfs one, getting rid of indirection through struct ecryptfs_dentry_info, along with the entire struct ecryptfs_dentry_info machinery.

---

author maintainer
#117 lore raw

PATCH 52/52 fs/namespace.c: sanitize descriptions for {__,}lookup_mnt()

In reply to Al Viro

Comments regarding "shadow mounts" were stale - no such thing anymore. Document the locking requirements for __lookup_mnt().

---
 fs/namespace.c | 41 ++++++++++++-----------------------------
 1 file changed, 12 insertions(+), 29 deletions(-)
fs/namespace.c +12 -29
index 82cab5459ec7..538313b3b7d9 100644
@@ -825,24 +825,16 @@ static bool legitimize_mnt(struct vfsmount *bastard, unsigned seq)
 }
 
 /**
- * __lookup_mnt - find first child mount
+ * __lookup_mnt - mount hash lookup
  * @mnt:	parent mount
- * @dentry:	mountpoint
+ * @dentry:	dentry of mountpoint
  *
- * If @mnt has a child mount @c mounted @dentry find and return it.
+ * If @mnt has a child mount @c mounted on @dentry find and return it.
+ * Caller must either hold the spinlock component of @mount_lock or
+ * hold rcu_read_lock(), sample the seqcount component before the call
+ * and recheck it afterwards.
  *
- * Note that the child mount @c need not be unique. There are cases
- * where shadow mounts are created. For example, during mount
- * propagation when a source mount @mnt whose root got overmounted by a
- * mount @o after path lookup but before @namespace_sem could be
- * acquired gets copied and propagated. So @mnt gets copied including
- * @o. When @mnt is propagated to a destination mount @d that already
- * has another mount @n mounted at the same mountpoint then the source
- * mount @mnt will be tucked beneath @n, i.e., @n will be mounted on
- * @mnt and @mnt mounted on @d. Now both @n and @o are mounted at @mnt
- * on @dentry.
- *
- * Return: The first child of @mnt mounted @dentry or NULL.
+ * Return: The child of @mnt mounted on @dentry or %NULL.
  */
 struct mount *__lookup_mnt(struct vfsmount *mnt, struct dentry *dentry)
 {
@@ -855,21 +847,12 @@ struct mount *__lookup_mnt(struct vfsmount *mnt, struct dentry *dentry)
 	return NULL;
 }
 
-/*
- * lookup_mnt - Return the first child mount mounted at path
- *
- * "First" means first mounted chronologically.  If you create the
- * following mounts:
- *
- * mount /dev/sda1 /mnt
- * mount /dev/sda2 /mnt
- * mount /dev/sda3 /mnt
- *
- * Then lookup_mnt() on the base /mnt dentry in the root mount will
- * return successively the root dentry and vfsmount of /dev/sda1, then
- * /dev/sda2, then /dev/sda3, then NULL.
+/**
+ * lookup_mnt - Return the child mount mounted at given location
+ * @path:	location in the namespace
  *
- * lookup_mnt takes a reference to the found vfsmount.
+ * Acquires and returns a new reference to mount at given location
+ * or %NULL if nothing is mounted there.
  */
 struct vfsmount *lookup_mnt(const struct path *path)
 {
Signature · 1 line
2.47.2
maintainer Reviewed-by on v1 1/52
#119 lore raw

Re: [PATCH 01/52] fs/namespace.c: fix the namespace_sem guard mess

In reply to Al Viro
Quoted 11 lines · Al Viro

If anything, namespace_lock should be DEFINE_LOCK_GUARD_0, not DEFINE_GUARD. That way we
* do not need to feed it a bogus argument
* do not get gcc trying to compare an address of static in
file variable with -4097 - and, if we are unlucky, trying to keep it in a register, with spills and all such.

The same problems apply to grabbing namespace_sem shared.

Rename it to namespace_excl, add namespace_shared, convert the existing users:

Quoted 7 lines

guard(namespace_lock, &namespace_sem) => guard(namespace_excl)() guard(rwsem_read, &namespace_sem) => guard(namespace_shared)() scoped_guard(namespace_lock, &namespace_sem) => scoped_guard(namespace_excl) scoped_guard(rwsem_read, &namespace_sem) => scoped_guard(namespace_shared)

---

#120 lore raw
In reply to Al Viro

So for fun I asked one of these A.I. tools wtf "CFT" actually means. And I have to say it did not disappoint:

Looking at this Linux kernel mailing list context about mount-related patches, "CFT" likely stands for "Call For Testing" in Al Viro's typical terse style. But since you asked for alternative interpretations:

- Can't Find Testers
- Completely Funtested Trash
- Christian's Frustration Trigger
- Cryptic Fileystem Torture
- Carefully Fabricated Terrorcode
- Code For Torvalds
- Chaotic Fs Tweaking
- Crash Friendly Technology
- Coffee Fueled Tinkering
- Confusing Fsdevel Tradition

I vote for "Carefully Fabricated Terrorcode".

Quoted 236 lines · Al Viro

Most of this pile is basically an attempt to see how well do
cleanup.h-style mechanisms apply in mount handling. That stuff lives in
git://git.kernel.org/pub/scm/linux/kernel/git/viro/vfs.git #work.mount Rebased to -rc3 (used to be a bit past -rc2, branched at mount fixes merge) Individual patches in followups.

Please, help with review and testing. It seems to survive the
local beating and code generation seems to be OK, but more testing would be a good thing and I would really like to see comments on that stuff.

This is not all I've got around mount handling, but I'd rather
get that thing out for review before starting to sort out other local mount-related branches.

Series overview:

Part 1: guards.

This part starts with infrastructure, followed by one-by-one
conversions to the guard/scoped_guard in some of the places that fit that well enough. Note that one of those places turned out to be taking mount_lock for no reason whatsoever; I already see places where we do write_seqlock when read_seqlock_excl would suffice, etc.

Folks, _please_ don't do any bulk conversions in that area.
IMO one area where RAII becomes dangerous is locking; usually it's not a big deal to delay freeing some object a bit, but delay dropping a lock and you risk introducing deadlocks that will be bloody hard to spot. It _has_ to be done carefully; we had trouble in that area several times over the last year or so in fs/namespace.c alone. Another fun problem is that quite a few comments regarding the locking in there are stale. We still have the comments that talk about mount lock as if it had been an rwlock-like thing. It hadn't been that for more than a decade now. It needs to be documented sanely; so do the access rules to the data structures involved. I hope to get some of that into the tree this cycle, but it's still in progress.

1/52) fs/namespace.c: fix the namespace_sem guard mess
New guards: namespace_excl and namespace_shared. The former implies
the latter, as for anything rwsem-like. No inode locks, no dropping the final references, no opening files, etc. in scope of those.
2/52) introduced guards for mount_lock
New guards: mount_writer, mount_locked_reader. That's write_seqlock
and read_seqlock_excl on mount_lock; obviously, nothing blocking should be done in scope of those.
3/52) fs/namespace.c: allow to drop vfsmount references via __free(mntput)
Missing DEFINE_FREE (for mntput()); local in fs/namespace.c, to be
used only for keeping shit out of namespace_... and mount_... scopes.

4/52)  __detach_mounts(): use guards
5/52)  __is_local_mountpoint(): use guards
6/52)  do_change_type(): use guards
7/52)  do_set_group(): use guards
8/52)  mark_mounts_for_expiry(): use guards
9/52)  put_mnt_ns(): use guards

10/52) mnt_already_visible(): use guards
a bunch of clear-cut conversions, with explanations of the reasons
why this or that guard is needed.
11/52) check_for_nsfs_mounts(): no need to take locks
... and here we have one where it turns out that locking had been
excessive. Iterating through a subtree in mount_locked_reader scope is safe, all right, but (1) mount_writer is not needed here at all and (2) namespace_shared + a reference held to the root of subtree is also enough. All callers had (2) already. Documented the locking requirements for function, removed {,un}lock_mount_hash() in it...
12/52) propagate_mnt(): use scoped_guard(mount_locked_reader) for mnt_set_mountpoint()
This one is interesting - existing code had been equivalent to
scoped_guard(mount_locked_reader), and it's right for that call. However, mnt_set_mountpoint() generally requires mount_writer - the only reason we get away with that here is that the mount in question never had been reachable from the mounts visible to other threads.

13/52)  has_locked_children(): use guards
14/52)  mnt_set_expiry(): use guards
15/52)  path_is_under(): use guards

more clear-cut conversions with explanations.
16/52) current_chrooted(): don't bother with follow_down_one() 17/52) current_chrooted(): use guards
this pair might be better off with #16 taken to the beginning
of the series (or to a separate branch merge into this one); no better reason to do as I had than wanting to keep the guard infrastructure in the very beginning.

Part 2: turning unlock_mount() into __cleanup.

Environment for mounting something on given location consists of:
1) namespace_excl scope
2) parent mount - the one we'll be attaching things to.
3) mountpoint to be, protected from disappearing under us.
4) inode of that mountpoint's dentry held exclusive. Unfortunately, we can't take inode locks in namespace_excl scopes.
And we want to cope with the possibility that somebody has managed to mount something on that place while we'd been taking locks. "Cope" part is simple for finish_automount() ("drop our mount and go away quietly; somebody triggered it before we did"), but for everything else it's trickier - "use whatever's overmounting that place now (with the right locks, please)".
lock_mount() does all of that (do_lock_mount(), actually), with
unlock_mount() closing the scope. And it's definitely a good candidate for __cleanup()-based approach, except that
* the damn thing can return an error and conditional variants of that infrastructure are too revolting.
* parent mount is returned in a fucking awful way - we modify the struct path passed to us as location to mount on and then its ->mnt is the parent to be... except for the "beneath" variant where we play convoluted games with "no, here we want the parent of that". Implementation is also vulnerable to umount propagtion races.
* the structure we set up (everything except the parent) is inserted into a linked list by lock_mount(). That excludes DEFINE_CLASS() - it wants the value formed and then copied to the variable we are defining.
* it contains an implicit namespace_excl scope, so path_put() and its ilk *must* be done after the unlock_mount(). And most of the users have gotos past that.
The first two problems are solved by adding an explicit pointer
to parent mount into struct pinned_mountpoint. Having lock_mount()
failure reported by setting it to ERR_PTR(-E...) allows to avoid the problem with expressing the constructor failure. The third one is dealt with by defining local macros to be used instead of CLASS - I went with LOCK_MOUNT(mp, path) which defines struct pinned_mountpoint mp with __cleanup(unlock_mount) and sets it up. If anybody has better suggestions, I'll be glad to hear those.
The last one is dealt with by massaging the users to form that
would have all post-unlock_mount() stuff done by __free().

First, several trivial cleanups:

18/52)  do_move_mount(): trim local variables
19/52)  do_move_mount(): deal with the checks on old_path early
20/52)  move_mount(2): take sanity checks in 'beneath' case into do_lock_mount()
21/52)  finish_automount(): simplify the ELOOP check

Getting rid of post-unlock_mount() stuff:

22/52)  do_loopback(): use __free(path_put) to deal with old_path
23/52)  pivot_root(2): use __free() to deal with struct path in it
24/52)  finish_automount(): take the lock_mount() analogue into a helper

this one turns the open-coded logics into lock_mount_exact() with
the same kind of calling conventions as lock_mount() and do_lock_mount() 25/52) do_new_mount_rc(): use __free() to deal with dropping mnt on failure 26/52) finish_automount(): use __free() to deal with dropping mnt on failure

This is the main part:
27/52) change calling conventions for lock_mount() et.al.

Followups, cleaning up the games with parent mount in the user:

28/52)  do_move_mount(): use the parent mount returned by do_lock_mount()
29/52)  do_add_mount(): switch to passing pinned_mountpoint instead of mountpoint + path
30/52)  graft_tree(), attach_recursive_mnt() - pass pinned_mountpoint

Part 3: getting rid of mutating struct path there.

do_lock_mount() is still playing silly buggers with struct path it
had been given - the logics in that thing hadn't changed. It's not a pretty function and it's racy as well; the thing is, by this point its users have almost no use for the changed contents of struct path - dentry can be derived from struct mountpoint, parent mount to use is provided directly and we want that a lot more than modified path->mnt. There's only one place (in can_move_mount_beneath()) where we still want that and it's not hard to reconstruct the value by *original* path->mnt value + parent mount to be used.

	Getting rid of ->dentry uses.

31/52) pivot_root(2): use old_mp.mp->m_dentry instead of old.dentry 32/52) don't bother passing new_path->dentry to can_move_mount_beneath()

A helper, already open-coded in a couple of places; carved out of
the next patch to keep it reasonably small
33/52) new helper: topmost_overmount()

Rewrite of do_lock_mount() to keep path constant + trivial change
in do_move_mount() to adjust the argument it passes to can_move_mount_beneath():
34/52) do_lock_mount(): don't modify path.

Part 5: a bunch of trivial cleanups (mostly constifications)

35/52)  constify check_mnt()
36/52)  do_mount_setattr(): constify path argument
37/52)  do_set_group(): constify path arguments
38/52)  drop_collected_paths(): constify arguments
39/52)  collect_paths(): constify the return value
40/52)  do_move_mount(), vfs_move_mount(), do_move_mount_old(): constify struct path argument(s)
41/52)  mnt_warn_timestamp_expiry(): constify struct path argument
42/52)  do_new_mount{,_fc}(): constify struct path argument
43/52)  do_{loopback,change_type,remount,reconfigure_mnt}(): constify struct path argument
44/52)  path_mount(): constify struct path argument
45/52)  may_copy_tree(), __do_loopback(): constify struct path argument
46/52)  path_umount(): constify struct path argument
47/52)  constify can_move_mount_beneath() arguments
48/52)  do_move_mount_old(): use __free(path_put)
49/52)  do_mount(): use __free(path_put)

Part 6: assorted stuff, will grow.

50/52) umount_tree(): take all victims out of propagation graph at once [had been earlier]
For each removed mount we need to calculate where the slaves
will end up. To avoid duplicating that work, do it for all mounts to be removed at once, taking the mounts themselves out of propagation graph as we go, then do all transfers; the duplicate work on finding destinations is avoided since if we run into a mount that already had destination found, we don't need to trace the rest of the way. That's guaranteed O(removed mounts) for finding destinations and removing from propagation graph and O(surviving mounts that have master removed) for transfers.

51/52) ecryptfs: get rid of pointless mount references in ecryptfs dentries

	->lower_path.mnt has the same value for all dentries on given

ecryptfs instance and if somebody goes for mountpoint-crossing variant where that would not be true, we can deal with that when it happens (and _not_ with duplicating these reference into each dentry).
As it is, we are better off just sticking a reference into
ecryptfs-private part of superblock and keeping it pinned until ->kill_sb().
That way we can stick a reference to underlying dentry right into
->d_fsdata of ecryptfs one, getting rid of indirection through struct ecryptfs_dentry_info, along with the entire struct ecryptfs_dentry_info machinery.

52/52) fs/namespace.c: sanitize descriptions for {__,}lookup_mnt()
Comments regarding "shadow mounts" were stale - no such thing
anymore. Document the locking requirements for __lookup_mnt()...

FWIW, the current diffstat:

 fs/ecryptfs/dentry.c          |  14 +-
 fs/ecryptfs/ecryptfs_kernel.h |  27 +-
 fs/ecryptfs/file.c            |  15 +-
 fs/ecryptfs/inode.c           |  19 +-
 fs/ecryptfs/main.c            |  24 +-
 fs/internal.h                 |   4 +-
 fs/mount.h                    |  12 +
 fs/namespace.c                | 775 +++++++++++++++++++-----------------------
 fs/pnode.c                    |  75 ++--
 fs/pnode.h                    |   1 +
 include/linux/mount.h         |   4 +-
 kernel/audit_tree.c           |  12 +-
 12 files changed, 464 insertions(+), 518 deletions(-)
#121 lore raw
In reply to Al Viro
Quoted 10 lines · Al Viro

Most of this pile is basically an attempt to see how well do
cleanup.h-style mechanisms apply in mount handling. That stuff lives in
git://git.kernel.org/pub/scm/linux/kernel/git/viro/vfs.git #work.mount Rebased to -rc3 (used to be a bit past -rc2, branched at mount fixes merge) Individual patches in followups.

Please, help with review and testing. It seems to survive the
local beating and code generation seems to be OK, but more testing would be a good thing and I would really like to see comments on that stuff.

Btw, I just realized that basically none of your commits have any lore links in them. That kinda sucks because I very very often just look at a commit and then use the link to jump to the mailing list discussion for more context about a change and how it came about.

So pretty please can you start adding lore links to your commits when applying if it's not fucking up your workflow too much?

Quoted 226 lines

This is not all I've got around mount handling, but I'd rather
get that thing out for review before starting to sort out other local mount-related branches.

Series overview:

Part 1: guards.

This part starts with infrastructure, followed by one-by-one
conversions to the guard/scoped_guard in some of the places that fit that well enough. Note that one of those places turned out to be taking mount_lock for no reason whatsoever; I already see places where we do write_seqlock when read_seqlock_excl would suffice, etc.

Folks, _please_ don't do any bulk conversions in that area.
IMO one area where RAII becomes dangerous is locking; usually it's not a big deal to delay freeing some object a bit, but delay dropping a lock and you risk introducing deadlocks that will be bloody hard to spot. It _has_ to be done carefully; we had trouble in that area several times over the last year or so in fs/namespace.c alone. Another fun problem is that quite a few comments regarding the locking in there are stale. We still have the comments that talk about mount lock as if it had been an rwlock-like thing. It hadn't been that for more than a decade now. It needs to be documented sanely; so do the access rules to the data structures involved. I hope to get some of that into the tree this cycle, but it's still in progress.

1/52) fs/namespace.c: fix the namespace_sem guard mess
New guards: namespace_excl and namespace_shared. The former implies
the latter, as for anything rwsem-like. No inode locks, no dropping the final references, no opening files, etc. in scope of those.
2/52) introduced guards for mount_lock
New guards: mount_writer, mount_locked_reader. That's write_seqlock
and read_seqlock_excl on mount_lock; obviously, nothing blocking should be done in scope of those.
3/52) fs/namespace.c: allow to drop vfsmount references via __free(mntput)
Missing DEFINE_FREE (for mntput()); local in fs/namespace.c, to be
used only for keeping shit out of namespace_... and mount_... scopes.

4/52)  __detach_mounts(): use guards
5/52)  __is_local_mountpoint(): use guards
6/52)  do_change_type(): use guards
7/52)  do_set_group(): use guards
8/52)  mark_mounts_for_expiry(): use guards
9/52)  put_mnt_ns(): use guards

10/52) mnt_already_visible(): use guards
a bunch of clear-cut conversions, with explanations of the reasons
why this or that guard is needed.
11/52) check_for_nsfs_mounts(): no need to take locks
... and here we have one where it turns out that locking had been
excessive. Iterating through a subtree in mount_locked_reader scope is safe, all right, but (1) mount_writer is not needed here at all and (2) namespace_shared + a reference held to the root of subtree is also enough. All callers had (2) already. Documented the locking requirements for function, removed {,un}lock_mount_hash() in it...
12/52) propagate_mnt(): use scoped_guard(mount_locked_reader) for mnt_set_mountpoint()
This one is interesting - existing code had been equivalent to
scoped_guard(mount_locked_reader), and it's right for that call. However, mnt_set_mountpoint() generally requires mount_writer - the only reason we get away with that here is that the mount in question never had been reachable from the mounts visible to other threads.

13/52)  has_locked_children(): use guards
14/52)  mnt_set_expiry(): use guards
15/52)  path_is_under(): use guards

more clear-cut conversions with explanations.
16/52) current_chrooted(): don't bother with follow_down_one() 17/52) current_chrooted(): use guards
this pair might be better off with #16 taken to the beginning
of the series (or to a separate branch merge into this one); no better reason to do as I had than wanting to keep the guard infrastructure in the very beginning.

Part 2: turning unlock_mount() into __cleanup.

Environment for mounting something on given location consists of:
1) namespace_excl scope
2) parent mount - the one we'll be attaching things to.
3) mountpoint to be, protected from disappearing under us.
4) inode of that mountpoint's dentry held exclusive. Unfortunately, we can't take inode locks in namespace_excl scopes.
And we want to cope with the possibility that somebody has managed to mount something on that place while we'd been taking locks. "Cope" part is simple for finish_automount() ("drop our mount and go away quietly; somebody triggered it before we did"), but for everything else it's trickier - "use whatever's overmounting that place now (with the right locks, please)".
lock_mount() does all of that (do_lock_mount(), actually), with
unlock_mount() closing the scope. And it's definitely a good candidate for __cleanup()-based approach, except that
* the damn thing can return an error and conditional variants of that infrastructure are too revolting.
* parent mount is returned in a fucking awful way - we modify the struct path passed to us as location to mount on and then its ->mnt is the parent to be... except for the "beneath" variant where we play convoluted games with "no, here we want the parent of that". Implementation is also vulnerable to umount propagtion races.
* the structure we set up (everything except the parent) is inserted into a linked list by lock_mount(). That excludes DEFINE_CLASS() - it wants the value formed and then copied to the variable we are defining.
* it contains an implicit namespace_excl scope, so path_put() and its ilk *must* be done after the unlock_mount(). And most of the users have gotos past that.
The first two problems are solved by adding an explicit pointer
to parent mount into struct pinned_mountpoint. Having lock_mount()
failure reported by setting it to ERR_PTR(-E...) allows to avoid the problem with expressing the constructor failure. The third one is dealt with by defining local macros to be used instead of CLASS - I went with LOCK_MOUNT(mp, path) which defines struct pinned_mountpoint mp with __cleanup(unlock_mount) and sets it up. If anybody has better suggestions, I'll be glad to hear those.
The last one is dealt with by massaging the users to form that
would have all post-unlock_mount() stuff done by __free().

First, several trivial cleanups:

18/52)  do_move_mount(): trim local variables
19/52)  do_move_mount(): deal with the checks on old_path early
20/52)  move_mount(2): take sanity checks in 'beneath' case into do_lock_mount()
21/52)  finish_automount(): simplify the ELOOP check

Getting rid of post-unlock_mount() stuff:

22/52)  do_loopback(): use __free(path_put) to deal with old_path
23/52)  pivot_root(2): use __free() to deal with struct path in it
24/52)  finish_automount(): take the lock_mount() analogue into a helper

this one turns the open-coded logics into lock_mount_exact() with
the same kind of calling conventions as lock_mount() and do_lock_mount() 25/52) do_new_mount_rc(): use __free() to deal with dropping mnt on failure 26/52) finish_automount(): use __free() to deal with dropping mnt on failure

This is the main part:
27/52) change calling conventions for lock_mount() et.al.

Followups, cleaning up the games with parent mount in the user:

28/52)  do_move_mount(): use the parent mount returned by do_lock_mount()
29/52)  do_add_mount(): switch to passing pinned_mountpoint instead of mountpoint + path
30/52)  graft_tree(), attach_recursive_mnt() - pass pinned_mountpoint

Part 3: getting rid of mutating struct path there.

do_lock_mount() is still playing silly buggers with struct path it
had been given - the logics in that thing hadn't changed. It's not a pretty function and it's racy as well; the thing is, by this point its users have almost no use for the changed contents of struct path - dentry can be derived from struct mountpoint, parent mount to use is provided directly and we want that a lot more than modified path->mnt. There's only one place (in can_move_mount_beneath()) where we still want that and it's not hard to reconstruct the value by *original* path->mnt value + parent mount to be used.

	Getting rid of ->dentry uses.

31/52) pivot_root(2): use old_mp.mp->m_dentry instead of old.dentry 32/52) don't bother passing new_path->dentry to can_move_mount_beneath()

A helper, already open-coded in a couple of places; carved out of
the next patch to keep it reasonably small
33/52) new helper: topmost_overmount()

Rewrite of do_lock_mount() to keep path constant + trivial change
in do_move_mount() to adjust the argument it passes to can_move_mount_beneath():
34/52) do_lock_mount(): don't modify path.

Part 5: a bunch of trivial cleanups (mostly constifications)

35/52)  constify check_mnt()
36/52)  do_mount_setattr(): constify path argument
37/52)  do_set_group(): constify path arguments
38/52)  drop_collected_paths(): constify arguments
39/52)  collect_paths(): constify the return value
40/52)  do_move_mount(), vfs_move_mount(), do_move_mount_old(): constify struct path argument(s)
41/52)  mnt_warn_timestamp_expiry(): constify struct path argument
42/52)  do_new_mount{,_fc}(): constify struct path argument
43/52)  do_{loopback,change_type,remount,reconfigure_mnt}(): constify struct path argument
44/52)  path_mount(): constify struct path argument
45/52)  may_copy_tree(), __do_loopback(): constify struct path argument
46/52)  path_umount(): constify struct path argument
47/52)  constify can_move_mount_beneath() arguments
48/52)  do_move_mount_old(): use __free(path_put)
49/52)  do_mount(): use __free(path_put)

Part 6: assorted stuff, will grow.

50/52) umount_tree(): take all victims out of propagation graph at once [had been earlier]
For each removed mount we need to calculate where the slaves
will end up. To avoid duplicating that work, do it for all mounts to be removed at once, taking the mounts themselves out of propagation graph as we go, then do all transfers; the duplicate work on finding destinations is avoided since if we run into a mount that already had destination found, we don't need to trace the rest of the way. That's guaranteed O(removed mounts) for finding destinations and removing from propagation graph and O(surviving mounts that have master removed) for transfers.

51/52) ecryptfs: get rid of pointless mount references in ecryptfs dentries

	->lower_path.mnt has the same value for all dentries on given

ecryptfs instance and if somebody goes for mountpoint-crossing variant where that would not be true, we can deal with that when it happens (and _not_ with duplicating these reference into each dentry).
As it is, we are better off just sticking a reference into
ecryptfs-private part of superblock and keeping it pinned until ->kill_sb().
That way we can stick a reference to underlying dentry right into
->d_fsdata of ecryptfs one, getting rid of indirection through struct ecryptfs_dentry_info, along with the entire struct ecryptfs_dentry_info machinery.

52/52) fs/namespace.c: sanitize descriptions for {__,}lookup_mnt()
Comments regarding "shadow mounts" were stale - no such thing
anymore. Document the locking requirements for __lookup_mnt()...

FWIW, the current diffstat:

 fs/ecryptfs/dentry.c          |  14 +-
 fs/ecryptfs/ecryptfs_kernel.h |  27 +-
 fs/ecryptfs/file.c            |  15 +-
 fs/ecryptfs/inode.c           |  19 +-
 fs/ecryptfs/main.c            |  24 +-
 fs/internal.h                 |   4 +-
 fs/mount.h                    |  12 +
 fs/namespace.c                | 775 +++++++++++++++++++-----------------------
 fs/pnode.c                    |  75 ++--
 fs/pnode.h                    |   1 +
 include/linux/mount.h         |   4 +-
 kernel/audit_tree.c           |  12 +-
 12 files changed, 464 insertions(+), 518 deletions(-)
author maintainer
#122 lore raw
Quoted 12 lines · Christian Brauner
Quoted 10 lines · Al Viro

Most of this pile is basically an attempt to see how well do
cleanup.h-style mechanisms apply in mount handling. That stuff lives in
git://git.kernel.org/pub/scm/linux/kernel/git/viro/vfs.git #work.mount Rebased to -rc3 (used to be a bit past -rc2, branched at mount fixes merge) Individual patches in followups.

Please, help with review and testing. It seems to survive the
local beating and code generation seems to be OK, but more testing would be a good thing and I would really like to see comments on that stuff.

Quoted 7 lines

Btw, I just realized that basically none of your commits have any lore links in them. That kinda sucks because I very very often just look at a commit and then use the link to jump to the mailing list discussion for more context about a change and how it came about.

So pretty please can you start adding lore links to your commits when applying if it's not fucking up your workflow too much?

Links to what, at the first posting? Confused...

author maintainer
#123 lore raw
Quoted 18 lines · Al Viro
Quoted 17 lines · Christian Brauner
Quoted 10 lines · Al Viro

Most of this pile is basically an attempt to see how well do
cleanup.h-style mechanisms apply in mount handling. That stuff lives in
git://git.kernel.org/pub/scm/linux/kernel/git/viro/vfs.git #work.mount Rebased to -rc3 (used to be a bit past -rc2, branched at mount fixes merge) Individual patches in followups.

Please, help with review and testing. It seems to survive the
local beating and code generation seems to be OK, but more testing would be a good thing and I would really like to see comments on that stuff.

Btw, I just realized that basically none of your commits have any lore links in them. That kinda sucks because I very very often just look at a commit and then use the link to jump to the mailing list discussion for more context about a change and how it came about.

Quoted 4 lines
Quoted 2 lines

So pretty please can you start adding lore links to your commits when applying if it's not fucking up your workflow too much?

Links to what, at the first posting? Confused...

I mean, this _is_ what I hope would be a discussion of that stuff - that's what request for comments stands for, after all. How is that supposed to work? Going back through the queue and slapping lore links at the same time as the reviewed-by etc. are applied? I honestly have no idea what practice do you have in mind - ~95% of the time I'm sitting in nvi - it serves as IDE for me; mutt takes a large part of the rest. Browser is something that gets used occasionally when I have to...

#124 lore raw
Quoted 4 lines · Al Viro

I mean, this _is_ what I hope would be a discussion of that stuff - that's what request for comments stands for, after all. How is that supposed to work? Going back through the queue and slapping lore links at the same time as the reviewed-by etc. are applied?

Lore links are useful when a maintainer is applying someone else's patches into their git tree. I think that's what Christian was thinking about. In this case, however, where the maintainer is the one autoring/sending the patches the patches, there is the chicken-and-egg prblem that you've described, and so I don't understand why Christian has made that request.

Usually I just construct the lore URL from the Message ID from the patch series, but what I've seen other olks do for very large patch sets is that they'll also publish the patches on git, for example from Darrick's recent fuse/iomap patches, he included a link in the patchset cover letter to:

https://git.kernel.org/cgit/linux/kernel/git/djwong/xfs-linux.git/log/?h=fuse-iomap-cache

- Ted

#125 lore raw
In reply to Al Viro
Quoted 24 lines · Al Viro
Quoted 18 lines · Al Viro
Quoted 17 lines · Christian Brauner
Quoted 10 lines · Al Viro

Most of this pile is basically an attempt to see how well do
cleanup.h-style mechanisms apply in mount handling. That stuff lives in
git://git.kernel.org/pub/scm/linux/kernel/git/viro/vfs.git #work.mount Rebased to -rc3 (used to be a bit past -rc2, branched at mount fixes merge) Individual patches in followups.

Please, help with review and testing. It seems to survive the
local beating and code generation seems to be OK, but more testing would be a good thing and I would really like to see comments on that stuff.

Btw, I just realized that basically none of your commits have any lore links in them. That kinda sucks because I very very often just look at a commit and then use the link to jump to the mailing list discussion for more context about a change and how it came about.

Quoted 4 lines
Quoted 2 lines

So pretty please can you start adding lore links to your commits when applying if it's not fucking up your workflow too much?

Links to what, at the first posting? Confused...

Quoted 7 lines

I mean, this _is_ what I hope would be a discussion of that stuff - that's what request for comments stands for, after all. How is that supposed to work? Going back through the queue and slapping lore links at the same time as the reviewed-by etc. are applied? I honestly have no idea what practice do you have in mind - ~95% of the time I'm sitting in nvi - it serves as IDE for me; mutt takes a large part of the rest. Browser is something that gets used occasionally when I have to...

You misunderstand.
Once you apply your series to the tree that you intend to merge simply add the lore links to the patches of the last version. I don't give a single damn whether someone _sends_ patches with lore links. That is not what this is about. I care that I can git log at mainline and figure out where that patch was discussed, pull down the discussion via b4 or other tooling, without having to search lore.

IOW, what I asked you about is once the patches end up in mainline they please have links to the discussion where they came from. I do it for all patches no matter if I pick them up from someone else or if I'm applying my own:

commit c237aa9884f238e1480897463ca034877ca7530b
Author: Christian Brauner <[email protected]>

kernfs: don't fail listing extended attributes

<snip>

    Link: https://lore.kernel.org/20250819-ahndung-abgaben-524a535f8101@brauner

^^^^^^^^^^^^^^^^^

I'm not doing that for my own personal wellness cure but for every other poor bastard (granted, including me because one year later it's all swapped out) who looks at commits in the git tree and wants to either jump to a link in the browser or wants to use tooling to just pull the whole discussion from the list.

#126 lore raw
Quoted 2 lines · Christian Brauner

I'm not doing that for my own personal wellness cure

Please only do this for things that were actually discussed.

Because for *my* wellness cure, I get really damn annoyed when I wonder about some context of a commit, and follow a link to look at the background, and all I see is that SAME DAMN PATCH that I already looked at, and wondered about, then that link damn well wasted my time.

It's annoying as hell.

And no, some "maybe people add acks or context later" is not a valid reason to add a link. If there was no discussion about it at the time it was committed, a link to some mailing list posting by definition doesn't explain why the commit exists.

Linus

#127 lore raw
Quoted 5 lines · Linus Torvalds

And no, some "maybe people add acks or context later" is not a valid reason to add a link. If there was no discussion about it at the time it was committed, a link to some mailing list posting by definition doesn't explain why the commit exists.

Side note: relevant later discussion of patches obviously does happen, but it's actually more likely to be independent of the mailing list posting, and instead refer to the commit ID - and the shortlog of the commit - than to the original posting.

Yes, some bots do obviously traverse the mailing list for patch series to look at and test, but those bots are the ones that the developer / maintainer should have reacted to *before* the commit goes upstream, so finding them after-the-fact is simply not a high priority.

A much more common thing is that the "context added later" is a result of people and bots reporting problems with a commit that has hit the git trees, and they do *not* generally reply to the original posting.

So instead those much more relevant reports will typically make an entirely new thread, mentioning the commit ID and the subject line.

Which is why I think it is so bass-ackwards to add a link to the posting in the commit. That literally is useless garbage unless the posting generated discussion. The link to the posting is not likely to be the most relevant thing: it tends to be *much* more productive to instead search lore for the commit ID and the subject line of the commit.

That will obviously find the original posting of the patch too, but it will *also* find those much more relevant and likely reports about people/bots reporting issues with a commit in the git tree.

This is why I hate those pointless links so much. They are worthless garbage. And the "but maybe somebody adds context later" is intellectually dishonest, since that later context is likely *not* found behind that link, but through other means entirely.

Linus

#128 lore raw
Quoted 6 lines · Linus Torvalds

Which is why I think it is so bass-ackwards to add a link to the posting in the commit. That literally is useless garbage unless the posting generated discussion. The link to the posting is not likely to be the most relevant thing: it tends to be *much* more productive to instead search lore for the commit ID and the subject line of the commit.

Main trouble is that we can't always reliably arrive at the source of the patch in lore based on the commit. The subject line can be tricky to search for if it uses quotes, brackets, or other characters that aren't reliably tokenized. Furthermore, there can be situations where the results can be ambiguous. For example, a [PATCH v7] could have been posted after the maintainer had already accepted [PATCH v6], in which case the maintainer will ask for a new bugfix series to be sent instead.

Similarly, we can't reliably go from the commit to the patch-id that we can use to search the archives:

- the maintainer may have rebased the patch series, resulting in a different patch-id
- the original submission may have been generated with a different patch algorithm (histogram vs. myers is the usual culprit)
- the maintainer may have tweaked the patch for cosmetic reasons

All of the above may result in a different git-patch-id that no longer matches the original submission.

I have recommended that Link: trailers indicating the provenance of the series should use a dedicated domain name: patch.msgid.link. This should clearly indicate to you that following this link will take you to the original submission, not to any other discussion. I haven't yet made this the default in b4, but I should probably do that.

Anyone can already make this their default by setting the following in their .gitconfig:

[b4]
linkmask = https://patch.msgid.link/%s

-K

#129 lore raw
Quoted 5 lines · Konstantin Ryabitsev

I have recommended that Link: trailers indicating the provenance of the series should use a dedicated domain name: patch.msgid.link. This should clearly indicate to you that following this link will take you to the original submission, not to any other discussion.

That doesn't fix anything. It only reinforces the basic stupidity of marking the WRONG DIRECTION.

The fact is, YOU CANNOT SANELY MARK THE COMMIT. Dammit, why do people ignore this *fundamental* issue? You literally cannot add information to the commit that doesn't exist yet, and the threads that refer to bugs etc quite fundamentally WILL NOT EXIST YET when the commit is posted.

The actual *useful* information about a commit is the discussions it resulted in, not the posting of the patch.

And those will almost invariably be unrelated to the patch submission, since they either talked about the problems that the patch *fixed*, or talk about the problems that the patch *caused* (ie the thread starts with some random "My machine no longer boots", and then goes on from there as people try to figure out what caused it.

So the *relevant* links are pretty much by definition not the link to the posting of the patch.

Is it really so hard to understand and accept this fundamental issue?

It's the *message* that should be indexed and marked, not the commit.

What you want to find is messages on the mailing list that mention the commit, not the other way around. The other way around is completely pointless and CANNOT BE AUTOMATED. Any automation by definition will only add noise, not "information".

Really. The only valid link is a link to *pre-existing* discussion, not to some stupid "this is where I posted this patch", which is entirely and utterly immaterial.

And dammit, lore could do this. Here's one suggested model that at least gets the direction of indexing right (I'm not claiming it's the only model, or the best model, but it sure as hell beats getting the fundamentals completely wrong):

(a) messages with patches can be indexed by the patch-id of said patch

This might well be useful in its own right ("search for this patch"), and would be good for the series where the same patch ends up being re-posted because the whole series was re-posted.

IOW, just that trivial thing would already allow the lore web interface to link to "this patch has been posted before", which is useful information on its own, totally aside from any future archeology.

But it's not the end goal, it's only a small step to *get* to the end goal:

(b) messages that mention a commit ID (or a subject line) could then
have referrals to the patch-id of said commit.

No, you don't want to do a whole-text search every time you look for a commit. That's fine for manual stuff, but it's much too expensive for any sane automation. But you *can* (and lore already does) scan messages at message posting time, and find when people refer to a commit, and then index that message *once* by the patch ID of the commit.

Now, this *is* fundamentally useful in a very different way: if you have somebody who bisected something and mentions a commit as a result, you'd now *find* that kind of message, and the history leading up to it.

So when people read threads on lore about bugs being bisected, think how useful it would be if that thread would basically auto-populate with "this message refers to this patch".

And the final step is

(c) have some 'b4' infrastructure to look up emails pertaining to a
commit - by doing the patch ID and then looking up the indexing above

Look, now you have a "open web browser with the history of not just where the patch was originally posted, but where that commit was *mentioned*".

Notice how fundamentally more useful this is from some link to where the patch was posted? And absolutely nothing in the above implies tagging the commit with useless information.

I look at the "Link:" tags quite regularly, and I can tell you that when it's a posting tag, it almost invariably is completely and totally useless. We *have* people who add those, and they only add noise and very little value.

Do not add more of those useless garbage links in the name of "automation". It's not automating anything useful, it's only automating garbage.

Because the *commit* already has all the information that is relevant
- it's not the commit that is missing a link. It's the other side.

Which is why those links to lore patch submission events are so STUPID. They add nothing. Doing them in the name of "automation" is crazy. It's entirely pointless. It's garbage and it's mis-designed, because it's not understanding the problem.

Linus

#130 lore raw
Quoted 8 lines · Linus Torvalds
Quoted 5 lines · Konstantin Ryabitsev

I have recommended that Link: trailers indicating the provenance of the series should use a dedicated domain name: patch.msgid.link. This should clearly indicate to you that following this link will take you to the original submission, not to any other discussion.

Quoted 8 lines

That doesn't fix anything. It only reinforces the basic stupidity of marking the WRONG DIRECTION.

The fact is, YOU CANNOT SANELY MARK THE COMMIT. Dammit, why do people ignore this *fundamental* issue? You literally cannot add information to the commit that doesn't exist yet, and the threads that refer to bugs etc quite fundamentally WILL NOT EXIST YET when the commit is posted.

I'm not sure what you mean. The Link: trailer is added when the maintainer pulls in the series into their tree. It's not put there by the submitter. The maintainer marks a reliable mapping of "this commit came from this thread" and we the use this info for multiple purposes:

1. letting the submitter know when their series is accepted into the maintainer's tree
2. marking the series as "mainlined" when we find that commit in your tree
3. it reliably marks provenance for tools like cregit, which largely have to guess this info

It serves a real purpose.

Quoted 1 line

It's the *message* that should be indexed and marked, not the commit.

We cannot *reliably* map commits to patches. A commit can be represented as any number of patches, all resulting in different patch-id's -- it can be generated with a different number of context lines, with a different patch algorithm, it could have been rebased, etc. Maintainers do edit patches they receive, including the subject lines. I know, because attempting to automate things without a provenance Link: results in false-positives for projects like netdev.

Quoted 10 lines

Really. The only valid link is a link to *pre-existing* discussion, not to some stupid "this is where I posted this patch", which is entirely and utterly immaterial.

And dammit, lore could do this. Here's one suggested model that at least gets the direction of indexing right (I'm not claiming it's the only model, or the best model, but it sure as hell beats getting the fundamentals completely wrong):

(a) messages with patches can be indexed by the patch-id of said patch

They already do, it's been there for a long time now. Here's a random one:
https://lore.kernel.org/lkml/?q=patchid%3A09b124c33929efcffe0ce8df0a805f54d5962f60

Quoted 3 lines

This might well be useful in its own right ("search for this patch"), and would be good for the series where the same patch ends up being re-posted because the whole series was re-posted.

This is how we are able to pull in trailers sent to previous series, if the patch-id hasn't changed.

Quoted 9 lines

IOW, just that trivial thing would already allow the lore web interface to link to "this patch has been posted before", which is useful information on its own, totally aside from any future archeology.

But it's not the end goal, it's only a small step to *get* to the end goal:

(b) messages that mention a commit ID (or a subject line) could then
have referrals to the patch-id of said commit.

To reiterate, a commit is not a patch, so *we cannot reliably arrive from a commit to always the same patch-id*. We've discovered it the hard way when you recommended that people send you patches with --histogram and we suddenly could no longer reliably map commits to patches, because on our end we generated patches with the default (myers) and they did not match the patches generated with --histogram, so our automation broke.

This is what I am trying to convey -- commits don't reliably map to patches, because the same commit can generate any number of perfectly valid patches, all with different patch-id's.

-K

author maintainer
#131 lore raw
Quoted 4 lines · Konstantin Ryabitsev

I'm not sure what you mean. The Link: trailer is added when the maintainer pulls in the series into their tree. It's not put there by the submitter. The maintainer marks a reliable mapping of "this commit came from this thread" and we the use this info for multiple purposes:

You are overloading the terms here - "pull" as in (basically) git am and "pull" as in git pull and its ilk...

And I still don't understand how is that supposed to apply when patches are _developed_ in git branches. In situation when submitter == maintainer.

#132 lore raw
Quoted 10 lines · Al Viro
Quoted 4 lines

I'm not sure what you mean. The Link: trailer is added when the maintainer pulls in the series into their tree. It's not put there by the submitter. The maintainer marks a reliable mapping of "this commit came from this thread" and we the use this info for multiple purposes:

You are overloading the terms here - "pull" as in (basically) git am and "pull" as in git pull and its ilk...

And I still don't understand how is that supposed to apply when patches are _developed_ in git branches. In situation when submitter == maintainer.

Then there's no external provenance, so there is no need for this kind of mapping. You will submit your changes as a pull request and you'll get notified when it's merged (via the PR tracker bot).

There is a hybrid workflow as well:

- maintainer develops a patch series
- maintainer sends it to the list for review
- maintainer pulls in the trailers

In that case, we don't automatically put provenance trailers into patches, but you can still achieve the same result if instead of merging your local branch you merge the series from the list, but this is more of a corner case scenario.

-K

#133 lore raw
In reply to Konstantin Ryabitsev
Quoted 3 lines · Konstantin Ryabitsev

I'm not sure what you mean. The Link: trailer is added when the maintainer pulls in the series into their tree.

That's my point. Adding it to the commit at that point is entirely useless, because

(a) that email doesn't have the *reason* for the patch (or rather, if
it does, then the link to the email is pointless, since the *real* reason was mentioned already)

(b) at that point clearly it doesn't have any *problems* associated
with it either, since if it did, it shouldn't have been included in the first place.

So there is absolutely zero information in the link.

It's pure pointless noise.

Quoted 3 lines

maintainer marks a reliable mapping of "this commit came from this thread" and

It serves a real purpose.

It damn well does not serve any purpose at all, because there is nothing useful there.

Your logic isn't logic - it's just empty words.

I can come up with tons of "reliable mappings". How about we make the automation add the weather.com report for the weather in Kuala Lumpur when b4 downloads the series? We could do that reliably too.

Notice how the reliability of something is entirely irrelevant. Just because you can reliably automate it doesn't make it relevant information.

And dammit, it's WORSE than worthless information. I _constantly_ end up being disappointed by those useless links, and I've wasted time following them in the hope of finding something useful.

So it's actually reliably NEGATIVE information that wastes peoples time.

Quoted 1 line

We cannot *reliably* map commits to patches.

What we care about is about things being *USEFUL*.

"Reliable" is entirely irrelevant if it's not useful.

Because reliable but useless is still useless.

And always will be.

So I'll take "Useful information that you might not always have", every single time over "Useless, but always there".

Get it?

Linus

#134 lore raw
Quoted 6 lines · Linus Torvalds
Quoted 3 lines · Konstantin Ryabitsev

I'm not sure what you mean. The Link: trailer is added when the maintainer pulls in the series into their tree.

Quoted 6 lines

That's my point. Adding it to the commit at that point is entirely useless, because

(a) that email doesn't have the *reason* for the patch (or rather, if
it does, then the link to the email is pointless, since the *real* reason was mentioned already)

From a maintainer's perspective, the reason why I keep the link in is because I'm dumb-ass lazy. My workflow involves looking at patchwork, cutting-and-pasting the Message-Id, and then passing it to b4. Looking through a 20 patch series to figure out which one rates a
Link: trailer, and which one doesn't is a pain in the *ss, and in the off-chance that there *is* a meaningful and deep discussion, it would be nice to be able to capture it. But it might be in patch #4; or patch #12; or patches #14 and #15. Also, there might be an extended conversation thread in the patch series description (patch #0) and it would be useful to be able to get a link to it.

So here's a set of feature requests for b4.

(a) It would be cool(tm), if there was a way for b4 to automatically detect whether or not there was a reply to a patch at the time that "b4 am" is run; if there is, to include the patch series. If there isn't an e-mail reply, skip the the Link: trailer.

(b) In the case of a patch series, it would be useful to include some kind of trailer indicating that a group of patches are logically grouped together (maybe a patch-series: that has the message id to the the series header, or the first patch if there is no patch #0) --- because one of the other ways that I figure out that a series of commits are part of a patch series is by looking at the Link:
field since if the messages are generated using "git send-email" it's usually obvious from the message id. This has also come up from some of the folks who want this for their web-based review systems. I don't care about that, but if it solves multiple use cases at once, that's great.

(c) Include a link tag to the patch series description e-mail message (if present) in the first commit of the patch series so it's possible to read the patch #0 description of the patch series, since otherwise this can get be hard to find in the git history.

(d) For bonus points, if there is a way to determine a link to the previous versions of the patch series, it would be useful for to incude link: tags to previous versions of the patch if and only if there were e-mail comments to say, the v5, v12, and v27 versions of the patch.

(e) If there is some way we can easily capture lore.kernel.org URL for the vN-1 version of the patch series in the vN commit description header, in "b4 prep" that would be *excellent*. I don't think it can do this today, but if it can, can we make sure it's defaulted to on, and then we should **really** market the heck out of b4 prep?

The bottom line is I'd love to make Linus less cranky; but I'd also love
it if I didn't have to do the extra work by hand. :-) Because if I do
have to do it by hand, I will probably screw up, and my preference has been to err on the side of having the link, so it's there when I'm having to code code archeology --- even if most of the time it's not strictly speaking necessary.

Cheers,

- Ted

#135 lore raw
Quoted 6 lines · Theodore Ts'o

So here's a set of feature requests for b4.

(a) It would be cool(tm), if there was a way for b4 to automatically detect whether or not there was a reply to a patch at the time that "b4 am" is run; if there is, to include the patch series. If there isn't an e-mail reply, skip the the Link: trailer.

I'm afraid this would mostly breed confusion.

Quoted 11 lines

(b) In the case of a patch series, it would be useful to include some kind of trailer indicating that a group of patches are logically grouped together (maybe a patch-series: that has the message id to the the series header, or the first patch if there is no patch #0) --- because one of the other ways that I figure out that a series of commits are part of a patch series is by looking at the Link:
field since if the messages are generated using "git send-email" it's usually obvious from the message id. This has also come up from some of the folks who want this for their web-based review systems. I don't care about that, but if it solves multiple use cases at once, that's great.

This is already in place with the change-id trailer (and the corresponding X-Change-ID email header).

However, only b4 puts those in. Series prepared and sent with git-send-email don't have any identifier like that.

Quoted 4 lines

(c) Include a link tag to the patch series description e-mail message (if present) in the first commit of the patch series so it's possible to read the patch #0 description of the patch series, since otherwise this can get be hard to find in the git history.

We're talking about the lore.kernel.org web interface?

Quoted 5 lines

(d) For bonus points, if there is a way to determine a link to the previous versions of the patch series, it would be useful for to incude link: tags to previous versions of the patch if and only if there were e-mail comments to say, the v5, v12, and v27 versions of the patch.

Again, are we talking in the context of the lore.kernel.org web interface? The initial discussion about Link: tags was about them being present in git commits.

Quoted 6 lines

(e) If there is some way we can easily capture lore.kernel.org URL for the vN-1 version of the patch series in the vN commit description header, in "b4 prep" that would be *excellent*. I don't think it can do this today, but if it can, can we make sure it's defaulted to on, and then we should **really** market the heck out of b4 prep?

You can do this for any b4-prep sent series by just searching for the change-id string. E.g.:

https://lore.kernel.org/lkml/?q=20241018-pmu_event_info-986e21ce6bd3

b4 prep is used quite extensively these days, but it's far from being predominant.

Quoted 6 lines

The bottom line is I'd love to make Linus less cranky; but I'd also love
it if I didn't have to do the extra work by hand. :-) Because if I do
have to do it by hand, I will probably screw up, and my preference has been to err on the side of having the link, so it's there when I'm having to code code archeology --- even if most of the time it's not strictly speaking necessary.

This doesn't ultimately solve the problem that we're butting heads about -- that it's impossible to reliably match a commit to its provenance. Using Link:
trailers indicating where the patch came from is the only reliable mechanism we have thus far, because it establishes this relationship unequivocally. However, these links annoy Linus, who would like this to be automated in some other way behind the scenes. I'd love to be able to do so, but short of running some kind of "provenance transparency log" of curated commit -> message-id mappings, I don't see how it's possible.

-K

author maintainer
#136 lore raw

RFC PATCH v2 1/1 mount-related stuff

In reply to Al Viro

Branch force-pushed into
git://git.kernel.org/pub/scm/linux/kernel/git/viro/vfs.git #work.mount (also visible as #v2.mount, #v1.mount being the previous version) Individual patches in followups.

Still -rc3-based, seems to survive local beating. Please, help with review and testing.

Note: no links in commits, I still don't understand what kind of use is expected in this situation.

Changes since v1 (aside of reviewed-by applied):

In #13, #14 and #15 scoped_guard replaced with guard. I don't like
it, but I can live with it.

Between old #18 and #19: do_new_mount_fc() switched to use of fc_mount().
vfs_get_tree() call moved from the caller into the function itself, unlock + vfs_create_mount() reordered to before the checks in there and collapsed with vfs_get_tree() into a call of fc_mount(). Cleanup aside, that avoids the difference between the lexical scope of mnt and the actual lifetime of that reference.

	Differs from the variant posted in https://lore.kernel.org/all/20250826182124.GV39973@ZenIV/
only by fixing an obvious braino - fetching fc->root->d_sb should be done after
successful fc_mount(), not before it.
	That change modifies old #25 (now #26) "do_new_mount_rc(): use __free()

to deal with dropping mnt on failure".

Added to the end of queue: cleanup of populating a new namespace with
a tree (open_detached_copy() and copy_mnt_ns()); both end up using guards, BTW.

	5 commits, #54..#58
	* open_detached_copy(): don't bother with mount_lock_hash()

It's useless there right now - namespace_excl is quite enough.
* open_detached_copy(): separate creation of namespace into helper
Creation of namespace and opening that FMODE_NEED_UNMOUNT file are better off separated - cleaner that way.
* mnt_ns_tree_remove(): DTRT if mnt_ns had never been added to mnt_ns_list
Currently it (and free_mnt_ns()) can't be used with non-anon namespace before the insertion into mnt_ns_tree; very easy to make it work in such situation as well - in fact, the old "is it non-anonymous" check is not needed anymore.
* copy_mnt_ns(): use the regular mechanism for freeing empty mnt_ns on failure
Use the previous patch to avoid weird open-coding of free_mnt_ns().
* copy_mnt_ns(): use guards
... and __free(mntput) for rootmnt/pwdmnt.

	Added to the end of queue: handling of ->s_mounts/->mnt_instance and

mnt_hold_writers().
Each mount is associated with the same dentry (sub)tree of the same
filesystem through its entire lifetime. They are allocated empty, then (in the same function that had called allocator) attached to dentry tree and stay like that all the way to destructor (cleanup_mnt()).
Unfortunately, as soon as they are attached to a tree, they become
reachable from shared data structures - we maintain the set of all mounts associated with given superblock. Having to worry about that while we are still setting them up is inconvenient. Thankfully, the accesses via that set are *very* limited - only sb_prepare_remount_readonly() goes there and the only thing it does to a mount is setting/clearing MNT_WRITE_HOLD and checking the write count (guaranteed to be zero during setup, since there's nobody who could've asked for write access by that point).

	Turns out it's easy to take MNT_WRITE_HOLD out of ->mnt_flags and

basically move it into the same thing that establishes linkage in per-superblock set of mounts. That makes accesses via that set isolated from the rest of struct mount; as far as we are concerned, this set is no longer a way to reach the mount from shared data structures and mount remains private to caller until it is explicitly made reachable (by mounting, attaching to overlayfs as a layer, etc.).
FWIW, I think we should get rid of the "empty" state of struct mount
and have allocator take the root dentry as additional argument. Hadn't done that yet; this series removes the need to delay attaching a partially set up mount to filesystem - we can do that from the very beginning now.

	5 commits, #59..#63
	* setup_mnt(): primitive for connecting a mount to filesystem

Identical logics in clone_mnt() and vfs_create_mount() => common helper
* preparations to taking MNT_WRITE_HOLD out of ->mnt_flags
Change the representation of set from list_head list to something equivalent to hlist one, with forward linkage going to the entire struct mount rather than embedded hlist_node.
* struct mount: relocate MNT_WRITE_HOLD bit
Steal the LSB of back links in the set representation to store it. We only traverse the list forwards and all changes are under mount_lock, same as for all mnt_hold_writers()/mnt_unhold_writers() pairs, so it's pretty uncomplicated.
* simplify the callers of mnt_unhold_writers()
* WRITE_HOLD machinery: no need for to bump mount_lock seqcount
The last part is another group of "we only need mount_locked_reader" cases

Diffstat:

 fs/ecryptfs/dentry.c          |  14 +-
 fs/ecryptfs/ecryptfs_kernel.h |  27 +-
 fs/ecryptfs/file.c            |  15 +-
 fs/ecryptfs/inode.c           |  19 +-
 fs/ecryptfs/main.c            |  24 +-
 fs/internal.h                 |   4 +-
 fs/mount.h                    |  16 +-
 fs/namespace.c                | 989 +++++++++++++++++++-----------------------
 fs/pnode.c                    |  75 +++-
 fs/pnode.h                    |   1 +
 fs/super.c                    |   3 +-
 include/linux/fs.h            |   2 +-
 include/linux/mount.h         |   7 +-
 kernel/audit_tree.c           |  12 +-
 14 files changed, 573 insertions(+), 635 deletions(-)
author maintainer
#137 lore raw

PATCH v2 1/63 fs/namespace.c: fix the namespace_sem guard mess

If anything, namespace_lock should be DEFINE_LOCK_GUARD_0, not DEFINE_GUARD. That way we
* do not need to feed it a bogus argument
* do not get gcc trying to compare an address of static in
file variable with -4097 - and, if we are unlucky, trying to keep it in a register, with spills and all such.

The same problems apply to grabbing namespace_sem shared.

Rename it to namespace_excl, add namespace_shared, convert the existing users:

guard(namespace_lock, &namespace_sem) => guard(namespace_excl)() guard(rwsem_read, &namespace_sem) => guard(namespace_shared)() scoped_guard(namespace_lock, &namespace_sem) => scoped_guard(namespace_excl) scoped_guard(rwsem_read, &namespace_sem) => scoped_guard(namespace_shared)

---
 fs/namespace.c | 18 +++++++++++-------
 1 file changed, 11 insertions(+), 7 deletions(-)
fs/namespace.c +11 -7
index ae6d1312b184..fcea65587ff9 100644
@@ -82,6 +82,12 @@ static LIST_HEAD(ex_mountpoints); /* protected by namespace_sem */
 static struct mnt_namespace *emptied_ns; /* protected by namespace_sem */
 static DEFINE_SEQLOCK(mnt_ns_tree_lock);
 
+static inline void namespace_lock(void);
+static void namespace_unlock(void);
+DEFINE_LOCK_GUARD_0(namespace_excl, namespace_lock(), namespace_unlock())
+DEFINE_LOCK_GUARD_0(namespace_shared, down_read(&namespace_sem),
+				      up_read(&namespace_sem))
+
 #ifdef CONFIG_FSNOTIFY
 LIST_HEAD(notify_list); /* protected by namespace_sem */
 #endif
@@ -1776,8 +1782,6 @@ static inline void namespace_lock(void)
 	down_write(&namespace_sem);
 }
 
-DEFINE_GUARD(namespace_lock, struct rw_semaphore *, namespace_lock(), namespace_unlock())
-
 enum umount_tree_flags {
 	UMOUNT_SYNC = 1,
 	UMOUNT_PROPAGATE = 2,
@@ -2306,7 +2310,7 @@ struct path *collect_paths(const struct path *path,
 	struct path *res = prealloc, *to_free = NULL;
 	unsigned n = 0;
 
-	guard(rwsem_read)(&namespace_sem);
+	guard(namespace_shared)();
 
 	if (!check_mnt(root))
 		return ERR_PTR(-EINVAL);
@@ -2361,7 +2365,7 @@ void dissolve_on_fput(struct vfsmount *mnt)
 			return;
 	}
 
-	scoped_guard(namespace_lock, &namespace_sem) {
+	scoped_guard(namespace_excl) {
 		if (!anon_ns_root(m))
 			return;
 
@@ -2435,7 +2439,7 @@ struct vfsmount *clone_private_mount(const struct path *path)
 	struct mount *old_mnt = real_mount(path->mnt);
 	struct mount *new_mnt;
 
-	guard(rwsem_read)(&namespace_sem);
+	guard(namespace_shared)();
 
 	if (IS_MNT_UNBINDABLE(old_mnt))
 		return ERR_PTR(-EINVAL);
@@ -5957,7 +5961,7 @@ SYSCALL_DEFINE4(statmount, const struct mnt_id_req __user *, req,
 	if (ret)
 		return ret;
 
-	scoped_guard(rwsem_read, &namespace_sem)
+	scoped_guard(namespace_shared)
 		ret = do_statmount(ks, kreq.mnt_id, kreq.mnt_ns_id, ns);
 
 	if (!ret)
@@ -6079,7 +6083,7 @@ SYSCALL_DEFINE4(listmount, const struct mnt_id_req __user *, req,
 	 * We only need to guard against mount topology changes as
 	 * listmount() doesn't care about any mount properties.
 	 */
-	scoped_guard(rwsem_read, &namespace_sem)
+	scoped_guard(namespace_shared)
 		ret = do_listmount(ns, kreq.mnt_id, last_mnt_id, kmnt_ids,
 				   nr_mnt_ids, (flags & LISTMOUNT_REVERSE));
 	if (ret <= 0)
Signature · 1 line
2.47.2
author maintainer
#138 lore raw

PATCH v2 2/63 introduced guards for mount_lock

mount_writer: write_seqlock; that's an equivalent of {un,}lock_mount_hash()
mount_locked_reader: read_seqlock_excl; these tend to be open-coded.

No bulk conversions, please - if nothing else, quite a few places take use mount_writer form when mount_locked_reader is sufficent. It needs to be dealt with carefully.

---
 fs/mount.h | 5 +++++
 1 file changed, 5 insertions(+)
fs/mount.h +5 -0
index 97737051a8b9..ed8c83ba836a 100644
@@ -154,6 +154,11 @@ static inline void get_mnt_ns(struct mnt_namespace *ns)
 
 extern seqlock_t mount_lock;
 
+DEFINE_LOCK_GUARD_0(mount_writer, write_seqlock(&mount_lock),
+		    write_sequnlock(&mount_lock))
+DEFINE_LOCK_GUARD_0(mount_locked_reader, read_seqlock_excl(&mount_lock),
+		    read_sequnlock_excl(&mount_lock))
+
 struct proc_mounts {
 	struct mnt_namespace *ns;
 	struct path root;
Signature · 1 line
2.47.2
maintainer Reviewed-by on v2 2/63
#139 lore raw

Re: [PATCH v2 02/63] introduced guards for mount_lock

Quoted 9 lines · Al Viro

mount_writer: write_seqlock; that's an equivalent of {un,}lock_mount_hash()
mount_locked_reader: read_seqlock_excl; these tend to be open-coded.

No bulk conversions, please - if nothing else, quite a few places take use mount_writer form when mount_locked_reader is sufficent. It needs to be dealt with carefully.

---

author maintainer
#140 lore raw

PATCH v2 3/63 fs/namespace.c: allow to drop vfsmount references via __free(mntput)

In reply to Al Viro

Note that just as path_put, it should never be done in scope of namespace_sem, be it shared or exclusive.

---
 fs/namespace.c | 2 ++
 1 file changed, 2 insertions(+)
fs/namespace.c +2 -0
index fcea65587ff9..767ab751ee2a 100644
@@ -88,6 +88,8 @@ DEFINE_LOCK_GUARD_0(namespace_excl, namespace_lock(), namespace_unlock())
 DEFINE_LOCK_GUARD_0(namespace_shared, down_read(&namespace_sem),
 				      up_read(&namespace_sem))
 
+DEFINE_FREE(mntput, struct vfsmount *, if (!IS_ERR(_T)) mntput(_T))
+
 #ifdef CONFIG_FSNOTIFY
 LIST_HEAD(notify_list); /* protected by namespace_sem */
 #endif
Signature · 1 line
2.47.2
author maintainer
#141 lore raw

PATCH v2 4/63 __detach_mounts(): use guards

In reply to Al Viro

Clean fit for guards use; guards can't be weaker due to umount_tree() calls.

---
 fs/namespace.c | 10 ++++------
 1 file changed, 4 insertions(+), 6 deletions(-)
fs/namespace.c +4 -6
index 767ab751ee2a..1ae1ab8815c9 100644
@@ -2032,10 +2032,11 @@ void __detach_mounts(struct dentry *dentry)
 	struct pinned_mountpoint mp = {};
 	struct mount *mnt;
 
-	namespace_lock();
-	lock_mount_hash();
+	guard(namespace_excl)();
+	guard(mount_writer)();
+
 	if (!lookup_mountpoint(dentry, &mp))
-		goto out_unlock;
+		return;
 
 	event++;
 	while (mp.node.next) {
@@ -2047,9 +2048,6 @@ void __detach_mounts(struct dentry *dentry)
 		else umount_tree(mnt, UMOUNT_CONNECTED);
 	}
 	unpin_mountpoint(&mp);
-out_unlock:
-	unlock_mount_hash();
-	namespace_unlock();
 }
 
 /*
Signature · 1 line
2.47.2
maintainer Reviewed-by on v2 4/63
#142 lore raw

Re: [PATCH v2 04/63] __detach_mounts(): use guards

Quoted 2 lines · Al Viro

Clean fit for guards use; guards can't be weaker due to umount_tree() calls.
---

Did you drop my earlier RvB on accident? In any case:

author maintainer
#143 lore raw

PATCH v2 5/63 __is_local_mountpoint(): use guards

In reply to Al Viro

clean fit; namespace_shared due to iterating through ns->mounts.

---
 fs/namespace.c | 15 ++++++---------
 1 file changed, 6 insertions(+), 9 deletions(-)
fs/namespace.c +6 -9
index 1ae1ab8815c9..f1460ddd1486 100644
@@ -906,17 +906,14 @@ bool __is_local_mountpoint(const struct dentry *dentry)
 {
 	struct mnt_namespace *ns = current->nsproxy->mnt_ns;
 	struct mount *mnt, *n;
-	bool is_covered = false;
 
-	down_read(&namespace_sem);
-	rbtree_postorder_for_each_entry_safe(mnt, n, &ns->mounts, mnt_node) {
-		is_covered = (mnt->mnt_mountpoint == dentry);
-		if (is_covered)
-			break;
-	}
-	up_read(&namespace_sem);
+	guard(namespace_shared)();
+
+	rbtree_postorder_for_each_entry_safe(mnt, n, &ns->mounts, mnt_node)
+		if (mnt->mnt_mountpoint == dentry)
+			return true;
 
-	return is_covered;
+	return false;
 }
 
 struct pinned_mountpoint {
Signature · 1 line
2.47.2
author maintainer
#144 lore raw

PATCH v2 6/63 do_change_type(): use guards

In reply to Al Viro

clean fit; namespace_excl to modify propagation graph

---
 fs/namespace.c | 13 ++++++-------
 1 file changed, 6 insertions(+), 7 deletions(-)
fs/namespace.c +6 -7
index f1460ddd1486..a6a7b068770a 100644
@@ -2899,7 +2899,7 @@ static int do_change_type(struct path *path, int ms_flags)
 	struct mount *mnt = real_mount(path->mnt);
 	int recurse = ms_flags & MS_REC;
 	int type;
-	int err = 0;
+	int err;
 
 	if (!path_mounted(path))
 		return -EINVAL;
@@ -2908,23 +2908,22 @@ static int do_change_type(struct path *path, int ms_flags)
 	if (!type)
 		return -EINVAL;
 
-	namespace_lock();
+	guard(namespace_excl)();
+
 	err = may_change_propagation(mnt);
 	if (err)
-		goto out_unlock;
+		return err;
 
 	if (type == MS_SHARED) {
 		err = invent_group_ids(mnt, recurse);
 		if (err)
-			goto out_unlock;
+			return err;
 	}
 
 	for (m = mnt; m; m = (recurse ? next_mnt(m, mnt) : NULL))
 		change_mnt_propagation(m, type);
 
- out_unlock:
-	namespace_unlock();
-	return err;
+	return 0;
 }
 
 /* may_copy_tree() - check if a mount tree can be copied
Signature · 1 line
2.47.2
author maintainer
#145 lore raw

PATCH v2 7/63 do_set_group(): use guards

In reply to Al Viro

clean fit; namespace_excl to modify propagation graph

---
 fs/namespace.c | 33 +++++++++++++--------------------
 1 file changed, 13 insertions(+), 20 deletions(-)
fs/namespace.c +13 -20
index a6a7b068770a..13e2f3837a26 100644
@@ -3349,47 +3349,44 @@ static inline int tree_contains_unbindable(struct mount *mnt)
 
 static int do_set_group(struct path *from_path, struct path *to_path)
 {
-	struct mount *from, *to;
+	struct mount *from = real_mount(from_path->mnt);
+	struct mount *to = real_mount(to_path->mnt);
 	int err;
 
-	from = real_mount(from_path->mnt);
-	to = real_mount(to_path->mnt);
-
-	namespace_lock();
+	guard(namespace_excl)();
 
 	err = may_change_propagation(from);
 	if (err)
-		goto out;
+		return err;
 	err = may_change_propagation(to);
 	if (err)
-		goto out;
+		return err;
 
-	err = -EINVAL;
 	/* To and From paths should be mount roots */
 	if (!path_mounted(from_path))
-		goto out;
+		return -EINVAL;
 	if (!path_mounted(to_path))
-		goto out;
+		return -EINVAL;
 
 	/* Setting sharing groups is only allowed across same superblock */
 	if (from->mnt.mnt_sb != to->mnt.mnt_sb)
-		goto out;
+		return -EINVAL;
 
 	/* From mount root should be wider than To mount root */
 	if (!is_subdir(to->mnt.mnt_root, from->mnt.mnt_root))
-		goto out;
+		return -EINVAL;
 
 	/* From mount should not have locked children in place of To's root */
 	if (__has_locked_children(from, to->mnt.mnt_root))
-		goto out;
+		return -EINVAL;
 
 	/* Setting sharing groups is only allowed on private mounts */
 	if (IS_MNT_SHARED(to) || IS_MNT_SLAVE(to))
-		goto out;
+		return -EINVAL;
 
 	/* From should not be private */
 	if (!IS_MNT_SHARED(from) && !IS_MNT_SLAVE(from))
-		goto out;
+		return -EINVAL;
 
 	if (IS_MNT_SLAVE(from)) {
 		hlist_add_behind(&to->mnt_slave, &from->mnt_slave);
@@ -3401,11 +3398,7 @@ static int do_set_group(struct path *from_path, struct path *to_path)
 		list_add(&to->mnt_share, &from->mnt_share);
 		set_mnt_shared(to);
 	}
-
-	err = 0;
-out:
-	namespace_unlock();
-	return err;
+	return 0;
 }
 
 /**
Signature · 1 line
2.47.2
author maintainer
#146 lore raw

PATCH v2 8/63 mark_mounts_for_expiry(): use guards

In reply to Al Viro

Clean fit; guards can't be weaker due to umount_tree() calls.

---
 fs/namespace.c | 6 ++----
 1 file changed, 2 insertions(+), 4 deletions(-)
fs/namespace.c +2 -4
index 13e2f3837a26..898a6b7307e4 100644
@@ -3886,8 +3886,8 @@ void mark_mounts_for_expiry(struct list_head *mounts)
 	if (list_empty(mounts))
 		return;
 
-	namespace_lock();
-	lock_mount_hash();
+	guard(namespace_excl)();
+	guard(mount_writer)();
 
 	/* extract from the expiration list every vfsmount that matches the
 	 * following criteria:
@@ -3909,8 +3909,6 @@ void mark_mounts_for_expiry(struct list_head *mounts)
 		touch_mnt_namespace(mnt->mnt_ns);
 		umount_tree(mnt, UMOUNT_PROPAGATE|UMOUNT_SYNC);
 	}
-	unlock_mount_hash();
-	namespace_unlock();
 }
 
 EXPORT_SYMBOL_GPL(mark_mounts_for_expiry);
Signature · 1 line
2.47.2
author maintainer
#147 lore raw

PATCH v2 9/63 put_mnt_ns(): use guards

In reply to Al Viro

clean fit; guards can't be weaker due to umount_tree() call. Setting emptied_ns requires namespace_excl, but not anything mount_lock-related.

---
 fs/namespace.c | 6 ++----
 1 file changed, 2 insertions(+), 4 deletions(-)
fs/namespace.c +2 -4
index 898a6b7307e4..86a86be2b0ef 100644
@@ -6153,12 +6153,10 @@ void put_mnt_ns(struct mnt_namespace *ns)
 {
 	if (!refcount_dec_and_test(&ns->ns.count))
 		return;
-	namespace_lock();
+	guard(namespace_excl)();
 	emptied_ns = ns;
-	lock_mount_hash();
+	guard(mount_writer)();
 	umount_tree(ns->root, 0);
-	unlock_mount_hash();
-	namespace_unlock();
 }
 
 struct vfsmount *kern_mount(struct file_system_type *type)
Signature · 1 line
2.47.2
author maintainer
#148 lore raw

PATCH v2 10/63 mnt_already_visible(): use guards

In reply to Al Viro

clean fit; namespace_shared due to iterating through ns->mounts.

---
 fs/namespace.c | 10 +++-------
 1 file changed, 3 insertions(+), 7 deletions(-)
fs/namespace.c +3 -7
index 86a86be2b0ef..a5d37b97088f 100644
@@ -6232,9 +6232,8 @@ static bool mnt_already_visible(struct mnt_namespace *ns,
 {
 	int new_flags = *new_mnt_flags;
 	struct mount *mnt, *n;
-	bool visible = false;
 
-	down_read(&namespace_sem);
+	guard(namespace_shared)();
 	rbtree_postorder_for_each_entry_safe(mnt, n, &ns->mounts, mnt_node) {
 		struct mount *child;
 		int mnt_flags;
@@ -6281,13 +6280,10 @@ static bool mnt_already_visible(struct mnt_namespace *ns,
 		/* Preserve the locked attributes */
 		*new_mnt_flags |= mnt_flags & (MNT_LOCK_READONLY | \
 					       MNT_LOCK_ATIME);
-		visible = true;
-		goto found;
+		return true;
 	next:	;
 	}
-found:
-	up_read(&namespace_sem);
-	return visible;
+	return false;
 }
 
 static bool mount_too_revealing(const struct super_block *sb, int *new_mnt_flags)
Signature · 1 line
2.47.2
author maintainer
#149 lore raw

PATCH v2 11/63 check_for_nsfs_mounts(): no need to take locks

In reply to Al Viro

Currently we are taking mount_writer; what that function needs is either mount_locked_reader (we are not changing anything, we just want to iterate through the subtree) or namespace_shared and a reference held by caller on the root of subtree - that's also enough to stabilize the topology.

The thing is, all callers are already holding at least namespace_shared as well as a reference to the root of subtree.

Let's make the callers provide locking warranties - don't mess with mount_lock in check_for_nsfs_mounts() itself and document the locking requirements.

---
 fs/namespace.c | 16 +++++-----------
 1 file changed, 5 insertions(+), 11 deletions(-)
fs/namespace.c +5 -11
index a5d37b97088f..59948cbf9c47 100644
@@ -2402,21 +2402,15 @@ bool has_locked_children(struct mount *mnt, struct dentry *dentry)
  * specified subtree.  Such references can act as pins for mount namespaces
  * that aren't checked by the mount-cycle checking code, thereby allowing
  * cycles to be made.
+ *
+ * locks: mount_locked_reader || namespace_shared && pinned(subtree)
  */
 static bool check_for_nsfs_mounts(struct mount *subtree)
 {
-	struct mount *p;
-	bool ret = false;
-
-	lock_mount_hash();
-	for (p = subtree; p; p = next_mnt(p, subtree))
+	for (struct mount *p = subtree; p; p = next_mnt(p, subtree))
 		if (mnt_ns_loop(p->mnt.mnt_root))
-			goto out;
-
-	ret = true;
-out:
-	unlock_mount_hash();
-	return ret;
+			return false;
+	return true;
 }
 
 /**
Signature · 1 line
2.47.2
author maintainer
#150 lore raw

PATCH v2 12/63 propagate_mnt(): use scoped_guard(mount_locked_reader) for mnt_set_mountpoint()

In reply to Al Viro
---
 fs/pnode.c | 5 ++---
 1 file changed, 2 insertions(+), 3 deletions(-)
fs/pnode.c +2 -3
index 6f7d02f3fa98..0702d45d856d 100644
@@ -304,9 +304,8 @@ int propagate_mnt(struct mount *dest_mnt, struct mountpoint *dest_mp,
 				err = PTR_ERR(this);
 				break;
 			}
-			read_seqlock_excl(&mount_lock);
-			mnt_set_mountpoint(n, dest_mp, this);
-			read_sequnlock_excl(&mount_lock);
+			scoped_guard(mount_locked_reader)
+				mnt_set_mountpoint(n, dest_mp, this);
 			if (n->mnt_master)
 				SET_MNT_MARK(n->mnt_master);
 			copy = this;
Signature · 1 line
2.47.2
author maintainer
#151 lore raw

PATCH v2 13/63 has_locked_children(): use guards

In reply to Al Viro

... and document the locking requirements of __has_locked_children()

---
 fs/namespace.c | 9 +++------
 1 file changed, 3 insertions(+), 6 deletions(-)
fs/namespace.c +3 -6
index 59948cbf9c47..2cb3cb8307ca 100644
@@ -2373,6 +2373,7 @@ void dissolve_on_fput(struct vfsmount *mnt)
 	}
 }
 
+/* locks: namespace_shared && pinned(mnt) || mount_locked_reader */
 static bool __has_locked_children(struct mount *mnt, struct dentry *dentry)
 {
 	struct mount *child;
@@ -2389,12 +2390,8 @@ static bool __has_locked_children(struct mount *mnt, struct dentry *dentry)
 
 bool has_locked_children(struct mount *mnt, struct dentry *dentry)
 {
-	bool res;
-
-	read_seqlock_excl(&mount_lock);
-	res = __has_locked_children(mnt, dentry);
-	read_sequnlock_excl(&mount_lock);
-	return res;
+	guard(mount_locked_reader)();
+	return __has_locked_children(mnt, dentry);
 }
 
 /*
Signature · 1 line
2.47.2
author maintainer
#153 lore raw

PATCH v2 14/63 mnt_set_expiry(): use guards

In reply to Al Viro

The reason why it needs only mount_locked_reader is that there's no lockless accesses of expiry lists.

---
 fs/namespace.c | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)
fs/namespace.c +1 -2
index 2cb3cb8307ca..db25c81d7f68 100644
@@ -3858,9 +3858,8 @@ int finish_automount(struct vfsmount *m, const struct path *path)
  */
 void mnt_set_expiry(struct vfsmount *mnt, struct list_head *expiry_list)
 {
-	read_seqlock_excl(&mount_lock);
+	guard(mount_locked_reader)();
 	list_add_tail(&real_mount(mnt)->mnt_expire, expiry_list);
-	read_sequnlock_excl(&mount_lock);
 }
 EXPORT_SYMBOL(mnt_set_expiry);
 
Signature · 1 line
2.47.2
author maintainer
#155 lore raw

PATCH v2 15/63 path_is_under(): use guards

In reply to Al Viro

... and document that locking requirements for is_path_reachable(). There is one questionable caller in do_listmount() where we are not holding mount_lock *and* might not have the first argument mounted. However, in that case it will immediately return true without having to look at the ancestors. Might be cleaner to move the check into non-LSTM_ROOT case which it really belongs in - there the check is not always true and is_mounted() is guaranteed.

Document the locking environments for is_path_reachable() callers:
get_peer_under_root()
get_dominating_id()
do_statmount()
do_listmount()

---
 fs/namespace.c | 11 +++++------
 fs/pnode.c     |  3 ++-
 2 files changed, 7 insertions(+), 7 deletions(-)
fs/namespace.c +5 -6
index db25c81d7f68..6aabf0045389 100644
@@ -4592,7 +4592,7 @@ SYSCALL_DEFINE5(move_mount,
 /*
  * Return true if path is reachable from root
  *
- * namespace_sem or mount_lock is held
+ * locks: mount_locked_reader || namespace_shared && is_mounted(mnt)
  */
 bool is_path_reachable(struct mount *mnt, struct dentry *dentry,
 			 const struct path *root)
@@ -4606,11 +4606,8 @@ bool is_path_reachable(struct mount *mnt, struct dentry *dentry,
 
 bool path_is_under(const struct path *path1, const struct path *path2)
 {
-	bool res;
-	read_seqlock_excl(&mount_lock);
-	res = is_path_reachable(real_mount(path1->mnt), path1->dentry, path2);
-	read_sequnlock_excl(&mount_lock);
-	return res;
+	guard(mount_locked_reader)();
+	return is_path_reachable(real_mount(path1->mnt), path1->dentry, path2);
 }
 EXPORT_SYMBOL(path_is_under);
 
@@ -5689,6 +5686,7 @@ static int grab_requested_root(struct mnt_namespace *ns, struct path *root)
 			     STATMOUNT_MNT_UIDMAP | \
 			     STATMOUNT_MNT_GIDMAP)
 
+/* locks: namespace_shared */
 static int do_statmount(struct kstatmount *s, u64 mnt_id, u64 mnt_ns_id,
 			struct mnt_namespace *ns)
 {
@@ -5949,6 +5947,7 @@ SYSCALL_DEFINE4(statmount, const struct mnt_id_req __user *, req,
 	return ret;
 }
 
+/* locks: namespace_shared */
 static ssize_t do_listmount(struct mnt_namespace *ns, u64 mnt_parent_id,
 			    u64 last_mnt_id, u64 *mnt_ids, size_t nr_mnt_ids,
 			    bool reverse)
fs/pnode.c +2 -1
index 0702d45d856d..edaf9d9d0eaf 100644
@@ -29,6 +29,7 @@ static inline struct mount *next_slave(struct mount *p)
 	return hlist_entry(p->mnt_slave.next, struct mount, mnt_slave);
 }
 
+/* locks: namespace_shared && is_mounted(mnt) */
 static struct mount *get_peer_under_root(struct mount *mnt,
 					 struct mnt_namespace *ns,
 					 const struct path *root)
@@ -50,7 +51,7 @@ static struct mount *get_peer_under_root(struct mount *mnt,
  * Get ID of closest dominating peer group having a representative
  * under the given root.
  *
- * Caller must hold namespace_sem
+ * locks: namespace_shared
  */
 int get_dominating_id(struct mount *mnt, const struct path *root)
 {
Signature · 1 line
2.47.2
author maintainer
#156 lore raw

PATCH v2 16/63 current_chrooted(): don't bother with follow_down_one()

In reply to Al Viro

All we need here is to follow ->overmount on root mount of namespace...

---
 fs/namespace.c | 18 ++++++++----------
 1 file changed, 8 insertions(+), 10 deletions(-)
fs/namespace.c +8 -10
index 6aabf0045389..cf680fbf015e 100644
@@ -6194,24 +6194,22 @@ bool our_mnt(struct vfsmount *mnt)
 bool current_chrooted(void)
 {
 	/* Does the current process have a non-standard root */
-	struct path ns_root;
+	struct mount *root = current->nsproxy->mnt_ns->root;
 	struct path fs_root;
 	bool chrooted;
 
+	get_fs_root(current->fs, &fs_root);
+
 	/* Find the namespace root */
-	ns_root.mnt = &current->nsproxy->mnt_ns->root->mnt;
-	ns_root.dentry = ns_root.mnt->mnt_root;
-	path_get(&ns_root);
-	while (d_mountpoint(ns_root.dentry) && follow_down_one(&ns_root))
-		;
+	read_seqlock_excl(&mount_lock);
 
-	get_fs_root(current->fs, &fs_root);
+	while (unlikely(root->overmount))
+		root = root->overmount;
 
-	chrooted = !path_equal(&fs_root, &ns_root);
+	chrooted = fs_root.mnt != &root->mnt || !path_mounted(&fs_root);
 
+	read_sequnlock_excl(&mount_lock);
 	path_put(&fs_root);
-	path_put(&ns_root);
-
 	return chrooted;
 }
 
Signature · 1 line
2.47.2
author maintainer
#157 lore raw

PATCH v2 17/63 current_chrooted(): use guards

In reply to Al Viro

here a use of __free(path_put) for dropping fs_root is enough to make guard(mount_locked_reader) fit...

---
 fs/namespace.c | 15 ++++++---------
 1 file changed, 6 insertions(+), 9 deletions(-)
fs/namespace.c +6 -9
index cf680fbf015e..0474b3a93dbf 100644
@@ -6194,23 +6194,20 @@ bool our_mnt(struct vfsmount *mnt)
 bool current_chrooted(void)
 {
 	/* Does the current process have a non-standard root */
-	struct mount *root = current->nsproxy->mnt_ns->root;
-	struct path fs_root;
-	bool chrooted;
+	struct path fs_root __free(path_put) = {};
+	struct mount *root;
 
 	get_fs_root(current->fs, &fs_root);
 
 	/* Find the namespace root */
-	read_seqlock_excl(&mount_lock);
 
+	guard(mount_locked_reader)();
+
+	root = current->nsproxy->mnt_ns->root;
 	while (unlikely(root->overmount))
 		root = root->overmount;
 
-	chrooted = fs_root.mnt != &root->mnt || !path_mounted(&fs_root);
-
-	read_sequnlock_excl(&mount_lock);
-	path_put(&fs_root);
-	return chrooted;
+	return fs_root.mnt != &root->mnt || !path_mounted(&fs_root);
 }
 
 static bool mnt_already_visible(struct mnt_namespace *ns,
Signature · 1 line
2.47.2
author maintainer
#158 lore raw

PATCH v2 18/63 switch do_new_mount_fc() to fc_mount()

In reply to Al Viro

Prior to the call of do_new_mount_fc() the caller has just done successful vfs_get_tree(). Then do_new_mount_fc() does several checks on resulting superblock, and either does fc_drop_locked() and returns an error or proceeds to unlock the superblock and call vfs_create_mount().

The thing is, there's no reason to delay that unlock + vfs_create_mount() - the tests do not rely upon the state of ->s_umount and
fc_drop_locked()
put_fs_context()
is equivalent to

	unlock ->s_umount
	put_fs_context()

Doing vfs_create_mount() before the checks allows us to move vfs_get_tree() from caller to do_new_mount_fc() and collapse it with vfs_create_mount() into an fc_mount() call.

---
 fs/namespace.c | 29 ++++++++++++-----------------
 1 file changed, 12 insertions(+), 17 deletions(-)
fs/namespace.c +12 -17
index 0474b3a93dbf..9b575c9eee0b 100644
@@ -3705,25 +3705,20 @@ static bool mount_too_revealing(const struct super_block *sb, int *new_mnt_flags
 static int do_new_mount_fc(struct fs_context *fc, struct path *mountpoint,
 			   unsigned int mnt_flags)
 {
-	struct vfsmount *mnt;
 	struct pinned_mountpoint mp = {};
-	struct super_block *sb = fc->root->d_sb;
+	struct super_block *sb;
+	struct vfsmount *mnt = fc_mount(fc);
 	int error;
 
+	if (IS_ERR(mnt))
+		return PTR_ERR(mnt);
+
+	sb = fc->root->d_sb;
 	error = security_sb_kern_mount(sb);
 	if (!error && mount_too_revealing(sb, &mnt_flags))
 		error = -EPERM;
-
-	if (unlikely(error)) {
-		fc_drop_locked(fc);
-		return error;
-	}
-
-	up_write(&sb->s_umount);
-
-	mnt = vfs_create_mount(fc);
-	if (IS_ERR(mnt))
-		return PTR_ERR(mnt);
+	if (unlikely(error))
+		goto out;
 
 	mnt_warn_timestamp_expiry(mountpoint, mnt);
 
@@ -3731,10 +3726,12 @@ static int do_new_mount_fc(struct fs_context *fc, struct path *mountpoint,
 	if (!error) {
 		error = do_add_mount(real_mount(mnt), mp.mp,
 				     mountpoint, mnt_flags);
+		if (!error)
+			mnt = NULL;	// consumed on success
 		unlock_mount(&mp);
 	}
-	if (error < 0)
-		mntput(mnt);
+out:
+	mntput(mnt);
 	return error;
 }
 
@@ -3788,8 +3785,6 @@ static int do_new_mount(struct path *path, const char *fstype, int sb_flags,
 		err = parse_monolithic_mount_data(fc, data);
 	if (!err && !mount_capable(fc))
 		err = -EPERM;
-	if (!err)
-		err = vfs_get_tree(fc);
 	if (!err)
 		err = do_new_mount_fc(fc, path, mnt_flags);
 
Signature · 1 line
2.47.2
maintainer Reviewed-by on v2 18/63
#159 lore raw

Re: [PATCH v2 18/63] switch do_new_mount_fc() to fc_mount()

Quoted 13 lines · Al Viro

Prior to the call of do_new_mount_fc() the caller has just done successful vfs_get_tree(). Then do_new_mount_fc() does several checks on resulting superblock, and either does fc_drop_locked() and returns an error or proceeds to unlock the superblock and call vfs_create_mount().

The thing is, there's no reason to delay that unlock + vfs_create_mount() - the tests do not rely upon the state of ->s_umount and
fc_drop_locked()
put_fs_context()
is equivalent to

	unlock ->s_umount
	put_fs_context()
Quoted 6 lines

Doing vfs_create_mount() before the checks allows us to move vfs_get_tree() from caller to do_new_mount_fc() and collapse it with vfs_create_mount() into an fc_mount() call.

---

Quoted 10 lines
 fs/namespace.c | 29 ++++++++++++-----------------
 1 file changed, 12 insertions(+), 17 deletions(-)
fs/namespace.c +0 -0
index 0474b3a93dbf..9b575c9eee0b 100644
@@ -3705,25 +3705,20 @@ static bool mount_too_revealing(const struct super_block *sb, int *new_mnt_flags
 static int do_new_mount_fc(struct fs_context *fc, struct path *mountpoint,
 			   unsigned int mnt_flags)

fs/namespace.c static bool mount_too_revealing(const struct super_block *sb, int *new_mnt_flags · line 3714

Quoted 10 lines
fs/namespace.c +4 -2
 {
-	struct vfsmount *mnt;
 	struct pinned_mountpoint mp = {};
-	struct super_block *sb = fc->root->d_sb;
+	struct super_block *sb;
+	struct vfsmount *mnt = fc_mount(fc);
 	int error;
 
+	if (IS_ERR(mnt))
+		return PTR_ERR(mnt);

Fwiw, I find this pattern where the variable is assigned by function call at declaration time in the middle of other variables and then immediately further below check for the error to be rather ugly. I'd much rather just do:

+ struct vfsmount *mnt;
int error;

mnt = fc_mount(fc)
+ if (IS_ERR(mnt))
+ return PTR_ERR(mnt);

But anyway, I acknowledge the difference in taste here is really not that important.

author maintainer
#160 lore raw

PATCH v2 19/63 do_move_mount(): trim local variables

In reply to Al Viro

Both 'parent' and 'ns' are used at most once, no point precalculating those...

---
 fs/namespace.c | 12 ++++--------
 1 file changed, 4 insertions(+), 8 deletions(-)
fs/namespace.c +4 -8
index 9b575c9eee0b..ad9b5687ff15 100644
@@ -3564,10 +3564,8 @@ static inline bool may_use_mount(struct mount *mnt)
 static int do_move_mount(struct path *old_path,
 			 struct path *new_path, enum mnt_tree_flags_t flags)
 {
-	struct mnt_namespace *ns;
 	struct mount *p;
 	struct mount *old;
-	struct mount *parent;
 	struct pinned_mountpoint mp;
 	int err;
 	bool beneath = flags & MNT_TREE_BENEATH;
@@ -3578,8 +3576,6 @@ static int do_move_mount(struct path *old_path,
 
 	old = real_mount(old_path->mnt);
 	p = real_mount(new_path->mnt);
-	parent = old->mnt_parent;
-	ns = old->mnt_ns;
 
 	err = -EINVAL;
 
@@ -3588,12 +3584,12 @@ static int do_move_mount(struct path *old_path,
 		/* ... it should be detachable from parent */
 		if (!mnt_has_parent(old) || IS_MNT_LOCKED(old))
 			goto out;
+		/* ... which should not be shared */
+		if (IS_MNT_SHARED(old->mnt_parent))
+			goto out;
 		/* ... and the target should be in our namespace */
 		if (!check_mnt(p))
 			goto out;
-		/* parent of the source should not be shared */
-		if (IS_MNT_SHARED(parent))
-			goto out;
 	} else {
 		/*
 		 * otherwise the source must be the root of some anon namespace.
@@ -3605,7 +3601,7 @@ static int do_move_mount(struct path *old_path,
 		 * subsequent checks would've rejected that, but they lose
 		 * some corner cases if we check it early.
 		 */
-		if (ns == p->mnt_ns)
+		if (old->mnt_ns == p->mnt_ns)
 			goto out;
 		/*
 		 * Target should be either in our namespace or in an acceptable
Signature · 1 line
2.47.2
author maintainer
#161 lore raw

PATCH v2 20/63 do_move_mount(): deal with the checks on old_path early

In reply to Al Viro

1) checking that location we want to move does point to root of some mount can be done before anything else; that property is not going to change and having it already verified simplifies the analysis.

2) checking the type agreement between what we are trying to move and what we are trying to move it onto also belongs in the very beginning - do_lock_mount() might end up switching new_path to something that overmounts the original location, but... the same type agreement applies to overmounts, so we could just as well check against the original location.

3) since we know that old_path->dentry is the root of old_path->mnt, there's no point bothering with path_is_overmounted() in can_move_mount_beneath(); it's simply a check for the mount we are trying to move having non-NULL ->overmount. And with that, we can switch can_move_mount_beneath() to taking old instead of old_path, leaving no uses of old_path past the original checks.

---
 fs/namespace.c | 29 +++++++++++++----------------
 1 file changed, 13 insertions(+), 16 deletions(-)
fs/namespace.c +13 -16
index ad9b5687ff15..74c67ea1b5a8 100644
@@ -3433,7 +3433,7 @@ static bool mount_is_ancestor(const struct mount *p1, const struct mount *p2)
 
 /**
  * can_move_mount_beneath - check that we can mount beneath the top mount
- * @from: mount to mount beneath
+ * @mnt_from: mount we are trying to move
  * @to:   mount under which to mount
  * @mp:   mountpoint of @to
  *
@@ -3443,7 +3443,7 @@ static bool mount_is_ancestor(const struct mount *p1, const struct mount *p2)
  *   root or the rootfs of the namespace.
  * - Make sure that the caller can unmount the topmost mount ensuring
  *   that the caller could reveal the underlying mountpoint.
- * - Ensure that nothing has been mounted on top of @from before we
+ * - Ensure that nothing has been mounted on top of @mnt_from before we
  *   grabbed @namespace_sem to avoid creating pointless shadow mounts.
  * - Prevent mounting beneath a mount if the propagation relationship
  *   between the source mount, parent mount, and top mount would lead to
@@ -3452,12 +3452,11 @@ static bool mount_is_ancestor(const struct mount *p1, const struct mount *p2)
  * Context: This function expects namespace_lock() to be held.
  * Return: On success 0, and on error a negative error code is returned.
  */
-static int can_move_mount_beneath(const struct path *from,
+static int can_move_mount_beneath(struct mount *mnt_from,
 				  const struct path *to,
 				  const struct mountpoint *mp)
 {
-	struct mount *mnt_from = real_mount(from->mnt),
-		     *mnt_to = real_mount(to->mnt),
+	struct mount *mnt_to = real_mount(to->mnt),
 		     *parent_mnt_to = mnt_to->mnt_parent;
 
 	if (!mnt_has_parent(mnt_to))
@@ -3470,7 +3469,7 @@ static int can_move_mount_beneath(const struct path *from,
 		return -EINVAL;
 
 	/* Avoid creating shadow mounts during mount propagation. */
-	if (path_overmounted(from))
+	if (mnt_from->overmount)
 		return -EINVAL;
 
 	/*
@@ -3565,16 +3564,21 @@ static int do_move_mount(struct path *old_path,
 			 struct path *new_path, enum mnt_tree_flags_t flags)
 {
 	struct mount *p;
-	struct mount *old;
+	struct mount *old = real_mount(old_path->mnt);
 	struct pinned_mountpoint mp;
 	int err;
 	bool beneath = flags & MNT_TREE_BENEATH;
 
+	if (!path_mounted(old_path))
+		return -EINVAL;
+
+	if (d_is_dir(new_path->dentry) != d_is_dir(old_path->dentry))
+		return -EINVAL;
+
 	err = do_lock_mount(new_path, &mp, beneath);
 	if (err)
 		return err;
 
-	old = real_mount(old_path->mnt);
 	p = real_mount(new_path->mnt);
 
 	err = -EINVAL;
@@ -3611,15 +3615,8 @@ static int do_move_mount(struct path *old_path,
 			goto out;
 	}
 
-	if (!path_mounted(old_path))
-		goto out;
-
-	if (d_is_dir(new_path->dentry) !=
-	    d_is_dir(old_path->dentry))
-		goto out;
-
 	if (beneath) {
-		err = can_move_mount_beneath(old_path, new_path, mp.mp);
+		err = can_move_mount_beneath(old, new_path, mp.mp);
 		if (err)
 			goto out;
 
Signature · 1 line
2.47.2
author maintainer
#162 lore raw

PATCH v2 21/63 move_mount(2): take sanity checks in 'beneath' case into do_lock_mount()

In reply to Al Viro

We want to mount beneath the given location. For that operation to make sense, location must be the root of some mount that has something under it. Currently we let it proceed if those requirements are not met, with rather meaningless results, and have that bogosity caught further down the road; let's fail early instead - do_lock_mount() doesn't make sense unless those conditions hold, and checking them there makes things simpler.

---
 fs/namespace.c | 15 +++++++--------
 1 file changed, 7 insertions(+), 8 deletions(-)
fs/namespace.c +7 -8
index 74c67ea1b5a8..86c6dd432b13 100644
@@ -2768,12 +2768,19 @@ static int do_lock_mount(struct path *path, struct pinned_mountpoint *pinned, bo
 	struct path under = {};
 	int err = -ENOENT;
 
+	if (unlikely(beneath) && !path_mounted(path))
+		return -EINVAL;
+
 	for (;;) {
 		struct mount *m = real_mount(mnt);
 
 		if (beneath) {
 			path_put(&under);
 			read_seqlock_excl(&mount_lock);
+			if (unlikely(!mnt_has_parent(m))) {
+				read_sequnlock_excl(&mount_lock);
+				return -EINVAL;
+			}
 			under.mnt = mntget(&m->mnt_parent->mnt);
 			under.dentry = dget(m->mnt_mountpoint);
 			read_sequnlock_excl(&mount_lock);
@@ -3437,8 +3444,6 @@ static bool mount_is_ancestor(const struct mount *p1, const struct mount *p2)
  * @to:   mount under which to mount
  * @mp:   mountpoint of @to
  *
- * - Make sure that @to->dentry is actually the root of a mount under
- *   which we can mount another mount.
  * - Make sure that nothing can be mounted beneath the caller's current
  *   root or the rootfs of the namespace.
  * - Make sure that the caller can unmount the topmost mount ensuring
@@ -3459,12 +3464,6 @@ static int can_move_mount_beneath(struct mount *mnt_from,
 	struct mount *mnt_to = real_mount(to->mnt),
 		     *parent_mnt_to = mnt_to->mnt_parent;
 
-	if (!mnt_has_parent(mnt_to))
-		return -EINVAL;
-
-	if (!path_mounted(to))
-		return -EINVAL;
-
 	if (IS_MNT_LOCKED(mnt_to))
 		return -EINVAL;
 
Signature · 1 line
2.47.2
author maintainer
#163 lore raw

PATCH v2 22/63 finish_automount(): simplify the ELOOP check

In reply to Al Viro

It's enough to check that dentries match; if path->dentry is equal to m->mnt_root, superblocks will match as well.

---
 fs/namespace.c | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)
fs/namespace.c +1 -2
index 86c6dd432b13..bdb33270ac6e 100644
@@ -3798,8 +3798,7 @@ int finish_automount(struct vfsmount *m, const struct path *path)
 
 	mnt = real_mount(m);
 
-	if (m->mnt_sb == path->mnt->mnt_sb &&
-	    m->mnt_root == dentry) {
+	if (m->mnt_root == path->dentry) {
 		err = -ELOOP;
 		goto discard;
 	}
Signature · 1 line
2.47.2
author maintainer
#164 lore raw

PATCH v2 23/63 do_loopback(): use __free(path_put) to deal with old_path

In reply to Al Viro

preparations for making unlock_mount() a __cleanup(); can't have path_put() inside mount_lock scope.

---
 fs/namespace.c | 9 +++------
 1 file changed, 3 insertions(+), 6 deletions(-)
fs/namespace.c +3 -6
index bdb33270ac6e..245cf2d19a6b 100644
@@ -3014,7 +3014,7 @@ static struct mount *__do_loopback(struct path *old_path, int recurse)
 static int do_loopback(struct path *path, const char *old_name,
 				int recurse)
 {
-	struct path old_path;
+	struct path old_path __free(path_put) = {};
 	struct mount *mnt = NULL, *parent;
 	struct pinned_mountpoint mp = {};
 	int err;
@@ -3024,13 +3024,12 @@ static int do_loopback(struct path *path, const char *old_name,
 	if (err)
 		return err;
 
-	err = -EINVAL;
 	if (mnt_ns_loop(old_path.dentry))
-		goto out;
+		return -EINVAL;
 
 	err = lock_mount(path, &mp);
 	if (err)
-		goto out;
+		return err;
 
 	parent = real_mount(path->mnt);
 	if (!check_mnt(parent))
@@ -3050,8 +3049,6 @@ static int do_loopback(struct path *path, const char *old_name,
 	}
 out2:
 	unlock_mount(&mp);
-out:
-	path_put(&old_path);
 	return err;
 }
 
Signature · 1 line
2.47.2
author maintainer
#165 lore raw

PATCH v2 24/63 pivot_root(2): use __free() to deal with struct path in it

In reply to Al Viro

preparations for making unlock_mount() a __cleanup(); can't have path_put() inside mount_lock scope.

---
 fs/namespace.c | 19 +++++++------------
 1 file changed, 7 insertions(+), 12 deletions(-)
fs/namespace.c +7 -12
index 245cf2d19a6b..90b62ee882da 100644
@@ -4622,7 +4622,9 @@ EXPORT_SYMBOL(path_is_under);
 SYSCALL_DEFINE2(pivot_root, const char __user *, new_root,
 		const char __user *, put_old)
 {
-	struct path new, old, root;
+	struct path new __free(path_put) = {};
+	struct path old __free(path_put) = {};
+	struct path root __free(path_put) = {};
 	struct mount *new_mnt, *root_mnt, *old_mnt, *root_parent, *ex_parent;
 	struct pinned_mountpoint old_mp = {};
 	int error;
@@ -4633,21 +4635,21 @@ SYSCALL_DEFINE2(pivot_root, const char __user *, new_root,
 	error = user_path_at(AT_FDCWD, new_root,
 			     LOOKUP_FOLLOW | LOOKUP_DIRECTORY, &new);
 	if (error)
-		goto out0;
+		return error;
 
 	error = user_path_at(AT_FDCWD, put_old,
 			     LOOKUP_FOLLOW | LOOKUP_DIRECTORY, &old);
 	if (error)
-		goto out1;
+		return error;
 
 	error = security_sb_pivotroot(&old, &new);
 	if (error)
-		goto out2;
+		return error;
 
 	get_fs_root(current->fs, &root);
 	error = lock_mount(&old, &old_mp);
 	if (error)
-		goto out3;
+		return error;
 
 	error = -EINVAL;
 	new_mnt = real_mount(new.mnt);
@@ -4705,13 +4707,6 @@ SYSCALL_DEFINE2(pivot_root, const char __user *, new_root,
 	error = 0;
 out4:
 	unlock_mount(&old_mp);
-out3:
-	path_put(&root);
-out2:
-	path_put(&old);
-out1:
-	path_put(&new);
-out0:
 	return error;
 }
 
Signature · 1 line
2.47.2
author maintainer
#166 lore raw

PATCH v2 25/63 finish_automount(): take the lock_mount() analogue into a helper

In reply to Al Viro

finish_automount() can't use lock_mount() - it treats finding something already mounted as "quitely drop our mount and return 0", not as "mount on top of whatever mounted there". It's been open-coded; let's take it into a helper similar to lock_mount(). "something's already mounted" => -EBUSY, finish_automount() needs to distinguish it from the normal case and it can't happen in other failure cases.

---
 fs/namespace.c | 42 +++++++++++++++++++++++++-----------------
 1 file changed, 25 insertions(+), 17 deletions(-)
fs/namespace.c +25 -17
index 90b62ee882da..6251ee15f5f6 100644
@@ -3781,9 +3781,29 @@ static int do_new_mount(struct path *path, const char *fstype, int sb_flags,
 	return err;
 }
 
-int finish_automount(struct vfsmount *m, const struct path *path)
+static int lock_mount_exact(const struct path *path,
+			    struct pinned_mountpoint *mp)
 {
 	struct dentry *dentry = path->dentry;
+	int err;
+
+	inode_lock(dentry->d_inode);
+	namespace_lock();
+	if (unlikely(cant_mount(dentry)))
+		err = -ENOENT;
+	else if (path_overmounted(path))
+		err = -EBUSY;
+	else
+		err = get_mountpoint(dentry, mp);
+	if (unlikely(err)) {
+		namespace_unlock();
+		inode_unlock(dentry->d_inode);
+	}
+	return err;
+}
+
+int finish_automount(struct vfsmount *m, const struct path *path)
+{
 	struct pinned_mountpoint mp = {};
 	struct mount *mnt;
 	int err;
@@ -3805,20 +3825,11 @@ int finish_automount(struct vfsmount *m, const struct path *path)
 	 * that overmounts our mountpoint to be means "quitely drop what we've
 	 * got", not "try to mount it on top".
 	 */
-	inode_lock(dentry->d_inode);
-	namespace_lock();
-	if (unlikely(cant_mount(dentry))) {
-		err = -ENOENT;
-		goto discard_locked;
-	}
-	if (path_overmounted(path)) {
-		err = 0;
-		goto discard_locked;
+	err = lock_mount_exact(path, &mp);
+	if (unlikely(err)) {
+		mntput(m);
+		return err == -EBUSY ? 0 : err;
 	}
-	err = get_mountpoint(dentry, &mp);
-	if (err)
-		goto discard_locked;
-
 	err = do_add_mount(mnt, mp.mp, path,
 			   path->mnt->mnt_flags | MNT_SHRINKABLE);
 	unlock_mount(&mp);
@@ -3826,9 +3837,6 @@ int finish_automount(struct vfsmount *m, const struct path *path)
 		goto discard;
 	return 0;
 
-discard_locked:
-	namespace_unlock();
-	inode_unlock(dentry->d_inode);
 discard:
 	mntput(m);
 	return err;
Signature · 1 line
2.47.2
author maintainer
#167 lore raw

PATCH v2 26/63 do_new_mount_rc(): use __free() to deal with dropping mnt on failure

In reply to Al Viro

do_add_mount() consumes vfsmount on success; just follow it with conditional retain_and_null_ptr() on success and we can switch to __free() for mnt and be done with that - unlock_mount() is in the very end.

---
 fs/namespace.c | 13 ++++++-------
 1 file changed, 6 insertions(+), 7 deletions(-)
fs/namespace.c +6 -7
index 6251ee15f5f6..3551e51461a2 100644
@@ -3696,7 +3696,7 @@ static int do_new_mount_fc(struct fs_context *fc, struct path *mountpoint,
 {
 	struct pinned_mountpoint mp = {};
 	struct super_block *sb;
-	struct vfsmount *mnt = fc_mount(fc);
+	struct vfsmount *mnt __free(mntput) = fc_mount(fc);
 	int error;
 
 	if (IS_ERR(mnt))
@@ -3704,10 +3704,11 @@ static int do_new_mount_fc(struct fs_context *fc, struct path *mountpoint,
 
 	sb = fc->root->d_sb;
 	error = security_sb_kern_mount(sb);
-	if (!error && mount_too_revealing(sb, &mnt_flags))
-		error = -EPERM;
 	if (unlikely(error))
-		goto out;
+		return error;
+
+	if (unlikely(mount_too_revealing(sb, &mnt_flags)))
+		return -EPERM;
 
 	mnt_warn_timestamp_expiry(mountpoint, mnt);
 
@@ -3716,11 +3717,9 @@ static int do_new_mount_fc(struct fs_context *fc, struct path *mountpoint,
 		error = do_add_mount(real_mount(mnt), mp.mp,
 				     mountpoint, mnt_flags);
 		if (!error)
-			mnt = NULL;	// consumed on success
+			retain_and_null_ptr(mnt); // consumed on success
 		unlock_mount(&mp);
 	}
-out:
-	mntput(mnt);
 	return error;
 }
 
Signature · 1 line
2.47.2
maintainer Reviewed-by on v2 26/63
#168 lore raw

Re: [PATCH v2 26/63] do_new_mount_rc(): use __free() to deal with dropping mnt on failure

Quoted 7 lines · Al Viro

do_add_mount() consumes vfsmount on success; just follow it with conditional retain_and_null_ptr() on success and we can switch to __free() for mnt and be done with that - unlock_mount() is in the very end.

---

author maintainer
#169 lore raw

PATCH v2 27/63 finish_automount(): use __free() to deal with dropping mnt on failure

In reply to Al Viro

same story as with do_new_mount_fc().

---
 fs/namespace.c | 22 ++++++++--------------
 1 file changed, 8 insertions(+), 14 deletions(-)
fs/namespace.c +8 -14
index 3551e51461a2..779cfed04291 100644
@@ -3801,8 +3801,9 @@ static int lock_mount_exact(const struct path *path,
 	return err;
 }
 
-int finish_automount(struct vfsmount *m, const struct path *path)
+int finish_automount(struct vfsmount *__m, const struct path *path)
 {
+	struct vfsmount *m __free(mntput) = __m;
 	struct pinned_mountpoint mp = {};
 	struct mount *mnt;
 	int err;
@@ -3814,10 +3815,8 @@ int finish_automount(struct vfsmount *m, const struct path *path)
 
 	mnt = real_mount(m);
 
-	if (m->mnt_root == path->dentry) {
-		err = -ELOOP;
-		goto discard;
-	}
+	if (m->mnt_root == path->dentry)
+		return -ELOOP;
 
 	/*
 	 * we don't want to use lock_mount() - in this case finding something
@@ -3825,19 +3824,14 @@ int finish_automount(struct vfsmount *m, const struct path *path)
 	 * got", not "try to mount it on top".
 	 */
 	err = lock_mount_exact(path, &mp);
-	if (unlikely(err)) {
-		mntput(m);
+	if (unlikely(err))
 		return err == -EBUSY ? 0 : err;
-	}
+
 	err = do_add_mount(mnt, mp.mp, path,
 			   path->mnt->mnt_flags | MNT_SHRINKABLE);
+	if (likely(!err))
+		retain_and_null_ptr(m);
 	unlock_mount(&mp);
-	if (unlikely(err))
-		goto discard;
-	return 0;
-
-discard:
-	mntput(m);
 	return err;
 }
 
Signature · 1 line
2.47.2
author maintainer
#170 lore raw

PATCH v2 28/63 change calling conventions for lock_mount() et.al.

In reply to Al Viro

1) pinned_mountpoint gets a new member - struct mount *parent. Set only if we locked the sucker; ERR_PTR() - on failed attempt.

2) do_lock_mount() et.al. return void and set ->parent to
* on success with !beneath - mount corresponding to path->mnt
* on success with beneath - the parent of mount corresponding
to path->mnt
* in case of error - ERR_PTR(-E...).
IOW, we get the mount we will be actually mounting upon or ERR_PTR().

3) we can't use CLASS, since the pinned_mountpoint is placed on hlist during initialization, so we define local macros:
LOCK_MOUNT(mp, path) LOCK_MOUNT_MAYBE_BENEATH(mp, path, beneath) LOCK_MOUNT_EXACT(mp, path)
All of them declare and initialize struct pinned_mountpoint mp, with unlock_mount done via __cleanup().

Users converted.

---
 fs/namespace.c | 219 ++++++++++++++++++++++++-------------------------
 1 file changed, 108 insertions(+), 111 deletions(-)
fs/namespace.c +108 -111
index 779cfed04291..952e66bdb9bb 100644
@@ -919,6 +919,7 @@ bool __is_local_mountpoint(const struct dentry *dentry)
 struct pinned_mountpoint {
 	struct hlist_node node;
 	struct mountpoint *mp;
+	struct mount *parent;
 };
 
 static bool lookup_mountpoint(struct dentry *dentry, struct pinned_mountpoint *m)
@@ -2728,48 +2729,47 @@ static int attach_recursive_mnt(struct mount *source_mnt,
 }
 
 /**
- * do_lock_mount - lock mount and mountpoint
- * @path:    target path
- * @beneath: whether the intention is to mount beneath @path
+ * do_lock_mount - acquire environment for mounting
+ * @path:	target path
+ * @res:	context to set up
+ * @beneath:	whether the intention is to mount beneath @path
  *
- * Follow the mount stack on @path until the top mount @mnt is found. If
- * the initial @path->{mnt,dentry} is a mountpoint lookup the first
- * mount stacked on top of it. Then simply follow @{mnt,mnt->mnt_root}
- * until nothing is stacked on top of it anymore.
+ * To mount something at given location, we need
+ *	namespace_sem locked exclusive
+ *	inode of dentry we are mounting on locked exclusive
+ *	struct mountpoint for that dentry
+ *	struct mount we are mounting on
  *
- * Acquire the inode_lock() on the top mount's ->mnt_root to protect
- * against concurrent removal of the new mountpoint from another mount
- * namespace.
+ * Results are stored in caller-supplied context (pinned_mountpoint);
+ * on success we have res->parent and res->mp pointing to parent and
+ * mountpoint respectively and res->node inserted into the ->m_list
+ * of the mountpoint, making sure the mountpoint won't disappear.
+ * On failure we have res->parent set to ERR_PTR(-E...), res->mp
+ * left NULL, res->node - empty.
+ * In case of success do_lock_mount returns with locks acquired (in
+ * proper order - inode lock nests outside of namespace_sem).
  *
- * If @beneath is requested, acquire inode_lock() on @mnt's mountpoint
- * @mp on @mnt->mnt_parent must be acquired. This protects against a
- * concurrent unlink of @mp->mnt_dentry from another mount namespace
- * where @mnt doesn't have a child mount mounted @mp. A concurrent
- * removal of @mnt->mnt_root doesn't matter as nothing will be mounted
- * on top of it for @beneath.
+ * Request to mount on overmounted location is treated as "mount on
+ * top of whatever's overmounting it"; request to mount beneath
+ * a location - "mount immediately beneath the topmost mount at that
+ * place".
  *
- * In addition, @beneath needs to make sure that @mnt hasn't been
- * unmounted or moved from its current mountpoint in between dropping
- * @mount_lock and acquiring @namespace_sem. For the !@beneath case @mnt
- * being unmounted would be detected later by e.g., calling
- * check_mnt(mnt) in the function it's called from. For the @beneath
- * case however, it's useful to detect it directly in do_lock_mount().
- * If @mnt hasn't been unmounted then @mnt->mnt_mountpoint still points
- * to @mnt->mnt_mp->m_dentry. But if @mnt has been unmounted it will
- * point to @mnt->mnt_root and @mnt->mnt_mp will be NULL.
- *
- * Return: Either the target mountpoint on the top mount or the top
- *         mount's mountpoint.
+ * In all cases the location must not have been unmounted and the
+ * chosen mountpoint must be allowed to be mounted on.  For "beneath"
+ * case we also require the location to be at the root of a mount
+ * that has a parent (i.e. is not a root of some namespace).
  */
-static int do_lock_mount(struct path *path, struct pinned_mountpoint *pinned, bool beneath)
+static void do_lock_mount(struct path *path, struct pinned_mountpoint *res, bool beneath)
 {
 	struct vfsmount *mnt = path->mnt;
 	struct dentry *dentry;
 	struct path under = {};
 	int err = -ENOENT;
 
-	if (unlikely(beneath) && !path_mounted(path))
-		return -EINVAL;
+	if (unlikely(beneath) && !path_mounted(path)) {
+		res->parent = ERR_PTR(-EINVAL);
+		return;
+	}
 
 	for (;;) {
 		struct mount *m = real_mount(mnt);
@@ -2779,7 +2779,8 @@ static int do_lock_mount(struct path *path, struct pinned_mountpoint *pinned, bo
 			read_seqlock_excl(&mount_lock);
 			if (unlikely(!mnt_has_parent(m))) {
 				read_sequnlock_excl(&mount_lock);
-				return -EINVAL;
+				res->parent = ERR_PTR(-EINVAL);
+				return;
 			}
 			under.mnt = mntget(&m->mnt_parent->mnt);
 			under.dentry = dget(m->mnt_mountpoint);
@@ -2811,7 +2812,7 @@ static int do_lock_mount(struct path *path, struct pinned_mountpoint *pinned, bo
 			path->dentry = dget(mnt->mnt_root);
 			continue;	// got overmounted
 		}
-		err = get_mountpoint(dentry, pinned);
+		err = get_mountpoint(dentry, res);
 		if (err)
 			break;
 		if (beneath) {
@@ -2822,22 +2823,25 @@ static int do_lock_mount(struct path *path, struct pinned_mountpoint *pinned, bo
 			 * we are not dropping the final references here).
 			 */
 			path_put(&under);
+			res->parent = real_mount(path->mnt)->mnt_parent;
+			return;
 		}
-		return 0;
+		res->parent = real_mount(path->mnt);
+		return;
 	}
 	namespace_unlock();
 	inode_unlock(dentry->d_inode);
 	if (beneath)
 		path_put(&under);
-	return err;
+	res->parent = ERR_PTR(err);
 }
 
-static inline int lock_mount(struct path *path, struct pinned_mountpoint *m)
+static inline void lock_mount(struct path *path, struct pinned_mountpoint *m)
 {
-	return do_lock_mount(path, m, false);
+	do_lock_mount(path, m, false);
 }
 
-static void unlock_mount(struct pinned_mountpoint *m)
+static void __unlock_mount(struct pinned_mountpoint *m)
 {
 	inode_unlock(m->mp->m_dentry->d_inode);
 	read_seqlock_excl(&mount_lock);
@@ -2846,6 +2850,20 @@ static void unlock_mount(struct pinned_mountpoint *m)
 	namespace_unlock();
 }
 
+static inline void unlock_mount(struct pinned_mountpoint *m)
+{
+	if (!IS_ERR(m->parent))
+		__unlock_mount(m);
+}
+
+#define LOCK_MOUNT_MAYBE_BENEATH(mp, path, beneath) \
+	struct pinned_mountpoint mp __cleanup(unlock_mount) = {}; \
+	do_lock_mount((path), &mp, (beneath))
+#define LOCK_MOUNT(mp, path) LOCK_MOUNT_MAYBE_BENEATH(mp, (path), false)
+#define LOCK_MOUNT_EXACT(mp, path) \
+	struct pinned_mountpoint mp __cleanup(unlock_mount) = {}; \
+	lock_mount_exact((path), &mp)
+
 static int graft_tree(struct mount *mnt, struct mount *p, struct mountpoint *mp)
 {
 	if (mnt->mnt.mnt_sb->s_flags & SB_NOUSER)
@@ -3015,8 +3033,7 @@ static int do_loopback(struct path *path, const char *old_name,
 				int recurse)
 {
 	struct path old_path __free(path_put) = {};
-	struct mount *mnt = NULL, *parent;
-	struct pinned_mountpoint mp = {};
+	struct mount *mnt = NULL;
 	int err;
 	if (!old_name || !*old_name)
 		return -EINVAL;
@@ -3027,28 +3044,23 @@ static int do_loopback(struct path *path, const char *old_name,
 	if (mnt_ns_loop(old_path.dentry))
 		return -EINVAL;
 
-	err = lock_mount(path, &mp);
-	if (err)
-		return err;
+	LOCK_MOUNT(mp, path);
+	if (IS_ERR(mp.parent))
+		return PTR_ERR(mp.parent);
 
-	parent = real_mount(path->mnt);
-	if (!check_mnt(parent))
-		goto out2;
+	if (!check_mnt(mp.parent))
+		return -EINVAL;
 
 	mnt = __do_loopback(&old_path, recurse);
-	if (IS_ERR(mnt)) {
-		err = PTR_ERR(mnt);
-		goto out2;
-	}
+	if (IS_ERR(mnt))
+		return PTR_ERR(mnt);
 
-	err = graft_tree(mnt, parent, mp.mp);
+	err = graft_tree(mnt, mp.parent, mp.mp);
 	if (err) {
 		lock_mount_hash();
 		umount_tree(mnt, UMOUNT_SYNC);
 		unlock_mount_hash();
 	}
-out2:
-	unlock_mount(&mp);
 	return err;
 }
 
@@ -3561,7 +3573,6 @@ static int do_move_mount(struct path *old_path,
 {
 	struct mount *p;
 	struct mount *old = real_mount(old_path->mnt);
-	struct pinned_mountpoint mp;
 	int err;
 	bool beneath = flags & MNT_TREE_BENEATH;
 
@@ -3571,52 +3582,49 @@ static int do_move_mount(struct path *old_path,
 	if (d_is_dir(new_path->dentry) != d_is_dir(old_path->dentry))
 		return -EINVAL;
 
-	err = do_lock_mount(new_path, &mp, beneath);
-	if (err)
-		return err;
+	LOCK_MOUNT_MAYBE_BENEATH(mp, new_path, beneath);
+	if (IS_ERR(mp.parent))
+		return PTR_ERR(mp.parent);
 
 	p = real_mount(new_path->mnt);
 
-	err = -EINVAL;
-
 	if (check_mnt(old)) {
 		/* if the source is in our namespace... */
 		/* ... it should be detachable from parent */
 		if (!mnt_has_parent(old) || IS_MNT_LOCKED(old))
-			goto out;
+			return -EINVAL;
 		/* ... which should not be shared */
 		if (IS_MNT_SHARED(old->mnt_parent))
-			goto out;
+			return -EINVAL;
 		/* ... and the target should be in our namespace */
 		if (!check_mnt(p))
-			goto out;
+			return -EINVAL;
 	} else {
 		/*
 		 * otherwise the source must be the root of some anon namespace.
 		 */
 		if (!anon_ns_root(old))
-			goto out;
+			return -EINVAL;
 		/*
 		 * Bail out early if the target is within the same namespace -
 		 * subsequent checks would've rejected that, but they lose
 		 * some corner cases if we check it early.
 		 */
 		if (old->mnt_ns == p->mnt_ns)
-			goto out;
+			return -EINVAL;
 		/*
 		 * Target should be either in our namespace or in an acceptable
 		 * anon namespace, sensu check_anonymous_mnt().
 		 */
 		if (!may_use_mount(p))
-			goto out;
+			return -EINVAL;
 	}
 
 	if (beneath) {
 		err = can_move_mount_beneath(old, new_path, mp.mp);
 		if (err)
-			goto out;
+			return err;
 
-		err = -EINVAL;
 		p = p->mnt_parent;
 	}
 
@@ -3625,17 +3633,13 @@ static int do_move_mount(struct path *old_path,
 	 * mount which is shared.
 	 */
 	if (IS_MNT_SHARED(p) && tree_contains_unbindable(old))
-		goto out;
-	err = -ELOOP;
+		return -EINVAL;
 	if (!check_for_nsfs_mounts(old))
-		goto out;
+		return -ELOOP;
 	if (mount_is_ancestor(old, p))
-		goto out;
+		return -ELOOP;
 
-	err = attach_recursive_mnt(old, p, mp.mp);
-out:
-	unlock_mount(&mp);
-	return err;
+	return attach_recursive_mnt(old, p, mp.mp);
 }
 
 static int do_move_mount_old(struct path *path, const char *old_name)
@@ -3694,7 +3698,6 @@ static bool mount_too_revealing(const struct super_block *sb, int *new_mnt_flags
 static int do_new_mount_fc(struct fs_context *fc, struct path *mountpoint,
 			   unsigned int mnt_flags)
 {
-	struct pinned_mountpoint mp = {};
 	struct super_block *sb;
 	struct vfsmount *mnt __free(mntput) = fc_mount(fc);
 	int error;
@@ -3712,13 +3715,14 @@ static int do_new_mount_fc(struct fs_context *fc, struct path *mountpoint,
 
 	mnt_warn_timestamp_expiry(mountpoint, mnt);
 
-	error = lock_mount(mountpoint, &mp);
-	if (!error) {
+	LOCK_MOUNT(mp, mountpoint);
+	if (IS_ERR(mp.parent)) {
+		return PTR_ERR(mp.parent);
+	} else {
 		error = do_add_mount(real_mount(mnt), mp.mp,
 				     mountpoint, mnt_flags);
 		if (!error)
 			retain_and_null_ptr(mnt); // consumed on success
-		unlock_mount(&mp);
 	}
 	return error;
 }
@@ -3780,8 +3784,8 @@ static int do_new_mount(struct path *path, const char *fstype, int sb_flags,
 	return err;
 }
 
-static int lock_mount_exact(const struct path *path,
-			    struct pinned_mountpoint *mp)
+static void lock_mount_exact(const struct path *path,
+			     struct pinned_mountpoint *mp)
 {
 	struct dentry *dentry = path->dentry;
 	int err;
@@ -3797,14 +3801,15 @@ static int lock_mount_exact(const struct path *path,
 	if (unlikely(err)) {
 		namespace_unlock();
 		inode_unlock(dentry->d_inode);
+		mp->parent = ERR_PTR(err);
+	} else {
+		mp->parent = real_mount(path->mnt);
 	}
-	return err;
 }
 
 int finish_automount(struct vfsmount *__m, const struct path *path)
 {
 	struct vfsmount *m __free(mntput) = __m;
-	struct pinned_mountpoint mp = {};
 	struct mount *mnt;
 	int err;
 
@@ -3823,15 +3828,14 @@ int finish_automount(struct vfsmount *__m, const struct path *path)
 	 * that overmounts our mountpoint to be means "quitely drop what we've
 	 * got", not "try to mount it on top".
 	 */
-	err = lock_mount_exact(path, &mp);
-	if (unlikely(err))
-		return err == -EBUSY ? 0 : err;
+	LOCK_MOUNT_EXACT(mp, path);
+	if (IS_ERR(mp.parent))
+		return mp.parent == ERR_PTR(-EBUSY) ? 0 : PTR_ERR(mp.parent);
 
 	err = do_add_mount(mnt, mp.mp, path,
 			   path->mnt->mnt_flags | MNT_SHRINKABLE);
 	if (likely(!err))
 		retain_and_null_ptr(m);
-	unlock_mount(&mp);
 	return err;
 }
 
@@ -4627,7 +4631,6 @@ SYSCALL_DEFINE2(pivot_root, const char __user *, new_root,
 	struct path old __free(path_put) = {};
 	struct path root __free(path_put) = {};
 	struct mount *new_mnt, *root_mnt, *old_mnt, *root_parent, *ex_parent;
-	struct pinned_mountpoint old_mp = {};
 	int error;
 
 	if (!may_mount())
@@ -4648,45 +4651,42 @@ SYSCALL_DEFINE2(pivot_root, const char __user *, new_root,
 		return error;
 
 	get_fs_root(current->fs, &root);
-	error = lock_mount(&old, &old_mp);
-	if (error)
-		return error;
 
-	error = -EINVAL;
+	LOCK_MOUNT(old_mp, &old);
+	old_mnt = old_mp.parent;
+	if (IS_ERR(old_mnt))
+		return PTR_ERR(old_mnt);
+
 	new_mnt = real_mount(new.mnt);
 	root_mnt = real_mount(root.mnt);
-	old_mnt = real_mount(old.mnt);
 	ex_parent = new_mnt->mnt_parent;
 	root_parent = root_mnt->mnt_parent;
 	if (IS_MNT_SHARED(old_mnt) ||
 		IS_MNT_SHARED(ex_parent) ||
 		IS_MNT_SHARED(root_parent))
-		goto out4;
+		return -EINVAL;
 	if (!check_mnt(root_mnt) || !check_mnt(new_mnt))
-		goto out4;
+		return -EINVAL;
 	if (new_mnt->mnt.mnt_flags & MNT_LOCKED)
-		goto out4;
-	error = -ENOENT;
+		return -EINVAL;
 	if (d_unlinked(new.dentry))
-		goto out4;
-	error = -EBUSY;
+		return -ENOENT;
 	if (new_mnt == root_mnt || old_mnt == root_mnt)
-		goto out4; /* loop, on the same file system  */
-	error = -EINVAL;
+		return -EBUSY; /* loop, on the same file system  */
 	if (!path_mounted(&root))
-		goto out4; /* not a mountpoint */
+		return -EINVAL; /* not a mountpoint */
 	if (!mnt_has_parent(root_mnt))
-		goto out4; /* absolute root */
+		return -EINVAL; /* absolute root */
 	if (!path_mounted(&new))
-		goto out4; /* not a mountpoint */
+		return -EINVAL; /* not a mountpoint */
 	if (!mnt_has_parent(new_mnt))
-		goto out4; /* absolute root */
+		return -EINVAL; /* absolute root */
 	/* make sure we can reach put_old from new_root */
 	if (!is_path_reachable(old_mnt, old.dentry, &new))
-		goto out4;
+		return -EINVAL;
 	/* make certain new is below the root */
 	if (!is_path_reachable(new_mnt, new.dentry, &root))
-		goto out4;
+		return -EINVAL;
 	lock_mount_hash();
 	umount_mnt(new_mnt);
 	if (root_mnt->mnt.mnt_flags & MNT_LOCKED) {
@@ -4705,10 +4705,7 @@ SYSCALL_DEFINE2(pivot_root, const char __user *, new_root,
 	mnt_notify_add(root_mnt);
 	mnt_notify_add(new_mnt);
 	chroot_fs_refs(&root, &new);
-	error = 0;
-out4:
-	unlock_mount(&old_mp);
-	return error;
+	return 0;
 }
 
 static unsigned int recalc_flags(struct mount_kattr *kattr, struct mount *mnt)
Signature · 1 line
2.47.2
maintainer Reviewed-by on v2 28/63
#171 lore raw

Re: [PATCH v2 28/63] change calling conventions for lock_mount() et.al.

Quoted 18 lines · Al Viro

1) pinned_mountpoint gets a new member - struct mount *parent. Set only if we locked the sucker; ERR_PTR() - on failed attempt.

2) do_lock_mount() et.al. return void and set ->parent to
* on success with !beneath - mount corresponding to path->mnt
* on success with beneath - the parent of mount corresponding
to path->mnt
* in case of error - ERR_PTR(-E...).
IOW, we get the mount we will be actually mounting upon or ERR_PTR().

3) we can't use CLASS, since the pinned_mountpoint is placed on hlist during initialization, so we define local macros:
LOCK_MOUNT(mp, path) LOCK_MOUNT_MAYBE_BENEATH(mp, path, beneath) LOCK_MOUNT_EXACT(mp, path)
All of them declare and initialize struct pinned_mountpoint mp, with unlock_mount done via __cleanup().

Quoted 4 lines

Users converted.

---

This is nice! Thanks!

author maintainer
#172 lore raw

PATCH v2 29/63 do_move_mount(): use the parent mount returned by do_lock_mount()

In reply to Al Viro

After successful do_lock_mount() call, mp.parent is set to either real_mount(path->mnt) (for !beneath case) or to ->mnt_parent of that (for beneath). p is set to real_mount(path->mnt) and after several uses it's made equal to mp.parent. All uses prior to that care only about p->mnt_ns and since p->mnt_ns == parent->mnt_ns, we might as well use mp.parent all along.

---
 fs/namespace.c | 17 ++++++-----------
 1 file changed, 6 insertions(+), 11 deletions(-)
fs/namespace.c +6 -11
index 952e66bdb9bb..d57e727962da 100644
@@ -3571,7 +3571,6 @@ static inline bool may_use_mount(struct mount *mnt)
 static int do_move_mount(struct path *old_path,
 			 struct path *new_path, enum mnt_tree_flags_t flags)
 {
-	struct mount *p;
 	struct mount *old = real_mount(old_path->mnt);
 	int err;
 	bool beneath = flags & MNT_TREE_BENEATH;
@@ -3586,8 +3585,6 @@ static int do_move_mount(struct path *old_path,
 	if (IS_ERR(mp.parent))
 		return PTR_ERR(mp.parent);
 
-	p = real_mount(new_path->mnt);
-
 	if (check_mnt(old)) {
 		/* if the source is in our namespace... */
 		/* ... it should be detachable from parent */
@@ -3597,7 +3594,7 @@ static int do_move_mount(struct path *old_path,
 		if (IS_MNT_SHARED(old->mnt_parent))
 			return -EINVAL;
 		/* ... and the target should be in our namespace */
-		if (!check_mnt(p))
+		if (!check_mnt(mp.parent))
 			return -EINVAL;
 	} else {
 		/*
@@ -3610,13 +3607,13 @@ static int do_move_mount(struct path *old_path,
 		 * subsequent checks would've rejected that, but they lose
 		 * some corner cases if we check it early.
 		 */
-		if (old->mnt_ns == p->mnt_ns)
+		if (old->mnt_ns == mp.parent->mnt_ns)
 			return -EINVAL;
 		/*
 		 * Target should be either in our namespace or in an acceptable
 		 * anon namespace, sensu check_anonymous_mnt().
 		 */
-		if (!may_use_mount(p))
+		if (!may_use_mount(mp.parent))
 			return -EINVAL;
 	}
 
@@ -3624,22 +3621,20 @@ static int do_move_mount(struct path *old_path,
 		err = can_move_mount_beneath(old, new_path, mp.mp);
 		if (err)
 			return err;
-
-		p = p->mnt_parent;
 	}
 
 	/*
 	 * Don't move a mount tree containing unbindable mounts to a destination
 	 * mount which is shared.
 	 */
-	if (IS_MNT_SHARED(p) && tree_contains_unbindable(old))
+	if (IS_MNT_SHARED(mp.parent) && tree_contains_unbindable(old))
 		return -EINVAL;
 	if (!check_for_nsfs_mounts(old))
 		return -ELOOP;
-	if (mount_is_ancestor(old, p))
+	if (mount_is_ancestor(old, mp.parent))
 		return -ELOOP;
 
-	return attach_recursive_mnt(old, p, mp.mp);
+	return attach_recursive_mnt(old, mp.parent, mp.mp);
 }
 
 static int do_move_mount_old(struct path *path, const char *old_name)
Signature · 1 line
2.47.2
maintainer Reviewed-by on v2 29/63
#173 lore raw

Re: [PATCH v2 29/63] do_move_mount(): use the parent mount returned by do_lock_mount()

Quoted 9 lines · Al Viro

After successful do_lock_mount() call, mp.parent is set to either real_mount(path->mnt) (for !beneath case) or to ->mnt_parent of that (for beneath). p is set to real_mount(path->mnt) and after several uses it's made equal to mp.parent. All uses prior to that care only about p->mnt_ns and since p->mnt_ns == parent->mnt_ns, we might as well use mp.parent all along.

---

author maintainer
#174 lore raw

PATCH v2 30/63 do_add_mount(): switch to passing pinned_mountpoint instead of mountpoint + path

In reply to Al Viro

Both callers pass it a mountpoint reference picked from pinned_mountpoint and path it corresponds to.

First of all, path->dentry is equal to mp.mp->m_dentry. Furthermore, path->mnt is &mp.parent->mnt, making struct path contents redundant.

Pass it the address of that pinned_mountpoint instead; what's more, if we teach it to treat ERR_PTR(error) in ->parent as "bail out with that error" we can simplify the callers even more - do_add_mount() will do the right thing even when called after lock_mount() failure.

---
 fs/namespace.c | 32 +++++++++++++++-----------------
 1 file changed, 15 insertions(+), 17 deletions(-)
fs/namespace.c +15 -17
index d57e727962da..b236536bbbc9 100644
@@ -3657,10 +3657,13 @@ static int do_move_mount_old(struct path *path, const char *old_name)
 /*
  * add a mount into a namespace's mount tree
  */
-static int do_add_mount(struct mount *newmnt, struct mountpoint *mp,
-			const struct path *path, int mnt_flags)
+static int do_add_mount(struct mount *newmnt, const struct pinned_mountpoint *mp,
+			int mnt_flags)
 {
-	struct mount *parent = real_mount(path->mnt);
+	struct mount *parent = mp->parent;
+
+	if (IS_ERR(parent))
+		return PTR_ERR(parent);
 
 	mnt_flags &= ~MNT_INTERNAL_FLAGS;
 
@@ -3674,14 +3677,15 @@ static int do_add_mount(struct mount *newmnt, struct mountpoint *mp,
 	}
 
 	/* Refuse the same filesystem on the same mount point */
-	if (path->mnt->mnt_sb == newmnt->mnt.mnt_sb && path_mounted(path))
+	if (parent->mnt.mnt_sb == newmnt->mnt.mnt_sb &&
+	    parent->mnt.mnt_root == mp->mp->m_dentry)
 		return -EBUSY;
 
 	if (d_is_symlink(newmnt->mnt.mnt_root))
 		return -EINVAL;
 
 	newmnt->mnt.mnt_flags = mnt_flags;
-	return graft_tree(newmnt, parent, mp);
+	return graft_tree(newmnt, parent, mp->mp);
 }
 
 static bool mount_too_revealing(const struct super_block *sb, int *new_mnt_flags);
@@ -3711,14 +3715,9 @@ static int do_new_mount_fc(struct fs_context *fc, struct path *mountpoint,
 	mnt_warn_timestamp_expiry(mountpoint, mnt);
 
 	LOCK_MOUNT(mp, mountpoint);
-	if (IS_ERR(mp.parent)) {
-		return PTR_ERR(mp.parent);
-	} else {
-		error = do_add_mount(real_mount(mnt), mp.mp,
-				     mountpoint, mnt_flags);
-		if (!error)
-			retain_and_null_ptr(mnt); // consumed on success
-	}
+	error = do_add_mount(real_mount(mnt), &mp, mnt_flags);
+	if (!error)
+		retain_and_null_ptr(mnt); // consumed on success
 	return error;
 }
 
@@ -3824,11 +3823,10 @@ int finish_automount(struct vfsmount *__m, const struct path *path)
 	 * got", not "try to mount it on top".
 	 */
 	LOCK_MOUNT_EXACT(mp, path);
-	if (IS_ERR(mp.parent))
-		return mp.parent == ERR_PTR(-EBUSY) ? 0 : PTR_ERR(mp.parent);
+	if (mp.parent == ERR_PTR(-EBUSY))
+		return 0;
 
-	err = do_add_mount(mnt, mp.mp, path,
-			   path->mnt->mnt_flags | MNT_SHRINKABLE);
+	err = do_add_mount(mnt, &mp, path->mnt->mnt_flags | MNT_SHRINKABLE);
 	if (likely(!err))
 		retain_and_null_ptr(m);
 	return err;
Signature · 1 line
2.47.2
maintainer Reviewed-by on v2 30/63
#175 lore raw

Re: [PATCH v2 30/63] do_add_mount(): switch to passing pinned_mountpoint instead of mountpoint + path

Quoted 6 lines · Al Viro

Both callers pass it a mountpoint reference picked from pinned_mountpoint and path it corresponds to.

First of all, path->dentry is equal to mp.mp->m_dentry. Furthermore, path->mnt is &mp.parent->mnt, making struct path contents redundant.

Quoted 7 lines

Pass it the address of that pinned_mountpoint instead; what's more, if we teach it to treat ERR_PTR(error) in ->parent as "bail out with that error" we can simplify the callers even more - do_add_mount() will do the right thing even when called after lock_mount() failure.

---

author maintainer
#176 lore raw

PATCH v2 31/63 graft_tree(), attach_recursive_mnt() - pass pinned_mountpoint

In reply to Al Viro

parent and mountpoint always come from the same struct pinned_mountpoint now.

---
 fs/namespace.c | 20 ++++++++++----------
 1 file changed, 10 insertions(+), 10 deletions(-)
fs/namespace.c +10 -10
index b236536bbbc9..18d6ad0f4f76 100644
@@ -2549,8 +2549,7 @@ enum mnt_tree_flags_t {
 /**
  * attach_recursive_mnt - attach a source mount tree
  * @source_mnt: mount tree to be attached
- * @dest_mnt:   mount that @source_mnt will be mounted on
- * @dest_mp:    the mountpoint @source_mnt will be mounted at
+ * @dest:	the context for mounting at the place where the tree should go
  *
  *  NOTE: in the table below explains the semantics when a source mount
  *  of a given type is attached to a destination mount of a given type.
@@ -2613,10 +2612,11 @@ enum mnt_tree_flags_t {
  *         Otherwise a negative error code is returned.
  */
 static int attach_recursive_mnt(struct mount *source_mnt,
-				struct mount *dest_mnt,
-				struct mountpoint *dest_mp)
+				const struct pinned_mountpoint *dest)
 {
 	struct user_namespace *user_ns = current->nsproxy->mnt_ns->user_ns;
+	struct mount *dest_mnt = dest->parent;
+	struct mountpoint *dest_mp = dest->mp;
 	HLIST_HEAD(tree_list);
 	struct mnt_namespace *ns = dest_mnt->mnt_ns;
 	struct pinned_mountpoint root = {};
@@ -2864,16 +2864,16 @@ static inline void unlock_mount(struct pinned_mountpoint *m)
 	struct pinned_mountpoint mp __cleanup(unlock_mount) = {}; \
 	lock_mount_exact((path), &mp)
 
-static int graft_tree(struct mount *mnt, struct mount *p, struct mountpoint *mp)
+static int graft_tree(struct mount *mnt, const struct pinned_mountpoint *mp)
 {
 	if (mnt->mnt.mnt_sb->s_flags & SB_NOUSER)
 		return -EINVAL;
 
-	if (d_is_dir(mp->m_dentry) !=
+	if (d_is_dir(mp->mp->m_dentry) !=
 	      d_is_dir(mnt->mnt.mnt_root))
 		return -ENOTDIR;
 
-	return attach_recursive_mnt(mnt, p, mp);
+	return attach_recursive_mnt(mnt, mp);
 }
 
 static int may_change_propagation(const struct mount *m)
@@ -3055,7 +3055,7 @@ static int do_loopback(struct path *path, const char *old_name,
 	if (IS_ERR(mnt))
 		return PTR_ERR(mnt);
 
-	err = graft_tree(mnt, mp.parent, mp.mp);
+	err = graft_tree(mnt, &mp);
 	if (err) {
 		lock_mount_hash();
 		umount_tree(mnt, UMOUNT_SYNC);
@@ -3634,7 +3634,7 @@ static int do_move_mount(struct path *old_path,
 	if (mount_is_ancestor(old, mp.parent))
 		return -ELOOP;
 
-	return attach_recursive_mnt(old, mp.parent, mp.mp);
+	return attach_recursive_mnt(old, &mp);
 }
 
 static int do_move_mount_old(struct path *path, const char *old_name)
@@ -3685,7 +3685,7 @@ static int do_add_mount(struct mount *newmnt, const struct pinned_mountpoint *mp
 		return -EINVAL;
 
 	newmnt->mnt.mnt_flags = mnt_flags;
-	return graft_tree(newmnt, parent, mp->mp);
+	return graft_tree(newmnt, mp);
 }
 
 static bool mount_too_revealing(const struct super_block *sb, int *new_mnt_flags);
Signature · 1 line
2.47.2
author maintainer
#178 lore raw

PATCH v2 32/63 pivot_root(2): use old_mp.mp->m_dentry instead of old.dentry

In reply to Al Viro

That kills the last place where callers of lock_mount(path, &mp) used path->dentry.

---
 fs/namespace.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
fs/namespace.c +1 -1
index 18d6ad0f4f76..02bc5294071a 100644
@@ -4675,7 +4675,7 @@ SYSCALL_DEFINE2(pivot_root, const char __user *, new_root,
 	if (!mnt_has_parent(new_mnt))
 		return -EINVAL; /* absolute root */
 	/* make sure we can reach put_old from new_root */
-	if (!is_path_reachable(old_mnt, old.dentry, &new))
+	if (!is_path_reachable(old_mnt, old_mp.mp->m_dentry, &new))
 		return -EINVAL;
 	/* make certain new is below the root */
 	if (!is_path_reachable(new_mnt, new.dentry, &root))
Signature · 1 line
2.47.2
author maintainer
#179 lore raw

PATCH v2 33/63 don't bother passing new_path->dentry to can_move_mount_beneath()

In reply to Al Viro
---
 fs/namespace.c | 11 +++++------
 1 file changed, 5 insertions(+), 6 deletions(-)
fs/namespace.c +5 -6
index 02bc5294071a..085877bfaa5e 100644
@@ -3450,8 +3450,8 @@ static bool mount_is_ancestor(const struct mount *p1, const struct mount *p2)
 /**
  * can_move_mount_beneath - check that we can mount beneath the top mount
  * @mnt_from: mount we are trying to move
- * @to:   mount under which to mount
- * @mp:   mountpoint of @to
+ * @mnt_to:   mount under which to mount
+ * @mp:   mountpoint of @mnt_to
  *
  * - Make sure that nothing can be mounted beneath the caller's current
  *   root or the rootfs of the namespace.
@@ -3467,11 +3467,10 @@ static bool mount_is_ancestor(const struct mount *p1, const struct mount *p2)
  * Return: On success 0, and on error a negative error code is returned.
  */
 static int can_move_mount_beneath(struct mount *mnt_from,
-				  const struct path *to,
+				  struct mount *mnt_to,
 				  const struct mountpoint *mp)
 {
-	struct mount *mnt_to = real_mount(to->mnt),
-		     *parent_mnt_to = mnt_to->mnt_parent;
+	struct mount *parent_mnt_to = mnt_to->mnt_parent;
 
 	if (IS_MNT_LOCKED(mnt_to))
 		return -EINVAL;
@@ -3618,7 +3617,7 @@ static int do_move_mount(struct path *old_path,
 	}
 
 	if (beneath) {
-		err = can_move_mount_beneath(old, new_path, mp.mp);
+		err = can_move_mount_beneath(old, real_mount(new_path->mnt), mp.mp);
 		if (err)
 			return err;
 	}
Signature · 1 line
2.47.2
#180 lore raw

Re: [PATCH v2 33/63] don't bother passing new_path->dentry to can_move_mount_beneath()

fs/namespace.c static int do_move_mount(struct path *old_path, · line 3623

Quoted 7 lines · Al Viro
        if (beneath) {
fs/namespace.c +1 -1
-               err = can_move_mount_beneath(old, new_path, mp.mp);
+               err = can_move_mount_beneath(old, real_mount(new_path->mnt), mp.mp);
                if (err)
                        return err;
        }

Going through the patches, this is one that I think made things uglier... Most of them make me go "nice simplification".

(I'll have a separate comment on 61/63)

I certainly agree with the intent of the patch, but that can_move_mount_beneath() call line is now rather hard to read. It looked simpler before.

Maybe you could just split it into two lines, and write it as

        if (beneath) {
                struct mount *new_mnt = real_mount(new_path->mnt);
                err = can_move_mount_beneath(old, new_mnt, mp.mp);
                if (err)
                        return err;
        }

which makes slightly less happen in that one line (and it fits in 80 columns too - not a requirement, but still "good taste")

Long lines are better than randomly splitting lines unreadably into multiple lines, but short lines that are logically split are still preferred, I would say..

Linus

author maintainer
#181 lore raw

Re: [PATCH v2 33/63] don't bother passing new_path->dentry to can_move_mount_beneath()

Quoted 15 lines · Linus Torvalds

(I'll have a separate comment on 61/63)

I certainly agree with the intent of the patch, but that can_move_mount_beneath() call line is now rather hard to read. It looked simpler before.

Maybe you could just split it into two lines, and write it as

        if (beneath) {
                struct mount *new_mnt = real_mount(new_path->mnt);
                err = can_move_mount_beneath(old, new_mnt, mp.mp);
                if (err)
                        return err;
        }
Quoted 6 lines

which makes slightly less happen in that one line (and it fits in 80 columns too - not a requirement, but still "good taste")

Long lines are better than randomly splitting lines unreadably into multiple lines, but short lines that are logically split are still preferred, I would say..

FWIW, if you look at #35, you'll see this:

- err = can_move_mount_beneath(old, real_mount(new_path->mnt), mp.mp);
+ struct mount *over = real_mount(new_path->mnt);
+
+ if (mp.parent != over->mnt_parent)
+ over = mp.parent->overmount;
+ err = can_move_mount_beneath(old, over, mp.mp);

So... might as well introduce the variable in this one. Then this chunk becomes
@@ -3618,7 +3617,9 @@ static int do_move_mount(struct path *old_path,
}

if (beneath) {
- err = can_move_mount_beneath(old, new_path, mp.mp);
+ struct mount *over = real_mount(new_path->mnt);
+
+ err = can_move_mount_beneath(old, over, mp.mp);
if (err)
return err;
}
and the corresponding one in #35
@@ -3618,6 +3624,8 @@ static int do_move_mount(struct path *old_path,
if (beneath) {
struct mount *over = real_mount(new_path->mnt);

+ if (mp.parent != over->mnt_parent)
+ over = mp.parent->overmount;
err = can_move_mount_beneath(old, over, mp.mp); if (err)
return err;

OK, done - both certainly look better that way.

author maintainer
#182 lore raw

PATCH v2 34/63 new helper: topmost_overmount()

In reply to Al Viro

Returns the final (topmost) mount in the chain of overmounts starting at given mount. Same locking rules as for any mount tree traversal - either the spinlock side of mount_lock, or rcu + sample the seqcount side of mount_lock before the call and recheck afterwards.

---
 fs/mount.h     | 7 +++++++
 fs/namespace.c | 9 +++------
 2 files changed, 10 insertions(+), 6 deletions(-)
fs/mount.h +7 -0
index ed8c83ba836a..04d0eadc4c10 100644
@@ -235,4 +235,11 @@ static inline void mnt_notify_add(struct mount *m)
 }
 #endif
 
+static inline struct mount *topmost_overmount(struct mount *m)
+{
+	while (m->overmount)
+		m = m->overmount;
+	return m;
+}
+
 struct mnt_namespace *mnt_ns_from_dentry(struct dentry *dentry);
fs/namespace.c +3 -6
index 085877bfaa5e..ebecb03972c5 100644
@@ -2696,10 +2696,9 @@ static int attach_recursive_mnt(struct mount *source_mnt,
 				 child->mnt_mountpoint);
 		commit_tree(child);
 		if (q) {
+			struct mount *r = topmost_overmount(child);
 			struct mountpoint *mp = root.mp;
-			struct mount *r = child;
-			while (unlikely(r->overmount))
-				r = r->overmount;
+
 			if (unlikely(shorter) && child != source_mnt)
 				mp = shorter;
 			mnt_change_mountpoint(r, mp, q);
@@ -6171,9 +6170,7 @@ bool current_chrooted(void)
 
 	guard(mount_locked_reader)();
 
-	root = current->nsproxy->mnt_ns->root;
-	while (unlikely(root->overmount))
-		root = root->overmount;
+	root = topmost_overmount(current->nsproxy->mnt_ns->root);
 
 	return fs_root.mnt != &root->mnt || !path_mounted(&fs_root);
 }
Signature · 1 line
2.47.2
author maintainer
#183 lore raw

PATCH v2 35/63 do_lock_mount(): don't modify path.

In reply to Al Viro

Currently do_lock_mount() has the target path switched to whatever might be overmounting it. We _do_ want to have the parent mount/mountpoint chosen on top of the overmounting pile; however, the way it's done has unpleasant races - if umount propagation removes the overmount while we'd been trying to set the environment up, we might end up failing if our target path strays into that overmount just before the overmount gets kicked out.

Users of do_lock_mount() do not need the target path changed - they have all information in res->{parent,mp}; only one place (in do_move_mount()) currently uses the resulting path->mnt, and that value is trivial to reconstruct by the original value of path->mnt + chosen parent mount.

Let's keep the target path unchanged; it avoids a bunch of subtle races and it's not hard to do:
do
as mount_locked_reader
find the prospective parent mount/mountpoint dentry grab references if it's not the original target
lock the prospective mountpoint dentry
take namespace_sem exclusive
if prospective parent/mountpoint would be different now
err = -EAGAIN
else if location has been unmounted
err = -ENOENT
else if mountpoint dentry is not allowed to be mounted on
err = -ENOENT
else if beneath and the top of the pile was the absolute root
err = -EINVAL
else
try to get struct mountpoint (by dentry), set err to 0 on success and -ENO{MEM,ENT} on failure
if err != 0
res->parent = ERR_PTR(err)
drop locks
else
res->parent = prospective parent
drop temporary references
while err == -EAGAIN

A somewhat subtle part is that dropping temporary references is allowed. Neither mounts nor dentries should be evicted by a thread that holds namespace_sem. On success we are dropping those references under namespace_sem, so we need to be sure that these are not the last references remaining. However, on success we'd already verified (under namespace_sem) that original target is still mounted and that mount and dentry we are about to drop are still reachable from it via the mount tree. That guarantees that we are not about to drop the last remaining references.

---
 fs/namespace.c | 126 ++++++++++++++++++++++++++-----------------------
 1 file changed, 68 insertions(+), 58 deletions(-)
fs/namespace.c +68 -58
index ebecb03972c5..b77d2df606a1 100644
@@ -2727,6 +2727,27 @@ static int attach_recursive_mnt(struct mount *source_mnt,
 	return err;
 }
 
+static inline struct mount *where_to_mount(const struct path *path,
+					   struct dentry **dentry,
+					   bool beneath)
+{
+	struct mount *m;
+
+	if (unlikely(beneath)) {
+		m = topmost_overmount(real_mount(path->mnt));
+		*dentry = m->mnt_mountpoint;
+		return m->mnt_parent;
+	} else {
+		m = __lookup_mnt(path->mnt, *dentry = path->dentry);
+		if (unlikely(m)) {
+			m = topmost_overmount(m);
+			*dentry = m->mnt.mnt_root;
+			return m;
+		}
+		return real_mount(path->mnt);
+	}
+}
+
 /**
  * do_lock_mount - acquire environment for mounting
  * @path:	target path
@@ -2758,84 +2779,69 @@ static int attach_recursive_mnt(struct mount *source_mnt,
  * case we also require the location to be at the root of a mount
  * that has a parent (i.e. is not a root of some namespace).
  */
-static void do_lock_mount(struct path *path, struct pinned_mountpoint *res, bool beneath)
+static void do_lock_mount(const struct path *path,
+			  struct pinned_mountpoint *res,
+			  bool beneath)
 {
-	struct vfsmount *mnt = path->mnt;
-	struct dentry *dentry;
-	struct path under = {};
-	int err = -ENOENT;
+	int err;
 
 	if (unlikely(beneath) && !path_mounted(path)) {
 		res->parent = ERR_PTR(-EINVAL);
 		return;
 	}
 
-	for (;;) {
-		struct mount *m = real_mount(mnt);
-
-		if (beneath) {
-			path_put(&under);
-			read_seqlock_excl(&mount_lock);
-			if (unlikely(!mnt_has_parent(m))) {
-				read_sequnlock_excl(&mount_lock);
-				res->parent = ERR_PTR(-EINVAL);
-				return;
+	do {
+		struct dentry *dentry, *d;
+		struct mount *m, *n;
+
+		scoped_guard(mount_locked_reader) {
+			m = where_to_mount(path, &dentry, beneath);
+			if (&m->mnt != path->mnt) {
+				mntget(&m->mnt);
+				dget(dentry);
 			}
-			under.mnt = mntget(&m->mnt_parent->mnt);
-			under.dentry = dget(m->mnt_mountpoint);
-			read_sequnlock_excl(&mount_lock);
-			dentry = under.dentry;
-		} else {
-			dentry = path->dentry;
 		}
 
 		inode_lock(dentry->d_inode);
 		namespace_lock();
 
-		if (unlikely(cant_mount(dentry) || !is_mounted(mnt)))
-			break;		// not to be mounted on
+		// check if the chain of mounts (if any) has changed.
+		scoped_guard(mount_locked_reader)
+			n = where_to_mount(path, &d, beneath);
 
-		if (beneath && unlikely(m->mnt_mountpoint != dentry ||
-				        &m->mnt_parent->mnt != under.mnt)) {
-			namespace_unlock();
-			inode_unlock(dentry->d_inode);
-			continue;	// got moved
-		}
+		if (unlikely(n != m || dentry != d))
+			err = -EAGAIN;		// something moved, retry
+		else if (unlikely(cant_mount(dentry) || !is_mounted(path->mnt)))
+			err = -ENOENT;		// not to be mounted on
+		else if (beneath && &m->mnt == path->mnt && !m->overmount)
+			err = -EINVAL;
+		else
+			err = get_mountpoint(dentry, res);
 
-		mnt = lookup_mnt(path);
-		if (unlikely(mnt)) {
+		if (unlikely(err)) {
+			res->parent = ERR_PTR(err);
 			namespace_unlock();
 			inode_unlock(dentry->d_inode);
-			path_put(path);
-			path->mnt = mnt;
-			path->dentry = dget(mnt->mnt_root);
-			continue;	// got overmounted
+		} else {
+			res->parent = m;
 		}
-		err = get_mountpoint(dentry, res);
-		if (err)
-			break;
-		if (beneath) {
-			/*
-			 * @under duplicates the references that will stay
-			 * at least until namespace_unlock(), so the path_put()
-			 * below is safe (and OK to do under namespace_lock -
-			 * we are not dropping the final references here).
-			 */
-			path_put(&under);
-			res->parent = real_mount(path->mnt)->mnt_parent;
-			return;
+		/*
+		 * Drop the temporary references.  This is subtle - on success
+		 * we are doing that under namespace_sem, which would normally
+		 * be forbidden.  However, in that case we are guaranteed that
+		 * refcounts won't reach zero, since we know that path->mnt
+		 * is mounted and thus all mounts reachable from it are pinned
+		 * and stable, along with their mountpoints and roots.
+		 */
+		if (&m->mnt != path->mnt) {
+			dput(dentry);
+			mntput(&m->mnt);
 		}
-		res->parent = real_mount(path->mnt);
-		return;
-	}
-	namespace_unlock();
-	inode_unlock(dentry->d_inode);
-	if (beneath)
-		path_put(&under);
-	res->parent = ERR_PTR(err);
+	} while (err == -EAGAIN);
 }
 
-static inline void lock_mount(struct path *path, struct pinned_mountpoint *m)
+static inline void lock_mount(const struct path *path,
+			      struct pinned_mountpoint *m)
 {
 	do_lock_mount(path, m, false);
 }
@@ -3616,7 +3622,11 @@ static int do_move_mount(struct path *old_path,
 	}
 
 	if (beneath) {
-		err = can_move_mount_beneath(old, real_mount(new_path->mnt), mp.mp);
+		struct mount *over = real_mount(new_path->mnt);
+
+		if (mp.parent != over->mnt_parent)
+			over = mp.parent->overmount;
+		err = can_move_mount_beneath(old, over, mp.mp);
 		if (err)
 			return err;
 	}
Signature · 1 line
2.47.2
maintainer Reviewed-by on v2 35/63
#184 lore raw

Re: [PATCH v2 35/63] do_lock_mount(): don't modify path.

Quoted 64 lines · Al Viro

Currently do_lock_mount() has the target path switched to whatever might be overmounting it. We _do_ want to have the parent mount/mountpoint chosen on top of the overmounting pile; however, the way it's done has unpleasant races - if umount propagation removes the overmount while we'd been trying to set the environment up, we might end up failing if our target path strays into that overmount just before the overmount gets kicked out.

Users of do_lock_mount() do not need the target path changed - they have all information in res->{parent,mp}; only one place (in do_move_mount()) currently uses the resulting path->mnt, and that value is trivial to reconstruct by the original value of path->mnt + chosen parent mount.

Let's keep the target path unchanged; it avoids a bunch of subtle races and it's not hard to do:
do
as mount_locked_reader
find the prospective parent mount/mountpoint dentry grab references if it's not the original target
lock the prospective mountpoint dentry
take namespace_sem exclusive
if prospective parent/mountpoint would be different now
err = -EAGAIN
else if location has been unmounted
err = -ENOENT
else if mountpoint dentry is not allowed to be mounted on
err = -ENOENT
else if beneath and the top of the pile was the absolute root
err = -EINVAL
else
try to get struct mountpoint (by dentry), set err to 0 on success and -ENO{MEM,ENT} on failure
if err != 0
res->parent = ERR_PTR(err)
drop locks
else
res->parent = prospective parent
drop temporary references
while err == -EAGAIN

A somewhat subtle part is that dropping temporary references is allowed. Neither mounts nor dentries should be evicted by a thread that holds namespace_sem. On success we are dropping those references under namespace_sem, so we need to be sure that these are not the last references remaining. However, on success we'd already verified (under namespace_sem) that original target is still mounted and that mount and dentry we are about to drop are still reachable from it via the mount tree. That guarantees that we are not about to drop the last remaining references.

---
 fs/namespace.c | 126 ++++++++++++++++++++++++++-----------------------
 1 file changed, 68 insertions(+), 58 deletions(-)
fs/namespace.c +0 -0
index ebecb03972c5..b77d2df606a1 100644
@@ -2727,6 +2727,27 @@ static int attach_recursive_mnt(struct mount *source_mnt,
 	return err;
 }
 

fs/namespace.c static int attach_recursive_mnt(struct mount *source_mnt, · line 2739

Quoted 10 lines
fs/namespace.c +10 -0
+static inline struct mount *where_to_mount(const struct path *path,
+					   struct dentry **dentry,
+					   bool beneath)
+{
+	struct mount *m;
+
+	if (unlikely(beneath)) {
+		m = topmost_overmount(real_mount(path->mnt));
+		*dentry = m->mnt_mountpoint;
+		return m->mnt_parent;

No need for that else. This can just be:

if (unlikely(beneath)) {
	m = topmost_overmount(real_mount(path->mnt));
	*dentry = m->mnt_mountpoint;
	return m->mnt_parent;
}

m = __lookup_mnt(path->mnt, *dentry = path->dentry);

if (unlikely(m)) {
	m = topmost_overmount(m);
	*dentry = m->mnt.mnt_root;
	return m;
}

return real_mount(path->mnt);

fs/namespace.c static int attach_recursive_mnt(struct mount *source_mnt, · line 2741

Quoted 2 lines
fs/namespace.c +2 -0
+	} else {
+		m = __lookup_mnt(path->mnt, *dentry = path->dentry);

The assignment to *dentry during argument passing looks really weird. I would prefer if we didn't do that.

Quoted 106 lines
fs/namespace.c +37 -44
+		if (unlikely(m)) {
+			m = topmost_overmount(m);
+			*dentry = m->mnt.mnt_root;
+			return m;
+		}
+		return real_mount(path->mnt);
+	}
+}
+
 /**
  * do_lock_mount - acquire environment for mounting
  * @path:	target path
@@ -2758,84 +2779,69 @@ static int attach_recursive_mnt(struct mount *source_mnt,
  * case we also require the location to be at the root of a mount
  * that has a parent (i.e. is not a root of some namespace).
  */
-static void do_lock_mount(struct path *path, struct pinned_mountpoint *res, bool beneath)
+static void do_lock_mount(const struct path *path,
+			  struct pinned_mountpoint *res,
+			  bool beneath)
 {
-	struct vfsmount *mnt = path->mnt;
-	struct dentry *dentry;
-	struct path under = {};
-	int err = -ENOENT;
+	int err;
 
 	if (unlikely(beneath) && !path_mounted(path)) {
 		res->parent = ERR_PTR(-EINVAL);
 		return;
 	}
 
-	for (;;) {
-		struct mount *m = real_mount(mnt);
-
-		if (beneath) {
-			path_put(&under);
-			read_seqlock_excl(&mount_lock);
-			if (unlikely(!mnt_has_parent(m))) {
-				read_sequnlock_excl(&mount_lock);
-				res->parent = ERR_PTR(-EINVAL);
-				return;
+	do {
+		struct dentry *dentry, *d;
+		struct mount *m, *n;
+
+		scoped_guard(mount_locked_reader) {
+			m = where_to_mount(path, &dentry, beneath);
+			if (&m->mnt != path->mnt) {
+				mntget(&m->mnt);
+				dget(dentry);
 			}
-			under.mnt = mntget(&m->mnt_parent->mnt);
-			under.dentry = dget(m->mnt_mountpoint);
-			read_sequnlock_excl(&mount_lock);
-			dentry = under.dentry;
-		} else {
-			dentry = path->dentry;
 		}
 
 		inode_lock(dentry->d_inode);
 		namespace_lock();
 
-		if (unlikely(cant_mount(dentry) || !is_mounted(mnt)))
-			break;		// not to be mounted on
+		// check if the chain of mounts (if any) has changed.
+		scoped_guard(mount_locked_reader)
+			n = where_to_mount(path, &d, beneath);
 
-		if (beneath && unlikely(m->mnt_mountpoint != dentry ||
-				        &m->mnt_parent->mnt != under.mnt)) {
-			namespace_unlock();
-			inode_unlock(dentry->d_inode);
-			continue;	// got moved
-		}
+		if (unlikely(n != m || dentry != d))
+			err = -EAGAIN;		// something moved, retry
+		else if (unlikely(cant_mount(dentry) || !is_mounted(path->mnt)))
+			err = -ENOENT;		// not to be mounted on
+		else if (beneath && &m->mnt == path->mnt && !m->overmount)
+			err = -EINVAL;
+		else
+			err = get_mountpoint(dentry, res);
 
-		mnt = lookup_mnt(path);
-		if (unlikely(mnt)) {
+		if (unlikely(err)) {
+			res->parent = ERR_PTR(err);
 			namespace_unlock();
 			inode_unlock(dentry->d_inode);
-			path_put(path);
-			path->mnt = mnt;
-			path->dentry = dget(mnt->mnt_root);
-			continue;	// got overmounted
+		} else {
+			res->parent = m;
 		}
-		err = get_mountpoint(dentry, res);
-		if (err)
-			break;
-		if (beneath) {
-			/*
-			 * @under duplicates the references that will stay
-			 * at least until namespace_unlock(), so the path_put()
-			 * below is safe (and OK to do under namespace_lock -
-			 * we are not dropping the final references here).

fs/namespace.c static int attach_recursive_mnt(struct mount *source_mnt, · line 2833

Quoted 10 lines
fs/namespace.c +6 -4
-			 */
-			path_put(&under);
-			res->parent = real_mount(path->mnt)->mnt_parent;
-			return;
+		/*
+		 * Drop the temporary references.  This is subtle - on success
+		 * we are doing that under namespace_sem, which would normally
+		 * be forbidden.  However, in that case we are guaranteed that
+		 * refcounts won't reach zero, since we know that path->mnt
+		 * is mounted and thus all mounts reachable from it are pinned

"is mounted and we hold the namespace semaphore and thus all mounts reachable [...]"

With these things fixed:

Unless I forgot something this means I should've gone through the whole series.

author maintainer
#185 lore raw

PATCH v2 36/63 constify check_mnt()

In reply to Al Viro
---
 fs/namespace.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
fs/namespace.c +1 -1
index b77d2df606a1..de894f96d9c2 100644
@@ -1010,7 +1010,7 @@ static void unpin_mountpoint(struct pinned_mountpoint *m)
 	}
 }
 
-static inline int check_mnt(struct mount *mnt)
+static inline int check_mnt(const struct mount *mnt)
 {
 	return mnt->mnt_ns == current->nsproxy->mnt_ns;
 }
Signature · 1 line
2.47.2
author maintainer
#186 lore raw

PATCH v2 37/63 do_mount_setattr(): constify path argument

In reply to Al Viro
---
 fs/namespace.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
fs/namespace.c +1 -1
index de894f96d9c2..5766d6a3a279 100644
@@ -4865,7 +4865,7 @@ static void mount_setattr_commit(struct mount_kattr *kattr, struct mount *mnt)
 	touch_mnt_namespace(mnt->mnt_ns);
 }
 
-static int do_mount_setattr(struct path *path, struct mount_kattr *kattr)
+static int do_mount_setattr(const struct path *path, struct mount_kattr *kattr)
 {
 	struct mount *mnt = real_mount(path->mnt);
 	int err = 0;
Signature · 1 line
2.47.2
author maintainer
#187 lore raw

PATCH v2 38/63 do_set_group(): constify path arguments

In reply to Al Viro
---
 fs/namespace.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
fs/namespace.c +1 -1
index 5766d6a3a279..e4ca76091bd7 100644
@@ -3359,7 +3359,7 @@ static inline int tree_contains_unbindable(struct mount *mnt)
 	return 0;
 }
 
-static int do_set_group(struct path *from_path, struct path *to_path)
+static int do_set_group(const struct path *from_path, const struct path *to_path)
 {
 	struct mount *from = real_mount(from_path->mnt);
 	struct mount *to = real_mount(to_path->mnt);
Signature · 1 line
2.47.2
author maintainer
#188 lore raw

PATCH v2 39/63 drop_collected_paths(): constify arguments

In reply to Al Viro

... and use that to constify the pointers in callers

---
 fs/namespace.c        |  4 ++--
 include/linux/mount.h |  2 +-
 kernel/audit_tree.c   | 12 ++++++------
 3 files changed, 9 insertions(+), 9 deletions(-)
fs/namespace.c +2 -2
index e4ca76091bd7..61dfa899bd57 100644
@@ -2334,9 +2334,9 @@ struct path *collect_paths(const struct path *path,
 	return res;
 }
 
-void drop_collected_paths(struct path *paths, struct path *prealloc)
+void drop_collected_paths(const struct path *paths, struct path *prealloc)
 {
-	for (struct path *p = paths; p->mnt; p++)
+	for (const struct path *p = paths; p->mnt; p++)
 		path_put(p);
 	if (paths != prealloc)
 		kfree(paths);
include/linux/mount.h +1 -1
index 5f9c053b0897..c09032463b36 100644
@@ -105,7 +105,7 @@ extern int may_umount(struct vfsmount *);
 int do_mount(const char *, const char __user *,
 		     const char *, unsigned long, void *);
 extern struct path *collect_paths(const struct path *, struct path *, unsigned);
-extern void drop_collected_paths(struct path *, struct path *);
+extern void drop_collected_paths(const struct path *, struct path *);
 extern void kern_unmount_array(struct vfsmount *mnt[], unsigned int num);
 
 extern int cifs_root_data(char **dev, char **opts);
kernel/audit_tree.c +6 -6
index b0eae2a3c895..32007edf0e55 100644
@@ -678,7 +678,7 @@ void audit_trim_trees(void)
 		struct audit_tree *tree;
 		struct path path;
 		struct audit_node *node;
-		struct path *paths;
+		const struct path *paths;
 		struct path array[16];
 		int err;
 
@@ -701,7 +701,7 @@ void audit_trim_trees(void)
 			struct audit_chunk *chunk = find_chunk(node);
 			/* this could be NULL if the watch is dying else where... */
 			node->index |= 1U<<31;
-			for (struct path *p = paths; p->dentry; p++) {
+			for (const struct path *p = paths; p->dentry; p++) {
 				struct inode *inode = p->dentry->d_inode;
 				if (inode_to_key(inode) == chunk->key) {
 					node->index &= ~(1U<<31);
@@ -740,9 +740,9 @@ void audit_put_tree(struct audit_tree *tree)
 	put_tree(tree);
 }
 
-static int tag_mounts(struct path *paths, struct audit_tree *tree)
+static int tag_mounts(const struct path *paths, struct audit_tree *tree)
 {
-	for (struct path *p = paths; p->dentry; p++) {
+	for (const struct path *p = paths; p->dentry; p++) {
 		int err = tag_chunk(p->dentry->d_inode, tree);
 		if (err)
 			return err;
@@ -805,7 +805,7 @@ int audit_add_tree_rule(struct audit_krule *rule)
 	struct audit_tree *seed = rule->tree, *tree;
 	struct path path;
 	struct path array[16];
-	struct path *paths;
+	const struct path *paths;
 	int err;
 
 	rule->tree = NULL;
@@ -877,7 +877,7 @@ int audit_tag_tree(char *old, char *new)
 	int failed = 0;
 	struct path path1, path2;
 	struct path array[16];
-	struct path *paths;
+	const struct path *paths;
 	int err;
 
 	err = kern_path(new, 0, &path2);
Signature · 1 line
2.47.2
author maintainer
#189 lore raw

PATCH v2 40/63 collect_paths(): constify the return value

In reply to Al Viro

callers have no business modifying the paths they get

---
 fs/namespace.c        | 4 ++--
 include/linux/mount.h | 4 ++--
 2 files changed, 4 insertions(+), 4 deletions(-)
fs/namespace.c +2 -2
index 61dfa899bd57..43f46d9e84fe 100644
@@ -2300,7 +2300,7 @@ static inline bool extend_array(struct path **res, struct path **to_free,
 	return p;
 }
 
-struct path *collect_paths(const struct path *path,
+const struct path *collect_paths(const struct path *path,
 			      struct path *prealloc, unsigned count)
 {
 	struct mount *root = real_mount(path->mnt);
@@ -2334,7 +2334,7 @@ struct path *collect_paths(const struct path *path,
 	return res;
 }
 
-void drop_collected_paths(const struct path *paths, struct path *prealloc)
+void drop_collected_paths(const struct path *paths, const struct path *prealloc)
 {
 	for (const struct path *p = paths; p->mnt; p++)
 		path_put(p);
include/linux/mount.h +2 -2
index c09032463b36..18e4b97f8a98 100644
@@ -104,8 +104,8 @@ extern int may_umount_tree(struct vfsmount *);
 extern int may_umount(struct vfsmount *);
 int do_mount(const char *, const char __user *,
 		     const char *, unsigned long, void *);
-extern struct path *collect_paths(const struct path *, struct path *, unsigned);
-extern void drop_collected_paths(const struct path *, struct path *);
+extern const struct path *collect_paths(const struct path *, struct path *, unsigned);
+extern void drop_collected_paths(const struct path *, const struct path *);
 extern void kern_unmount_array(struct vfsmount *mnt[], unsigned int num);
 
 extern int cifs_root_data(char **dev, char **opts);
Signature · 1 line
2.47.2
author maintainer
#190 lore raw

PATCH v2 41/63 do_move_mount(), vfs_move_mount(), do_move_mount_old(): constify struct path argument(s)

In reply to Al Viro
---
 fs/namespace.c | 10 ++++++----
 1 file changed, 6 insertions(+), 4 deletions(-)
fs/namespace.c +6 -4
index 43f46d9e84fe..70ae769ecf11 100644
@@ -3572,8 +3572,9 @@ static inline bool may_use_mount(struct mount *mnt)
 	return check_anonymous_mnt(mnt);
 }
 
-static int do_move_mount(struct path *old_path,
-			 struct path *new_path, enum mnt_tree_flags_t flags)
+static int do_move_mount(const struct path *old_path,
+			 const struct path *new_path,
+			 enum mnt_tree_flags_t flags)
 {
 	struct mount *old = real_mount(old_path->mnt);
 	int err;
@@ -3645,7 +3646,7 @@ static int do_move_mount(struct path *old_path,
 	return attach_recursive_mnt(old, &mp);
 }
 
-static int do_move_mount_old(struct path *path, const char *old_name)
+static int do_move_mount_old(const struct path *path, const char *old_name)
 {
 	struct path old_path;
 	int err;
@@ -4475,7 +4476,8 @@ SYSCALL_DEFINE3(fsmount, int, fs_fd, unsigned int, flags,
 	return ret;
 }
 
-static inline int vfs_move_mount(struct path *from_path, struct path *to_path,
+static inline int vfs_move_mount(const struct path *from_path,
+				 const struct path *to_path,
 				 enum mnt_tree_flags_t mflags)
 {
 	int ret;
Signature · 1 line
2.47.2
author maintainer
#191 lore raw

PATCH v2 42/63 mnt_warn_timestamp_expiry(): constify struct path argument

In reply to Al Viro
---
 fs/namespace.c | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)
fs/namespace.c +2 -1
index 70ae769ecf11..a7c840371a7f 100644
@@ -3230,7 +3230,8 @@ static void set_mount_attributes(struct mount *mnt, unsigned int mnt_flags)
 	touch_mnt_namespace(mnt->mnt_ns);
 }
 
-static void mnt_warn_timestamp_expiry(struct path *mountpoint, struct vfsmount *mnt)
+static void mnt_warn_timestamp_expiry(const struct path *mountpoint,
+				      struct vfsmount *mnt)
 {
 	struct super_block *sb = mnt->mnt_sb;
 
Signature · 1 line
2.47.2
author maintainer
#192 lore raw

PATCH v2 43/63 do_new_mount{,_fc}(): constify struct path argument

In reply to Al Viro
---
 fs/namespace.c | 7 ++++---
 1 file changed, 4 insertions(+), 3 deletions(-)
fs/namespace.c +4 -3
index a7c840371a7f..8ff54e0da446 100644
@@ -3704,7 +3704,7 @@ static bool mount_too_revealing(const struct super_block *sb, int *new_mnt_flags
  * Create a new mount using a superblock configuration and request it
  * be added to the namespace tree.
  */
-static int do_new_mount_fc(struct fs_context *fc, struct path *mountpoint,
+static int do_new_mount_fc(struct fs_context *fc, const struct path *mountpoint,
 			   unsigned int mnt_flags)
 {
 	struct super_block *sb;
@@ -3735,8 +3735,9 @@ static int do_new_mount_fc(struct fs_context *fc, struct path *mountpoint,
  * create a new mount for userspace and request it to be added into the
  * namespace's tree
  */
-static int do_new_mount(struct path *path, const char *fstype, int sb_flags,
-			int mnt_flags, const char *name, void *data)
+static int do_new_mount(const struct path *path, const char *fstype,
+			int sb_flags, int mnt_flags,
+			const char *name, void *data)
 {
 	struct file_system_type *type;
 	struct fs_context *fc;
Signature · 1 line
2.47.2
author maintainer
#193 lore raw

PATCH v2 44/63 do_{loopback,change_type,remount,reconfigure_mnt}(): constify struct path argument

In reply to Al Viro
---
 fs/namespace.c | 10 +++++-----
 1 file changed, 5 insertions(+), 5 deletions(-)
fs/namespace.c +5 -5
index 8ff54e0da446..6ae42f3a9f10 100644
@@ -2914,7 +2914,7 @@ static int flags_to_propagation_type(int ms_flags)
 /*
  * recursively change the type of the mountpoint.
  */
-static int do_change_type(struct path *path, int ms_flags)
+static int do_change_type(const struct path *path, int ms_flags)
 {
 	struct mount *m;
 	struct mount *mnt = real_mount(path->mnt);
@@ -3034,8 +3034,8 @@ static struct mount *__do_loopback(struct path *old_path, int recurse)
 /*
  * do loopback mount.
  */
-static int do_loopback(struct path *path, const char *old_name,
-				int recurse)
+static int do_loopback(const struct path *path, const char *old_name,
+		       int recurse)
 {
 	struct path old_path __free(path_put) = {};
 	struct mount *mnt = NULL;
@@ -3265,7 +3265,7 @@ static void mnt_warn_timestamp_expiry(const struct path *mountpoint,
  * superblock it refers to.  This is triggered by specifying MS_REMOUNT|MS_BIND
  * to mount(2).
  */
-static int do_reconfigure_mnt(struct path *path, unsigned int mnt_flags)
+static int do_reconfigure_mnt(const struct path *path, unsigned int mnt_flags)
 {
 	struct super_block *sb = path->mnt->mnt_sb;
 	struct mount *mnt = real_mount(path->mnt);
@@ -3302,7 +3302,7 @@ static int do_reconfigure_mnt(struct path *path, unsigned int mnt_flags)
  * If you've mounted a non-root directory somewhere and want to do remount
  * on it - tough luck.
  */
-static int do_remount(struct path *path, int ms_flags, int sb_flags,
+static int do_remount(const struct path *path, int ms_flags, int sb_flags,
 		      int mnt_flags, void *data)
 {
 	int err;
Signature · 1 line
2.47.2
author maintainer
#194 lore raw

PATCH v2 45/63 path_mount(): constify struct path argument

In reply to Al Viro

now it finally can be done.

---
 fs/internal.h  | 2 +-
 fs/namespace.c | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)
fs/internal.h +1 -1
index 38e8aab27bbd..fe88563b4822 100644
@@ -84,7 +84,7 @@ void mnt_put_write_access_file(struct file *file);
 extern void dissolve_on_fput(struct vfsmount *);
 extern bool may_mount(void);
 
-int path_mount(const char *dev_name, struct path *path,
+int path_mount(const char *dev_name, const struct path *path,
 		const char *type_page, unsigned long flags, void *data_page);
 int path_umount(struct path *path, int flags);
 
fs/namespace.c +1 -1
index 6ae42f3a9f10..34a71d5cdf88 100644
@@ -4018,7 +4018,7 @@ static char *copy_mount_string(const void __user *data)
  * Therefore, if this magic number is present, it carries no information
  * and must be discarded.
  */
-int path_mount(const char *dev_name, struct path *path,
+int path_mount(const char *dev_name, const struct path *path,
 		const char *type_page, unsigned long flags, void *data_page)
 {
 	unsigned int mnt_flags = 0, sb_flags;
Signature · 1 line
2.47.2
author maintainer
#195 lore raw

PATCH v2 46/63 may_copy_tree(), __do_loopback(): constify struct path argument

In reply to Al Viro
---
 fs/namespace.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)
fs/namespace.c +2 -2
index 34a71d5cdf88..b15632b70223 100644
@@ -2990,7 +2990,7 @@ static int do_change_type(const struct path *path, int ms_flags)
  *
  * Returns true if the mount tree can be copied, false otherwise.
  */
-static inline bool may_copy_tree(struct path *path)
+static inline bool may_copy_tree(const struct path *path)
 {
 	struct mount *mnt = real_mount(path->mnt);
 	const struct dentry_operations *d_op;
@@ -3012,7 +3012,7 @@ static inline bool may_copy_tree(struct path *path)
 }
 
 
-static struct mount *__do_loopback(struct path *old_path, int recurse)
+static struct mount *__do_loopback(const struct path *old_path, int recurse)
 {
 	struct mount *old = real_mount(old_path->mnt);
 
Signature · 1 line
2.47.2
author maintainer
#196 lore raw

PATCH v2 47/63 path_umount(): constify struct path argument

In reply to Al Viro
---
 fs/internal.h  | 2 +-
 fs/namespace.c | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)
fs/internal.h +1 -1
index fe88563b4822..549e6bd453b0 100644
@@ -86,7 +86,7 @@ extern bool may_mount(void);
 
 int path_mount(const char *dev_name, const struct path *path,
 		const char *type_page, unsigned long flags, void *data_page);
-int path_umount(struct path *path, int flags);
+int path_umount(const struct path *path, int flags);
 
 int show_path(struct seq_file *m, struct dentry *root);
 
fs/namespace.c +1 -1
index b15632b70223..a14cb2cabc1a 100644
@@ -2084,7 +2084,7 @@ static int can_umount(const struct path *path, int flags)
 }
 
 // caller is responsible for flags being sane
-int path_umount(struct path *path, int flags)
+int path_umount(const struct path *path, int flags)
 {
 	struct mount *mnt = real_mount(path->mnt);
 	int ret;
Signature · 1 line
2.47.2
author maintainer
#197 lore raw

PATCH v2 48/63 constify can_move_mount_beneath() arguments

In reply to Al Viro
---
 fs/namespace.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)
fs/namespace.c +2 -2
index a14cb2cabc1a..daca5e3bec38 100644
@@ -3472,8 +3472,8 @@ static bool mount_is_ancestor(const struct mount *p1, const struct mount *p2)
  * Context: This function expects namespace_lock() to be held.
  * Return: On success 0, and on error a negative error code is returned.
  */
-static int can_move_mount_beneath(struct mount *mnt_from,
-				  struct mount *mnt_to,
+static int can_move_mount_beneath(const struct mount *mnt_from,
+				  const struct mount *mnt_to,
 				  const struct mountpoint *mp)
 {
 	struct mount *parent_mnt_to = mnt_to->mnt_parent;
Signature · 1 line
2.47.2
author maintainer
#198 lore raw

PATCH v2 49/63 do_move_mount_old(): use __free(path_put)

In reply to Al Viro
---
 fs/namespace.c | 6 ++----
 1 file changed, 2 insertions(+), 4 deletions(-)
fs/namespace.c +2 -4
index daca5e3bec38..a57598ec422a 100644
@@ -3649,7 +3649,7 @@ static int do_move_mount(const struct path *old_path,
 
 static int do_move_mount_old(const struct path *path, const char *old_name)
 {
-	struct path old_path;
+	struct path old_path __free(path_put) = {};
 	int err;
 
 	if (!old_name || !*old_name)
@@ -3659,9 +3659,7 @@ static int do_move_mount_old(const struct path *path, const char *old_name)
 	if (err)
 		return err;
 
-	err = do_move_mount(&old_path, path, 0);
-	path_put(&old_path);
-	return err;
+	return do_move_mount(&old_path, path, 0);
 }
 
 /*
Signature · 1 line
2.47.2
author maintainer
#199 lore raw

PATCH v2 50/63 do_mount(): use __free(path_put)

In reply to Al Viro
---
 fs/namespace.c | 6 ++----
 1 file changed, 2 insertions(+), 4 deletions(-)
fs/namespace.c +2 -4
index a57598ec422a..b290e2b3bcfb 100644
@@ -4098,15 +4098,13 @@ int path_mount(const char *dev_name, const struct path *path,
 int do_mount(const char *dev_name, const char __user *dir_name,
 		const char *type_page, unsigned long flags, void *data_page)
 {
-	struct path path;
+	struct path path __free(path_put) = {};
 	int ret;
 
 	ret = user_path_at(AT_FDCWD, dir_name, LOOKUP_FOLLOW, &path);
 	if (ret)
 		return ret;
-	ret = path_mount(dev_name, &path, type_page, flags, data_page);
-	path_put(&path);
-	return ret;
+	return path_mount(dev_name, &path, type_page, flags, data_page);
 }
 
 static struct ucounts *inc_mnt_namespaces(struct user_namespace *ns)
Signature · 1 line
2.47.2
author maintainer
#200 lore raw

PATCH v2 51/63 umount_tree(): take all victims out of propagation graph at once

In reply to Al Viro

For each removed mount we need to calculate where the slaves will end up. To avoid duplicating that work, do it for all mounts to be removed at once, taking the mounts themselves out of propagation graph as we go, then do all transfers; the duplicate work on finding destinations is avoided since if we run into a mount that already had destination found, we don't need to trace the rest of the way. That's guaranteed O(removed mounts) for finding destinations and removing from propagation graph and O(surviving mounts that have master removed) for transfers.

---
 fs/namespace.c |  3 ++-
 fs/pnode.c     | 67 +++++++++++++++++++++++++++++++++++++++-----------
 fs/pnode.h     |  1 +
 3 files changed, 55 insertions(+), 16 deletions(-)
fs/namespace.c +2 -1
index b290e2b3bcfb..de9a88f45dc1 100644
@@ -1846,6 +1846,8 @@ static void umount_tree(struct mount *mnt, enum umount_tree_flags how)
 	if (how & UMOUNT_PROPAGATE)
 		propagate_umount(&tmp_list);
 
+	bulk_make_private(&tmp_list);
+
 	while (!list_empty(&tmp_list)) {
 		struct mnt_namespace *ns;
 		bool disconnect;
@@ -1870,7 +1872,6 @@ static void umount_tree(struct mount *mnt, enum umount_tree_flags how)
 				umount_mnt(p);
 			}
 		}
-		change_mnt_propagation(p, MS_PRIVATE);
 		if (disconnect)
 			hlist_add_head(&p->mnt_umount, &unmounted);
 
fs/pnode.c +52 -15

88 lines not on this page · raw message

fs/pnode.h +1 -0
index 00ab153e3e9d..b029db225f33 100644
@@ -42,6 +42,7 @@ static inline bool peers(const struct mount *m1, const struct mount *m2)
 }
 
 void change_mnt_propagation(struct mount *, int);
+void bulk_make_private(struct list_head *);
 int propagate_mnt(struct mount *, struct mountpoint *, struct mount *,
 		struct hlist_head *);
 void propagate_umount(struct list_head *);
Signature · 1 line
2.47.2
maintainer Reviewed-by on v2 51/63
#201 lore raw

Re: [PATCH v2 51/63] umount_tree(): take all victims out of propagation graph at once

Quoted 11 lines · Al Viro

For each removed mount we need to calculate where the slaves will end up. To avoid duplicating that work, do it for all mounts to be removed at once, taking the mounts themselves out of propagation graph as we go, then do all transfers; the duplicate work on finding destinations is avoided since if we run into a mount that already had destination found, we don't need to trace the rest of the way. That's guaranteed O(removed mounts) for finding destinations and removing from propagation graph and O(surviving mounts that have master removed) for transfers.

---

author maintainer
#202 lore raw

PATCH v2 52/63 ecryptfs: get rid of pointless mount references in ecryptfs dentries

In reply to Al Viro

->lower_path.mnt has the same value for all dentries on given ecryptfs instance and if somebody goes for mountpoint-crossing variant where that would not be true, we can deal with that when it happens (and _not_ with duplicating these reference into each dentry).

As it is, we are better off just sticking a reference into ecryptfs-private part of superblock and keeping it pinned until ->kill_sb().

That way we can stick a reference to underlying dentry right into ->d_fsdata of ecryptfs one, getting rid of indirection through struct ecryptfs_dentry_info, along with the entire struct ecryptfs_dentry_info machinery.

---
 fs/ecryptfs/dentry.c          | 14 +-------------
 fs/ecryptfs/ecryptfs_kernel.h | 27 +++++++++++----------------
 fs/ecryptfs/file.c            | 15 +++++++--------
 fs/ecryptfs/inode.c           | 19 +++++--------------
 fs/ecryptfs/main.c            | 24 ++++++------------------
 5 files changed, 30 insertions(+), 69 deletions(-)
fs/ecryptfs/dentry.c +1 -13
index 1dfd5b81d831..6648a924e31a 100644
@@ -59,14 +59,6 @@ static int ecryptfs_d_revalidate(struct inode *dir, const struct qstr *name,
 	return rc;
 }
 
-struct kmem_cache *ecryptfs_dentry_info_cache;
-
-static void ecryptfs_dentry_free_rcu(struct rcu_head *head)
-{
-	kmem_cache_free(ecryptfs_dentry_info_cache,
-		container_of(head, struct ecryptfs_dentry_info, rcu));
-}
-
 /**
  * ecryptfs_d_release
  * @dentry: The ecryptfs dentry
@@ -75,11 +67,7 @@ static void ecryptfs_dentry_free_rcu(struct rcu_head *head)
  */
 static void ecryptfs_d_release(struct dentry *dentry)
 {
-	struct ecryptfs_dentry_info *p = dentry->d_fsdata;
-	if (p) {
-		path_put(&p->lower_path);
-		call_rcu(&p->rcu, ecryptfs_dentry_free_rcu);
-	}
+	dput(dentry->d_fsdata);
 }
 
 const struct dentry_operations ecryptfs_dops = {
fs/ecryptfs/ecryptfs_kernel.h +11 -16
index 1f562e75d0e4..9e6ab0b41337 100644
@@ -258,13 +258,6 @@ struct ecryptfs_inode_info {
 	struct ecryptfs_crypt_stat crypt_stat;
 };
 
-/* dentry private data. Each dentry must keep track of a lower
- * vfsmount too. */
-struct ecryptfs_dentry_info {
-	struct path lower_path;
-	struct rcu_head rcu;
-};
-
 /**
  * ecryptfs_global_auth_tok - A key used to encrypt all new files under the mountpoint
  * @flags: Status flags
@@ -348,6 +341,7 @@ struct ecryptfs_mount_crypt_stat {
 /* superblock private data. */
 struct ecryptfs_sb_info {
 	struct super_block *wsi_sb;
+	struct vfsmount *lower_mnt;
 	struct ecryptfs_mount_crypt_stat mount_crypt_stat;
 };
 
@@ -494,22 +488,25 @@ ecryptfs_set_superblock_lower(struct super_block *sb,
 }
 
 static inline void
-ecryptfs_set_dentry_private(struct dentry *dentry,
-			    struct ecryptfs_dentry_info *dentry_info)
+ecryptfs_set_dentry_lower(struct dentry *dentry,
+			  struct dentry *lower_dentry)
 {
-	dentry->d_fsdata = dentry_info;
+	dentry->d_fsdata = lower_dentry;
 }
 
 static inline struct dentry *
 ecryptfs_dentry_to_lower(struct dentry *dentry)
 {
-	return ((struct ecryptfs_dentry_info *)dentry->d_fsdata)->lower_path.dentry;
+	return dentry->d_fsdata;
 }
 
-static inline const struct path *
-ecryptfs_dentry_to_lower_path(struct dentry *dentry)
+static inline struct path
+ecryptfs_lower_path(struct dentry *dentry)
 {
-	return &((struct ecryptfs_dentry_info *)dentry->d_fsdata)->lower_path;
+	return (struct path){
+		.mnt = ecryptfs_superblock_to_private(dentry->d_sb)->lower_mnt,
+		.dentry = ecryptfs_dentry_to_lower(dentry)
+	};
 }
 
 #define ecryptfs_printk(type, fmt, arg...) \
@@ -532,7 +529,6 @@ extern unsigned int ecryptfs_number_of_users;
 
 extern struct kmem_cache *ecryptfs_auth_tok_list_item_cache;
 extern struct kmem_cache *ecryptfs_file_info_cache;
-extern struct kmem_cache *ecryptfs_dentry_info_cache;
 extern struct kmem_cache *ecryptfs_inode_info_cache;
 extern struct kmem_cache *ecryptfs_sb_info_cache;
 extern struct kmem_cache *ecryptfs_header_cache;
@@ -557,7 +553,6 @@ int ecryptfs_encrypt_and_encode_filename(
 	size_t *encoded_name_size,
 	struct ecryptfs_mount_crypt_stat *mount_crypt_stat,
 	const char *name, size_t name_size);
-struct dentry *ecryptfs_lower_dentry(struct dentry *this_dentry);
 void ecryptfs_dump_hex(char *data, int bytes);
 int virt_to_scatterlist(const void *addr, int size, struct scatterlist *sg,
 			int sg_size);
fs/ecryptfs/file.c +7 -8
index 5f8f96da09fe..7929411837cf 100644
@@ -33,13 +33,12 @@ static ssize_t ecryptfs_read_update_atime(struct kiocb *iocb,
 				struct iov_iter *to)
 {
 	ssize_t rc;
-	const struct path *path;
 	struct file *file = iocb->ki_filp;
 
 	rc = generic_file_read_iter(iocb, to);
 	if (rc >= 0) {
-		path = ecryptfs_dentry_to_lower_path(file->f_path.dentry);
-		touch_atime(path);
+		struct path path = ecryptfs_lower_path(file->f_path.dentry);
+		touch_atime(&path);
 	}
 	return rc;
 }
@@ -59,12 +58,11 @@ static ssize_t ecryptfs_splice_read_update_atime(struct file *in, loff_t *ppos,
 						 size_t len, unsigned int flags)
 {
 	ssize_t rc;
-	const struct path *path;
 
 	rc = filemap_splice_read(in, ppos, pipe, len, flags);
 	if (rc >= 0) {
-		path = ecryptfs_dentry_to_lower_path(in->f_path.dentry);
-		touch_atime(path);
+		struct path path = ecryptfs_lower_path(in->f_path.dentry);
+		touch_atime(&path);
 	}
 	return rc;
 }
@@ -283,6 +281,7 @@ static int ecryptfs_dir_open(struct inode *inode, struct file *file)
 	 * ecryptfs_lookup() */
 	struct ecryptfs_file_info *file_info;
 	struct file *lower_file;
+	struct path path;
 
 	/* Released in ecryptfs_release or end of function if failure */
 	file_info = kmem_cache_zalloc(ecryptfs_file_info_cache, GFP_KERNEL);
@@ -292,8 +291,8 @@ static int ecryptfs_dir_open(struct inode *inode, struct file *file)
 				"Error attempting to allocate memory\n");
 		return -ENOMEM;
 	}
-	lower_file = dentry_open(ecryptfs_dentry_to_lower_path(ecryptfs_dentry),
-				 file->f_flags, current_cred());
+	path = ecryptfs_lower_path(ecryptfs_dentry);
+	lower_file = dentry_open(&path, file->f_flags, current_cred());
 	if (IS_ERR(lower_file)) {
 		printk(KERN_ERR "%s: Error attempting to initialize "
 			"the lower file for the dentry with name "
fs/ecryptfs/inode.c +5 -14
index 72fbe1316ab8..d2b262dc485d 100644
@@ -327,24 +327,15 @@ static int ecryptfs_i_size_read(struct dentry *dentry, struct inode *inode)
 static struct dentry *ecryptfs_lookup_interpose(struct dentry *dentry,
 				     struct dentry *lower_dentry)
 {
-	const struct path *path = ecryptfs_dentry_to_lower_path(dentry->d_parent);
+	struct dentry *lower_parent = ecryptfs_dentry_to_lower(dentry->d_parent);
 	struct inode *inode, *lower_inode;
-	struct ecryptfs_dentry_info *dentry_info;
 	int rc = 0;
 
-	dentry_info = kmem_cache_alloc(ecryptfs_dentry_info_cache, GFP_KERNEL);
-	if (!dentry_info) {
-		dput(lower_dentry);
-		return ERR_PTR(-ENOMEM);
-	}
-
 	fsstack_copy_attr_atime(d_inode(dentry->d_parent),
-				d_inode(path->dentry));
+				d_inode(lower_parent));
 	BUG_ON(!d_count(lower_dentry));
 
-	ecryptfs_set_dentry_private(dentry, dentry_info);
-	dentry_info->lower_path.mnt = mntget(path->mnt);
-	dentry_info->lower_path.dentry = lower_dentry;
+	ecryptfs_set_dentry_lower(dentry, lower_dentry);
 
 	/*
 	 * negative dentry can go positive under us here - its parent is not
@@ -1022,10 +1013,10 @@ static int ecryptfs_getattr(struct mnt_idmap *idmap,
 {
 	struct dentry *dentry = path->dentry;
 	struct kstat lower_stat;
+	struct path lower_path = ecryptfs_lower_path(dentry);
 	int rc;
 
-	rc = vfs_getattr_nosec(ecryptfs_dentry_to_lower_path(dentry),
-			       &lower_stat, request_mask, flags);
+	rc = vfs_getattr_nosec(&lower_path, &lower_stat, request_mask, flags);
 	if (!rc) {
 		fsstack_copy_attr_all(d_inode(dentry),
 				      ecryptfs_inode_to_lower(d_inode(dentry)));
fs/ecryptfs/main.c +6 -18
index eab1beb846d3..2afbcbbd9546 100644
@@ -106,15 +106,14 @@ static int ecryptfs_init_lower_file(struct dentry *dentry,
 				    struct file **lower_file)
 {
 	const struct cred *cred = current_cred();
-	const struct path *path = ecryptfs_dentry_to_lower_path(dentry);
+	struct path path = ecryptfs_lower_path(dentry);
 	int rc;
 
-	rc = ecryptfs_privileged_open(lower_file, path->dentry, path->mnt,
-				      cred);
+	rc = ecryptfs_privileged_open(lower_file, path.dentry, path.mnt, cred);
 	if (rc) {
 		printk(KERN_ERR "Error opening lower file "
 		       "for lower_dentry [0x%p] and lower_mnt [0x%p]; "
-		       "rc = [%d]\n", path->dentry, path->mnt, rc);
+		       "rc = [%d]\n", path.dentry, path.mnt, rc);
 		(*lower_file) = NULL;
 	}
 	return rc;
@@ -437,7 +436,6 @@ static int ecryptfs_get_tree(struct fs_context *fc)
 	struct ecryptfs_fs_context *ctx = fc->fs_private;
 	struct ecryptfs_sb_info *sbi = fc->s_fs_info;
 	struct ecryptfs_mount_crypt_stat *mount_crypt_stat;
-	struct ecryptfs_dentry_info *root_info;
 	const char *err = "Getting sb failed";
 	struct inode *inode;
 	struct path path;
@@ -543,14 +541,8 @@ static int ecryptfs_get_tree(struct fs_context *fc)
 		goto out_free;
 	}
 
-	rc = -ENOMEM;
-	root_info = kmem_cache_zalloc(ecryptfs_dentry_info_cache, GFP_KERNEL);
-	if (!root_info)
-		goto out_free;
-
-	/* ->kill_sb() will take care of root_info */
-	ecryptfs_set_dentry_private(s->s_root, root_info);
-	root_info->lower_path = path;
+	ecryptfs_set_dentry_lower(s->s_root, path.dentry);
+	sbi->lower_mnt = path.mnt;
 
 	s->s_flags |= SB_ACTIVE;
 	fc->root = dget(s->s_root);
@@ -580,6 +572,7 @@ static void ecryptfs_kill_block_super(struct super_block *sb)
 	kill_anon_super(sb);
 	if (!sb_info)
 		return;
+	mntput(sb_info->lower_mnt);
 	ecryptfs_destroy_mount_crypt_stat(&sb_info->mount_crypt_stat);
 	kmem_cache_free(ecryptfs_sb_info_cache, sb_info);
 }
@@ -667,11 +660,6 @@ static struct ecryptfs_cache_info {
 		.name = "ecryptfs_file_cache",
 		.size = sizeof(struct ecryptfs_file_info),
 	},
-	{
-		.cache = &ecryptfs_dentry_info_cache,
-		.name = "ecryptfs_dentry_info_cache",
-		.size = sizeof(struct ecryptfs_dentry_info),
-	},
 	{
 		.cache = &ecryptfs_inode_info_cache,
 		.name = "ecryptfs_inode_cache",
Signature · 1 line
2.47.2
author maintainer
#203 lore raw

PATCH v2 53/63 fs/namespace.c: sanitize descriptions for {__,}lookup_mnt()

In reply to Al Viro

Comments regarding "shadow mounts" were stale - no such thing anymore. Document the locking requirements for __lookup_mnt().

---
 fs/namespace.c | 41 ++++++++++++-----------------------------
 1 file changed, 12 insertions(+), 29 deletions(-)
fs/namespace.c +12 -29
index de9a88f45dc1..2e35f5eb4f81 100644
@@ -825,24 +825,16 @@ static bool legitimize_mnt(struct vfsmount *bastard, unsigned seq)
 }
 
 /**
- * __lookup_mnt - find first child mount
+ * __lookup_mnt - mount hash lookup
  * @mnt:	parent mount
- * @dentry:	mountpoint
+ * @dentry:	dentry of mountpoint
  *
- * If @mnt has a child mount @c mounted @dentry find and return it.
+ * If @mnt has a child mount @c mounted on @dentry find and return it.
+ * Caller must either hold the spinlock component of @mount_lock or
+ * hold rcu_read_lock(), sample the seqcount component before the call
+ * and recheck it afterwards.
  *
- * Note that the child mount @c need not be unique. There are cases
- * where shadow mounts are created. For example, during mount
- * propagation when a source mount @mnt whose root got overmounted by a
- * mount @o after path lookup but before @namespace_sem could be
- * acquired gets copied and propagated. So @mnt gets copied including
- * @o. When @mnt is propagated to a destination mount @d that already
- * has another mount @n mounted at the same mountpoint then the source
- * mount @mnt will be tucked beneath @n, i.e., @n will be mounted on
- * @mnt and @mnt mounted on @d. Now both @n and @o are mounted at @mnt
- * on @dentry.
- *
- * Return: The first child of @mnt mounted @dentry or NULL.
+ * Return: The child of @mnt mounted on @dentry or %NULL.
  */
 struct mount *__lookup_mnt(struct vfsmount *mnt, struct dentry *dentry)
 {
@@ -855,21 +847,12 @@ struct mount *__lookup_mnt(struct vfsmount *mnt, struct dentry *dentry)
 	return NULL;
 }
 
-/*
- * lookup_mnt - Return the first child mount mounted at path
- *
- * "First" means first mounted chronologically.  If you create the
- * following mounts:
- *
- * mount /dev/sda1 /mnt
- * mount /dev/sda2 /mnt
- * mount /dev/sda3 /mnt
- *
- * Then lookup_mnt() on the base /mnt dentry in the root mount will
- * return successively the root dentry and vfsmount of /dev/sda1, then
- * /dev/sda2, then /dev/sda3, then NULL.
+/**
+ * lookup_mnt - Return the child mount mounted at given location
+ * @path:	location in the namespace
  *
- * lookup_mnt takes a reference to the found vfsmount.
+ * Acquires and returns a new reference to mount at given location
+ * or %NULL if nothing is mounted there.
  */
 struct vfsmount *lookup_mnt(const struct path *path)
 {
Signature · 1 line
2.47.2
author maintainer
#204 lore raw

PATCH v2 54/63 open_detached_copy(): don't bother with mount_lock_hash()

In reply to Al Viro

we are holding namespace_sem and a reference to root of tree; iterating through that tree does not need mount_lock. Neither does the insertion into the rbtree of new namespace or incrementing the mount count of that namespace.

---
 fs/namespace.c | 2 --
 1 file changed, 2 deletions(-)
fs/namespace.c +0 -2
index 2e35f5eb4f81..425c33377770 100644
@@ -3086,14 +3086,12 @@ static struct file *open_detached_copy(struct path *path, bool recursive)
 		return ERR_CAST(mnt);
 	}
 
-	lock_mount_hash();
 	for (p = mnt; p; p = next_mnt(p, mnt)) {
 		mnt_add_to_ns(ns, p);
 		ns->nr_mounts++;
 	}
 	ns->root = mnt;
 	mntget(&mnt->mnt);
-	unlock_mount_hash();
 	namespace_unlock();
 
 	mntput(path->mnt);
Signature · 1 line
2.47.2
maintainer Reviewed-by on v2 54/63
#205 lore raw

Re: [PATCH v2 54/63] open_detached_copy(): don't bother with mount_lock_hash()

Quoted 7 lines · Al Viro

we are holding namespace_sem and a reference to root of tree; iterating through that tree does not need mount_lock. Neither does the insertion into the rbtree of new namespace or incrementing the mount count of that namespace.

---

author maintainer
#206 lore raw

PATCH v2 55/63 open_detached_copy(): separate creation of namespace into helper

In reply to Al Viro

... and convert the helper to use of a guard(namespace_excl)

---
 fs/namespace.c | 24 +++++++++++++++---------
 1 file changed, 15 insertions(+), 9 deletions(-)
fs/namespace.c +15 -9
index 425c33377770..c324800e770c 100644
@@ -3053,18 +3053,17 @@ static int do_loopback(const struct path *path, const char *old_name,
 	return err;
 }
 
-static struct file *open_detached_copy(struct path *path, bool recursive)
+static struct mnt_namespace *get_detached_copy(const struct path *path, bool recursive)
 {
 	struct mnt_namespace *ns, *mnt_ns = current->nsproxy->mnt_ns, *src_mnt_ns;
 	struct user_namespace *user_ns = mnt_ns->user_ns;
 	struct mount *mnt, *p;
-	struct file *file;
 
 	ns = alloc_mnt_ns(user_ns, true);
 	if (IS_ERR(ns))
-		return ERR_CAST(ns);
+		return ns;
 
-	namespace_lock();
+	guard(namespace_excl)();
 
 	/*
 	 * Record the sequence number of the source mount namespace.
@@ -3081,8 +3080,7 @@ static struct file *open_detached_copy(struct path *path, bool recursive)
 
 	mnt = __do_loopback(path, recursive);
 	if (IS_ERR(mnt)) {
-		namespace_unlock();
-		free_mnt_ns(ns);
+		emptied_ns = ns;
 		return ERR_CAST(mnt);
 	}
 
@@ -3091,11 +3089,19 @@ static struct file *open_detached_copy(struct path *path, bool recursive)
 		ns->nr_mounts++;
 	}
 	ns->root = mnt;
-	mntget(&mnt->mnt);
-	namespace_unlock();
+	return ns;
+}
+
+static struct file *open_detached_copy(struct path *path, bool recursive)
+{
+	struct mnt_namespace *ns = get_detached_copy(path, recursive);
+	struct file *file;
+
+	if (IS_ERR(ns))
+		return ERR_CAST(ns);
 
 	mntput(path->mnt);
-	path->mnt = &mnt->mnt;
+	path->mnt = mntget(&ns->root->mnt);
 	file = dentry_open(path, O_PATH, current_cred());
 	if (IS_ERR(file))
 		dissolve_on_fput(path->mnt);
Signature · 1 line
2.47.2
author maintainer
#208 lore raw

PATCH v2 56/63 mnt_ns_tree_remove(): DTRT if mnt_ns had never been added to mnt_ns_list

In reply to Al Viro

Actual removal is done under the lock, but for checking if need to bother the lockless list_empty() is safe - either that namespace never had never been added to mnt_ns_tree, in which case the list will stay empty, or whoever had allocated it has called mnt_ns_tree_add() and it has already run to completion. After that point list_empty() will become false and will remain false, no matter what we do with the neighbors in mnt_ns_list.

---
 fs/namespace.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
fs/namespace.c +1 -1
index c324800e770c..daa72292ea58 100644
@@ -195,7 +195,7 @@ static void mnt_ns_release_rcu(struct rcu_head *rcu)
 static void mnt_ns_tree_remove(struct mnt_namespace *ns)
 {
 	/* remove from global mount namespace list */
-	if (!is_anon_ns(ns)) {
+	if (!list_empty(&ns->mnt_ns_list)) {
 		mnt_ns_tree_write_lock();
 		rb_erase(&ns->mnt_ns_tree_node, &mnt_ns_tree);
 		list_bidir_del_rcu(&ns->mnt_ns_list);
Signature · 1 line
2.47.2
maintainer Reviewed-by on v2 56/63
#209 lore raw

Re: [PATCH v2 56/63] mnt_ns_tree_remove(): DTRT if mnt_ns had never been added to mnt_ns_list

Quoted 2 lines · Al Viro

Actual removal is done under the lock, but for checking if need to bother the lockless list_empty() is safe - either that namespace never had never

nit: two "never"s

Quoted 7 lines

been added to mnt_ns_tree, in which case the list will stay empty, or whoever had allocated it has called mnt_ns_tree_add() and it has already run to completion. After that point list_empty() will become false and will remain false, no matter what we do with the neighbors in mnt_ns_list.

---

Quoted 19 lines
 fs/namespace.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
fs/namespace.c +1 -1
index c324800e770c..daa72292ea58 100644
@@ -195,7 +195,7 @@ static void mnt_ns_release_rcu(struct rcu_head *rcu)
 static void mnt_ns_tree_remove(struct mnt_namespace *ns)
 {
 	/* remove from global mount namespace list */
-	if (!is_anon_ns(ns)) {
+	if (!list_empty(&ns->mnt_ns_list)) {
 		mnt_ns_tree_write_lock();
 		rb_erase(&ns->mnt_ns_tree_node, &mnt_ns_tree);
 		list_bidir_del_rcu(&ns->mnt_ns_list);
Signature · 1 line
2.47.2
author maintainer
#210 lore raw

PATCH v2 57/63 copy_mnt_ns(): use the regular mechanism for freeing empty mnt_ns on failure

In reply to Al Viro

Now that free_mnt_ns() works prior to mnt_ns_tree_add(), there's no need for an open-coded analogue free_mnt_ns() there - yes, we do avoid one call_rcu() use per failing call of clone() or unshare(), if they fail due to OOM in that particular spot, but it's not really worth bothering.

---
 fs/namespace.c | 4 +---
 1 file changed, 1 insertion(+), 3 deletions(-)
fs/namespace.c +1 -3
index daa72292ea58..a418555586ef 100644
@@ -4190,10 +4190,8 @@ struct mnt_namespace *copy_mnt_ns(unsigned long flags, struct mnt_namespace *ns,
 		copy_flags |= CL_SLAVE;
 	new = copy_tree(old, old->mnt.mnt_root, copy_flags);
 	if (IS_ERR(new)) {
+		emptied_ns = new_ns;
 		namespace_unlock();
-		ns_free_inum(&new_ns->ns);
-		dec_mnt_namespaces(new_ns->ucounts);
-		mnt_ns_release(new_ns);
 		return ERR_CAST(new);
 	}
 	if (user_ns != ns->user_ns) {
Signature · 1 line
2.47.2
maintainer Reviewed-by on v2 57/63
#211 lore raw

Re: [PATCH v2 57/63] copy_mnt_ns(): use the regular mechanism for freeing empty mnt_ns on failure

Quoted 7 lines · Al Viro

Now that free_mnt_ns() works prior to mnt_ns_tree_add(), there's no need for an open-coded analogue free_mnt_ns() there - yes, we do avoid one call_rcu() use per failing call of clone() or unshare(), if they fail due to OOM in that particular spot, but it's not really worth bothering.

---

author maintainer
#212 lore raw

PATCH v2 58/63 copy_mnt_ns(): use guards

In reply to Al Viro

* mntput() of rootmnt and pwdmnt done via __free(mntput)
* mnt_ns_tree_add() can be done within namespace_excl scope.

---
 fs/namespace.c | 17 ++++-------------
 1 file changed, 4 insertions(+), 13 deletions(-)
fs/namespace.c +4 -13
index a418555586ef..9e16231d4561 100644
@@ -4164,7 +4164,8 @@ struct mnt_namespace *copy_mnt_ns(unsigned long flags, struct mnt_namespace *ns,
 		struct user_namespace *user_ns, struct fs_struct *new_fs)
 {
 	struct mnt_namespace *new_ns;
-	struct vfsmount *rootmnt = NULL, *pwdmnt = NULL;
+	struct vfsmount *rootmnt __free(mntput) = NULL;
+	struct vfsmount *pwdmnt __free(mntput) = NULL;
 	struct mount *p, *q;
 	struct mount *old;
 	struct mount *new;
@@ -4183,7 +4184,7 @@ struct mnt_namespace *copy_mnt_ns(unsigned long flags, struct mnt_namespace *ns,
 	if (IS_ERR(new_ns))
 		return new_ns;
 
-	namespace_lock();
+	guard(namespace_excl)();
 	/* First pass: copy the tree topology */
 	copy_flags = CL_COPY_UNBINDABLE | CL_EXPIRE;
 	if (user_ns != ns->user_ns)
@@ -4191,13 +4192,11 @@ struct mnt_namespace *copy_mnt_ns(unsigned long flags, struct mnt_namespace *ns,
 	new = copy_tree(old, old->mnt.mnt_root, copy_flags);
 	if (IS_ERR(new)) {
 		emptied_ns = new_ns;
-		namespace_unlock();
 		return ERR_CAST(new);
 	}
 	if (user_ns != ns->user_ns) {
-		lock_mount_hash();
+		guard(mount_writer)();
 		lock_mnt_tree(new);
-		unlock_mount_hash();
 	}
 	new_ns->root = new;
 
@@ -4229,14 +4228,6 @@ struct mnt_namespace *copy_mnt_ns(unsigned long flags, struct mnt_namespace *ns,
 		while (p->mnt.mnt_root != q->mnt.mnt_root)
 			p = next_mnt(skip_mnt_tree(p), old);
 	}
-	namespace_unlock();
-
-	if (rootmnt)
-		mntput(rootmnt);
-	if (pwdmnt)
-		mntput(pwdmnt);
-
-	mnt_ns_tree_add(new_ns);
 	return new_ns;
 }
 
Signature · 1 line
2.47.2
#213 lore raw

Re: [PATCH v2 58/63] copy_mnt_ns(): use guards

Quoted 48 lines · Al Viro

* mntput() of rootmnt and pwdmnt done via __free(mntput)
* mnt_ns_tree_add() can be done within namespace_excl scope.

---
 fs/namespace.c | 17 ++++-------------
 1 file changed, 4 insertions(+), 13 deletions(-)
fs/namespace.c +4 -5
index a418555586ef..9e16231d4561 100644
@@ -4164,7 +4164,8 @@ struct mnt_namespace *copy_mnt_ns(unsigned long flags, struct mnt_namespace *ns,
 		struct user_namespace *user_ns, struct fs_struct *new_fs)
 {
 	struct mnt_namespace *new_ns;
-	struct vfsmount *rootmnt = NULL, *pwdmnt = NULL;
+	struct vfsmount *rootmnt __free(mntput) = NULL;
+	struct vfsmount *pwdmnt __free(mntput) = NULL;
 	struct mount *p, *q;
 	struct mount *old;
 	struct mount *new;
@@ -4183,7 +4184,7 @@ struct mnt_namespace *copy_mnt_ns(unsigned long flags, struct mnt_namespace *ns,
 	if (IS_ERR(new_ns))
 		return new_ns;
 
-	namespace_lock();
+	guard(namespace_excl)();
 	/* First pass: copy the tree topology */
 	copy_flags = CL_COPY_UNBINDABLE | CL_EXPIRE;
 	if (user_ns != ns->user_ns)
@@ -4191,13 +4192,11 @@ struct mnt_namespace *copy_mnt_ns(unsigned long flags, struct mnt_namespace *ns,
 	new = copy_tree(old, old->mnt.mnt_root, copy_flags);
 	if (IS_ERR(new)) {
 		emptied_ns = new_ns;
-		namespace_unlock();
 		return ERR_CAST(new);
 	}
 	if (user_ns != ns->user_ns) {
-		lock_mount_hash();
+		guard(mount_writer)();
 		lock_mnt_tree(new);
-		unlock_mount_hash();
 	}
 	new_ns->root = new;
 
@@ -4229,14 +4228,6 @@ struct mnt_namespace *copy_mnt_ns(unsigned long flags, struct mnt_namespace *ns,
 		while (p->mnt.mnt_root != q->mnt.mnt_root)

fs/namespace.c struct mnt_namespace *copy_mnt_ns(unsigned long flags, struct mnt_namespace *ns, · line 4239

Quoted 10 lines
fs/namespace.c +0 -8
 			p = next_mnt(skip_mnt_tree(p), old);
 	}
-	namespace_unlock();
-
-	if (rootmnt)
-		mntput(rootmnt);
-	if (pwdmnt)
-		mntput(pwdmnt);
-
-	mnt_ns_tree_add(new_ns);

The commit message states that "mnt_ns_tree_add() can be done within namespace_excl scope" suggesting that all this does is to widen the scope of the lock. But this change also removes the call to mnt_ns_tree_add() completely? Intentional?

author maintainer
#214 lore raw

PATCH v2 59/63 setup_mnt(): primitive for connecting a mount to filesystem

In reply to Al Viro

Take the identical logics in vfs_create_mount() and clone_mnt() into a new helper that takes an empty struct mount and attaches it to given dentry (sub)tree.

Should be called once in the lifetime of every mount, prior to making it visible in any data structures.

After that point ->mnt_root and ->mnt_sb never change; ->mnt_root is a counting reference to dentry and ->mnt_sb - an active reference to superblock.

Mount remains associated with that dentry tree all the way until the call of cleanup_mnt(), when the refcount eventually drops to zero.

---
 fs/namespace.c | 34 +++++++++++++++++-----------------
 1 file changed, 17 insertions(+), 17 deletions(-)
fs/namespace.c +17 -17
index 9e16231d4561..5af609ff43bc 100644
@@ -1195,6 +1195,21 @@ static void commit_tree(struct mount *mnt)
 	touch_mnt_namespace(n);
 }
 
+static void setup_mnt(struct mount *m, struct dentry *root)
+{
+	struct super_block *s = root->d_sb;
+
+	atomic_inc(&s->s_active);
+	m->mnt.mnt_sb = s;
+	m->mnt.mnt_root = dget(root);
+	m->mnt_mountpoint = m->mnt.mnt_root;
+	m->mnt_parent = m;
+
+	lock_mount_hash();
+	list_add_tail(&m->mnt_instance, &s->s_mounts);
+	unlock_mount_hash();
+}
+
 /**
  * vfs_create_mount - Create a mount for a configured superblock
  * @fc: The configuration context with the superblock attached
@@ -1218,15 +1233,8 @@ struct vfsmount *vfs_create_mount(struct fs_context *fc)
 	if (fc->sb_flags & SB_KERNMOUNT)
 		mnt->mnt.mnt_flags = MNT_INTERNAL;
 
-	atomic_inc(&fc->root->d_sb->s_active);
-	mnt->mnt.mnt_sb		= fc->root->d_sb;
-	mnt->mnt.mnt_root	= dget(fc->root);
-	mnt->mnt_mountpoint	= mnt->mnt.mnt_root;
-	mnt->mnt_parent		= mnt;
+	setup_mnt(mnt, fc->root);
 
-	lock_mount_hash();
-	list_add_tail(&mnt->mnt_instance, &mnt->mnt.mnt_sb->s_mounts);
-	unlock_mount_hash();
 	return &mnt->mnt;
 }
 EXPORT_SYMBOL(vfs_create_mount);
@@ -1284,7 +1292,6 @@ EXPORT_SYMBOL_GPL(vfs_kern_mount);
 static struct mount *clone_mnt(struct mount *old, struct dentry *root,
 					int flag)
 {
-	struct super_block *sb = old->mnt.mnt_sb;
 	struct mount *mnt;
 	int err;
 
@@ -1309,16 +1316,9 @@ static struct mount *clone_mnt(struct mount *old, struct dentry *root,
 	if (mnt->mnt_group_id)
 		set_mnt_shared(mnt);
 
-	atomic_inc(&sb->s_active);
 	mnt->mnt.mnt_idmap = mnt_idmap_get(mnt_idmap(&old->mnt));
 
-	mnt->mnt.mnt_sb = sb;
-	mnt->mnt.mnt_root = dget(root);
-	mnt->mnt_mountpoint = mnt->mnt.mnt_root;
-	mnt->mnt_parent = mnt;
-	lock_mount_hash();
-	list_add_tail(&mnt->mnt_instance, &sb->s_mounts);
-	unlock_mount_hash();
+	setup_mnt(mnt, root);
 
 	if (flag & CL_PRIVATE)	// we are done with it
 		return mnt;
Signature · 1 line
2.47.2