ceph: remove the extra slashes in the server path
authorXiubo Li <xiubli@redhat.com>
Fri, 20 Dec 2019 14:34:04 +0000 (09:34 -0500)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Mon, 13 Apr 2020 08:34:34 +0000 (10:34 +0200)
commit 4fbc0c711b2464ee1551850b85002faae0b775d5 upstream.

It's possible to pass the mount helper a server path that has more
than one contiguous slash character. For example:

  $ mount -t ceph 192.168.195.165:40176:/// /mnt/cephfs/

In the MDS server side the extra slashes of the server path will be
treated as snap dir, and then we can get the following debug logs:

  ceph:  mount opening path //
  ceph:  open_root_inode opening '//'
  ceph:  fill_trace 0000000059b8a3bc is_dentry 0 is_target 1
  ceph:  alloc_inode 00000000dc4ca00b
  ceph:  get_inode created new inode 00000000dc4ca00b 1.ffffffffffffffff ino 1
  ceph:  get_inode on 1=1.ffffffffffffffff got 00000000dc4ca00b

And then when creating any new file or directory under the mount
point, we can hit the following BUG_ON in ceph_fill_trace():

  BUG_ON(ceph_snap(dir) != dvino.snap);

Have the client ignore the extra slashes in the server path when
mounting. This will also canonicalize the path, so that identical mounts
can be consilidated.

1) "//mydir1///mydir//"
2) "/mydir1/mydir"
3) "/mydir1/mydir/"

Regardless of the internal treatment of these paths, the kernel still
stores the original string including the leading '/' for presentation
to userland.

URL: https://tracker.ceph.com/issues/42771
Signed-off-by: Xiubo Li <xiubli@redhat.com>
Reviewed-by: Jeff Layton <jlayton@kernel.org>
Signed-off-by: Ilya Dryomov <idryomov@gmail.com>
Signed-off-by: Luis Henriques <lhenriques@suse.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
fs/ceph/super.c

index 6b10b20bfe32bc88243647e1d2dd414a8b6ae52c..187fc47d7fbedcd8e65ed1110408560349b61ea3 100644 (file)
@@ -92,7 +92,6 @@ static int ceph_statfs(struct dentry *dentry, struct kstatfs *buf)
        return 0;
 }
 
-
 static int ceph_sync_fs(struct super_block *sb, int wait)
 {
        struct ceph_fs_client *fsc = ceph_sb_to_client(sb);
@@ -374,6 +373,73 @@ static int strcmp_null(const char *s1, const char *s2)
        return strcmp(s1, s2);
 }
 
+/**
+ * path_remove_extra_slash - Remove the extra slashes in the server path
+ * @server_path: the server path and could be NULL
+ *
+ * Return NULL if the path is NULL or only consists of "/", or a string
+ * without any extra slashes including the leading slash(es) and the
+ * slash(es) at the end of the server path, such as:
+ * "//dir1////dir2///" --> "dir1/dir2"
+ */
+static char *path_remove_extra_slash(const char *server_path)
+{
+       const char *path = server_path;
+       const char *cur, *end;
+       char *buf, *p;
+       int len;
+
+       /* if the server path is omitted */
+       if (!path)
+               return NULL;
+
+       /* remove all the leading slashes */
+       while (*path == '/')
+               path++;
+
+       /* if the server path only consists of slashes */
+       if (*path == '\0')
+               return NULL;
+
+       len = strlen(path);
+
+       buf = kmalloc(len + 1, GFP_KERNEL);
+       if (!buf)
+               return ERR_PTR(-ENOMEM);
+
+       end = path + len;
+       p = buf;
+       do {
+               cur = strchr(path, '/');
+               if (!cur)
+                       cur = end;
+
+               len = cur - path;
+
+               /* including one '/' */
+               if (cur != end)
+                       len += 1;
+
+               memcpy(p, path, len);
+               p += len;
+
+               while (cur <= end && *cur == '/')
+                       cur++;
+               path = cur;
+       } while (path < end);
+
+       *p = '\0';
+
+       /*
+        * remove the last slash if there has and just to make sure that
+        * we will get something like "dir1/dir2"
+        */
+       if (*(--p) == '/')
+               *p = '\0';
+
+       return buf;
+}
+
 static int compare_mount_options(struct ceph_mount_options *new_fsopt,
                                 struct ceph_options *new_opt,
                                 struct ceph_fs_client *fsc)
