mount-related stuff
Subsystems (5)
Al Viro
maintainer Most of this pile is basically an attempt to see how well do
cleanup.h-style mechanisms apply in mount handling. That stuff lives ingit://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 toscoped_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), withunlock_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(-)
Al Viro
maintainerPATCH 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
@@ -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
Al Viro
maintainerPATCH 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
@@ -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
Christian Brauner
maintainerRe: [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_writerform whenmount_locked_readeris sufficent. It needs to be dealt with carefully.Signed-off-by Al Viro <[email protected]>--- fs/mount.h | 5 +++++ 1 file changed, 5 insertions(+)fs/mount.h +5
-0@@ -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
Al Viro
maintainerRe: [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.
Al Viro
maintainerRe: [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 ofmount_lockfirst, 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.
Al Viro
maintainerRe: [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 needmount_locked_readerfor 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 incommit_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 inmount_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()?
Al Viro
maintainerRe: [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_NEWNSclone(2) or unshare(2) failing due to severe OOM. Do we give a damn about one extracall_rcu()for each of such failures?
mnt_ns_treehandling 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
@@ -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;
Re: [PATCH 02/52] introduced guards for mount_lock
In reply to Al ViroAl Viro <[email protected]>:
Quoted 8 lines
When the last reference to
mount past theumount_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 withmount_writer(has to be - there are mount hash changes and for thosemount_writeris 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
Al Viro
maintainerRe: [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 theumount_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 withmount_writer(has to be - there are mount hash changes and for thosemount_writeris 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.
Al Viro
maintainerPATCH 3/52 fs/namespace.c: allow to drop vfsmount references via __free(mntput)
In reply to Al ViroNote 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
@@ -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
Re: [PATCH 03/52] fs/namespace.c: allow to drop vfsmount references via __free(mntput)
Quoted 5 lines · Al Viro
Note that just as
path_put, it should never be done in scope ofnamespace_sem, be it shared or exclusive.Signed-off-by Al Viro <[email protected]>---
Al Viro
maintainerPATCH 4/52 __detach_mounts(): use guards
In reply to Al ViroClean 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
@@ -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
Re: [PATCH 04/52] __detach_mounts(): use guards
Quoted 2 lines · Al Viro
Clean fit for guards use; guards can't be weaker due to
umount_tree()calls.
---
Al Viro
maintainerPATCH 5/52 __is_local_mountpoint(): use guards
In reply to Al Viroclean 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
@@ -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
Re: [PATCH 05/52] __is_local_mountpoint(): use guards
Quoted 4 lines · Al Viro
clean fit;
namespace_shareddue to iterating throughns->mounts.Signed-off-by Al Viro <[email protected]>---
Al Viro
maintainerPATCH 6/52 do_change_type(): use guards
In reply to Al Viroclean fit; namespace_excl to modify propagation graph
--- fs/namespace.c | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-)
fs/namespace.c +6 -7
@@ -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
Re: [PATCH 06/52] do_change_type(): use guards
Quoted 4 lines · Al Viro
clean fit;
namespace_exclto modify propagation graphSigned-off-by Al Viro <[email protected]>---
Al Viro
maintainerPATCH 7/52 do_set_group(): use guards
In reply to Al Viroclean fit; namespace_excl to modify propagation graph
--- fs/namespace.c | 33 +++++++++++++-------------------- 1 file changed, 13 insertions(+), 20 deletions(-)
fs/namespace.c +13 -20
@@ -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
Re: [PATCH 07/52] do_set_group(): use guards
Quoted 4 lines · Al Viro
clean fit;
namespace_exclto modify propagation graphSigned-off-by Al Viro <[email protected]>---
Al Viro
maintainerPATCH 8/52 mark_mounts_for_expiry(): use guards
In reply to Al ViroClean 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
@@ -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
Re: [PATCH 08/52] mark_mounts_for_expiry(): use guards
Quoted 4 lines · Al Viro
Clean fit; guards can't be weaker due to
umount_tree()calls.Signed-off-by Al Viro <[email protected]>---
Al Viro
maintainerPATCH 9/52 put_mnt_ns(): use guards
In reply to Al Viroclean 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
@@ -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
Re: [PATCH 09/52] put_mnt_ns(): use guards
Quoted 6 lines · Al Viro
clean fit; guards can't be weaker due to
umount_tree()call. Settingemptied_nsrequiresnamespace_excl, but not anything mount_lock-related.Signed-off-by Al Viro <[email protected]>---
Christian Brauner
maintainerRe: [PATCH 09/52] put_mnt_ns(): use guards
In reply to Al ViroQuoted 14 lines · Al Viro
clean fit; guards can't be weaker due to
umount_tree()call. Settingemptied_nsrequiresnamespace_excl, but not anything mount_lock-related.Signed-off-by Al Viro <[email protected]>--- fs/namespace.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-)fs/namespace.c +0
-0
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.
Al Viro
maintainerRe: [PATCH 09/52] put_mnt_ns(): use guards
Quoted 8 lines · Christian Brauner
Another thing, did I miss
commit
aab771f34e63ef89e195b63d121abcb55eebfde6Author: 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_namespacetonamespace_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.
Al Viro
maintainerPATCH 10/52 mnt_already_visible(): use guards
In reply to Al Viroclean 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
@@ -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
Re: [PATCH 10/52] mnt_already_visible(): use guards
Quoted 4 lines · Al Viro
clean fit;
namespace_shareddue to iterating throughns->mounts.Signed-off-by Al Viro <[email protected]>---
Al Viro
maintainerPATCH 11/52 check_for_nsfs_mounts(): no need to take locks
In reply to Al ViroCurrently 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
@@ -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
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 eithermount_locked_reader(we are not changing anything, we just want to iterate through the subtree) ornamespace_sharedand 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_sharedas 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_lockincheck_for_nsfs_mounts()itself and document the locking requirements.Signed-off-by Al Viro <[email protected]>---
Al Viro
maintainerPATCH 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
@@ -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
Re: [PATCH 12/52] propagate_mnt(): use scoped_guard(mount_locked_reader) for mnt_set_mountpoint()
Quoted 2 lines · Al Viro
Signed-off-by Al Viro <[email protected]>---
Al Viro
maintainerPATCH 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
@@ -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
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
Al Viro
maintainerRe: [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 doguard(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...
Christian Brauner
maintainerRe: [PATCH 13/52] has_locked_children(): use guards
In reply to Al ViroQuoted 21 lines · Al Viro
... and document the locking requirements of
__has_locked_children()Signed-off-by Al Viro <[email protected]>--- fs/namespace.c | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-)fs/namespace.c +1
-0@@ -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
-6bool 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().
Al Viro
maintainerPATCH 14/52 mnt_set_expiry(): use guards
In reply to Al ViroThe 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
@@ -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
Christian Brauner
maintainerRe: [PATCH 14/52] mnt_set_expiry(): use guards
Quoted 13 lines · Al Viro
The reason why it needs only
mount_locked_readeris that there's no lockless accesses of expiry lists.Signed-off-by Al Viro <[email protected]>--- fs/namespace.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-)fs/namespace.c +0
-0
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.
Al Viro
maintainerPATCH 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
@@ -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
@@ -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
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 indo_listmount()where we are not holdingmount_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 andis_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()Signed-off-by Al Viro <[email protected]>---
Quoted 19 lines
fs/namespace.c | 12 ++++++------ fs/pnode.c | 3 ++- 2 files changed, 8 insertions(+), 7 deletions(-)fs/namespace.c +1
-1@@ -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
-5bool 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.
Al Viro
maintainerPATCH 16/52 current_chrooted(): don't bother with follow_down_one()
In reply to Al ViroAll 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
@@ -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 = ¤t->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
Re: [PATCH 16/52] current_chrooted(): don't bother with follow_down_one()
Quoted 4 lines · Al Viro
All we need here is to follow ->overmount on root mount of namespace...
Signed-off-by Al Viro <[email protected]>---
Al Viro
maintainerPATCH 17/52 current_chrooted(): use guards
In reply to Al Virohere 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
@@ -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
Re: [PATCH 17/52] current_chrooted(): use guards
Quoted 5 lines · Al Viro
here a use of
__free(path_put)for droppingfs_rootis enough to makeguard(mount_locked_reader)fit...Signed-off-by Al Viro <[email protected]>---
Al Viro
maintainerPATCH 18/52 do_move_mount(): trim local variables
In reply to Al ViroBoth '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
@@ -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
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...
Signed-off-by Al Viro <[email protected]>---
Quoted 55 lines
fs/namespace.c | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-)fs/namespace.c +4
-8@@ -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
Al Viro
maintainerPATCH 19/52 do_move_mount(): deal with the checks on old_path early
In reply to Al Viro1) 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
@@ -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
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 switchingnew_pathto 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->dentryis the root ofold_path->mnt, there's no point bothering withpath_is_overmounted()incan_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 switchcan_move_mount_beneath()to taking old instead ofold_path, leaving no uses ofold_pathpast the original checks.
Quoted 2 lines
Signed-off-by Al Viro <[email protected]>---
Al Viro
maintainerPATCH 20/52 move_mount(2): take sanity checks in 'beneath' case into do_lock_mount()
In reply to Al ViroWe 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
@@ -2768,12 +2768,19 @@ static int do_lock_mount(struct path *path, struct pinned_mountpoint *pinned, bostruct 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
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
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
Re: [PATCH 20/52] move_mount(2): take sanity checks in 'beneath' case into do_lock_mount()
In reply to Al ViroQuoted 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.Signed-off-by Al Viro <[email protected]>---
Well, do_lock_mount() was already convoluted enough that didn't want that in there as well. But I don't care,
Al Viro
maintainerRe: [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
Signed-off-by Al Viro <[email protected]>---
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...
Al Viro
maintainerPATCH 21/52 finish_automount(): simplify the ELOOP check
In reply to Al ViroIt'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
@@ -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
Re: [PATCH 21/52] finish_automount(): simplify the ELOOP check
Quoted 5 lines · Al Viro
It's enough to check that dentries match; if
path->dentryis equal tom->mnt_root, superblocks will match as well.Signed-off-by Al Viro <[email protected]>---
Al Viro
maintainerPATCH 22/52 do_loopback(): use __free(path_put) to deal with old_path
In reply to Al Viropreparations 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
@@ -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
Re: [PATCH 22/52] do_loopback(): use __free(path_put) to deal with old_path
Quoted 5 lines · Al Viro
preparations for making
unlock_mount()a__cleanup(); can't havepath_put()insidemount_lockscope.Signed-off-by Al Viro <[email protected]>---
Al Viro
maintainerPATCH 23/52 pivot_root(2): use __free() to deal with struct path in it
In reply to Al Viropreparations 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
@@ -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
Re: [PATCH 23/52] pivot_root(2): use __free() to deal with struct path in it
Quoted 5 lines · Al Viro
preparations for making
unlock_mount()a__cleanup(); can't havepath_put()insidemount_lockscope.Signed-off-by Al Viro <[email protected]>---
Al Viro
maintainerPATCH 24/52 finish_automount(): take the lock_mount() analogue into a helper
In reply to Al Virofinish_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
@@ -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
Re: [PATCH 24/52] finish_automount(): take the lock_mount() analogue into a helper
Quoted 9 lines · Al Viro
finish_automount()can't uselock_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 tolock_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.Signed-off-by Al Viro <[email protected]>---
Quoted 64 lines
fs/namespace.c | 42 +++++++++++++++++++++++++----------------- 1 file changed, 25 insertions(+), 17 deletions(-)fs/namespace.c +25
-14@@ -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);
Al Viro
maintainerPATCH 25/52 do_new_mount_rc(): use __free() to deal with dropping mnt on failure
In reply to Al Virodo_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
@@ -3694,7 +3694,6 @@ static bool mount_too_revealing(const struct super_block *sb, int *new_mnt_flagsstatic 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
Christian Brauner
maintainerRe: [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 conditionalretain_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.Signed-off-by Al Viro <[email protected]>---
fs/namespace.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-)fs/namespace.c +0
-1@@ -3694,7 +3694,6 @@ static bool mount_too_revealing(const struct super_block *sb, int *new_mnt_flagsstatic 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
-2if (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
Al Viro
maintainerRe: [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.
Christian Brauner
maintainerRe: [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);Al Viro
maintainerRe: [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
@@ -3689,24 +3689,22 @@ static bool mount_too_revealing(const struct super_block *sb, int *new_mnt_flagsstatic 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);
Al Viro
maintainerRe: [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()andmount_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
@@ -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);
Al Viro
maintainerRFC 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
@@ -3721,25 +3721,19 @@ static bool mount_too_revealing(const struct super_block *sb, int *new_mnt_flagsstatic 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);
Paul Moore
maintainerRe: [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 successfulvfs_get_tree(). Thendo_new_mount_fc()does several checks on resulting superblock, and either doesfc_drop_locked()and returns an error or proceeds to unlock the superblock and callvfs_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 tounlock ->s_umount put_fs_context()Doing
vfs_create_mount()before the checks allows us to movevfs_get_tree()from caller todo_new_mount_fc()and collapse it withvfs_create_mount()into anfc_mount()call.Signed-off-by Al Viro <[email protected]>
Signature · 1 line
paul-moore.com
Al Viro
maintainerPATCH 26/52 finish_automount(): use __free() to deal with dropping mnt on failure
In reply to Al Virosame story as with do_new_mount_fc().
--- fs/namespace.c | 22 ++++++++-------------- 1 file changed, 8 insertions(+), 14 deletions(-)
fs/namespace.c +8 -14
@@ -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
Re: [PATCH 26/52] finish_automount(): use __free() to deal with dropping mnt on failure
Quoted 4 lines · Al Viro
same story as with
do_new_mount_fc().Signed-off-by Al Viro <[email protected]>---
Ah right, here it is what I suggested earlier,
Al Viro
maintainerPATCH 27/52 change calling conventions for lock_mount() et.al.
In reply to Al Viro1) 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
@@ -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, boread_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_flagsstatic 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
Al Viro
maintainerPATCH 28/52 do_move_mount(): use the parent mount returned by do_lock_mount()
In reply to Al ViroAfter 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
@@ -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
Al Viro
maintainerPATCH 29/52 do_add_mount(): switch to passing pinned_mountpoint instead of mountpoint + path
In reply to Al ViroBoth 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
@@ -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
Al Viro
maintainerPATCH 30/52 graft_tree(), attach_recursive_mnt() - pass pinned_mountpoint
In reply to Al Viroparent 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
@@ -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 *mpreturn -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
Al Viro
maintainerPATCH 31/52 pivot_root(2): use old_mp.mp->m_dentry instead of old.dentry
In reply to Al ViroThat 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
@@ -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
Re: [PATCH 31/52] pivot_root(2): use old_mp.mp->m_dentry instead of old.dentry
Quoted 5 lines · Al Viro
That kills the last place where callers of
lock_mount(path, &mp)usedpath->dentry.Signed-off-by Al Viro <[email protected]>---
Al Viro
maintainerPATCH 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
@@ -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
Al Viro
maintainerPATCH 33/52 new helper: topmost_overmount()
In reply to Al ViroReturns 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
@@ -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
@@ -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
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 ofmount_lockbefore the call and recheck afterwards.Signed-off-by Al Viro <[email protected]>---
Al Viro
maintainerPATCH 34/52 do_lock_mount(): don't modify path.
In reply to Al ViroCurrently 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
@@ -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
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
Al Viro
maintainerPATCH 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
@@ -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
Re: [PATCH 35/52] constify check_mnt()
Quoted 2 lines · Al Viro
Signed-off-by Al Viro <[email protected]>---
Al Viro
maintainerPATCH 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
@@ -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
Re: [PATCH 36/52] do_mount_setattr(): constify path argument
Quoted 2 lines · Al Viro
Signed-off-by Al Viro <[email protected]>---
Al Viro
maintainerPATCH 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
@@ -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
Re: [PATCH 37/52] do_set_group(): constify path arguments
Quoted 2 lines · Al Viro
Signed-off-by Al Viro <[email protected]>---
Al Viro
maintainerPATCH 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
@@ -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
@@ -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
@@ -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
Re: [PATCH 38/52] drop_collected_paths(): constify arguments
Quoted 4 lines · Al Viro
... and use that to constify the pointers in callers
Signed-off-by Al Viro <[email protected]>---
Al Viro
maintainerPATCH 39/52 collect_paths(): constify the return value
In reply to Al Virocallers 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
@@ -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
@@ -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
Re: [PATCH 39/52] collect_paths(): constify the return value
Quoted 4 lines · Al Viro
callers have no business modifying the paths they get
Signed-off-by Al Viro <[email protected]>---
Al Viro
maintainerPATCH 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
@@ -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
Re: [PATCH 40/52] do_move_mount(), vfs_move_mount(), do_move_mount_old(): constify struct path argument(s)
Quoted 2 lines · Al Viro
Signed-off-by Al Viro <[email protected]>---
Al Viro
maintainerPATCH 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
@@ -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
Re: [PATCH 41/52] mnt_warn_timestamp_expiry(): constify struct path argument
Quoted 2 lines · Al Viro
Signed-off-by Al Viro <[email protected]>---
Al Viro
maintainerPATCH 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
@@ -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
Re: [PATCH 42/52] do_new_mount{,_fc}(): constify struct path argument
Quoted 2 lines · Al Viro
Signed-off-by Al Viro <[email protected]>---
Al Viro
maintainerPATCH 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
@@ -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
Re: [PATCH 43/52] do_{loopback,change_type,remount,reconfigure_mnt}(): constify struct path argument
Quoted 2 lines · Al Viro
Signed-off-by Al Viro <[email protected]>---
Al Viro
maintainerPATCH 44/52 path_mount(): constify struct path argument
In reply to Al Vironow 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
@@ -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
@@ -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
Re: [PATCH 44/52] path_mount(): constify struct path argument
Quoted 4 lines · Al Viro
now it finally can be done.
Signed-off-by Al Viro <[email protected]>---
Al Viro
maintainerPATCH 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
@@ -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
Re: [PATCH 45/52] may_copy_tree(), __do_loopback(): constify struct path argument
Quoted 2 lines · Al Viro
Signed-off-by Al Viro <[email protected]>---
Al Viro
maintainerPATCH 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
@@ -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
@@ -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
Re: [PATCH 46/52] path_umount(): constify struct path argument
Quoted 2 lines · Al Viro
Signed-off-by Al Viro <[email protected]>---
Re: [PATCH 46/52] path_umount(): constify struct path argument
In reply to Al ViroPlease, 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.)
Signature · 1 line
Askar Safin
Al Viro
maintainerRe: [PATCH 46/52] path_umount(): constify struct path argument
Quoted 9 lines · Askar Safin
Please, drop this patch.
I plan to inline
path_umountinto the only caller in v2 version of my initrd removal patchset
https://lore.kernel.org/lkml/[email protected]/
.(
path_umountis 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...
Al Viro
maintainerPATCH 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
@@ -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
Re: [PATCH 47/52] constify can_move_mount_beneath() arguments
Quoted 2 lines · Al Viro
Signed-off-by Al Viro <[email protected]>---
Al Viro
maintainerPATCH 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
@@ -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
Re: [PATCH 48/52] do_move_mount_old(): use __free(path_put)
Quoted 2 lines · Al Viro
Signed-off-by Al Viro <[email protected]>---
Al Viro
maintainerPATCH 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
@@ -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
Re: [PATCH 49/52] do_mount(): use __free(path_put)
Quoted 2 lines · Al Viro
Signed-off-by Al Viro <[email protected]>---
Al Viro
maintainerPATCH 50/52 umount_tree(): take all victims out of propagation graph at once
In reply to Al ViroFor 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
@@ -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
@@ -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
Al Viro
maintainerPATCH 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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
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 structecryptfs_dentry_infomachinery.Signed-off-by Al Viro <[email protected]>---
Al Viro
maintainerPATCH 52/52 fs/namespace.c: sanitize descriptions for {__,}lookup_mnt()
In reply to Al ViroComments 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
@@ -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
Re: [PATCH 52/52] fs/namespace.c: sanitize descriptions for {__,}lookup_mnt()
Quoted 5 lines · Al Viro
Comments regarding "shadow mounts" were stale - no such thing anymore. Document the locking requirements for
__lookup_mnt().Signed-off-by Al Viro <[email protected]>---
Re: [PATCH 01/52] fs/namespace.c: fix the namespace_sem guard mess
In reply to Al ViroQuoted 11 lines · Al Viro
If anything,
namespace_lockshould beDEFINE_LOCK_GUARD_0, notDEFINE_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_semshared.Rename it to
namespace_excl, addnamespace_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)Signed-off-by Al Viro <[email protected]>---
Christian Brauner
maintainerSo 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 ingit://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 takingmount_lockfor no reason whatsoever; I already see places where we dowrite_seqlockwhenread_seqlock_exclwould 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 infs/namespace.calone. 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 thenamespace_semguard mess
New guards:namespace_exclandnamespace_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 formount_lock
New guards:mount_writer,mount_locked_reader. That'swrite_seqlock
andread_seqlock_exclonmount_lock; obviously, nothing blocking should be done in scope of those.
3/52)fs/namespace.c: allow to drop vfsmount references via__free(mntput)
MissingDEFINE_FREE(formntput()); local infs/namespace.c, to be
used only for keeping shit out ofnamespace_... andmount_... 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 guards10/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 inmount_locked_readerscope is safe, all right, but (1)mount_writeris 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(): usescoped_guard(mount_locked_reader)formnt_set_mountpoint()
This one is interesting - existing code had been equivalent toscoped_guard(mount_locked_reader), and it's right for that call. However,mnt_set_mountpoint()generally requiresmount_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 guardsmore clear-cut conversions with explanations.
16/52)current_chrooted(): don't bother withfollow_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_exclscope
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 innamespace_exclscopes.
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 forfinish_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), withunlock_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 bylock_mount(). That excludesDEFINE_CLASS()- it wants the value formed and then copied to the variable we are defining.
* it contains an implicitnamespace_exclscope, sopath_put()and its ilk *must* be done after theunlock_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 structpinned_mountpoint. Havinglock_mount()
failure reported by setting it toERR_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 withLOCK_MOUNT(mp, path)which defines structpinned_mountpointmp 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 checkGetting 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 helperthis one turns the open-coded logics into
lock_mount_exact()with
the same kind of calling conventions aslock_mount()anddo_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 failureThis is the main part:
27/52) change calling conventions forlock_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_mountpointPart 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 modifiedpath->mnt. There's only one place (incan_move_mount_beneath()) where we still want that and it's not hard to reconstruct the value by *original*path->mntvalue + parent mount to be used.Getting rid of ->dentry uses.31/52)
pivot_root(2): useold_mp.mp->m_dentryinstead of old.dentry 32/52) don't bother passingnew_path->dentrytocan_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
indo_move_mount()to adjust the argument it passes tocan_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 givenecryptfs 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 structecryptfs_dentry_info, along with the entire structecryptfs_dentry_infomachinery.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(-)
Christian Brauner
maintainerQuoted 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 ingit://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 takingmount_lockfor no reason whatsoever; I already see places where we dowrite_seqlockwhenread_seqlock_exclwould 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 infs/namespace.calone. 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 thenamespace_semguard mess
New guards:namespace_exclandnamespace_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 formount_lock
New guards:mount_writer,mount_locked_reader. That'swrite_seqlock
andread_seqlock_exclonmount_lock; obviously, nothing blocking should be done in scope of those.
3/52)fs/namespace.c: allow to drop vfsmount references via__free(mntput)
MissingDEFINE_FREE(formntput()); local infs/namespace.c, to be
used only for keeping shit out ofnamespace_... andmount_... 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 guards10/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 inmount_locked_readerscope is safe, all right, but (1)mount_writeris 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(): usescoped_guard(mount_locked_reader)formnt_set_mountpoint()
This one is interesting - existing code had been equivalent toscoped_guard(mount_locked_reader), and it's right for that call. However,mnt_set_mountpoint()generally requiresmount_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 guardsmore clear-cut conversions with explanations.
16/52)current_chrooted(): don't bother withfollow_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_exclscope
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 innamespace_exclscopes.
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 forfinish_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), withunlock_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 bylock_mount(). That excludesDEFINE_CLASS()- it wants the value formed and then copied to the variable we are defining.
* it contains an implicitnamespace_exclscope, sopath_put()and its ilk *must* be done after theunlock_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 structpinned_mountpoint. Havinglock_mount()
failure reported by setting it toERR_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 withLOCK_MOUNT(mp, path)which defines structpinned_mountpointmp 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 checkGetting 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 helperthis one turns the open-coded logics into
lock_mount_exact()with
the same kind of calling conventions aslock_mount()anddo_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 failureThis is the main part:
27/52) change calling conventions forlock_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_mountpointPart 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 modifiedpath->mnt. There's only one place (incan_move_mount_beneath()) where we still want that and it's not hard to reconstruct the value by *original*path->mntvalue + parent mount to be used.Getting rid of ->dentry uses.31/52)
pivot_root(2): useold_mp.mp->m_dentryinstead of old.dentry 32/52) don't bother passingnew_path->dentrytocan_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
indo_move_mount()to adjust the argument it passes tocan_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 givenecryptfs 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 structecryptfs_dentry_info, along with the entire structecryptfs_dentry_infomachinery.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(-)
Al Viro
maintainerQuoted 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 ingit://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...
Al Viro
maintainerQuoted 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 ingit://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...
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
Christian Brauner
maintainerQuoted 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 ingit://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.
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
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
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
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
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
Al Viro
maintainerQuoted 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.
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
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
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
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
Al Viro
maintainerRFC PATCH v2 1/1 mount-related stuff
In reply to Al ViroBranch force-pushed intogit://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(-)
Al Viro
maintainerPATCH 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
@@ -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
Al Viro
maintainerPATCH 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
@@ -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
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_writerform whenmount_locked_readeris sufficent. It needs to be dealt with carefully.Signed-off-by Al Viro <[email protected]>---
Al Viro
maintainerPATCH v2 3/63 fs/namespace.c: allow to drop vfsmount references via __free(mntput)
In reply to Al ViroNote 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
@@ -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
Al Viro
maintainerPATCH v2 4/63 __detach_mounts(): use guards
In reply to Al ViroClean 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
@@ -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
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:
Al Viro
maintainerPATCH v2 5/63 __is_local_mountpoint(): use guards
In reply to Al Viroclean 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
@@ -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
Al Viro
maintainerPATCH v2 6/63 do_change_type(): use guards
In reply to Al Viroclean fit; namespace_excl to modify propagation graph
--- fs/namespace.c | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-)
fs/namespace.c +6 -7
@@ -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
Al Viro
maintainerPATCH v2 7/63 do_set_group(): use guards
In reply to Al Viroclean fit; namespace_excl to modify propagation graph
--- fs/namespace.c | 33 +++++++++++++-------------------- 1 file changed, 13 insertions(+), 20 deletions(-)
fs/namespace.c +13 -20
@@ -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
Al Viro
maintainerPATCH v2 8/63 mark_mounts_for_expiry(): use guards
In reply to Al ViroClean 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
@@ -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
Al Viro
maintainerPATCH v2 9/63 put_mnt_ns(): use guards
In reply to Al Viroclean 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
@@ -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
Al Viro
maintainerPATCH v2 10/63 mnt_already_visible(): use guards
In reply to Al Viroclean 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
@@ -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
Al Viro
maintainerPATCH v2 11/63 check_for_nsfs_mounts(): no need to take locks
In reply to Al ViroCurrently 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
@@ -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
Al Viro
maintainerPATCH 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
@@ -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
Al Viro
maintainerPATCH 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
@@ -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
Re: [PATCH v2 13/63] has_locked_children(): use guards
Quoted 4 lines · Al Viro
... and document the locking requirements of
__has_locked_children()Signed-off-by Al Viro <[email protected]>---
Al Viro
maintainerPATCH v2 14/63 mnt_set_expiry(): use guards
In reply to Al ViroThe 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
@@ -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
Re: [PATCH v2 14/63] mnt_set_expiry(): use guards
Quoted 5 lines · Al Viro
The reason why it needs only
mount_locked_readeris that there's no lockless accesses of expiry lists.Signed-off-by Al Viro <[email protected]>---
Al Viro
maintainerPATCH 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
@@ -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
@@ -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
Al Viro
maintainerPATCH v2 16/63 current_chrooted(): don't bother with follow_down_one()
In reply to Al ViroAll 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
@@ -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 = ¤t->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
Al Viro
maintainerPATCH v2 17/63 current_chrooted(): use guards
In reply to Al Virohere 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
@@ -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
Al Viro
maintainerPATCH v2 18/63 switch do_new_mount_fc() to fc_mount()
In reply to Al ViroPrior 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
@@ -3705,25 +3705,20 @@ static bool mount_too_revealing(const struct super_block *sb, int *new_mnt_flagsstatic 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
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 successfulvfs_get_tree(). Thendo_new_mount_fc()does several checks on resulting superblock, and either doesfc_drop_locked()and returns an error or proceeds to unlock the superblock and callvfs_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 tounlock ->s_umount put_fs_context()
Quoted 6 lines
Doing
vfs_create_mount()before the checks allows us to movevfs_get_tree()from caller todo_new_mount_fc()and collapse it withvfs_create_mount()into anfc_mount()call.Signed-off-by Al Viro <[email protected]>---
Quoted 10 lines
fs/namespace.c | 29 ++++++++++++----------------- 1 file changed, 12 insertions(+), 17 deletions(-)fs/namespace.c +0
-0@@ -3705,25 +3705,20 @@ static bool mount_too_revealing(const struct super_block *sb, int *new_mnt_flagsstatic 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.
Al Viro
maintainerPATCH v2 19/63 do_move_mount(): trim local variables
In reply to Al ViroBoth '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
@@ -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
Al Viro
maintainerPATCH v2 20/63 do_move_mount(): deal with the checks on old_path early
In reply to Al Viro1) 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
@@ -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
Al Viro
maintainerPATCH v2 21/63 move_mount(2): take sanity checks in 'beneath' case into do_lock_mount()
In reply to Al ViroWe 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
@@ -2768,12 +2768,19 @@ static int do_lock_mount(struct path *path, struct pinned_mountpoint *pinned, bostruct 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
Al Viro
maintainerPATCH v2 22/63 finish_automount(): simplify the ELOOP check
In reply to Al ViroIt'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
@@ -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
Al Viro
maintainerPATCH v2 23/63 do_loopback(): use __free(path_put) to deal with old_path
In reply to Al Viropreparations 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
@@ -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
Al Viro
maintainerPATCH v2 24/63 pivot_root(2): use __free() to deal with struct path in it
In reply to Al Viropreparations 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
@@ -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
Al Viro
maintainerPATCH v2 25/63 finish_automount(): take the lock_mount() analogue into a helper
In reply to Al Virofinish_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
@@ -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
Al Viro
maintainerPATCH v2 26/63 do_new_mount_rc(): use __free() to deal with dropping mnt on failure
In reply to Al Virodo_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
@@ -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
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 conditionalretain_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.Signed-off-by Al Viro <[email protected]>---
Al Viro
maintainerPATCH v2 27/63 finish_automount(): use __free() to deal with dropping mnt on failure
In reply to Al Virosame story as with do_new_mount_fc().
--- fs/namespace.c | 22 ++++++++-------------- 1 file changed, 8 insertions(+), 14 deletions(-)
fs/namespace.c +8 -14
@@ -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
Al Viro
maintainerPATCH v2 28/63 change calling conventions for lock_mount() et.al.
In reply to Al Viro1) 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
@@ -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, boread_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_flagsstatic 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
Re: [PATCH v2 28/63] change calling conventions for lock_mount() et.al.
Quoted 18 lines · Al Viro
1)
pinned_mountpointgets 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 topath->mnt
* on success with beneath - the parent of mount corresponding
topath->mnt
* in case of error -ERR_PTR(-E...).
IOW, we get the mount we will be actually mounting upon orERR_PTR().3) we can't use CLASS, since the
pinned_mountpointis 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 structpinned_mountpointmp, withunlock_mountdone via__cleanup().
Quoted 4 lines
Users converted.
Signed-off-by Al Viro <[email protected]>---
This is nice! Thanks!
Al Viro
maintainerPATCH v2 29/63 do_move_mount(): use the parent mount returned by do_lock_mount()
In reply to Al ViroAfter 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
@@ -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
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 eitherreal_mount(path->mnt)(for !beneath case) or to ->mnt_parent of that (for beneath). p is set toreal_mount(path->mnt)and after several uses it's made equal to mp.parent. All uses prior to that care only aboutp->mnt_nsand sincep->mnt_ns==parent->mnt_ns, we might as well use mp.parent all along.Signed-off-by Al Viro <[email protected]>---
Al Viro
maintainerPATCH v2 30/63 do_add_mount(): switch to passing pinned_mountpoint instead of mountpoint + path
In reply to Al ViroBoth 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
@@ -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
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_mountpointand path it corresponds to.First of all,
path->dentryis equal tomp.mp->m_dentry. Furthermore,path->mntis&mp.parent->mnt, making struct path contents redundant.
Quoted 7 lines
Pass it the address of that
pinned_mountpointinstead; what's more, if we teach it to treatERR_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 afterlock_mount()failure.Signed-off-by Al Viro <[email protected]>---
Al Viro
maintainerPATCH v2 31/63 graft_tree(), attach_recursive_mnt() - pass pinned_mountpoint
In reply to Al Viroparent 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
@@ -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 *mpreturn -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
Re: [PATCH v2 31/63] graft_tree(), attach_recursive_mnt() - pass pinned_mountpoint
Quoted 5 lines · Al Viro
parent and mountpoint always come from the same struct
pinned_mountpointnow.Signed-off-by Al Viro <[email protected]>---
Al Viro
maintainerPATCH v2 32/63 pivot_root(2): use old_mp.mp->m_dentry instead of old.dentry
In reply to Al ViroThat 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
@@ -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
Al Viro
maintainerPATCH 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
@@ -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
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
Al Viro
maintainerRe: [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.
Al Viro
maintainerPATCH v2 34/63 new helper: topmost_overmount()
In reply to Al ViroReturns 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
@@ -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
@@ -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
Al Viro
maintainerPATCH v2 35/63 do_lock_mount(): don't modify path.
In reply to Al ViroCurrently 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
@@ -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
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 (indo_move_mount()) currently uses the resultingpath->mnt, and that value is trivial to reconstruct by the original value ofpath->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
asmount_locked_reader
find the prospective parent mount/mountpoint dentry grab references if it's not the original target
lock the prospective mountpoint dentry
takenamespace_semexclusive
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 ==-EAGAINA 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 undernamespace_sem, so we need to be sure that these are not the last references remaining. However, on success we'd already verified (undernamespace_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.Signed-off-by Al Viro <[email protected]>--- fs/namespace.c | 126 ++++++++++++++++++++++++++----------------------- 1 file changed, 68 insertions(+), 58 deletions(-)fs/namespace.c +0
-0@@ -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.
Al Viro
maintainerPATCH 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
@@ -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
Al Viro
maintainerPATCH 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
@@ -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
Al Viro
maintainerPATCH 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
@@ -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
Al Viro
maintainerPATCH 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
@@ -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
@@ -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
@@ -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
Al Viro
maintainerPATCH v2 40/63 collect_paths(): constify the return value
In reply to Al Virocallers 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
@@ -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
@@ -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
Al Viro
maintainerPATCH 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
@@ -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
Al Viro
maintainerPATCH 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
@@ -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
Al Viro
maintainerPATCH 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
@@ -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
Al Viro
maintainerPATCH 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
@@ -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
Al Viro
maintainerPATCH v2 45/63 path_mount(): constify struct path argument
In reply to Al Vironow 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
@@ -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
@@ -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
Al Viro
maintainerPATCH 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
@@ -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
Al Viro
maintainerPATCH 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
@@ -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
@@ -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
Al Viro
maintainerPATCH 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
@@ -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
Al Viro
maintainerPATCH 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
@@ -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
Al Viro
maintainerPATCH 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
@@ -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
Al Viro
maintainerPATCH v2 51/63 umount_tree(): take all victims out of propagation graph at once
In reply to Al ViroFor 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
@@ -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
@@ -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
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.
Signed-off-by Al Viro <[email protected]>---
Al Viro
maintainerPATCH 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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
Al Viro
maintainerPATCH v2 53/63 fs/namespace.c: sanitize descriptions for {__,}lookup_mnt()
In reply to Al ViroComments 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
@@ -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
Al Viro
maintainerPATCH v2 54/63 open_detached_copy(): don't bother with mount_lock_hash()
In reply to Al Virowe 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
@@ -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
Re: [PATCH v2 54/63] open_detached_copy(): don't bother with mount_lock_hash()
Quoted 7 lines · Al Viro
we are holding
namespace_semand a reference to root of tree; iterating through that tree does not needmount_lock. Neither does the insertion into the rbtree of new namespace or incrementing the mount count of that namespace.Signed-off-by Al Viro <[email protected]>---
Al Viro
maintainerPATCH 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
@@ -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
Re: [PATCH v2 55/63] open_detached_copy(): separate creation of namespace into helper
Quoted 4 lines · Al Viro
... and convert the helper to use of a
guard(namespace_excl)Signed-off-by Al Viro <[email protected]>---
Al Viro
maintainerPATCH v2 56/63 mnt_ns_tree_remove(): DTRT if mnt_ns had never been added to mnt_ns_list
In reply to Al ViroActual 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
@@ -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
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 calledmnt_ns_tree_add()and it has already run to completion. After that pointlist_empty()will become false and will remain false, no matter what we do with the neighbors inmnt_ns_list.Signed-off-by Al Viro <[email protected]>---
Quoted 19 lines
fs/namespace.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)fs/namespace.c +1
-1@@ -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
Al Viro
maintainerPATCH v2 57/63 copy_mnt_ns(): use the regular mechanism for freeing empty mnt_ns on failure
In reply to Al ViroNow 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
@@ -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
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 tomnt_ns_tree_add(), there's no need for an open-coded analoguefree_mnt_ns()there - yes, we do avoid onecall_rcu()use per failing call ofclone()orunshare(), if they fail due to OOM in that particular spot, but it's not really worth bothering.Signed-off-by Al Viro <[email protected]>---
Al Viro
maintainerPATCH 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
@@ -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
Christian Brauner
maintainerRe: [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 withinnamespace_exclscope.Signed-off-by Al Viro <[email protected]>--- fs/namespace.c | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-)fs/namespace.c +4
-5@@ -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
-8p =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?
Al Viro
maintainerPATCH v2 59/63 setup_mnt(): primitive for connecting a mount to filesystem
In reply to Al ViroTake 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
@@ -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