-----BEGIN PGP SIGNATURE-----
 
 iHUEABYKAB0WIQRAhzRXHqcMeLMyaSiRxhvAZXjcogUCYjrxKAAKCRCRxhvAZXjc
 oi77AQDdLziGyeKSHlyPw1xeWiqsZVRLhhD07F8kVaAkjg3ZWwEAs3jL0Pzfnd7J
 IIx//6YK46cbfc471FdVuUDfMjkfVAw=
 =VaFd
 -----END PGP SIGNATURE-----

Merge tag 'fs.v5.18' of git://git.kernel.org/pub/scm/linux/kernel/git/brauner/linux

Pull mount_setattr updates from Christian Brauner:
 "This contains a few more patches to massage the mount_setattr()
  codepaths and one minor fix to reuse a helper we added some time back.

  The final two patches do similar cleanups in different ways. One patch
  is mine and the other is Al's who was nice enough to give me a branch
  for it.

  Since his came in later and my branch had been sitting in -next for
  quite some time we just put his on top instead of swap them"

* tag 'fs.v5.18' of git://git.kernel.org/pub/scm/linux/kernel/git/brauner/linux:
  mount_setattr(): clean the control flow and calling conventions
  fs: clean up mount_setattr control flow
  fs: don't open-code mnt_hold_writers()
  fs: simplify check in mount_setattr_commit()
  fs: add mnt_allow_writers() and simplify mount_setattr_prepare()
This commit is contained in:
Linus Torvalds 2022-03-24 09:55:15 -07:00
commit 15f2e3d6c1

View File

