Re: [PATCH 02/10] vfs: syscall: Add move_mount(2) to move mounts around

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



On 20/02/2019 12:32, Alan Jenkins wrote:
On 19/02/2019 17:08, David Howells wrote:
Add a move_mount() system call that will move a mount from one place to
another and, in the next commit, allow to attach an unattached mount tree.

The new system call looks like the following:

    int move_mount(int from_dfd, const char *from_path,
               int to_dfd, const char *to_path,
               unsigned int flags);

Signed-off-by: David Howells <dhowells@xxxxxxxxxx>
cc: linux-api@xxxxxxxxxxxxxxx
Signed-off-by: Al Viro <viro@xxxxxxxxxxxxxxxxxx>
---

  arch/x86/entry/syscalls/syscall_32.tbl |    1
  arch/x86/entry/syscalls/syscall_64.tbl |    1
  fs/namespace.c                         |  126 ++++++++++++++++++++++++--------
  include/linux/lsm_hooks.h              |    6 ++
  include/linux/security.h               |    7 ++
  include/linux/syscalls.h               |    3 +
  include/uapi/linux/mount.h             |   11 +++
  security/security.c                    |    5 +
  8 files changed, 129 insertions(+), 31 deletions(-)

diff --git a/fs/namespace.c b/fs/namespace.c
index 112d46f26fc3..f10122028a11 100644
--- a/fs/namespace.c
+++ b/fs/namespace.c
@@ -2537,72 +2537,81 @@ static inline int tree_contains_unbindable(struct mount *mnt)
      return 0;
  }
  -static int do_move_mount(struct path *path, const char *old_name)
+static int do_move_mount(struct path *old_path, struct path *new_path)
  {
-    struct path old_path, parent_path;
+    struct path parent_path = {.mnt = NULL, .dentry = NULL};
      struct mount *p;
      struct mount *old;
      struct mountpoint *mp;
      int err;
-    if (!old_name || !*old_name)
-        return -EINVAL;
-    err = kern_path(old_name, LOOKUP_FOLLOW, &old_path);
-    if (err)
-        return err;
  -    mp = lock_mount(path);
-    err = PTR_ERR(mp);
+    mp = lock_mount(new_path);
      if (IS_ERR(mp))
-        goto out;
+        return PTR_ERR(mp);
  -    old = real_mount(old_path.mnt);
-    p = real_mount(path->mnt);
+    old = real_mount(old_path->mnt);
+    p = real_mount(new_path->mnt);
        err = -EINVAL;
      if (!check_mnt(p) || !check_mnt(old))
-        goto out1;
+        goto out;
  -    if (old->mnt.mnt_flags & MNT_LOCKED)
-        goto out1;
+    if (!mnt_has_parent(old))
+        goto out;
  -    err = -EINVAL;
-    if (old_path.dentry != old_path.mnt->mnt_root)
-        goto out1;
+    if (old->mnt.mnt_flags & MNT_LOCKED)
+        goto out;
  -    if (!mnt_has_parent(old))
-        goto out1;
+    if (old_path->dentry != old_path->mnt->mnt_root)
+        goto out;
  -    if (d_is_dir(path->dentry) !=
-          d_is_dir(old_path.dentry))
-        goto out1;
+    if (d_is_dir(new_path->dentry) !=
+        d_is_dir(old_path->dentry))
+        goto out;
      /*
       * Don't move a mount residing in a shared parent.
       */
      if (IS_MNT_SHARED(old->mnt_parent))
-        goto out1;
+        goto out;
      /*
       * 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))
-        goto out1;
+        goto out;
      err = -ELOOP;
      for (; mnt_has_parent(p); p = p->mnt_parent)
          if (p == old)
-            goto out1;
+            goto out;
  -    err = attach_recursive_mnt(old, real_mount(path->mnt), mp, &parent_path);
+    err = attach_recursive_mnt(old, real_mount(new_path->mnt), mp,
+                   &parent_path);
      if (err)
-        goto out1;
+        goto out;
        /* if the mount is moved, it should no longer be expire
       * automatically */
      list_del_init(&old->mnt_expire);
-out1:
-    unlock_mount(mp);
  out:
+    unlock_mount(mp);
      if (!err)
          path_put(&parent_path);
+    return err;
+}
+
+static int do_move_mount_old(struct path *path, const char *old_name)
+{
+    struct path old_path;
+    int err;
+
+    if (!old_name || !*old_name)
+        return -EINVAL;
+
+    err = kern_path(old_name, LOOKUP_FOLLOW, &old_path);
+    if (err)
+        return err;
+
+    err = do_move_mount(&old_path, path);
      path_put(&old_path);
      return err;
  }
@@ -3050,7 +3059,7 @@ long do_mount(const char *dev_name, const char __user *dir_name,       else if (flags & (MS_SHARED | MS_PRIVATE | MS_SLAVE | MS_UNBINDABLE))
          retval = do_change_type(&path, flags);
      else if (flags & MS_MOVE)
-        retval = do_move_mount(&path, dev_name);
+        retval = do_move_mount_old(&path, dev_name);
      else
          retval = do_new_mount(&path, type_page, sb_flags, mnt_flags,
                        dev_name, data_page);
@@ -3278,6 +3287,61 @@ SYSCALL_DEFINE5(mount, char __user *, dev_name, char __user *, dir_name,
      return ksys_mount(dev_name, dir_name, type, flags, data);
  }
  +/*
+ * Move a mount from one place to another.
+ *
+ * Note the flags value is a combination of MOVE_MOUNT_* flags.
+ */
+SYSCALL_DEFINE5(move_mount,
+        int, from_dfd, const char *, from_pathname,
+        int, to_dfd, const char *, to_pathname,
+        unsigned int, flags)
+{
+    struct path from_path, to_path;
+    unsigned int lflags;
+    int ret = 0;
+
+    if (!may_mount())
+        return -EPERM;
+
+    if (flags & ~MOVE_MOUNT__MASK)
+        return -EINVAL;
+
+    /* If someone gives a pathname, they aren't permitted to move
+     * from an fd that requires unmount as we can't get at the flag
+     * to clear it afterwards.
+     */

Comment is incorrect.

* FMODE_NEED_UNMOUNT is never cleared.

* Technically I don't see anything preventing them giving a pathname, but it needs to be "." or equivalent.  Otherwise it will fail the "!attached" check in the next patch.


* The only argument I remember for preventing this, was that it might confuse users (not the kernel).  If you are allowed to move from a sub-mount, then in certain programming styles - like my shell script test cases - you might accidentally close the original file too early.  Then you won't be able to do move_mount() from the tree, because the tree was unmounted ("dissolved") when you closed it.

I think the description in the previous patch, for open_tree(), makes it clear though. "The detached tree will be dissolved on the final close of obtained file".

If there is a good reason, I expect we can simply remove the "!attached" part of the check.  If the constraint is generating more confusion than the added flexibility, I think that would be a good reason :-).

Sorry, I see it.  Although you are not clearing a flag, you have to free the old value of old->mnt_ns.  And that is not being reference-counted, it has a single owner, the file which has FMODE_NEED_UNMOUNT.  So it is not possible to simply remove the "!attached" check.

I still find the comment confusing, i.e. describing this as clearing a flag.

Alan



[Index of Archives]     [Linux USB Devel]     [Video for Linux]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]

  Powered by Linux