@@ -381,6 +447,7 @@ static int compare_mount_options(struct ceph_mount_options *new_fsopt,
        struct ceph_mount_options *fsopt1 = new_fsopt;
        struct ceph_mount_options *fsopt2 = fsc->mount_options;
        int ofs = offsetof(struct ceph_mount_options, snapdir_name);
+       char *p1, *p2;
        int ret;
 
        ret = memcmp(fsopt1, fsopt2, ofs);
@@ -393,9 +460,21 @@ static int compare_mount_options(struct ceph_mount_options *new_fsopt,
        ret = strcmp_null(fsopt1->mds_namespace, fsopt2->mds_namespace);
        if (ret)
                return ret;
-       ret = strcmp_null(fsopt1->server_path, fsopt2->server_path);
+
+       p1 = path_remove_extra_slash(fsopt1->server_path);
+       if (IS_ERR(p1))
+               return PTR_ERR(p1);
+       p2 = path_remove_extra_slash(fsopt2->server_path);
+       if (IS_ERR(p2)) {
+               kfree(p1);
+               return PTR_ERR(p2);
+       }
+       ret = strcmp_null(p1, p2);
+       kfree(p1);
+       kfree(p2);
        if (ret)
                return ret;
+
        ret = strcmp_null(fsopt1->fscache_uniq, fsopt2->fscache_uniq);
        if (ret)
                return ret;
@@ -451,12 +530,14 @@ static int parse_mount_options(struct ceph_mount_options **pfsopt,
         */
        dev_name_end = strchr(dev_name, '/');
        if (dev_name_end) {
-               if (strlen(dev_name_end) > 1) {
-                       fsopt->server_path = kstrdup(dev_name_end, GFP_KERNEL);
-                       if (!fsopt->server_path) {
-                               err = -ENOMEM;
-                               goto out;
-                       }
+               /*
+                * The server_path will include the whole chars from userland
+                * including the leading '/'.
+                */
+               fsopt->server_path = kstrdup(dev_name_end, GFP_KERNEL);
+               if (!fsopt->server_path) {
+                       err = -ENOMEM;
+                       goto out;
                }
        } else {
                dev_name_end = dev_name + strlen(dev_name);
@@ -760,7 +841,6 @@ static void destroy_caches(void)
        ceph_fscache_unregister();
 }
 
-
 /*
  * ceph_umount_begin - initiate forced umount.  Tear down down the
  * mount, skipping steps that may hang while waiting for server(s).
@@ -845,9 +925,6 @@ out:
        return root;
 }
 
-
-
-
 /*
  * mount: join the ceph cluster, and open root directory.
  */
@@ -861,7 +938,7 @@ static struct dentry *ceph_real_mount(struct ceph_fs_client *fsc)
        mutex_lock(&fsc->client->mount_mutex);
 
        if (!fsc->sb->s_root) {
-               const char *path;
+               const char *path, *p;
                err = __ceph_open_session(fsc->client, started);
                if (err < 0)
                        goto out;
@@ -873,19 +950,24 @@ static struct dentry *ceph_real_mount(struct ceph_fs_client *fsc)
                                goto out;
                }
 
-               if (!fsc->mount_options->server_path) {
-                       path = "";
-                       dout("mount opening path \\t\n");
-               } else {
-                       path = fsc->mount_options->server_path + 1;
-                       dout("mount opening path %s\n", path);
+               p = path_remove_extra_slash(fsc->mount_options->server_path);
+               if (IS_ERR(p)) {
+                       err = PTR_ERR(p);
+                       goto out;
                }
+               /* if the server path is omitted or just consists of '/' */
+               if (!p)
+                       path = "";
+               else
+                       path = p;
+               dout("mount opening path '%s'\n", path);
 
                err = ceph_fs_debugfs_init(fsc);
                if (err < 0)
                        goto out;
 
                root = open_root_dentry(fsc, path, started);
+               kfree(p);
                if (IS_ERR(root)) {
                        err = PTR_ERR(root);
                        goto out;