@ -563,12 +563,9 @@ int sb_prepare_remount_readonly(struct super_block *sb)
lock_mount_hash();
list_for_each_entry(mnt, &sb->s_mounts, mnt_instance) {
if (!(mnt->mnt.mnt_flags & MNT_READONLY)) {
mnt->mnt.mnt_flags |= MNT_WRITE_HOLD;
smp_mb();
if (mnt_get_writers(mnt) > 0) {
err = -EBUSY;
err = mnt_hold_writers(mnt);
if (err)
break;
}
}
}
if (!err && atomic_long_read(&sb->s_remove_count))
@ -4000,46 +3997,57 @@ static int can_idmap_mount(const struct mount_kattr *kattr, struct mount *mnt)
return 0;
}
static struct mount *mount_setattr_prepare(struct mount_kattr *kattr,
struct mount *mnt, int *err)
/**
* mnt_allow_writers() - check whether the attribute change allows writers
* @kattr: the new mount attributes
* @mnt: the mount to which @kattr will be applied
*
* Check whether thew new mount attributes in @kattr allow concurrent writers.
*
* Return: true if writers need to be held, false if not
*/
static inline bool mnt_allow_writers(const struct mount_kattr *kattr,
const struct mount *mnt)
{
struct mount *m = mnt, *last = NULL;
return !(kattr->attr_set & MNT_READONLY) ||
(mnt->mnt.mnt_flags & MNT_READONLY);
}
if (!is_mounted(&m->mnt)) {
*err = -EINVAL;
goto out;
}
static int mount_setattr_prepare(struct mount_kattr *kattr, struct mount *mnt)
{
struct mount *m;
int err;
if (!(mnt_has_parent(m) ? check_mnt(m) : is_anon_ns(m->mnt_ns))) {
*err = -EINVAL;
goto out;
}
do {
unsigned int flags;
flags = recalc_flags(kattr, m);
if (!can_change_locked_flags(m, flags)) {
*err = -EPERM;
goto out;
for (m = mnt; m; m = next_mnt(m, mnt)) {
if (!can_change_locked_flags(m, recalc_flags(kattr, m))) {
err = -EPERM;
break;
}
*err = can_idmap_mount(kattr, m);
if (*err)
goto out;
err = can_idmap_mount(kattr, m);
if (err)
break;
last = m;
if ((kattr->attr_set & MNT_READONLY) &&
!(m->mnt.mnt_flags & MNT_READONLY)) {
*err = mnt_hold_writers(m);
if (*err)
goto out;
if (!mnt_allow_writers(kattr, m)) {
err = mnt_hold_writers(m);
if (err)
break;
}
} while (kattr->recurse && (m = next_mnt(m, mnt)));
out:
return last;
if (!kattr->recurse)
return 0;
}
if (err) {
struct mount *p;
for (p = mnt; p != m; p = next_mnt(p, mnt)) {
/* If we had to hold writers unblock them. */
if (p->mnt.mnt_flags & MNT_WRITE_HOLD)
mnt_unhold_writers(p);
}
}
return err;
}
static void do_idmap_mount(const struct mount_kattr *kattr, struct mount *mnt)
@ -4067,48 +4075,32 @@ static void do_idmap_mount(const struct mount_kattr *kattr, struct mount *mnt)
put_user_ns(old_mnt_userns);
}
static void mount_setattr_commit(struct mount_kattr *kattr,
struct mount *mnt, struct mount *last,
int err)
static void mount_setattr_commit(struct mount_kattr *kattr, struct mount *mnt)
{
struct mount *m = mnt;
struct mount *m;
do {
if (!err) {
unsigned int flags;
for (m = mnt; m; m = next_mnt(m, mnt)) {
unsigned int flags;
do_idmap_mount(kattr, m);
flags = recalc_flags(kattr, m);
WRITE_ONCE(m->mnt.mnt_flags, flags);
}
do_idmap_mount(kattr, m);
flags = recalc_flags(kattr, m);
WRITE_ONCE(m->mnt.mnt_flags, flags);
/*
* We either set MNT_READONLY above so make it visible
* before ~MNT_WRITE_HOLD or we failed to recursively
* apply mount options.
*/
if ((kattr->attr_set & MNT_READONLY) &&
(m->mnt.mnt_flags & MNT_WRITE_HOLD))
/* If we had to hold writers unblock them. */
if (m->mnt.mnt_flags & MNT_WRITE_HOLD)
mnt_unhold_writers(m);
if (!err && kattr->propagation)
if (kattr->propagation)
change_mnt_propagation(m, kattr->propagation);
/*
* On failure, only cleanup until we found the first mount
* we failed to handle.
*/
if (err && m == last)
if (!kattr->recurse)
break;
} while (kattr->recurse && (m = next_mnt(m, mnt)));
if (!err)
touch_mnt_namespace(mnt->mnt_ns);
}
touch_mnt_namespace(mnt->mnt_ns);
}
static int do_mount_setattr(struct path *path, struct mount_kattr *kattr)
{
struct mount *mnt = real_mount(path->mnt), *last = NULL;
struct mount *mnt = real_mount(path->mnt);
int err = 0;
if (path->dentry != mnt->mnt.mnt_root)
@ -4129,16 +4121,32 @@ static int do_mount_setattr(struct path *path, struct mount_kattr *kattr)
}
}
err = -EINVAL;
lock_mount_hash();
/*
* Get the mount tree in a shape where we can change mount
* properties without failure.
*/
last = mount_setattr_prepare(kattr, mnt, &err);
if (last) /* Commit all changes or revert to the old state. */
mount_setattr_commit(kattr, mnt, last, err);
/* Ensure that this isn't anything purely vfs internal. */
if (!is_mounted(&mnt->mnt))
goto out;
/*
* If this is an attached mount make sure it's located in the callers
* mount namespace. If it's not don't let the caller interact with it.
* If this is a detached mount make sure it has an anonymous mount
* namespace attached to it, i.e. we've created it via OPEN_TREE_CLONE.
*/
if (!(mnt_has_parent(mnt) ? check_mnt(mnt) : is_anon_ns(mnt->mnt_ns)))
goto out;
/*
* First, we get the mount tree in a shape where we can change mount
* properties without failure. If we succeeded to do so we commit all
* changes and if we failed we clean up.
*/
err = mount_setattr_prepare(kattr, mnt);
if (!err)
mount_setattr_commit(kattr, mnt);
out:
unlock_mount_hash();
if (kattr->propagation) {