Skip to content

Commit a6c6065

Browse files
author
Miklos Szeredi
committed
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>
1 parent 02b69b2 commit a6c6065

File tree

7 files changed

+195
-30
lines changed

7 files changed

+195
-30
lines changed

Documentation/filesystems/overlayfs.txt

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,23 @@ directory.
130130
Readdir on directories that are not merged is simply handled by the
131131
underlying directory (upper or lower).
132132

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.
133150

134151
Non-directories
135152
---------------
@@ -189,8 +206,8 @@ If a file with multiple hard links is copied up, then this will
189206
"break" the link. Changes will not be propagated to other names
190207
referring to the same inode.
191208

192-
Directory trees are not copied up. If rename(2) is performed on a directory
193-
which is on the lower layer or is merged, then -EXDEV will be returned.
209+
Unless "redirect_dir" feature is enabled, rename(2) on a lower or merged
210+
directory will fail with EXDEV.
194211

195212
Changes to underlying filesystems
196213
---------------------------------

fs/overlayfs/copy_up.c

Lines changed: 6 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -324,17 +324,11 @@ static int ovl_copy_up_locked(struct dentry *workdir, struct dentry *upperdir,
324324
/*
325325
* Copy up a single dentry
326326
*
327-
* Directory renames only allowed on "pure upper" (already created on
328-
* upper filesystem, never copied up). Directories which are on lower or
329-
* are merged may not be renamed. For these -EXDEV is returned and
330-
* userspace has to deal with it. This means, when copying up a
331-
* directory we can rely on it and ancestors being stable.
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.
327+
* All renames start with copy up of source if necessary. The actual
328+
* rename will only proceed once the copy up was successful. Copy up uses
329+
* upper parent i_mutex for exclusion. Since rename can change d_parent it
330+
* is possible that the copy up will lock the old parent. At that point
331+
* the file will have already been copied up anyway.
338332
*/
339333
int ovl_copy_up_one(struct dentry *parent, struct dentry *dentry,
340334
struct path *lowerpath, struct kstat *stat)
@@ -346,7 +340,6 @@ int ovl_copy_up_one(struct dentry *parent, struct dentry *dentry,
346340
struct path parentpath;
347341
struct dentry *lowerdentry = lowerpath->dentry;
348342
struct dentry *upperdir;
349-
struct dentry *upperdentry;
350343
const char *link = NULL;
351344

352345
if (WARN_ON(!workdir))
@@ -372,8 +365,7 @@ int ovl_copy_up_one(struct dentry *parent, struct dentry *dentry,
372365
pr_err("overlayfs: failed to lock workdir+upperdir\n");
373366
goto out_unlock;
374367
}
375-
upperdentry = ovl_dentry_upper(dentry);
376-
if (upperdentry) {
368+
if (ovl_dentry_upper(dentry)) {
377369
/* Raced with another copy-up? Nothing to do, then... */
378370
err = 0;
379371
goto out_unlock;

fs/overlayfs/dir.c

Lines changed: 124 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
#include <linux/posix_acl.h>
1616
#include <linux/posix_acl_xattr.h>
1717
#include <linux/atomic.h>
18+
#include <linux/ratelimit.h>
1819
#include "overlayfs.h"
1920

2021
void ovl_cleanup(struct inode *wdir, struct dentry *wdentry)
@@ -757,6 +758,104 @@ static bool ovl_type_merge_or_lower(struct dentry *dentry)
757758
return OVL_TYPE_MERGE(type) || !OVL_TYPE_UPPER(type);
758759
}
759760

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+
760859
static int ovl_rename(struct inode *olddir, struct dentry *old,
761860
struct inode *newdir, struct dentry *new,
762861
unsigned int flags)
@@ -773,6 +872,7 @@ static int ovl_rename(struct inode *olddir, struct dentry *old,
773872
bool overwrite = !(flags & RENAME_EXCHANGE);
774873
bool is_dir = d_is_dir(old);
775874
bool new_is_dir = d_is_dir(new);
875+
bool samedir = olddir == newdir;
776876
struct dentry *opaquedir = NULL;
777877
const struct cred *old_cred = NULL;
778878

@@ -784,9 +884,9 @@ static int ovl_rename(struct inode *olddir, struct dentry *old,
784884

785885
/* Don't copy up directory trees */
786886
err = -EXDEV;
787-
if (is_dir && ovl_type_merge_or_lower(old))
887+
if (!ovl_can_move(old))
788888
goto out;
789-
if (!overwrite && new_is_dir && ovl_type_merge_or_lower(new))
889+
if (!overwrite && !ovl_can_move(new))
790890
goto out;
791891

792892
err = ovl_want_write(old);
@@ -837,7 +937,6 @@ static int ovl_rename(struct inode *olddir, struct dentry *old,
837937

838938
trap = lock_rename(new_upperdir, old_upperdir);
839939

840-
841940
olddentry = lookup_one_len(old->d_name.name, old_upperdir,
842941
old->d_name.len);
843942
err = PTR_ERR(olddentry);
@@ -880,18 +979,29 @@ static int ovl_rename(struct inode *olddir, struct dentry *old,
880979
if (WARN_ON(olddentry->d_inode == newdentry->d_inode))
881980
goto out_dput;
882981

883-
if (is_dir && !old_opaque && ovl_lower_positive(new)) {
884-
err = ovl_set_opaque(olddentry);
885-
if (err)
886-
goto out_dput;
887-
ovl_dentry_set_opaque(old, true);
982+
if (is_dir) {
983+
if (ovl_type_merge_or_lower(old)) {
984+
err = ovl_set_redirect(old, samedir);
985+
if (err)
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+
}
888993
}
889-
if (!overwrite &&
890-
new_is_dir && !new_opaque && ovl_lower_positive(old)) {
891-
err = ovl_set_opaque(newdentry);
892-
if (err)
893-
goto out_dput;
894-
ovl_dentry_set_opaque(new, true);
994+
if (!overwrite && new_is_dir) {
995+
if (ovl_type_merge_or_lower(new)) {
996+
err = ovl_set_redirect(new, samedir);
997+
if (err)
998+
goto out_dput;
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+
}
8951005
}
8961006

8971007
err = ovl_do_rename(old_upperdir->d_inode, olddentry,

fs/overlayfs/overlayfs.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,10 @@ void ovl_set_dir_cache(struct dentry *dentry, struct ovl_dir_cache *cache);
157157
bool ovl_dentry_is_opaque(struct dentry *dentry);
158158
bool ovl_dentry_is_whiteout(struct dentry *dentry);
159159
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);
160164
void ovl_dentry_update(struct dentry *dentry, struct dentry *upperdentry);
161165
void ovl_inode_init(struct inode *inode, struct inode *realinode,
162166
bool is_upper);

fs/overlayfs/ovl_entry.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ struct ovl_config {
1313
char *upperdir;
1414
char *workdir;
1515
bool default_permissions;
16+
bool redirect_dir;
1617
};
1718

1819
/* private information held for overlayfs's superblock */

fs/overlayfs/super.c

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,8 @@ enum {
226226
OPT_UPPERDIR,
227227
OPT_WORKDIR,
228228
OPT_DEFAULT_PERMISSIONS,
229+
OPT_REDIRECT_DIR_ON,
230+
OPT_REDIRECT_DIR_OFF,
229231
OPT_ERR,
230232
};
231233

@@ -234,6 +236,8 @@ static const match_table_t ovl_tokens = {
234236
{OPT_UPPERDIR, "upperdir=%s"},
235237
{OPT_WORKDIR, "workdir=%s"},
236238
{OPT_DEFAULT_PERMISSIONS, "default_permissions"},
239+
{OPT_REDIRECT_DIR_ON, "redirect_dir=on"},
240+
{OPT_REDIRECT_DIR_OFF, "redirect_dir=off"},
237241
{OPT_ERR, NULL}
238242
};
239243

@@ -298,6 +302,14 @@ static int ovl_parse_opt(char *opt, struct ovl_config *config)
298302
config->default_permissions = true;
299303
break;
300304

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+
301313
default:
302314
pr_err("overlayfs: unrecognized mount option \"%s\" or missing value\n", p);
303315
return -EINVAL;

fs/overlayfs/util.c

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,35 @@ void ovl_dentry_set_opaque(struct dentry *dentry, bool opaque)
176176
oe->opaque = opaque;
177177
}
178178

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+
179208
void ovl_dentry_update(struct dentry *dentry, struct dentry *upperdentry)
180209
{
181210
struct ovl_entry *oe = dentry->d_fsdata;

0 commit comments

Comments
 (0)