Merge branch 'overlayfs-next' of git://git.kernel.org/pub/scm/linux/kernel/git/mszeredi/vfs into for-next

This commit is contained in:
Al Viro 2015-02-20 04:58:52 -05:00
commit ce7b9facdf
7 changed files with 537 additions and 339 deletions

View file

@ -159,6 +159,22 @@ overlay filesystem (though an operation on the name of the file such as
rename or unlink will of course be noticed and handled).
Multiple lower layers
---------------------
Multiple lower layers can now be given using the the colon (":") as a
separator character between the directory names. For example:
mount -t overlay overlay -olowerdir=/lower1:/lower2:/lower3 /merged
As the example shows, "upperdir=" and "workdir=" may be omitted. In
that case the overlay will be read-only.
The specified lower directories will be stacked beginning from the
rightmost one and going left. In the above example lower1 will be the
top, lower2 the middle and lower3 the bottom layer.
Non-standard behavior
---------------------
@ -196,3 +212,15 @@ Changes to the underlying filesystems while part of a mounted overlay
filesystem are not allowed. If the underlying filesystem is changed,
the behavior of the overlay is undefined, though it will not result in
a crash or deadlock.
Testsuite
---------
There's testsuite developed by David Howells at:
git://git.infradead.org/users/dhowells/unionmount-testsuite.git
Run as root:
# cd unionmount-testsuite
# ./run --ov

View file

