diff options
author | Miklos Szeredi <mszeredi@redhat.com> | 2016-12-16 05:02:56 -0500 |
---|---|---|
committer | Miklos Szeredi <mszeredi@redhat.com> | 2016-12-16 05:02:56 -0500 |
commit | a6c6065511411c57167a6cdae0c33263fb662b51 (patch) | |
tree | cd2c7904884c557a0d7d26a88efc737a8885407a | |
parent | 02b69b284cd7815239fabfe895bfef9a9eb5a3ce (diff) |
ovl: redirect on rename-dir
Current code returns EXDEV when a directory would need to be copied up to
move. We could copy up the directory tree in this case, but there's
another, simpler solution: point to old lower directory from moved upper
directory.
This is achieved with a "trusted.overlay.redirect" xattr storing the path
relative to the root of the overlay. After such attribute has been set,
the directory can be moved without further actions required.
This is a backward incompatible feature, old kernels won't be able to
correctly mount an overlay containing redirected directories.
Signed-off-by: Miklos Szeredi <mszeredi@redhat.com>
-rw-r--r-- | Documentation/filesystems/overlayfs.txt | 21 | ||||
-rw-r--r-- | fs/overlayfs/copy_up.c | 20 | ||||
-rw-r--r-- | fs/overlayfs/dir.c | 138 | ||||
-rw-r--r-- | fs/overlayfs/overlayfs.h | 4 | ||||
-rw-r--r-- | fs/overlayfs/ovl_entry.h | 1 | ||||
-rw-r--r-- | fs/overlayfs/super.c | 12 | ||||
-rw-r--r-- | fs/overlayfs/util.c | 29 |
7 files changed, 195 insertions, 30 deletions
diff --git a/Documentation/filesystems/overlayfs.txt b/Documentation/filesystems/overlayfs.txt index 7aeb8e8d80cf..fb6f3070c6fe 100644 --- a/Documentation/filesystems/overlayfs.txt +++ b/Documentation/filesystems/overlayfs.txt | |||
@@ -130,6 +130,23 @@ directory. | |||
130 | Readdir on directories that are not merged is simply handled by the | 130 | Readdir on directories that are not merged is simply handled by the |
131 | underlying directory (upper or lower). | 131 | underlying directory (upper or lower). |
132 | 132 | ||
133 | renaming directories | ||
134 | -------------------- | ||
135 | |||
136 | When renaming a directory that is on the lower layer or merged (i.e. the | ||
137 | directory was not created on the upper layer to start with) overlayfs can | ||
138 | handle it in two different ways: | ||
139 | |||
140 | 1) return EXDEV error: this error is returned by rename(2) when trying to | ||
141 | move a file or directory across filesystem boundaries. Hence | ||
142 | applications are usually prepared to hande this error (mv(1) for example | ||
143 | recursively copies the directory tree). This is the default behavior. | ||
144 | |||
145 | 2) If the "redirect_dir" feature is enabled, then the directory will be | ||
146 | copied up (but not the contents). Then the "trusted.overlay.redirect" | ||
147 | extended attribute is set to the path of the original location from the | ||
148 | root of the overlay. Finally the directory is moved to the new | ||
149 | location. | ||
133 | 150 | ||
134 | Non-directories | 151 | Non-directories |
135 | --------------- | 152 | --------------- |
@@ -189,8 +206,8 @@ If a file with multiple hard links is copied up, then this will | |||
189 | "break" the link. Changes will not be propagated to other names | 206 | "break" the link. Changes will not be propagated to other names |
190 | referring to the same inode. | 207 | referring to the same inode. |
191 | 208 | ||
192 | Directory trees are not copied up. If rename(2) is performed on a directory | 209 | Unless "redirect_dir" feature is enabled, rename(2) on a lower or merged |
193 | which is on the lower layer or is merged, then -EXDEV will be returned. | 210 | directory will fail with EXDEV. |
194 | 211 | ||
195 | Changes to underlying filesystems | 212 | Changes to underlying filesystems |
196 | --------------------------------- | 213 | --------------------------------- |
diff --git a/fs/overlayfs/copy_up.c b/fs/overlayfs/copy_up.c index f18c1a616e9e..e191c631b17f 100644 --- a/fs/overlayfs/copy_up.c +++ b/fs/overlayfs/copy_up.c | |||
@@ -324,17 +324,11 @@ out_cleanup: | |||
324 | /* | 324 | /* |
325 | * Copy up a single dentry | 325 | * Copy up a single dentry |
326 | * | 326 | * |
327 | * Directory renames only allowed on "pure upper" (already created on | 327 | * All renames start with copy up of source if necessary. The actual |
328 | * upper filesystem, never copied up). Directories which are on lower or | 328 | * rename will only proceed once the copy up was successful. Copy up uses |
329 | * are merged may not be renamed. For these -EXDEV is returned and | 329 | * upper parent i_mutex for exclusion. Since rename can change d_parent it |
330 | * userspace has to deal with it. This means, when copying up a | 330 | * is possible that the copy up will lock the old parent. At that point |
331 | * directory we can rely on it and ancestors being stable. | 331 | * the file will have already been copied up anyway. |
332 | * | ||
333 | * Non-directory renames start with copy up of source if necessary. The | ||
334 | * actual rename will only proceed once the copy up was successful. Copy | ||
335 | * up uses upper parent i_mutex for exclusion. Since rename can change | ||
336 | * d_parent it is possible that the copy up will lock the old parent. At | ||
337 | * that point the file will have already been copied up anyway. | ||
338 | */ | 332 | */ |
339 | int ovl_copy_up_one(struct dentry *parent, struct dentry *dentry, | 333 | int ovl_copy_up_one(struct dentry *parent, struct dentry *dentry, |
340 | struct path *lowerpath, struct kstat *stat) | 334 | struct path *lowerpath, struct kstat *stat) |
@@ -346,7 +340,6 @@ int ovl_copy_up_one(struct dentry *parent, struct dentry *dentry, | |||
346 | struct path parentpath; | 340 | struct path parentpath; |
347 | struct dentry *lowerdentry = lowerpath->dentry; | 341 | struct dentry *lowerdentry = lowerpath->dentry; |
348 | struct dentry *upperdir; | 342 | struct dentry *upperdir; |
349 | struct dentry *upperdentry; | ||
350 | const char *link = NULL; | 343 | const char *link = NULL; |
351 | 344 | ||
352 | if (WARN_ON(!workdir)) | 345 | if (WARN_ON(!workdir)) |
@@ -372,8 +365,7 @@ int ovl_copy_up_one(struct dentry *parent, struct dentry *dentry, | |||
372 | pr_err("overlayfs: failed to lock workdir+upperdir\n"); | 365 | pr_err("overlayfs: failed to lock workdir+upperdir\n"); |
373 | goto out_unlock; | 366 | goto out_unlock; |
374 | } | 367 | } |
375 | upperdentry = ovl_dentry_upper(dentry); | 368 | if (ovl_dentry_upper(dentry)) { |
376 | if (upperdentry) { | ||
377 | /* Raced with another copy-up? Nothing to do, then... */ | 369 | /* Raced with another copy-up? Nothing to do, then... */ |
378 | err = 0; | 370 | err = 0; |
379 | goto out_unlock; | 371 | goto out_unlock; |
diff --git a/fs/overlayfs/dir.c b/fs/overlayfs/dir.c index f24b6b967901..c1de84c1c5ec 100644 --- a/fs/overlayfs/dir.c +++ b/fs/overlayfs/dir.c | |||
@@ -15,6 +15,7 @@ | |||
15 | #include <linux/posix_acl.h> | 15 | #include <linux/posix_acl.h> |
16 | #include <linux/posix_acl_xattr.h> | 16 | #include <linux/posix_acl_xattr.h> |
17 | #include <linux/atomic.h> | 17 | #include <linux/atomic.h> |
18 | #include <linux/ratelimit.h> | ||
18 | #include "overlayfs.h" | 19 | #include "overlayfs.h" |
19 | 20 | ||
20 | void ovl_cleanup(struct inode *wdir, struct dentry *wdentry) | 21 | void ovl_cleanup(struct inode *wdir, struct dentry *wdentry) |
@@ -757,6 +758,104 @@ static bool ovl_type_merge_or_lower(struct dentry *dentry) | |||
757 | return OVL_TYPE_MERGE(type) || !OVL_TYPE_UPPER(type); | 758 | return OVL_TYPE_MERGE(type) || !OVL_TYPE_UPPER(type); |
758 | } | 759 | } |
759 | 760 | ||
761 | static bool ovl_can_move(struct dentry *dentry) | ||
762 | { | ||
763 | return ovl_redirect_dir(dentry->d_sb) || | ||
764 | !d_is_dir(dentry) || !ovl_type_merge_or_lower(dentry); | ||
765 | } | ||
766 | |||
767 | #define OVL_REDIRECT_MAX 256 | ||
768 | |||
769 | static char *ovl_get_redirect(struct dentry *dentry, bool samedir) | ||
770 | { | ||
771 | char *buf, *ret; | ||
772 | struct dentry *d, *tmp; | ||
773 | int buflen = OVL_REDIRECT_MAX + 1; | ||
774 | |||
775 | if (samedir) { | ||
776 | ret = kstrndup(dentry->d_name.name, dentry->d_name.len, | ||
777 | GFP_KERNEL); | ||
778 | goto out; | ||
779 | } | ||
780 | |||
781 | buf = ret = kmalloc(buflen, GFP_TEMPORARY); | ||
782 | if (!buf) | ||
783 | goto out; | ||
784 | |||
785 | buflen--; | ||
786 | buf[buflen] = '\0'; | ||
787 | for (d = dget(dentry); !IS_ROOT(d);) { | ||
788 | const char *name; | ||
789 | int thislen; | ||
790 | |||
791 | spin_lock(&d->d_lock); | ||
792 | name = ovl_dentry_get_redirect(d); | ||
793 | if (name) { | ||
794 | thislen = strlen(name); | ||
795 | } else { | ||
796 | name = d->d_name.name; | ||
797 | thislen = d->d_name.len; | ||
798 | } | ||
799 | |||
800 | /* If path is too long, fall back to userspace move */ | ||
801 | if (thislen + (name[0] != '/') > buflen) { | ||
802 | ret = ERR_PTR(-EXDEV); | ||
803 | spin_unlock(&d->d_lock); | ||
804 | goto out_put; | ||
805 | } | ||
806 | |||
807 | buflen -= thislen; | ||
808 | memcpy(&buf[buflen], name, thislen); | ||
809 | tmp = dget_dlock(d->d_parent); | ||
810 | spin_unlock(&d->d_lock); | ||
811 | |||
812 | dput(d); | ||
813 | d = tmp; | ||
814 | |||
815 | /* Absolute redirect: finished */ | ||
816 | if (buf[buflen] == '/') | ||
817 | break; | ||
818 | buflen--; | ||
819 | buf[buflen] = '/'; | ||
820 | } | ||
821 | ret = kstrdup(&buf[buflen], GFP_KERNEL); | ||
822 | out_put: | ||
823 | dput(d); | ||
824 | kfree(buf); | ||
825 | out: | ||
826 | return ret ? ret : ERR_PTR(-ENOMEM); | ||
827 | } | ||
828 | |||
829 | static int ovl_set_redirect(struct dentry *dentry, bool samedir) | ||
830 | { | ||
831 | int err; | ||
832 | const char *redirect = ovl_dentry_get_redirect(dentry); | ||
833 | |||
834 | if (redirect && (samedir || redirect[0] == '/')) | ||
835 | return 0; | ||
836 | |||
837 | redirect = ovl_get_redirect(dentry, samedir); | ||
838 | if (IS_ERR(redirect)) | ||
839 | return PTR_ERR(redirect); | ||
840 | |||
841 | err = ovl_do_setxattr(ovl_dentry_upper(dentry), OVL_XATTR_REDIRECT, | ||
842 | redirect, strlen(redirect), 0); | ||
843 | if (!err) { | ||
844 | spin_lock(&dentry->d_lock); | ||
845 | ovl_dentry_set_redirect(dentry, redirect); | ||
846 | spin_unlock(&dentry->d_lock); | ||
847 | } else { | ||
848 | kfree(redirect); | ||
849 | if (err == -EOPNOTSUPP) | ||
850 | ovl_clear_redirect_dir(dentry->d_sb); | ||
851 | else | ||
852 | pr_warn_ratelimited("overlay: failed to set redirect (%i)\n", err); | ||
853 | /* Fall back to userspace copy-up */ | ||
854 | err = -EXDEV; | ||
855 | } | ||
856 | return err; | ||
857 | } | ||
858 | |||
760 | static int ovl_rename(struct inode *olddir, struct dentry *old, | 859 | static int ovl_rename(struct inode *olddir, struct dentry *old, |
761 | struct inode *newdir, struct dentry *new, | 860 | struct inode *newdir, struct dentry *new, |
762 | unsigned int flags) | 861 | unsigned int flags) |
@@ -773,6 +872,7 @@ static int ovl_rename(struct inode *olddir, struct dentry *old, | |||
773 | bool overwrite = !(flags & RENAME_EXCHANGE); | 872 | bool overwrite = !(flags & RENAME_EXCHANGE); |
774 | bool is_dir = d_is_dir(old); | 873 | bool is_dir = d_is_dir(old); |
775 | bool new_is_dir = d_is_dir(new); | 874 | bool new_is_dir = d_is_dir(new); |
875 | bool samedir = olddir == newdir; | ||
776 | struct dentry *opaquedir = NULL; | 876 | struct dentry *opaquedir = NULL; |
777 | const struct cred *old_cred = NULL; | 877 | const struct cred *old_cred = NULL; |
778 | 878 | ||
@@ -784,9 +884,9 @@ static int ovl_rename(struct inode *olddir, struct dentry *old, | |||
784 | 884 | ||
785 | /* Don't copy up directory trees */ | 885 | /* Don't copy up directory trees */ |
786 | err = -EXDEV; | 886 | err = -EXDEV; |
787 | if (is_dir && ovl_type_merge_or_lower(old)) | 887 | if (!ovl_can_move(old)) |
788 | goto out; | 888 | goto out; |
789 | if (!overwrite && new_is_dir && ovl_type_merge_or_lower(new)) | 889 | if (!overwrite && !ovl_can_move(new)) |
790 | goto out; | 890 | goto out; |
791 | 891 | ||
792 | err = ovl_want_write(old); | 892 | err = ovl_want_write(old); |
@@ -837,7 +937,6 @@ static int ovl_rename(struct inode *olddir, struct dentry *old, | |||
837 | 937 | ||
838 | trap = lock_rename(new_upperdir, old_upperdir); | 938 | trap = lock_rename(new_upperdir, old_upperdir); |
839 | 939 | ||
840 | |||
841 | olddentry = lookup_one_len(old->d_name.name, old_upperdir, | 940 | olddentry = lookup_one_len(old->d_name.name, old_upperdir, |
842 | old->d_name.len); | 941 | old->d_name.len); |
843 | err = PTR_ERR(olddentry); | 942 | err = PTR_ERR(olddentry); |
@@ -880,18 +979,29 @@ static int ovl_rename(struct inode *olddir, struct dentry *old, | |||
880 | if (WARN_ON(olddentry->d_inode == newdentry->d_inode)) | 979 | if (WARN_ON(olddentry->d_inode == newdentry->d_inode)) |
881 | goto out_dput; | 980 | goto out_dput; |
882 | 981 | ||
883 | if (is_dir && !old_opaque && ovl_lower_positive(new)) { | 982 | if (is_dir) { |
884 | err = ovl_set_opaque(olddentry); | 983 | if (ovl_type_merge_or_lower(old)) { |
885 | if (err) | 984 | err = ovl_set_redirect(old, samedir); |
886 | goto out_dput; | 985 | if (err) |
887 | ovl_dentry_set_opaque(old, true); | 986 | goto out_dput; |
987 | } else if (!old_opaque && ovl_lower_positive(new)) { | ||
988 | err = ovl_set_opaque(olddentry); | ||
989 | if (err) | ||
990 | goto out_dput; | ||
991 | ovl_dentry_set_opaque(old, true); | ||
992 | } | ||
888 | } | 993 | } |
889 | if (!overwrite && | 994 | if (!overwrite && new_is_dir) { |
890 | new_is_dir && !new_opaque && ovl_lower_positive(old)) { | 995 | if (ovl_type_merge_or_lower(new)) { |
891 | err = ovl_set_opaque(newdentry); | 996 | err = ovl_set_redirect(new, samedir); |
892 | if (err) | 997 | if (err) |
893 | goto out_dput; | 998 | goto out_dput; |
894 | ovl_dentry_set_opaque(new, true); | 999 | } else if (!new_opaque && ovl_lower_positive(old)) { |
1000 | err = ovl_set_opaque(newdentry); | ||
1001 | if (err) | ||
1002 | goto out_dput; | ||
1003 | ovl_dentry_set_opaque(new, true); | ||
1004 | } | ||
895 | } | 1005 | } |
896 | 1006 | ||
897 | err = ovl_do_rename(old_upperdir->d_inode, olddentry, | 1007 | err = ovl_do_rename(old_upperdir->d_inode, olddentry, |
diff --git a/fs/overlayfs/overlayfs.h b/fs/overlayfs/overlayfs.h index e76d9d529e64..bdda37fa3f67 100644 --- a/fs/overlayfs/overlayfs.h +++ b/fs/overlayfs/overlayfs.h | |||
@@ -157,6 +157,10 @@ void ovl_set_dir_cache(struct dentry *dentry, struct ovl_dir_cache *cache); | |||
157 | bool ovl_dentry_is_opaque(struct dentry *dentry); | 157 | bool ovl_dentry_is_opaque(struct dentry *dentry); |
158 | bool ovl_dentry_is_whiteout(struct dentry *dentry); | 158 | bool ovl_dentry_is_whiteout(struct dentry *dentry); |
159 | void ovl_dentry_set_opaque(struct dentry *dentry, bool opaque); | 159 | void ovl_dentry_set_opaque(struct dentry *dentry, bool opaque); |
160 | bool ovl_redirect_dir(struct super_block *sb); | ||
161 | void ovl_clear_redirect_dir(struct super_block *sb); | ||
162 | const char *ovl_dentry_get_redirect(struct dentry *dentry); | ||
163 | void ovl_dentry_set_redirect(struct dentry *dentry, const char *redirect); | ||
160 | void ovl_dentry_update(struct dentry *dentry, struct dentry *upperdentry); | 164 | void ovl_dentry_update(struct dentry *dentry, struct dentry *upperdentry); |
161 | void ovl_inode_init(struct inode *inode, struct inode *realinode, | 165 | void ovl_inode_init(struct inode *inode, struct inode *realinode, |
162 | bool is_upper); | 166 | bool is_upper); |
diff --git a/fs/overlayfs/ovl_entry.h b/fs/overlayfs/ovl_entry.h index eb29882b6a54..d14bca1850d9 100644 --- a/fs/overlayfs/ovl_entry.h +++ b/fs/overlayfs/ovl_entry.h | |||
@@ -13,6 +13,7 @@ struct ovl_config { | |||
13 | char *upperdir; | 13 | char *upperdir; |
14 | char *workdir; | 14 | char *workdir; |
15 | bool default_permissions; | 15 | bool default_permissions; |
16 | bool redirect_dir; | ||
16 | }; | 17 | }; |
17 | 18 | ||
18 | /* private information held for overlayfs's superblock */ | 19 | /* private information held for overlayfs's superblock */ |
diff --git a/fs/overlayfs/super.c b/fs/overlayfs/super.c index 4e44e865b716..520f9ab0e9ef 100644 --- a/fs/overlayfs/super.c +++ b/fs/overlayfs/super.c | |||
@@ -226,6 +226,8 @@ enum { | |||
226 | OPT_UPPERDIR, | 226 | OPT_UPPERDIR, |
227 | OPT_WORKDIR, | 227 | OPT_WORKDIR, |
228 | OPT_DEFAULT_PERMISSIONS, | 228 | OPT_DEFAULT_PERMISSIONS, |
229 | OPT_REDIRECT_DIR_ON, | ||
230 | OPT_REDIRECT_DIR_OFF, | ||
229 | OPT_ERR, | 231 | OPT_ERR, |
230 | }; | 232 | }; |
231 | 233 | ||
@@ -234,6 +236,8 @@ static const match_table_t ovl_tokens = { | |||
234 | {OPT_UPPERDIR, "upperdir=%s"}, | 236 | {OPT_UPPERDIR, "upperdir=%s"}, |
235 | {OPT_WORKDIR, "workdir=%s"}, | 237 | {OPT_WORKDIR, "workdir=%s"}, |
236 | {OPT_DEFAULT_PERMISSIONS, "default_permissions"}, | 238 | {OPT_DEFAULT_PERMISSIONS, "default_permissions"}, |
239 | {OPT_REDIRECT_DIR_ON, "redirect_dir=on"}, | ||
240 | {OPT_REDIRECT_DIR_OFF, "redirect_dir=off"}, | ||
237 | {OPT_ERR, NULL} | 241 | {OPT_ERR, NULL} |
238 | }; | 242 | }; |
239 | 243 | ||
@@ -298,6 +302,14 @@ static int ovl_parse_opt(char *opt, struct ovl_config *config) | |||
298 | config->default_permissions = true; | 302 | config->default_permissions = true; |
299 | break; | 303 | break; |
300 | 304 | ||
305 | case OPT_REDIRECT_DIR_ON: | ||
306 | config->redirect_dir = true; | ||
307 | break; | ||
308 | |||
309 | case OPT_REDIRECT_DIR_OFF: | ||
310 | config->redirect_dir = false; | ||
311 | break; | ||
312 | |||
301 | default: | 313 | default: |
302 | pr_err("overlayfs: unrecognized mount option \"%s\" or missing value\n", p); | 314 | pr_err("overlayfs: unrecognized mount option \"%s\" or missing value\n", p); |
303 | return -EINVAL; | 315 | return -EINVAL; |
diff --git a/fs/overlayfs/util.c b/fs/overlayfs/util.c index 0d45a84468d2..260b215852a3 100644 --- a/fs/overlayfs/util.c +++ b/fs/overlayfs/util.c | |||
@@ -176,6 +176,35 @@ void ovl_dentry_set_opaque(struct dentry *dentry, bool opaque) | |||
176 | oe->opaque = opaque; | 176 | oe->opaque = opaque; |
177 | } | 177 | } |
178 | 178 | ||
179 | bool ovl_redirect_dir(struct super_block *sb) | ||
180 | { | ||
181 | struct ovl_fs *ofs = sb->s_fs_info; | ||
182 | |||
183 | return ofs->config.redirect_dir; | ||
184 | } | ||
185 | |||
186 | void ovl_clear_redirect_dir(struct super_block *sb) | ||
187 | { | ||
188 | struct ovl_fs *ofs = sb->s_fs_info; | ||
189 | |||
190 | ofs->config.redirect_dir = false; | ||
191 | } | ||
192 | |||
193 | const char *ovl_dentry_get_redirect(struct dentry *dentry) | ||
194 | { | ||
195 | struct ovl_entry *oe = dentry->d_fsdata; | ||
196 | |||
197 | return oe->redirect; | ||
198 | } | ||
199 | |||
200 | void ovl_dentry_set_redirect(struct dentry *dentry, const char *redirect) | ||
201 | { | ||
202 | struct ovl_entry *oe = dentry->d_fsdata; | ||
203 | |||
204 | kfree(oe->redirect); | ||
205 | oe->redirect = redirect; | ||
206 | } | ||
207 | |||
179 | void ovl_dentry_update(struct dentry *dentry, struct dentry *upperdentry) | 208 | void ovl_dentry_update(struct dentry *dentry, struct dentry *upperdentry) |
180 | { | 209 | { |
181 | struct ovl_entry *oe = dentry->d_fsdata; | 210 | struct ovl_entry *oe = dentry->d_fsdata; |