Release 4.10 fs/overlayfs/dir.c
/*
*
* Copyright (C) 2011 Novell Inc.
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 as published by
* the Free Software Foundation.
*/
#include <linux/fs.h>
#include <linux/namei.h>
#include <linux/xattr.h>
#include <linux/security.h>
#include <linux/cred.h>
#include <linux/module.h>
#include <linux/posix_acl.h>
#include <linux/posix_acl_xattr.h>
#include <linux/atomic.h>
#include <linux/ratelimit.h>
#include "overlayfs.h"
static unsigned short ovl_redirect_max = 256;
module_param_named(redirect_max, ovl_redirect_max, ushort, 0644);
MODULE_PARM_DESC(ovl_redirect_max,
"Maximum length of absolute redirect xattr value");
void ovl_cleanup(struct inode *wdir, struct dentry *wdentry)
{
int err;
dget(wdentry);
if (d_is_dir(wdentry))
err = ovl_do_rmdir(wdir, wdentry);
else
err = ovl_do_unlink(wdir, wdentry);
dput(wdentry);
if (err) {
pr_err("overlayfs: cleanup of '%pd2' failed (%i)\n",
wdentry, err);
}
}
Contributors
| Person | Tokens | Prop | Commits | CommitProp |
miklos szeredi | miklos szeredi | 68 | 98.55% | 1 | 50.00% |
david howells | david howells | 1 | 1.45% | 1 | 50.00% |
| Total | 69 | 100.00% | 2 | 100.00% |
struct dentry *ovl_lookup_temp(struct dentry *workdir, struct dentry *dentry)
{
struct dentry *temp;
char name[20];
static atomic_t temp_id = ATOMIC_INIT(0);
/* counter is allowed to wrap, since temp dentries are ephemeral */
snprintf(name, sizeof(name), "#%x", atomic_inc_return(&temp_id));
temp = lookup_one_len(name, workdir, strlen(name));
if (!IS_ERR(temp) && temp->d_inode) {
pr_err("overlayfs: workdir/%s already exists\n", name);
dput(temp);
temp = ERR_PTR(-EIO);
}
return temp;
}
Contributors
| Person | Tokens | Prop | Commits | CommitProp |
miklos szeredi | miklos szeredi | 91 | 85.05% | 1 | 50.00% |
richard weinberger | richard weinberger | 16 | 14.95% | 1 | 50.00% |
| Total | 107 | 100.00% | 2 | 100.00% |
/* caller holds i_mutex on workdir */
static struct dentry *ovl_whiteout(struct dentry *workdir,
struct dentry *dentry)
{
int err;
struct dentry *whiteout;
struct inode *wdir = workdir->d_inode;
whiteout = ovl_lookup_temp(workdir, dentry);
if (IS_ERR(whiteout))
return whiteout;
err = ovl_do_whiteout(wdir, whiteout);
if (err) {
dput(whiteout);
whiteout = ERR_PTR(err);
}
return whiteout;
}
Contributors
| Person | Tokens | Prop | Commits | CommitProp |
miklos szeredi | miklos szeredi | 84 | 100.00% | 1 | 100.00% |
| Total | 84 | 100.00% | 1 | 100.00% |
int ovl_create_real(struct inode *dir, struct dentry *newdentry,
struct cattr *attr, struct dentry *hardlink, bool debug)
{
int err;
if (newdentry->d_inode)
return -ESTALE;
if (hardlink) {
err = ovl_do_link(hardlink, dir, newdentry, debug);
} else {
switch (attr->mode & S_IFMT) {
case S_IFREG:
err = ovl_do_create(dir, newdentry, attr->mode, debug);
break;
case S_IFDIR:
err = ovl_do_mkdir(dir, newdentry, attr->mode, debug);
break;
case S_IFCHR:
case S_IFBLK:
case S_IFIFO:
case S_IFSOCK:
err = ovl_do_mknod(dir, newdentry,
attr->mode, attr->rdev, debug);
break;
case S_IFLNK:
err = ovl_do_symlink(dir, newdentry, attr->link, debug);
break;
default:
err = -EPERM;
}
}
if (!err && WARN_ON(!newdentry->d_inode)) {
/*
* Not quite sure if non-instantiated dentry is legal or not.
* VFS doesn't seem to care so check and warn here.
*/
err = -ENOENT;
}
return err;
}
Contributors
| Person | Tokens | Prop | Commits | CommitProp |
miklos szeredi | miklos szeredi | 183 | 95.31% | 1 | 50.00% |
al viro | al viro | 9 | 4.69% | 1 | 50.00% |
| Total | 192 | 100.00% | 2 | 100.00% |
static int ovl_set_opaque(struct dentry *dentry, struct dentry *upperdentry)
{
int err;
err = ovl_do_setxattr(upperdentry, OVL_XATTR_OPAQUE, "y", 1, 0);
if (!err)
ovl_dentry_set_opaque(dentry);
return err;
}
Contributors
| Person | Tokens | Prop | Commits | CommitProp |
miklos szeredi | miklos szeredi | 46 | 97.87% | 2 | 66.67% |
hujianyang | hujianyang | 1 | 2.13% | 1 | 33.33% |
| Total | 47 | 100.00% | 3 | 100.00% |
static int ovl_dir_getattr(struct vfsmount *mnt, struct dentry *dentry,
struct kstat *stat)
{
int err;
enum ovl_path_type type;
struct path realpath;
const struct cred *old_cred;
type = ovl_path_real(dentry, &realpath);
old_cred = ovl_override_creds(dentry->d_sb);
err = vfs_getattr(&realpath, stat);
revert_creds(old_cred);
if (err)
return err;
stat->dev = dentry->d_sb->s_dev;
stat->ino = dentry->d_inode->i_ino;
/*
* It's probably not worth it to count subdirs to get the
* correct link count. nlink=1 seems to pacify 'find' and
* other utilities.
*/
if (OVL_TYPE_MERGE(type))
stat->nlink = 1;
return 0;
}
Contributors
| Person | Tokens | Prop | Commits | CommitProp |
miklos szeredi | miklos szeredi | 96 | 82.76% | 2 | 66.67% |
vivek goyal | vivek goyal | 20 | 17.24% | 1 | 33.33% |
| Total | 116 | 100.00% | 3 | 100.00% |
/* Common operations required to be done after creation of file on upper */
static void ovl_instantiate(struct dentry *dentry, struct inode *inode,
struct dentry *newdentry, bool hardlink)
{
ovl_dentry_version_inc(dentry->d_parent);
ovl_dentry_update(dentry, newdentry);
if (!hardlink) {
ovl_inode_update(inode, d_inode(newdentry));
ovl_copyattr(newdentry->d_inode, inode);
} else {
WARN_ON(ovl_inode_real(inode, NULL) != d_inode(newdentry));
inc_nlink(inode);
}
d_instantiate(dentry, inode);
}
Contributors
| Person | Tokens | Prop | Commits | CommitProp |
vivek goyal | vivek goyal | 51 | 54.26% | 1 | 33.33% |
miklos szeredi | miklos szeredi | 43 | 45.74% | 2 | 66.67% |
| Total | 94 | 100.00% | 3 | 100.00% |
static bool ovl_type_merge(struct dentry *dentry)
{
return OVL_TYPE_MERGE(ovl_path_type(dentry));
}
Contributors
| Person | Tokens | Prop | Commits | CommitProp |
amir goldstein | amir goldstein | 20 | 100.00% | 1 | 100.00% |
| Total | 20 | 100.00% | 1 | 100.00% |
static int ovl_create_upper(struct dentry *dentry, struct inode *inode,
struct cattr *attr, struct dentry *hardlink)
{
struct dentry *upperdir = ovl_dentry_upper(dentry->d_parent);
struct inode *udir = upperdir->d_inode;
struct dentry *newdentry;
int err;
if (!hardlink && !IS_POSIXACL(udir))
attr->mode &= ~current_umask();
inode_lock_nested(udir, I_MUTEX_PARENT);
newdentry = lookup_one_len(dentry->d_name.name, upperdir,
dentry->d_name.len);
err = PTR_ERR(newdentry);
if (IS_ERR(newdentry))
goto out_unlock;
err = ovl_create_real(udir, newdentry, attr, hardlink, false);
if (err)
goto out_dput;
if (ovl_type_merge(dentry->d_parent)) {
/* Setting opaque here is just an optimization, allow to fail */
ovl_set_opaque(dentry, newdentry);
}
ovl_instantiate(dentry, inode, newdentry, !!hardlink);
newdentry = NULL;
out_dput:
dput(newdentry);
out_unlock:
inode_unlock(udir);
return err;
}
Contributors
| Person | Tokens | Prop | Commits | CommitProp |
miklos szeredi | miklos szeredi | 165 | 85.94% | 3 | 42.86% |
amir goldstein | amir goldstein | 19 | 9.90% | 1 | 14.29% |
al viro | al viro | 6 | 3.12% | 2 | 28.57% |
vivek goyal | vivek goyal | 2 | 1.04% | 1 | 14.29% |
| Total | 192 | 100.00% | 7 | 100.00% |
static int ovl_lock_rename_workdir(struct dentry *workdir,
struct dentry *upperdir)
{
/* Workdir should not be the same as upperdir */
if (workdir == upperdir)
goto err;
/* Workdir should not be subdir of upperdir and vice versa */
if (lock_rename(workdir, upperdir) != NULL)
goto err_unlock;
return 0;
err_unlock:
unlock_rename(workdir, upperdir);
err:
pr_err("overlayfs: failed to lock workdir+upperdir\n");
return -EIO;
}
Contributors
| Person | Tokens | Prop | Commits | CommitProp |
miklos szeredi | miklos szeredi | 64 | 100.00% | 1 | 100.00% |
| Total | 64 | 100.00% | 1 | 100.00% |
static struct dentry *ovl_clear_empty(struct dentry *dentry,
struct list_head *list)
{
struct dentry *workdir = ovl_workdir(dentry);
struct inode *wdir = workdir->d_inode;
struct dentry *upperdir = ovl_dentry_upper(dentry->d_parent);
struct inode *udir = upperdir->d_inode;
struct path upperpath;
struct dentry *upper;
struct dentry *opaquedir;
struct kstat stat;
int err;
if (WARN_ON(!workdir))
return ERR_PTR(-EROFS);
err = ovl_lock_rename_workdir(workdir, upperdir);
if (err)
goto out;
ovl_path_upper(dentry, &upperpath);
err = vfs_getattr(&upperpath, &stat);
if (err)
goto out_unlock;
err = -ESTALE;
if (!S_ISDIR(stat.mode))
goto out_unlock;
upper = upperpath.dentry;
if (upper->d_parent->d_inode != udir)
goto out_unlock;
opaquedir = ovl_lookup_temp(workdir, dentry);
err = PTR_ERR(opaquedir);
if (IS_ERR(opaquedir))
goto out_unlock;
err = ovl_create_real(wdir, opaquedir,
&(struct cattr){.mode = stat.mode}, NULL, true);
if (err)
goto out_dput;
err = ovl_copy_xattr(upper, opaquedir);
if (err)
goto out_cleanup;
err = ovl_set_opaque(dentry, opaquedir);
if (err)
goto out_cleanup;
inode_lock(opaquedir->d_inode);
err = ovl_set_attr(opaquedir, &stat);
inode_unlock(opaquedir->d_inode);
if (err)
goto out_cleanup;
err = ovl_do_rename(wdir, opaquedir, udir, upper, RENAME_EXCHANGE);
if (err)
goto out_cleanup;
ovl_cleanup_whiteouts(upper, list);
ovl_cleanup(wdir, upper);
unlock_rename(workdir, upperdir);
/* dentry's upper doesn't match now, get rid of it */
d_drop(dentry);
return opaquedir;
out_cleanup:
ovl_cleanup(wdir, opaquedir);
out_dput:
dput(opaquedir);
out_unlock:
unlock_rename(workdir, upperdir);
out:
return ERR_PTR(err);
}
Contributors
| Person | Tokens | Prop | Commits | CommitProp |
miklos szeredi | miklos szeredi | 368 | 96.59% | 3 | 60.00% |
al viro | al viro | 13 | 3.41% | 2 | 40.00% |
| Total | 381 | 100.00% | 5 | 100.00% |
static struct dentry *ovl_check_empty_and_clear(struct dentry *dentry)
{
int err;
struct dentry *ret = NULL;
enum ovl_path_type type = ovl_path_type(dentry);
LIST_HEAD(list);
err = ovl_check_empty_dir(dentry, &list);
if (err) {
ret = ERR_PTR(err);
goto out_free;
}
/*
* When removing an empty opaque directory, then it makes no sense to
* replace it with an exact replica of itself.
*
* If no upperdentry then skip clearing whiteouts.
*
* Can race with copy-up, since we don't hold the upperdir mutex.
* Doesn't matter, since copy-up can't create a non-empty directory
* from an empty one.
*/
if (OVL_TYPE_UPPER(type) && OVL_TYPE_MERGE(type))
ret = ovl_clear_empty(dentry, &list);
out_free:
ovl_cache_free(&list);
return ret;
}
Contributors
| Person | Tokens | Prop | Commits | CommitProp |
miklos szeredi | miklos szeredi | 97 | 100.00% | 3 | 100.00% |
| Total | 97 | 100.00% | 3 | 100.00% |
static int ovl_set_upper_acl(struct dentry *upperdentry, const char *name,
const struct posix_acl *acl)
{
void *buffer;
size_t size;
int err;
if (!IS_ENABLED(CONFIG_FS_POSIX_ACL) || !acl)
return 0;
size = posix_acl_to_xattr(NULL, acl, NULL, 0);
buffer = kmalloc(size, GFP_KERNEL);
if (!buffer)
return -ENOMEM;
size = posix_acl_to_xattr(&init_user_ns, acl, buffer, size);
err = size;
if (err < 0)
goto out_free;
err = vfs_setxattr(upperdentry, name, buffer, size, XATTR_CREATE);
out_free:
kfree(buffer);
return err;
}
Contributors
| Person | Tokens | Prop | Commits | CommitProp |
miklos szeredi | miklos szeredi | 129 | 100.00% | 1 | 100.00% |
| Total | 129 | 100.00% | 1 | 100.00% |
static int ovl_create_over_whiteout(struct dentry *dentry, struct inode *inode,
struct cattr *cattr,
struct dentry *hardlink)
{
struct dentry *workdir = ovl_workdir(dentry);
struct inode *wdir = workdir->d_inode;
struct dentry *upperdir = ovl_dentry_upper(dentry->d_parent);
struct inode *udir = upperdir->d_inode;
struct dentry *upper;
struct dentry *newdentry;
int err;
struct posix_acl *acl, *default_acl;
if (WARN_ON(!workdir))
return -EROFS;
if (!hardlink) {
err = posix_acl_create(dentry->d_parent->d_inode,
&cattr->mode, &default_acl, &acl);
if (err)
return err;
}
err = ovl_lock_rename_workdir(workdir, upperdir);
if (err)
goto out;
newdentry = ovl_lookup_temp(workdir, dentry);
err = PTR_ERR(newdentry);
if (IS_ERR(newdentry))
goto out_unlock;
upper = lookup_one_len(dentry->d_name.name, upperdir,
dentry->d_name.len);
err = PTR_ERR(upper);
if (IS_ERR(upper))
goto out_dput;
err = ovl_create_real(wdir, newdentry, cattr, hardlink, true);
if (err)
goto out_dput2;
/*
* mode could have been mutilated due to umask (e.g. sgid directory)
*/
if (!hardlink &&
!S_ISLNK(cattr->mode) &&
newdentry->d_inode->i_mode != cattr->mode) {
struct iattr attr = {
.ia_valid = ATTR_MODE,
.ia_mode = cattr->mode,
};
inode_lock(newdentry->d_inode);
err = notify_change(newdentry, &attr, NULL);
inode_unlock(newdentry->d_inode);
if (err)
goto out_cleanup;
}
if (!hardlink) {
err = ovl_set_upper_acl(newdentry, XATTR_NAME_POSIX_ACL_ACCESS,
acl);
if (err)
goto out_cleanup;
err = ovl_set_upper_acl(newdentry, XATTR_NAME_POSIX_ACL_DEFAULT,
default_acl);
if (err)
goto out_cleanup;
}
if (!hardlink && S_ISDIR(cattr->mode)) {
err = ovl_set_opaque(dentry, newdentry);
if (err)
goto out_cleanup;
err = ovl_do_rename(wdir, newdentry, udir, upper,
RENAME_EXCHANGE);
if (err)
goto out_cleanup;
ovl_cleanup(wdir, upper);
} else {
err = ovl_do_rename(wdir, newdentry, udir, upper, 0);
if (err)
goto out_cleanup;
}
ovl_instantiate(dentry, inode, newdentry, !!hardlink);
newdentry = NULL;
out_dput2:
dput(upper);
out_dput:
dput(newdentry);
out_unlock:
unlock_rename(workdir, upperdir);
out:
if (!hardlink) {
posix_acl_release(acl);
posix_acl_release(default_acl);
}
return err;
out_cleanup:
ovl_cleanup(wdir, newdentry);
goto out_dput2;
}
Contributors
| Person | Tokens | Prop | Commits | CommitProp |
miklos szeredi | miklos szeredi | 503 | 98.05% | 6 | 75.00% |
al viro | al viro | 8 | 1.56% | 1 | 12.50% |
vivek goyal | vivek goyal | 2 | 0.39% | 1 | 12.50% |
| Total | 513 | 100.00% | 8 | 100.00% |
static int ovl_create_or_link(struct dentry *dentry, struct inode *inode,
struct cattr *attr, struct dentry *hardlink)
{
int err;
const struct cred *old_cred;
struct cred *override_cred;
err = ovl_copy_up(dentry->d_parent);
if (err)
return err;
old_cred = ovl_override_creds(dentry->d_sb);
err = -ENOMEM;
override_cred = prepare_creds();
if (override_cred) {
override_cred->fsuid = inode->i_uid;
override_cred->fsgid = inode->i_gid;
if (!hardlink) {
err = security_dentry_create_files_as(dentry,
attr->mode, &dentry->d_name, old_cred,
override_cred);
if (err) {
put_cred(override_cred);
goto out_revert_creds;
}
}
put_cred(override_creds(override_cred));
put_cred(override_cred);
if (!ovl_dentry_is_whiteout(dentry))
err = ovl_create_upper(dentry, inode, attr,
hardlink);
else
err = ovl_create_over_whiteout(dentry, inode, attr,
hardlink);
}
out_revert_creds:
revert_creds(old_cred);
if (!err) {
struct inode *realinode = d_inode(ovl_dentry_upper(dentry));
WARN_ON(inode->i_mode != realinode->i_mode);
WARN_ON(!uid_eq(inode->i_uid, realinode->i_uid));
WARN_ON(!gid_eq(inode->i_gid, realinode->i_gid));
}
return err;
}
Contributors
| Person | Tokens | Prop | Commits | CommitProp |
miklos szeredi | miklos szeredi | 175 | 68.09% | 5 | 55.56% |
vivek goyal | vivek goyal | 73 | 28.40% | 2 | 22.22% |
al viro | al viro | 5 | 1.95% | 1 | 11.11% |
antonio murdaca | antonio murdaca | 4 | 1.56% | 1 | 11.11% |
| Total | 257 | 100.00% | 9 | 100.00% |
static int ovl_create_object(struct dentry *dentry, int mode, dev_t rdev,
const char *link)
{
int err;
struct inode *inode;
struct cattr attr = {
.rdev = rdev,
.link = link,
};
err = ovl_want_write(dentry);
if (err)
goto out;
err = -ENOMEM;
inode = ovl_new_inode(dentry->d_sb, mode, rdev);
if (!inode)
goto out_drop_write;
inode_init_owner(inode, dentry->d_parent->d_inode, mode);
attr.mode = inode->i_mode;
err = ovl_create_or_link(dentry, inode, &attr, NULL);
if (err)
iput(inode);
out_drop_write:
ovl_drop_write(dentry);
out:
return err;
}
Contributors
| Person | Tokens | Prop | Commits | CommitProp |
miklos szeredi | miklos szeredi | 133 | 93.66% | 3 | 75.00% |
al viro | al viro | 9 | 6.34% | 1 | 25.00% |
| Total | 142 | 100.00% | 4 | 100.00% |
static int ovl_create(struct inode *dir, struct dentry *dentry, umode_t mode,
bool excl)
{
return ovl_create_object(dentry, (mode & 07777) | S_IFREG, 0, NULL);
}
Contributors
| Person | Tokens | Prop | Commits | CommitProp |
miklos szeredi | miklos szeredi | 40 | 100.00% | 1 | 100.00% |
| Total | 40 | 100.00% | 1 | 100.00% |
static int ovl_mkdir(struct inode *dir, struct dentry *dentry, umode_t mode)
{
return ovl_create_object(dentry, (mode & 07777) | S_IFDIR, 0, NULL);
}
Contributors
| Person | Tokens | Prop | Commits | CommitProp |
miklos szeredi | miklos szeredi | 37 | 100.00% | 1 | 100.00% |
| Total | 37 | 100.00% | 1 | 100.00% |
static int ovl_mknod(struct inode *dir, struct dentry *dentry, umode_t mode,
dev_t rdev)
{
/* Don't allow creation of "whiteout" on overlay */
if (S_ISCHR(mode) && rdev == WHITEOUT_DEV)
return -EPERM;
return ovl_create_object(dentry, mode, rdev, NULL);
}
Contributors
| Person | Tokens | Prop | Commits | CommitProp |
miklos szeredi | miklos szeredi | 50 | 100.00% | 1 | 100.00% |
| Total | 50 | 100.00% | 1 | 100.00% |
static int ovl_symlink(struct inode *dir, struct dentry *dentry,
const char *link)
{
return ovl_create_object(dentry, S_IFLNK, 0, link);
}
Contributors
| Person | Tokens | Prop | Commits | CommitProp |
miklos szeredi | miklos szeredi | 33 | 100.00% | 1 | 100.00% |
| Total | 33 | 100.00% | 1 | 100.00% |
static int ovl_link(struct dentry *old, struct inode *newdir,
struct dentry *new)
{
int err;
struct inode *inode;
err = ovl_want_write(old);
if (err)
goto out;
err = ovl_copy_up(old);
if (err)
goto out_drop_write;
inode = d_inode(old);
ihold(inode);
err = ovl_create_or_link(new, inode, NULL, ovl_dentry_upper(old));
if (err)
iput(inode);
out_drop_write:
ovl_drop_write(old);
out:
return err;
}
Contributors
| Person | Tokens | Prop | Commits | CommitProp |
miklos szeredi | miklos szeredi | 106 | 100.00% | 2 | 100.00% |
| Total | 106 | 100.00% | 2 | 100.00% |
static int ovl_remove_and_whiteout(struct dentry *dentry, bool is_dir)
{
struct dentry *workdir = ovl_workdir(dentry);
struct inode *wdir = workdir->d_inode;
struct dentry *upperdir = ovl_dentry_upper(dentry->d_parent);
struct inode *udir = upperdir->d_inode;
struct dentry *whiteout;
struct dentry *upper;
struct dentry *opaquedir = NULL;
int err;
int flags = 0;
if (WARN_ON(!workdir))
return -EROFS;
if (is_dir) {
opaquedir = ovl_check_empty_and_clear(dentry);
err = PTR_ERR(opaquedir);
if (IS_ERR(opaquedir))
goto out;
}
err = ovl_lock_rename_workdir(workdir, upperdir);
if (err)
goto out_dput;
upper = lookup_one_len(dentry->d_name.name, upperdir,
dentry->d_name.len);
err = PTR_ERR(upper);
if (IS_ERR(upper))
goto out_unlock;
err = -ESTALE;
if ((opaquedir && upper != opaquedir) ||
(!opaquedir && ovl_dentry_upper(dentry) &&
upper != ovl_dentry_upper(dentry))) {
goto out_dput_upper;
}
whiteout = ovl_whiteout(workdir, dentry);
err = PTR_ERR(whiteout);
if (IS_ERR(whiteout))
goto out_dput_upper;
if (d_is_dir(upper))
flags = RENAME_EXCHANGE;
err = ovl_do_rename(wdir, whiteout, udir, upper, flags);
if (err)
goto kill_whiteout;
if (flags)
ovl_cleanup(