@ -191,7 +191,6 @@ int ovl_set_attr(struct dentry *upperdentry, struct kstat *stat)
ovl_set_timestamps(upperdentry, stat);
return err;
}
static int ovl_copy_up_locked(struct dentry *workdir, struct dentry *upperdir,
@ -385,7 +384,7 @@ int ovl_copy_up(struct dentry *dentry)
struct kstat stat;
enum ovl_path_type type = ovl_path_type(dentry);
if (type != OVL_PATH_LOWER)
if (OVL_TYPE_UPPER(type))
break;
next = dget(dentry);
@ -394,7 +393,7 @@ int ovl_copy_up(struct dentry *dentry)
parent = dget_parent(next);
type = ovl_path_type(parent);
if (type != OVL_PATH_LOWER)
if (OVL_TYPE_UPPER(type))
break;
dput(next);

View file

@ -118,14 +118,14 @@ int ovl_create_real(struct inode *dir, struct dentry *newdentry,
static int ovl_set_opaque(struct dentry *upperdentry)
{
return ovl_do_setxattr(upperdentry, ovl_opaque_xattr, "y", 1, 0);
return ovl_do_setxattr(upperdentry, OVL_XATTR_OPAQUE, "y", 1, 0);
}
static void ovl_remove_opaque(struct dentry *upperdentry)
{
int err;
err = ovl_do_removexattr(upperdentry, ovl_opaque_xattr);
err = ovl_do_removexattr(upperdentry, OVL_XATTR_OPAQUE);
if (err) {
pr_warn("overlayfs: failed to remove opaque from '%s' (%i)\n",
upperdentry->d_name.name, err);
@ -152,7 +152,7 @@ static int ovl_dir_getattr(struct vfsmount *mnt, struct dentry *dentry,
* correct link count. nlink=1 seems to pacify 'find' and
* other utilities.
*/
if (type == OVL_PATH_MERGE)
if (OVL_TYPE_MERGE(type))
stat->nlink = 1;
return 0;
@ -506,7 +506,7 @@ static int ovl_remove_and_whiteout(struct dentry *dentry, bool is_dir)
struct dentry *opaquedir = NULL;
int err;
if (is_dir) {
if (is_dir && OVL_TYPE_MERGE_OR_LOWER(ovl_path_type(dentry))) {
opaquedir = ovl_check_empty_and_clear(dentry);
err = PTR_ERR(opaquedir);
if (IS_ERR(opaquedir))
@ -630,7 +630,7 @@ static int ovl_do_remove(struct dentry *dentry, bool is_dir)
goto out_drop_write;
type = ovl_path_type(dentry);
if (type == OVL_PATH_PURE_UPPER) {
if (OVL_TYPE_PURE_UPPER(type)) {
err = ovl_remove_upper(dentry, is_dir);
} else {
const struct cred *old_cred;
@ -712,7 +712,7 @@ static int ovl_rename2(struct inode *olddir, struct dentry *old,
/* Don't copy up directory trees */
old_type = ovl_path_type(old);
err = -EXDEV;
if ((old_type == OVL_PATH_LOWER || old_type == OVL_PATH_MERGE) && is_dir)
if (OVL_TYPE_MERGE_OR_LOWER(old_type) && is_dir)
goto out;
if (new->d_inode) {
@ -725,25 +725,25 @@ static int ovl_rename2(struct inode *olddir, struct dentry *old,
new_type = ovl_path_type(new);
err = -EXDEV;
if (!overwrite && (new_type == OVL_PATH_LOWER || new_type == OVL_PATH_MERGE) && new_is_dir)
if (!overwrite && OVL_TYPE_MERGE_OR_LOWER(new_type) && new_is_dir)
goto out;
err = 0;
if (new_type == OVL_PATH_LOWER && old_type == OVL_PATH_LOWER) {
if (!OVL_TYPE_UPPER(new_type) && !OVL_TYPE_UPPER(old_type)) {
if (ovl_dentry_lower(old)->d_inode ==
ovl_dentry_lower(new)->d_inode)
goto out;
}
if (new_type != OVL_PATH_LOWER && old_type != OVL_PATH_LOWER) {
if (OVL_TYPE_UPPER(new_type) && OVL_TYPE_UPPER(old_type)) {
if (ovl_dentry_upper(old)->d_inode ==
ovl_dentry_upper(new)->d_inode)
goto out;
}
} else {
if (ovl_dentry_is_opaque(new))
new_type = OVL_PATH_UPPER;
new_type = __OVL_PATH_UPPER;
else
new_type = OVL_PATH_PURE_UPPER;
new_type = __OVL_PATH_UPPER | __OVL_PATH_PURE;
}
err = ovl_want_write(old);
@ -763,8 +763,8 @@ static int ovl_rename2(struct inode *olddir, struct dentry *old,
goto out_drop_write;
}
old_opaque = old_type != OVL_PATH_PURE_UPPER;
new_opaque = new_type != OVL_PATH_PURE_UPPER;
old_opaque = !OVL_TYPE_PURE_UPPER(old_type);
new_opaque = !OVL_TYPE_PURE_UPPER(new_type);
if (old_opaque || new_opaque) {
err = -ENOMEM;
@ -787,7 +787,7 @@ static int ovl_rename2(struct inode *olddir, struct dentry *old,
old_cred = override_creds(override_cred);
}
if (overwrite && (new_type == OVL_PATH_LOWER || new_type == OVL_PATH_MERGE) && new_is_dir) {
if (overwrite && OVL_TYPE_MERGE_OR_LOWER(new_type) && new_is_dir) {
opaquedir = ovl_check_empty_and_clear(new);
err = PTR_ERR(opaquedir);
if (IS_ERR(opaquedir)) {

View file

@ -205,7 +205,7 @@ static int ovl_readlink(struct dentry *dentry, char __user *buf, int bufsiz)
static bool ovl_is_private_xattr(const char *name)
{
return strncmp(name, "trusted.overlay.", 14) == 0;
return strncmp(name, OVL_XATTR_PRE_NAME, OVL_XATTR_PRE_LEN) == 0;
}
int ovl_setxattr(struct dentry *dentry, const char *name,
@ -238,7 +238,10 @@ int ovl_setxattr(struct dentry *dentry, const char *name,
static bool ovl_need_xattr_filter(struct dentry *dentry,
enum ovl_path_type type)
{
return type == OVL_PATH_UPPER && S_ISDIR(dentry->d_inode->i_mode);
if ((type & (__OVL_PATH_PURE | __OVL_PATH_UPPER)) == __OVL_PATH_UPPER)
return S_ISDIR(dentry->d_inode->i_mode);
else
return false;
}
ssize_t ovl_getxattr(struct dentry *dentry, const char *name,
@ -299,7 +302,7 @@ int ovl_removexattr(struct dentry *dentry, const char *name)
if (ovl_need_xattr_filter(dentry, type) && ovl_is_private_xattr(name))
goto out_drop_write;
if (type == OVL_PATH_LOWER) {
if (!OVL_TYPE_UPPER(type)) {
err = vfs_getxattr(realpath.dentry, name, NULL, 0);
if (err < 0)
goto out_drop_write;
@ -321,7 +324,7 @@ int ovl_removexattr(struct dentry *dentry, const char *name)
static bool ovl_open_need_copy_up(int flags, enum ovl_path_type type,
struct dentry *realdentry)
{
if (type != OVL_PATH_LOWER)
if (OVL_TYPE_UPPER(type))
return false;
if (special_file(realdentry->d_inode->i_mode))
@ -430,5 +433,4 @@ struct inode *ovl_new_inode(struct super_block *sb, umode_t mode,
}
return inode;
}

View file

@ -12,13 +12,20 @@
struct ovl_entry;
enum ovl_path_type {
OVL_PATH_PURE_UPPER,
OVL_PATH_UPPER,
OVL_PATH_MERGE,
OVL_PATH_LOWER,
__OVL_PATH_PURE = (1 << 0),
__OVL_PATH_UPPER = (1 << 1),
__OVL_PATH_MERGE = (1 << 2),
};
extern const char *ovl_opaque_xattr;
#define OVL_TYPE_UPPER(type) ((type) & __OVL_PATH_UPPER)
#define OVL_TYPE_MERGE(type) ((type) & __OVL_PATH_MERGE)
#define OVL_TYPE_PURE_UPPER(type) ((type) & __OVL_PATH_PURE)
#define OVL_TYPE_MERGE_OR_LOWER(type) \
(OVL_TYPE_MERGE(type) || !OVL_TYPE_UPPER(type))
#define OVL_XATTR_PRE_NAME "trusted.overlay."
#define OVL_XATTR_PRE_LEN 16
#define OVL_XATTR_OPAQUE OVL_XATTR_PRE_NAME"opaque"
static inline int ovl_do_rmdir(struct inode *dir, struct dentry *dentry)
{
@ -130,6 +137,7 @@ void ovl_dentry_version_inc(struct dentry *dentry);
void ovl_path_upper(struct dentry *dentry, struct path *path);
void ovl_path_lower(struct dentry *dentry, struct path *path);
enum ovl_path_type ovl_path_real(struct dentry *dentry, struct path *path);
int ovl_path_next(int idx, struct dentry *dentry, struct path *path);
struct dentry *ovl_dentry_upper(struct dentry *dentry);
struct dentry *ovl_dentry_lower(struct dentry *dentry);
struct dentry *ovl_dentry_real(struct dentry *dentry);

View file

@ -24,7 +24,6 @@ struct ovl_cache_entry {
struct list_head l_node;
struct rb_node node;
bool is_whiteout;
bool is_cursor;
char name[];
};
@ -40,6 +39,7 @@ struct ovl_readdir_data {
struct rb_root root;
struct list_head *list;
struct list_head middle;
struct dentry *dir;
int count;
int err;
};
@ -48,7 +48,7 @@ struct ovl_dir_file {
bool is_real;
bool is_upper;
struct ovl_dir_cache *cache;
struct ovl_cache_entry cursor;
struct list_head *cursor;
struct file *realfile;
struct file *upperfile;
};
@ -79,23 +79,49 @@ static struct ovl_cache_entry *ovl_cache_entry_find(struct rb_root *root,
return NULL;
}
static struct ovl_cache_entry *ovl_cache_entry_new(const char *name, int len,
static struct ovl_cache_entry *ovl_cache_entry_new(struct dentry *dir,
const char *name, int len,
u64 ino, unsigned int d_type)
{
struct ovl_cache_entry *p;
size_t size = offsetof(struct ovl_cache_entry, name[len + 1]);
p = kmalloc(size, GFP_KERNEL);
if (p) {
memcpy(p->name, name, len);
p->name[len] = '\0';
p->len = len;
p->type = d_type;
p->ino = ino;
p->is_whiteout = false;
p->is_cursor = false;
}
if (!p)
return NULL;
memcpy(p->name, name, len);
p->name[len] = '\0';
p->len = len;
p->type = d_type;
p->ino = ino;
p->is_whiteout = false;
if (d_type == DT_CHR) {
struct dentry *dentry;
const struct cred *old_cred;
struct cred *override_cred;
override_cred = prepare_creds();
if (!override_cred) {
kfree(p);
return NULL;
}
/*
* CAP_DAC_OVERRIDE for lookup
*/
cap_raise(override_cred->cap_effective, CAP_DAC_OVERRIDE);
old_cred = override_creds(override_cred);
dentry = lookup_one_len(name, dir, len);
if (!IS_ERR(dentry)) {
p->is_whiteout = ovl_is_whiteout(dentry);
dput(dentry);
}
revert_creds(old_cred);
put_cred(override_cred);
}
return p;
}
@ -122,7 +148,7 @@ static int ovl_cache_entry_add_rb(struct ovl_readdir_data *rdd,
return 0;
}
p = ovl_cache_entry_new(name, len, ino, d_type);
p = ovl_cache_entry_new(rdd->dir, name, len, ino, d_type);
if (p == NULL)
return -ENOMEM;
@ -143,7 +169,7 @@ static int ovl_fill_lower(struct ovl_readdir_data *rdd,
if (p) {
list_move_tail(&p->l_node, &rdd->middle);
} else {
p = ovl_cache_entry_new(name, namelen, ino, d_type);
p = ovl_cache_entry_new(rdd->dir, name, namelen, ino, d_type);
if (p == NULL)
rdd->err = -ENOMEM;
else
@ -168,7 +194,6 @@ static void ovl_cache_put(struct ovl_dir_file *od, struct dentry *dentry)
{
struct ovl_dir_cache *cache = od->cache;
list_del_init(&od->cursor.l_node);
WARN_ON(cache->refcount <= 0);
cache->refcount--;
if (!cache->refcount) {
@ -204,6 +229,7 @@ static inline int ovl_dir_read(struct path *realpath,
if (IS_ERR(realfile))
return PTR_ERR(realfile);
rdd->dir = realpath->dentry;
rdd->ctx.pos = 0;
do {
rdd->count = 0;
@ -227,108 +253,58 @@ static void ovl_dir_reset(struct file *file)
if (cache && ovl_dentry_version_get(dentry) != cache->version) {
ovl_cache_put(od, dentry);
od->cache = NULL;
od->cursor = NULL;
}
WARN_ON(!od->is_real && type != OVL_PATH_MERGE);
if (od->is_real && type == OVL_PATH_MERGE)
WARN_ON(!od->is_real && !OVL_TYPE_MERGE(type));
if (od->is_real && OVL_TYPE_MERGE(type))
od->is_real = false;
}
static int ovl_dir_mark_whiteouts(struct dentry *dir,
struct ovl_readdir_data *rdd)
{
struct ovl_cache_entry *p;
struct dentry *dentry;
const struct cred *old_cred;
struct cred *override_cred;
override_cred = prepare_creds();
if (!override_cred) {
ovl_cache_free(rdd->list);
return -ENOMEM;
}
/*
* CAP_DAC_OVERRIDE for lookup
*/
cap_raise(override_cred->cap_effective, CAP_DAC_OVERRIDE);
old_cred = override_creds(override_cred);
mutex_lock(&dir->d_inode->i_mutex);
list_for_each_entry(p, rdd->list, l_node) {
if (p->is_cursor)
continue;
if (p->type != DT_CHR)
continue;
dentry = lookup_one_len(p->name, dir, p->len);
if (IS_ERR(dentry))
continue;
p->is_whiteout = ovl_is_whiteout(dentry);
dput(dentry);
}
mutex_unlock(&dir->d_inode->i_mutex);
revert_creds(old_cred);
put_cred(override_cred);
return 0;
}
static int ovl_dir_read_merged(struct dentry *dentry, struct list_head *list)
{
int err;
struct path lowerpath;
struct path upperpath;
struct path realpath;
struct ovl_readdir_data rdd = {
.ctx.actor = ovl_fill_merge,
.list = list,
.root = RB_ROOT,
.is_merge = false,
};
int idx, next;
ovl_path_lower(dentry, &lowerpath);
ovl_path_upper(dentry, &upperpath);
for (idx = 0; idx != -1; idx = next) {
next = ovl_path_next(idx, dentry, &realpath);
if (upperpath.dentry) {
err = ovl_dir_read(&upperpath, &rdd);
if (err)
goto out;
if (lowerpath.dentry) {
err = ovl_dir_mark_whiteouts(upperpath.dentry, &rdd);
if (next != -1) {
err = ovl_dir_read(&realpath, &rdd);
if (err)
goto out;
break;
} else {
/*
* Insert lowest layer entries before upper ones, this
* allows offsets to be reasonably constant
*/
list_add(&rdd.middle, rdd.list);
rdd.is_merge = true;
err = ovl_dir_read(&realpath, &rdd);
list_del(&rdd.middle);
}
}
if (lowerpath.dentry) {
/*
* Insert lowerpath entries before upperpath ones, this allows
* offsets to be reasonably constant
*/
list_add(&rdd.middle, rdd.list);
rdd.is_merge = true;
err = ovl_dir_read(&lowerpath, &rdd);
list_del(&rdd.middle);
}
out:
return err;
}
static void ovl_seek_cursor(struct ovl_dir_file *od, loff_t pos)
{
struct ovl_cache_entry *p;
struct list_head *p;
loff_t off = 0;
list_for_each_entry(p, &od->cache->entries, l_node) {
if (p->is_cursor)
continue;
list_for_each(p, &od->cache->entries) {
if (off >= pos)
break;
off++;
}
list_move_tail(&od->cursor.l_node, &p->l_node);
/* Cursor is safe since the cache is stable */
od->cursor = p;
}
static struct ovl_dir_cache *ovl_cache_get(struct dentry *dentry)
@ -367,6 +343,7 @@ static int ovl_iterate(struct file *file, struct dir_context *ctx)
{
struct ovl_dir_file *od = file->private_data;
struct dentry *dentry = file->f_path.dentry;
struct ovl_cache_entry *p;
if (!ctx->pos)
ovl_dir_reset(file);
@ -385,19 +362,13 @@ static int ovl_iterate(struct file *file, struct dir_context *ctx)
ovl_seek_cursor(od, ctx->pos);
}
while (od->cursor.l_node.next != &od->cache->entries) {
struct ovl_cache_entry *p;
p = list_entry(od->cursor.l_node.next, struct ovl_cache_entry, l_node);
/* Skip cursors */
if (!p->is_cursor) {
if (!p->is_whiteout) {
if (!dir_emit(ctx, p->name, p->len, p->ino, p->type))
break;
}
ctx->pos++;
}
list_move(&od->cursor.l_node, &p->l_node);
while (od->cursor != &od->cache->entries) {
p = list_entry(od->cursor, struct ovl_cache_entry, l_node);
if (!p->is_whiteout)
if (!dir_emit(ctx, p->name, p->len, p->ino, p->type))
break;
od->cursor = p->l_node.next;
ctx->pos++;
}
return 0;
}
@ -452,7 +423,7 @@ static int ovl_dir_fsync(struct file *file, loff_t start, loff_t end,
/*
* Need to check if we started out being a lower dir, but got copied up
*/
if (!od->is_upper && ovl_path_type(dentry) != OVL_PATH_LOWER) {
if (!od->is_upper && OVL_TYPE_UPPER(ovl_path_type(dentry))) {
struct inode *inode = file_inode(file);
realfile = lockless_dereference(od->upperfile);
@ -516,11 +487,9 @@ static int ovl_dir_open(struct inode *inode, struct file *file)
kfree(od);
return PTR_ERR(realfile);
}
INIT_LIST_HEAD(&od->cursor.l_node);
od->realfile = realfile;
od->is_real = (type != OVL_PATH_MERGE);
od->is_upper = (type != OVL_PATH_LOWER);
od->cursor.is_cursor = true;
od->is_real = !OVL_TYPE_MERGE(type);
od->is_upper = OVL_TYPE_UPPER(type);
file->private_data = od;
return 0;

View file

@ -35,7 +35,8 @@ struct ovl_config {
/* private information held for overlayfs's superblock */
struct ovl_fs {
struct vfsmount *upper_mnt;
struct vfsmount *lower_mnt;
unsigned numlower;
struct vfsmount **lower_mnt;
struct dentry *workdir;
long lower_namelen;
/* pathnames of lower and upper dirs, for show_options */
@ -47,7 +48,6 @@ struct ovl_dir_cache;
/* private information held for every overlayfs dentry */
struct ovl_entry {
struct dentry *__upperdentry;
struct dentry *lowerdentry;
struct ovl_dir_cache *cache;
union {
struct {
@ -56,30 +56,36 @@ struct ovl_entry {
};
struct rcu_head rcu;
};
unsigned numlower;
struct path lowerstack[];
};
const char *ovl_opaque_xattr = "trusted.overlay.opaque";
#define OVL_MAX_STACK 500
static struct dentry *__ovl_dentry_lower(struct ovl_entry *oe)
{
return oe->numlower ? oe->lowerstack[0].dentry : NULL;
}
enum ovl_path_type ovl_path_type(struct dentry *dentry)
{
struct ovl_entry *oe = dentry->d_fsdata;
enum ovl_path_type type = 0;
if (oe->__upperdentry) {
if (oe->lowerdentry) {
type = __OVL_PATH_UPPER;
if (oe->numlower) {
if (S_ISDIR(dentry->d_inode->i_mode))
return OVL_PATH_MERGE;
else
return OVL_PATH_UPPER;
} else {
if (oe->opaque)
return OVL_PATH_UPPER;
else
return OVL_PATH_PURE_UPPER;
type |= __OVL_PATH_MERGE;
} else if (!oe->opaque) {
type |= __OVL_PATH_PURE;
}
} else {
return OVL_PATH_LOWER;
if (oe->numlower > 1)
type |= __OVL_PATH_MERGE;
}
return type;
}
static struct dentry *ovl_upperdentry_dereference(struct ovl_entry *oe)
@ -98,10 +104,9 @@ void ovl_path_upper(struct dentry *dentry, struct path *path)
enum ovl_path_type ovl_path_real(struct dentry *dentry, struct path *path)
{
enum ovl_path_type type = ovl_path_type(dentry);
if (type == OVL_PATH_LOWER)
if (!OVL_TYPE_UPPER(type))
ovl_path_lower(dentry, path);
else
ovl_path_upper(dentry, path);
@ -120,7 +125,7 @@ struct dentry *ovl_dentry_lower(struct dentry *dentry)
{
struct ovl_entry *oe = dentry->d_fsdata;
return oe->lowerdentry;
return __ovl_dentry_lower(oe);
}
struct dentry *ovl_dentry_real(struct dentry *dentry)
@ -130,7 +135,7 @@ struct dentry *ovl_dentry_real(struct dentry *dentry)
realdentry = ovl_upperdentry_dereference(oe);
if (!realdentry)
realdentry = oe->lowerdentry;
realdentry = __ovl_dentry_lower(oe);
return realdentry;
}
@ -143,7 +148,7 @@ struct dentry *ovl_entry_real(struct ovl_entry *oe, bool *is_upper)
if (realdentry) {
*is_upper = true;
} else {
realdentry = oe->lowerdentry;
realdentry = __ovl_dentry_lower(oe);
*is_upper = false;
}
return realdentry;
@ -165,11 +170,9 @@ void ovl_set_dir_cache(struct dentry *dentry, struct ovl_dir_cache *cache)
void ovl_path_lower(struct dentry *dentry, struct path *path)
{
struct ovl_fs *ofs = dentry->d_sb->s_fs_info;
struct ovl_entry *oe = dentry->d_fsdata;
path->mnt = ofs->lower_mnt;
path->dentry = oe->lowerdentry;
*path = oe->numlower ? oe->lowerstack[0] : (struct path) { NULL, NULL };
}
int ovl_want_write(struct dentry *dentry)
@ -249,7 +252,7 @@ static bool ovl_is_opaquedir(struct dentry *dentry)
if (!S_ISDIR(inode->i_mode) || !inode->i_op->getxattr)
return false;
res = inode->i_op->getxattr(dentry, ovl_opaque_xattr, &val, 1);
res = inode->i_op->getxattr(dentry, OVL_XATTR_OPAQUE, &val, 1);
if (res == 1 && val == 'y')
return true;
@ -261,8 +264,11 @@ static void ovl_dentry_release(struct dentry *dentry)
struct ovl_entry *oe = dentry->d_fsdata;
if (oe) {
unsigned int i;
dput(oe->__upperdentry);
dput(oe->lowerdentry);
for (i = 0; i < oe->numlower; i++)
dput(oe->lowerstack[i].dentry);
kfree_rcu(oe, rcu);
}
}
@ -271,9 +277,15 @@ static const struct dentry_operations ovl_dentry_operations = {
.d_release = ovl_dentry_release,
};
static struct ovl_entry *ovl_alloc_entry(void)
static struct ovl_entry *ovl_alloc_entry(unsigned int numlower)
{
return kzalloc(sizeof(struct ovl_entry), GFP_KERNEL);
size_t size = offsetof(struct ovl_entry, lowerstack[numlower]);
struct ovl_entry *oe = kzalloc(size, GFP_KERNEL);
if (oe)
oe->numlower = numlower;
return oe;
}
static inline struct dentry *ovl_lookup_real(struct dentry *dir,
@ -295,82 +307,154 @@ static inline struct dentry *ovl_lookup_real(struct dentry *dir,
return dentry;
}
/*
* Returns next layer in stack starting from top.
* Returns -1 if this is the last layer.
*/
int ovl_path_next(int idx, struct dentry *dentry, struct path *path)
{
struct ovl_entry *oe = dentry->d_fsdata;
BUG_ON(idx < 0);
if (idx == 0) {
ovl_path_upper(dentry, path);
if (path->dentry)
return oe->numlower ? 1 : -1;
idx++;
}
BUG_ON(idx > oe->numlower);
*path = oe->lowerstack[idx - 1];
return (idx < oe->numlower) ? idx + 1 : -1;
}
struct dentry *ovl_lookup(struct inode *dir, struct dentry *dentry,
unsigned int flags)
{
struct ovl_entry *oe;
struct dentry *upperdir;
struct dentry *lowerdir;
struct dentry *upperdentry = NULL;
struct dentry *lowerdentry = NULL;
struct ovl_entry *poe = dentry->d_parent->d_fsdata;
struct path *stack = NULL;
struct dentry *upperdir, *upperdentry = NULL;
unsigned int ctr = 0;
struct inode *inode = NULL;
bool upperopaque = false;
struct dentry *this, *prev = NULL;
unsigned int i;
int err;
err = -ENOMEM;
oe = ovl_alloc_entry();
if (!oe)
goto out;
upperdir = ovl_dentry_upper(dentry->d_parent);
lowerdir = ovl_dentry_lower(dentry->d_parent);
upperdir = ovl_upperdentry_dereference(poe);
if (upperdir) {
upperdentry = ovl_lookup_real(upperdir, &dentry->d_name);
err = PTR_ERR(upperdentry);
if (IS_ERR(upperdentry))
goto out_put_dir;
this = ovl_lookup_real(upperdir, &dentry->d_name);
err = PTR_ERR(this);
if (IS_ERR(this))
goto out;
if (lowerdir && upperdentry) {
if (ovl_is_whiteout(upperdentry)) {
dput(upperdentry);
upperdentry = NULL;
oe->opaque = true;
} else if (ovl_is_opaquedir(upperdentry)) {
oe->opaque = true;
if (this) {
if (ovl_is_whiteout(this)) {
dput(this);
this = NULL;
upperopaque = true;
} else if (poe->numlower && ovl_is_opaquedir(this)) {
upperopaque = true;
}
}
}
if (lowerdir && !oe->opaque) {
lowerdentry = ovl_lookup_real(lowerdir, &dentry->d_name);
err = PTR_ERR(lowerdentry);
if (IS_ERR(lowerdentry))
goto out_dput_upper;
upperdentry = prev = this;
}
if (lowerdentry && upperdentry &&
(!S_ISDIR(upperdentry->d_inode->i_mode) ||
!S_ISDIR(lowerdentry->d_inode->i_mode))) {
dput(lowerdentry);
lowerdentry = NULL;
oe->opaque = true;
if (!upperopaque && poe->numlower) {
err = -ENOMEM;
stack = kcalloc(poe->numlower, sizeof(struct path), GFP_KERNEL);
if (!stack)
goto out_put_upper;
}
if (lowerdentry || upperdentry) {
for (i = 0; !upperopaque && i < poe->numlower; i++) {
bool opaque = false;
struct path lowerpath = poe->lowerstack[i];
this = ovl_lookup_real(lowerpath.dentry, &dentry->d_name);
err = PTR_ERR(this);
if (IS_ERR(this)) {
/*
* If it's positive, then treat ENAMETOOLONG as ENOENT.
*/
if (err == -ENAMETOOLONG && (upperdentry || ctr))
continue;
goto out_put;
}
if (!this)
continue;
if (ovl_is_whiteout(this)) {
dput(this);
break;
}
/*
* Only makes sense to check opaque dir if this is not the
* lowermost layer.
*/
if (i < poe->numlower - 1 && ovl_is_opaquedir(this))
opaque = true;
if (prev && (!S_ISDIR(prev->d_inode->i_mode) ||
!S_ISDIR(this->d_inode->i_mode))) {
/*
* FIXME: check for upper-opaqueness maybe better done
* in remove code.
*/
if (prev == upperdentry)
upperopaque = true;
dput(this);
break;
}
/*
* If this is a non-directory then stop here.
*/
if (!S_ISDIR(this->d_inode->i_mode))
opaque = true;
stack[ctr].dentry = this;
stack[ctr].mnt = lowerpath.mnt;
ctr++;
prev = this;
if (opaque)
break;
}
oe = ovl_alloc_entry(ctr);
err = -ENOMEM;
if (!oe)
goto out_put;
if (upperdentry || ctr) {
struct dentry *realdentry;
realdentry = upperdentry ? upperdentry : lowerdentry;
realdentry = upperdentry ? upperdentry : stack[0].dentry;
err = -ENOMEM;
inode = ovl_new_inode(dentry->d_sb, realdentry->d_inode->i_mode,
oe);
if (!inode)
goto out_dput;
goto out_free_oe;
ovl_copyattr(realdentry->d_inode, inode);
}
oe->opaque = upperopaque;
oe->__upperdentry = upperdentry;
oe->lowerdentry = lowerdentry;
memcpy(oe->lowerstack, stack, sizeof(struct path) * ctr);
kfree(stack);
dentry->d_fsdata = oe;
d_add(dentry, inode);
return NULL;
out_dput:
dput(lowerdentry);
out_dput_upper:
dput(upperdentry);
out_put_dir:
out_free_oe:
kfree(oe);
out_put:
for (i = 0; i < ctr; i++)
dput(stack[i].dentry);
kfree(stack);
out_put_upper:
dput(upperdentry);
out:
return ERR_PTR(err);
}
@ -383,10 +467,12 @@ struct file *ovl_path_open(struct path *path, int flags)
static void ovl_put_super(struct super_block *sb)
{
struct ovl_fs *ufs = sb->s_fs_info;
unsigned i;
dput(ufs->workdir);
mntput(ufs->upper_mnt);
mntput(ufs->lower_mnt);
for (i = 0; i < ufs->numlower; i++)
mntput(ufs->lower_mnt[i]);
kfree(ufs->config.lowerdir);
kfree(ufs->config.upperdir);
@ -400,7 +486,7 @@ static void ovl_put_super(struct super_block *sb)
* @buf: The struct kstatfs to fill in with stats
*
* Get the filesystem statistics. As writes always target the upper layer
* filesystem pass the statfs to the same filesystem.
* filesystem pass the statfs to the upper filesystem (if it exists)
*/
static int ovl_statfs(struct dentry *dentry, struct kstatfs *buf)
{
@ -409,7 +495,7 @@ static int ovl_statfs(struct dentry *dentry, struct kstatfs *buf)
struct path path;
int err;
ovl_path_upper(root_dentry, &path);
ovl_path_real(root_dentry, &path);
err = vfs_statfs(&path, buf);
if (!err) {
@ -432,8 +518,21 @@ static int ovl_show_options(struct seq_file *m, struct dentry *dentry)
struct ovl_fs *ufs = sb->s_fs_info;
seq_printf(m, ",lowerdir=%s", ufs->config.lowerdir);
seq_printf(m, ",upperdir=%s", ufs->config.upperdir);
seq_printf(m, ",workdir=%s", ufs->config.workdir);
if (ufs->config.upperdir) {
seq_printf(m, ",upperdir=%s", ufs->config.upperdir);
seq_printf(m, ",workdir=%s", ufs->config.workdir);
}
return 0;
}
static int ovl_remount(struct super_block *sb, int *flags, char *data)
{
struct ovl_fs *ufs = sb->s_fs_info;
if (!(*flags & MS_RDONLY) &&
(!ufs->upper_mnt || (ufs->upper_mnt->mnt_sb->s_flags & MS_RDONLY)))
return -EROFS;
return 0;
}
@ -441,6 +540,7 @@ static const struct super_operations ovl_super_operations = {
.put_super = ovl_put_super,
.statfs = ovl_statfs,
.show_options = ovl_show_options,
.remount_fs = ovl_remount,
};
enum {
@ -585,24 +685,6 @@ static void ovl_unescape(char *s)
}
}
static int ovl_mount_dir(const char *name, struct path *path)
{
int err;
char *tmp = kstrdup(name, GFP_KERNEL);
if (!tmp)
return -ENOMEM;
ovl_unescape(tmp);
err = kern_path(tmp, LOOKUP_FOLLOW, path);
if (err) {
pr_err("overlayfs: failed to resolve '%s': %i\n", tmp, err);
err = -EINVAL;
}
kfree(tmp);
return err;
}
static bool ovl_is_allowed_fs_type(struct dentry *root)
{
const struct dentry_operations *dop = root->d_op;
@ -622,6 +704,75 @@ static bool ovl_is_allowed_fs_type(struct dentry *root)
return true;
}
static int ovl_mount_dir_noesc(const char *name, struct path *path)
{
int err = -EINVAL;
if (!*name) {
pr_err("overlayfs: empty lowerdir\n");
goto out;
}
err = kern_path(name, LOOKUP_FOLLOW, path);
if (err) {
pr_err("overlayfs: failed to resolve '%s': %i\n", name, err);
goto out;
}
err = -EINVAL;
if (!ovl_is_allowed_fs_type(path->dentry)) {
pr_err("overlayfs: filesystem on '%s' not supported\n", name);
goto out_put;
}
if (!S_ISDIR(path->dentry->d_inode->i_mode)) {
pr_err("overlayfs: '%s' not a directory\n", name);
goto out_put;
}
return 0;
out_put:
path_put(path);
out:
return err;
}
static int ovl_mount_dir(const char *name, struct path *path)
{
int err = -ENOMEM;
char *tmp = kstrdup(name, GFP_KERNEL);
if (tmp) {
ovl_unescape(tmp);
err = ovl_mount_dir_noesc(tmp, path);
kfree(tmp);
}
return err;
}
static int ovl_lower_dir(const char *name, struct path *path, long *namelen,
int *stack_depth)
{
int err;
struct kstatfs statfs;
err = ovl_mount_dir_noesc(name, path);
if (err)
goto out;
err = vfs_statfs(path, &statfs);
if (err) {
pr_err("overlayfs: statfs failed on '%s'\n", name);
goto out_put;
}
*namelen = max(*namelen, statfs.f_namelen);
*stack_depth = max(*stack_depth, path->mnt->mnt_sb->s_stack_depth);
return 0;
out_put:
path_put(path);
out:
return err;
}
/* Workdir should not be subdir of upperdir and vice versa */
static bool ovl_workdir_ok(struct dentry *workdir, struct dentry *upperdir)
{
@ -634,16 +785,39 @@ static bool ovl_workdir_ok(struct dentry *workdir, struct dentry *upperdir)
return ok;
}
static unsigned int ovl_split_lowerdirs(char *str)
{
unsigned int ctr = 1;
char *s, *d;
for (s = d = str;; s++, d++) {
if (*s == '\\') {
s++;
} else if (*s == ':') {
*d = '\0';
ctr++;
continue;
}
*d = *s;
if (!*s)
break;
}
return ctr;
}
static int ovl_fill_super(struct super_block *sb, void *data, int silent)
{
struct path lowerpath;
struct path upperpath;
struct path workpath;
struct inode *root_inode;
struct path upperpath = { NULL, NULL };
struct path workpath = { NULL, NULL };
struct dentry *root_dentry;
struct ovl_entry *oe;
struct ovl_fs *ufs;
struct kstatfs statfs;
struct path *stack = NULL;
char *lowertmp;
char *lower;
unsigned int numlower;
unsigned int stacklen = 0;
unsigned int i;
int err;
err = -ENOMEM;
@ -655,123 +829,135 @@ static int ovl_fill_super(struct super_block *sb, void *data, int silent)
if (err)
goto out_free_config;
/* FIXME: workdir is not needed for a R/O mount */
err = -EINVAL;
if (!ufs->config.upperdir || !ufs->config.lowerdir ||
!ufs->config.workdir) {
pr_err("overlayfs: missing upperdir or lowerdir or workdir\n");
if (!ufs->config.lowerdir) {
pr_err("overlayfs: missing 'lowerdir'\n");
goto out_free_config;
}
sb->s_stack_depth = 0;
if (ufs->config.upperdir) {
/* FIXME: workdir is not needed for a R/O mount */
if (!ufs->config.workdir) {
pr_err("overlayfs: missing 'workdir'\n");
goto out_free_config;
}
err = ovl_mount_dir(ufs->config.upperdir, &upperpath);
if (err)
goto out_free_config;
err = ovl_mount_dir(ufs->config.workdir, &workpath);
if (err)
goto out_put_upperpath;
err = -EINVAL;
if (upperpath.mnt != workpath.mnt) {
pr_err("overlayfs: workdir and upperdir must reside under the same mount\n");
goto out_put_workpath;
}
if (!ovl_workdir_ok(workpath.dentry, upperpath.dentry)) {
pr_err("overlayfs: workdir and upperdir must be separate subtrees\n");
goto out_put_workpath;
}
sb->s_stack_depth = upperpath.mnt->mnt_sb->s_stack_depth;
}
err = -ENOMEM;
lowertmp = kstrdup(ufs->config.lowerdir, GFP_KERNEL);
if (!lowertmp)
goto out_put_workpath;
err = -EINVAL;
stacklen = ovl_split_lowerdirs(lowertmp);
if (stacklen > OVL_MAX_STACK)
goto out_free_lowertmp;
stack = kcalloc(stacklen, sizeof(struct path), GFP_KERNEL);
if (!stack)
goto out_free_lowertmp;
lower = lowertmp;
for (numlower = 0; numlower < stacklen; numlower++) {
err = ovl_lower_dir(lower, &stack[numlower],
&ufs->lower_namelen, &sb->s_stack_depth);
if (err)
goto out_put_lowerpath;
lower = strchr(lower, '\0') + 1;
}
err = -EINVAL;
sb->s_stack_depth++;
if (sb->s_stack_depth > FILESYSTEM_MAX_STACK_DEPTH) {
pr_err("overlayfs: maximum fs stacking depth exceeded\n");
goto out_put_lowerpath;
}
if (ufs->config.upperdir) {
ufs->upper_mnt = clone_private_mount(&upperpath);
err = PTR_ERR(ufs->upper_mnt);
if (IS_ERR(ufs->upper_mnt)) {
pr_err("overlayfs: failed to clone upperpath\n");
goto out_put_lowerpath;
}
ufs->workdir = ovl_workdir_create(ufs->upper_mnt, workpath.dentry);
err = PTR_ERR(ufs->workdir);
if (IS_ERR(ufs->workdir)) {
pr_err("overlayfs: failed to create directory %s/%s\n",
ufs->config.workdir, OVL_WORKDIR_NAME);
goto out_put_upper_mnt;
}
}
err = -ENOMEM;
oe = ovl_alloc_entry();
if (oe == NULL)
goto out_free_config;
ufs->lower_mnt = kcalloc(numlower, sizeof(struct vfsmount *), GFP_KERNEL);
if (ufs->lower_mnt == NULL)
goto out_put_workdir;
for (i = 0; i < numlower; i++) {
struct vfsmount *mnt = clone_private_mount(&stack[i]);
err = ovl_mount_dir(ufs->config.upperdir, &upperpath);
if (err)
goto out_free_oe;
err = PTR_ERR(mnt);
if (IS_ERR(mnt)) {
pr_err("overlayfs: failed to clone lowerpath\n");
goto out_put_lower_mnt;
}
/*
* Make lower_mnt R/O. That way fchmod/fchown on lower file
* will fail instead of modifying lower fs.
*/
mnt->mnt_flags |= MNT_READONLY;
err = ovl_mount_dir(ufs->config.lowerdir, &lowerpath);
if (err)
goto out_put_upperpath;
err = ovl_mount_dir(ufs->config.workdir, &workpath);
if (err)
goto out_put_lowerpath;
err = -EINVAL;
if (!S_ISDIR(upperpath.dentry->d_inode->i_mode) ||
!S_ISDIR(lowerpath.dentry->d_inode->i_mode) ||
!S_ISDIR(workpath.dentry->d_inode->i_mode)) {
pr_err("overlayfs: upperdir or lowerdir or workdir not a directory\n");
goto out_put_workpath;
ufs->lower_mnt[ufs->numlower] = mnt;
ufs->numlower++;
}
if (upperpath.mnt != workpath.mnt) {
pr_err("overlayfs: workdir and upperdir must reside under the same mount\n");
goto out_put_workpath;
}
if (!ovl_workdir_ok(workpath.dentry, upperpath.dentry)) {
pr_err("overlayfs: workdir and upperdir must be separate subtrees\n");
goto out_put_workpath;
}
if (!ovl_is_allowed_fs_type(upperpath.dentry)) {
pr_err("overlayfs: filesystem of upperdir is not supported\n");
goto out_put_workpath;
}
if (!ovl_is_allowed_fs_type(lowerpath.dentry)) {
pr_err("overlayfs: filesystem of lowerdir is not supported\n");
goto out_put_workpath;
}
err = vfs_statfs(&lowerpath, &statfs);
if (err) {
pr_err("overlayfs: statfs failed on lowerpath\n");
goto out_put_workpath;
}
ufs->lower_namelen = statfs.f_namelen;
sb->s_stack_depth = max(upperpath.mnt->mnt_sb->s_stack_depth,
lowerpath.mnt->mnt_sb->s_stack_depth) + 1;
err = -EINVAL;
if (sb->s_stack_depth > FILESYSTEM_MAX_STACK_DEPTH) {
pr_err("overlayfs: maximum fs stacking depth exceeded\n");
goto out_put_workpath;
}
ufs->upper_mnt = clone_private_mount(&upperpath);
err = PTR_ERR(ufs->upper_mnt);
if (IS_ERR(ufs->upper_mnt)) {
pr_err("overlayfs: failed to clone upperpath\n");
goto out_put_workpath;
}
ufs->lower_mnt = clone_private_mount(&lowerpath);
err = PTR_ERR(ufs->lower_mnt);
if (IS_ERR(ufs->lower_mnt)) {
pr_err("overlayfs: failed to clone lowerpath\n");
goto out_put_upper_mnt;
}
ufs->workdir = ovl_workdir_create(ufs->upper_mnt, workpath.dentry);
err = PTR_ERR(ufs->workdir);
if (IS_ERR(ufs->workdir)) {
pr_err("overlayfs: failed to create directory %s/%s\n",
ufs->config.workdir, OVL_WORKDIR_NAME);
goto out_put_lower_mnt;
}
/*
* Make lower_mnt R/O. That way fchmod/fchown on lower file
* will fail instead of modifying lower fs.
*/
ufs->lower_mnt->mnt_flags |= MNT_READONLY;
/* If the upper fs is r/o, we mark overlayfs r/o too */
if (ufs->upper_mnt->mnt_sb->s_flags & MS_RDONLY)
/* If the upper fs is r/o or nonexistent, we mark overlayfs r/o too */
if (!ufs->upper_mnt || (ufs->upper_mnt->mnt_sb->s_flags & MS_RDONLY))
sb->s_flags |= MS_RDONLY;
sb->s_d_op = &ovl_dentry_operations;
err = -ENOMEM;
root_inode = ovl_new_inode(sb, S_IFDIR, oe);
if (!root_inode)
goto out_put_workdir;
oe = ovl_alloc_entry(numlower);
if (!oe)
goto out_put_lower_mnt;
root_dentry = d_make_root(root_inode);
root_dentry = d_make_root(ovl_new_inode(sb, S_IFDIR, oe));
if (!root_dentry)
goto out_put_workdir;
goto out_free_oe;
mntput(upperpath.mnt);
mntput(lowerpath.mnt);
for (i = 0; i < numlower; i++)
mntput(stack[i].mnt);
path_put(&workpath);
kfree(lowertmp);
oe->__upperdentry = upperpath.dentry;
oe->lowerdentry = lowerpath.dentry;
for (i = 0; i < numlower; i++) {
oe->lowerstack[i].dentry = stack[i].dentry;
oe->lowerstack[i].mnt = ufs->lower_mnt[i];
}
root_dentry->d_fsdata = oe;
@ -782,20 +968,26 @@ static int ovl_fill_super(struct super_block *sb, void *data, int silent)
return 0;
out_put_workdir:
dput(ufs->workdir);
out_put_lower_mnt:
mntput(ufs->lower_mnt);
out_put_upper_mnt:
mntput(ufs->upper_mnt);
out_put_workpath:
path_put(&workpath);
out_put_lowerpath:
path_put(&lowerpath);
out_put_upperpath:
path_put(&upperpath);
out_free_oe:
kfree(oe);
out_put_lower_mnt:
for (i = 0; i < ufs->numlower; i++)
mntput(ufs->lower_mnt[i]);
kfree(ufs->lower_mnt);
out_put_workdir:
dput(ufs->workdir);
out_put_upper_mnt:
mntput(ufs->upper_mnt);
out_put_lowerpath:
for (i = 0; i < numlower; i++)
path_put(&stack[i]);
kfree(stack);
out_free_lowertmp:
kfree(lowertmp);
out_put_workpath:
path_put(&workpath);
out_put_upperpath:
path_put(&upperpath);
out_free_config:
kfree(ufs->config.lowerdir);
kfree(ufs->config.upperdir);