| From 4fbc0c711b2464ee1551850b85002faae0b775d5 Mon Sep 17 00:00:00 2001 |
| From: Xiubo Li <xiubli@redhat.com> |
| Date: Fri, 20 Dec 2019 09:34:04 -0500 |
| Subject: ceph: remove the extra slashes in the server path |
| |
| From: Xiubo Li <xiubli@redhat.com> |
| |
| 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 | 118 ++++++++++++++++++++++++++++++++++++++++++++++---------- |
| 1 file changed, 99 insertions(+), 19 deletions(-) |
| |
| --- a/fs/ceph/super.c |
| +++ b/fs/ceph/super.c |
| @@ -85,7 +85,6 @@ static int ceph_statfs(struct dentry *de |
| return 0; |
| } |
| |
| - |
| static int ceph_sync_fs(struct super_block *sb, int wait) |
| { |
| struct ceph_fs_client *fsc = ceph_sb_to_client(sb); |
| @@ -321,6 +320,73 @@ static int strcmp_null(const char *s1, c |
| 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) |
| @@ -328,6 +394,7 @@ static int compare_mount_options(struct |
| 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); |
| @@ -341,7 +408,17 @@ static int compare_mount_options(struct |
| 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; |
| |
| @@ -396,12 +473,14 @@ static int parse_mount_options(struct ce |
| */ |
| 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); |
| @@ -725,7 +804,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). |
| @@ -812,9 +890,6 @@ out: |
| return root; |
| } |
| |
| - |
| - |
| - |
| /* |
| * mount: join the ceph cluster, and open root directory. |
| */ |
| @@ -828,24 +903,29 @@ static struct dentry *ceph_real_mount(st |
| 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; |
| |
| - 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; |