blob: d9482a0ec970dc4e18f69e0c7f95d52628b21c81 [file] [log] [blame]
/*
* Copyright (C) 2005-2013 Junjiro R. Okajima
*
* This program, aufs is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
/*
* inode operations (add entry)
*/
#include "aufs.h"
/*
* final procedure of adding a new entry, except link(2).
* remove whiteout, instantiate, copyup the parent dir's times and size
* and update version.
* if it failed, re-create the removed whiteout.
*/
static int epilog(struct inode *dir, aufs_bindex_t bindex,
struct dentry *wh_dentry, struct dentry *dentry)
{
int err, rerr;
aufs_bindex_t bwh;
struct path h_path;
struct inode *inode, *h_dir;
struct dentry *wh;
bwh = -1;
if (wh_dentry) {
h_dir = wh_dentry->d_parent->d_inode; /* dir inode is locked */
IMustLock(h_dir);
AuDebugOn(au_h_iptr(dir, bindex) != h_dir);
bwh = au_dbwh(dentry);
h_path.dentry = wh_dentry;
h_path.mnt = au_sbr_mnt(dir->i_sb, bindex);
err = au_wh_unlink_dentry(au_h_iptr(dir, bindex), &h_path,
dentry);
if (unlikely(err))
goto out;
}
inode = au_new_inode(dentry, /*must_new*/1);
if (!IS_ERR(inode)) {
d_instantiate(dentry, inode);
dir = dentry->d_parent->d_inode; /* dir inode is locked */
IMustLock(dir);
if (au_ibstart(dir) == au_dbstart(dentry))
au_cpup_attr_timesizes(dir);
dir->i_version++;
return 0; /* success */
}
err = PTR_ERR(inode);
if (!wh_dentry)
goto out;
/* revert */
/* dir inode is locked */
wh = au_wh_create(dentry, bwh, wh_dentry->d_parent);
rerr = PTR_ERR(wh);
if (IS_ERR(wh)) {
AuIOErr("%.*s reverting whiteout failed(%d, %d)\n",
AuDLNPair(dentry), err, rerr);
err = -EIO;
} else
dput(wh);
out:
return err;
}
static int au_d_may_add(struct dentry *dentry)
{
int err;
err = 0;
if (unlikely(d_unhashed(dentry)))
err = -ENOENT;
if (unlikely(dentry->d_inode))
err = -EEXIST;
return err;
}
/*
* simple tests for the adding inode operations.
* following the checks in vfs, plus the parent-child relationship.
*/
int au_may_add(struct dentry *dentry, aufs_bindex_t bindex,
struct dentry *h_parent, int isdir)
{
int err;
umode_t h_mode;
struct dentry *h_dentry;
struct inode *h_inode;
err = -ENAMETOOLONG;
if (unlikely(dentry->d_name.len > AUFS_MAX_NAMELEN))
goto out;
h_dentry = au_h_dptr(dentry, bindex);
h_inode = h_dentry->d_inode;
if (!dentry->d_inode) {
err = -EEXIST;
if (unlikely(h_inode))
goto out;
} else {
/* rename(2) case */
err = -EIO;
if (unlikely(!h_inode || !h_inode->i_nlink))
goto out;
h_mode = h_inode->i_mode;
if (!isdir) {
err = -EISDIR;
if (unlikely(S_ISDIR(h_mode)))
goto out;
} else if (unlikely(!S_ISDIR(h_mode))) {
err = -ENOTDIR;
goto out;
}
}
err = 0;
/* expected parent dir is locked */
if (unlikely(h_parent != h_dentry->d_parent))
err = -EIO;
out:
AuTraceErr(err);
return err;
}
/*
* initial procedure of adding a new entry.
* prepare writable branch and the parent dir, lock it,
* and lookup whiteout for the new entry.
*/
static struct dentry*
lock_hdir_lkup_wh(struct dentry *dentry, struct au_dtime *dt,
struct dentry *src_dentry, struct au_pin *pin,
struct au_wr_dir_args *wr_dir_args)
{
struct dentry *wh_dentry, *h_parent;
struct super_block *sb;
struct au_branch *br;
int err;
unsigned int udba;
aufs_bindex_t bcpup;
AuDbg("%.*s\n", AuDLNPair(dentry));
err = au_wr_dir(dentry, src_dentry, wr_dir_args);
bcpup = err;
wh_dentry = ERR_PTR(err);
if (unlikely(err < 0))
goto out;
sb = dentry->d_sb;
udba = au_opt_udba(sb);
err = au_pin(pin, dentry, bcpup, udba,
AuPin_DI_LOCKED | AuPin_MNT_WRITE);
wh_dentry = ERR_PTR(err);
if (unlikely(err))
goto out;
h_parent = au_pinned_h_parent(pin);
if (udba != AuOpt_UDBA_NONE
&& au_dbstart(dentry) == bcpup)
err = au_may_add(dentry, bcpup, h_parent,
au_ftest_wrdir(wr_dir_args->flags, ISDIR));
else if (unlikely(dentry->d_name.len > AUFS_MAX_NAMELEN))
err = -ENAMETOOLONG;
wh_dentry = ERR_PTR(err);
if (unlikely(err))
goto out_unpin;
br = au_sbr(sb, bcpup);
if (dt) {
struct path tmp = {
.dentry = h_parent,
.mnt = au_br_mnt(br)
};
au_dtime_store(dt, au_pinned_parent(pin), &tmp);
}
wh_dentry = NULL;
if (bcpup != au_dbwh(dentry))
goto out; /* success */
wh_dentry = au_wh_lkup(h_parent, &dentry->d_name, br);
out_unpin:
if (IS_ERR(wh_dentry))
au_unpin(pin);
out:
return wh_dentry;
}
/* ---------------------------------------------------------------------- */
enum { Mknod, Symlink, Creat };
struct simple_arg {
int type;
union {
struct {
umode_t mode;
bool want_excl;
} c;
struct {
const char *symname;
} s;
struct {
umode_t mode;
dev_t dev;
} m;
} u;
};
static int add_simple(struct inode *dir, struct dentry *dentry,
struct simple_arg *arg)
{
int err;
aufs_bindex_t bstart;
unsigned char created;
struct dentry *wh_dentry, *parent;
struct inode *h_dir;
/* to reuduce stack size */
struct {
struct au_dtime dt;
struct au_pin pin;
struct path h_path;
struct au_wr_dir_args wr_dir_args;
} *a;
AuDbg("%.*s\n", AuDLNPair(dentry));
IMustLock(dir);
err = -ENOMEM;
a = kmalloc(sizeof(*a), GFP_NOFS);
if (unlikely(!a))
goto out;
a->wr_dir_args.force_btgt = -1;
a->wr_dir_args.flags = AuWrDir_ADD_ENTRY;
parent = dentry->d_parent; /* dir inode is locked */
err = aufs_read_lock(dentry, AuLock_DW | AuLock_GEN);
if (unlikely(err))
goto out_free;
err = au_d_may_add(dentry);
if (unlikely(err))
goto out_unlock;
di_write_lock_parent(parent);
wh_dentry = lock_hdir_lkup_wh(dentry, &a->dt, /*src_dentry*/NULL,
&a->pin, &a->wr_dir_args);
err = PTR_ERR(wh_dentry);
if (IS_ERR(wh_dentry))
goto out_parent;
bstart = au_dbstart(dentry);
a->h_path.dentry = au_h_dptr(dentry, bstart);
a->h_path.mnt = au_sbr_mnt(dentry->d_sb, bstart);
h_dir = au_pinned_h_dir(&a->pin);
switch (arg->type) {
case Creat:
err = vfsub_create(h_dir, &a->h_path, arg->u.c.mode,
arg->u.c.want_excl);
break;
case Symlink:
err = vfsub_symlink(h_dir, &a->h_path, arg->u.s.symname);
break;
case Mknod:
err = vfsub_mknod(h_dir, &a->h_path, arg->u.m.mode,
arg->u.m.dev);
break;
default:
BUG();
}
created = !err;
if (!err)
err = epilog(dir, bstart, wh_dentry, dentry);
/* revert */
if (unlikely(created && err && a->h_path.dentry->d_inode)) {
int rerr;
rerr = vfsub_unlink(h_dir, &a->h_path, /*force*/0);
if (rerr) {
AuIOErr("%.*s revert failure(%d, %d)\n",
AuDLNPair(dentry), err, rerr);
err = -EIO;
}
au_dtime_revert(&a->dt);
}
au_unpin(&a->pin);
dput(wh_dentry);
out_parent:
di_write_unlock(parent);
out_unlock:
if (unlikely(err)) {
au_update_dbstart(dentry);
d_drop(dentry);
}
aufs_read_unlock(dentry, AuLock_DW);
out_free:
kfree(a);
out:
return err;
}
int aufs_mknod(struct inode *dir, struct dentry *dentry, umode_t mode,
dev_t dev)
{
struct simple_arg arg = {
.type = Mknod,
.u.m = {
.mode = mode,
.dev = dev
}
};
return add_simple(dir, dentry, &arg);
}
int aufs_symlink(struct inode *dir, struct dentry *dentry, const char *symname)
{
struct simple_arg arg = {
.type = Symlink,
.u.s.symname = symname
};
return add_simple(dir, dentry, &arg);
}
int aufs_create(struct inode *dir, struct dentry *dentry, umode_t mode,
bool want_excl)
{
struct simple_arg arg = {
.type = Creat,
.u.c = {
.mode = mode,
.want_excl = want_excl
}
};
return add_simple(dir, dentry, &arg);
}
/* ---------------------------------------------------------------------- */
struct au_link_args {
aufs_bindex_t bdst, bsrc;
struct au_pin pin;
struct path h_path;
struct dentry *src_parent, *parent;
};
static int au_cpup_before_link(struct dentry *src_dentry,
struct au_link_args *a)
{
int err;
struct dentry *h_src_dentry;
struct au_cp_generic cpg = {
.dentry = src_dentry,
.bdst = a->bdst,
.bsrc = a->bsrc,
.len = -1,
.pin = &a->pin,
.flags = AuCpup_DTIME | AuCpup_HOPEN /* | AuCpup_KEEPLINO */
};
di_read_lock_parent(a->src_parent, AuLock_IR);
err = au_test_and_cpup_dirs(src_dentry, a->bdst);
if (unlikely(err))
goto out;
h_src_dentry = au_h_dptr(src_dentry, a->bsrc);
err = au_pin(&a->pin, src_dentry, a->bdst,
au_opt_udba(src_dentry->d_sb),
AuPin_DI_LOCKED | AuPin_MNT_WRITE);
if (unlikely(err))
goto out;
err = au_sio_cpup_simple(&cpg);
au_unpin(&a->pin);
out:
di_read_unlock(a->src_parent, AuLock_IR);
return err;
}
static int au_cpup_or_link(struct dentry *src_dentry, struct dentry *dentry,
struct au_link_args *a)
{
int err;
unsigned char plink;
aufs_bindex_t bend;
struct dentry *h_src_dentry;
struct inode *h_inode, *inode;
struct super_block *sb;
struct file *h_file;
plink = 0;
h_inode = NULL;
sb = src_dentry->d_sb;
inode = src_dentry->d_inode;
if (au_ibstart(inode) <= a->bdst)
h_inode = au_h_iptr(inode, a->bdst);
if (!h_inode || !h_inode->i_nlink) {
/* copyup src_dentry as the name of dentry. */
bend = au_dbend(dentry);
if (bend < a->bsrc)
au_set_dbend(dentry, a->bsrc);
au_set_h_dptr(dentry, a->bsrc,
dget(au_h_dptr(src_dentry, a->bsrc)));
dget(a->h_path.dentry);
au_set_h_dptr(dentry, a->bdst, NULL);
dentry->d_inode = src_dentry->d_inode; /* tmp */
h_file = au_h_open_pre(dentry, a->bsrc);
if (IS_ERR(h_file))
err = PTR_ERR(h_file);
else {
struct au_cp_generic cpg = {
.dentry = dentry,
.bdst = a->bdst,
.bsrc = -1,
.len = -1,
.pin = &a->pin,
.flags = AuCpup_KEEPLINO
};
err = au_sio_cpup_simple(&cpg);
au_h_open_post(dentry, a->bsrc, h_file);
if (!err) {
dput(a->h_path.dentry);
a->h_path.dentry = au_h_dptr(dentry, a->bdst);
} else
au_set_h_dptr(dentry, a->bdst,
a->h_path.dentry);
}
dentry->d_inode = NULL; /* restore */
au_set_h_dptr(dentry, a->bsrc, NULL);
au_set_dbend(dentry, bend);
} else {
/* the inode of src_dentry already exists on a.bdst branch */
h_src_dentry = d_find_alias(h_inode);
if (!h_src_dentry && au_plink_test(inode)) {
plink = 1;
h_src_dentry = au_plink_lkup(inode, a->bdst);
err = PTR_ERR(h_src_dentry);
if (IS_ERR(h_src_dentry))
goto out;
if (unlikely(!h_src_dentry->d_inode)) {
dput(h_src_dentry);
h_src_dentry = NULL;
}
}
if (h_src_dentry) {
err = vfsub_link(h_src_dentry, au_pinned_h_dir(&a->pin),
&a->h_path);
dput(h_src_dentry);
} else {
AuIOErr("no dentry found for hi%lu on b%d\n",
h_inode->i_ino, a->bdst);
err = -EIO;
}
}
if (!err && !plink)
au_plink_append(inode, a->bdst, a->h_path.dentry);
out:
AuTraceErr(err);
return err;
}
int aufs_link(struct dentry *src_dentry, struct inode *dir,
struct dentry *dentry)
{
int err, rerr;
struct au_dtime dt;
struct au_link_args *a;
struct dentry *wh_dentry, *h_src_dentry;
struct inode *inode;
struct super_block *sb;
struct au_wr_dir_args wr_dir_args = {
/* .force_btgt = -1, */
.flags = AuWrDir_ADD_ENTRY
};
IMustLock(dir);
inode = src_dentry->d_inode;
IMustLock(inode);
err = -ENOMEM;
a = kzalloc(sizeof(*a), GFP_NOFS);
if (unlikely(!a))
goto out;
a->parent = dentry->d_parent; /* dir inode is locked */
err = aufs_read_and_write_lock2(dentry, src_dentry,
AuLock_NOPLM | AuLock_GEN);
if (unlikely(err))
goto out_kfree;
err = au_d_hashed_positive(src_dentry);
if (unlikely(err))
goto out_unlock;
err = au_d_may_add(dentry);
if (unlikely(err))
goto out_unlock;
a->src_parent = dget_parent(src_dentry);
wr_dir_args.force_btgt = au_ibstart(inode);
di_write_lock_parent(a->parent);
wr_dir_args.force_btgt = au_wbr(dentry, wr_dir_args.force_btgt);
wh_dentry = lock_hdir_lkup_wh(dentry, &dt, src_dentry, &a->pin,
&wr_dir_args);
err = PTR_ERR(wh_dentry);
if (IS_ERR(wh_dentry))
goto out_parent;
err = 0;
sb = dentry->d_sb;
a->bdst = au_dbstart(dentry);
a->h_path.dentry = au_h_dptr(dentry, a->bdst);
a->h_path.mnt = au_sbr_mnt(sb, a->bdst);
a->bsrc = au_ibstart(inode);
h_src_dentry = au_h_d_alias(src_dentry, a->bsrc);
if (!h_src_dentry) {
a->bsrc = au_dbstart(src_dentry);
h_src_dentry = au_h_d_alias(src_dentry, a->bsrc);
AuDebugOn(!h_src_dentry);
} else if (IS_ERR(h_src_dentry))
goto out_parent;
if (au_opt_test(au_mntflags(sb), PLINK)) {
if (a->bdst < a->bsrc
/* && h_src_dentry->d_sb != a->h_path.dentry->d_sb */)
err = au_cpup_or_link(src_dentry, dentry, a);
else
err = vfsub_link(h_src_dentry, au_pinned_h_dir(&a->pin),
&a->h_path);
dput(h_src_dentry);
} else {
/*
* copyup src_dentry to the branch we process,
* and then link(2) to it.
*/
dput(h_src_dentry);
if (a->bdst < a->bsrc
/* && h_src_dentry->d_sb != a->h_path.dentry->d_sb */) {
au_unpin(&a->pin);
di_write_unlock(a->parent);
err = au_cpup_before_link(src_dentry, a);
di_write_lock_parent(a->parent);
if (!err)
err = au_pin(&a->pin, dentry, a->bdst,
au_opt_udba(sb),
AuPin_DI_LOCKED | AuPin_MNT_WRITE);
if (unlikely(err))
goto out_wh;
}
if (!err) {
h_src_dentry = au_h_dptr(src_dentry, a->bdst);
err = -ENOENT;
if (h_src_dentry && h_src_dentry->d_inode)
err = vfsub_link(h_src_dentry,
au_pinned_h_dir(&a->pin),
&a->h_path);
}
}
if (unlikely(err))
goto out_unpin;
if (wh_dentry) {
a->h_path.dentry = wh_dentry;
err = au_wh_unlink_dentry(au_pinned_h_dir(&a->pin), &a->h_path,
dentry);
if (unlikely(err))
goto out_revert;
}
dir->i_version++;
if (au_ibstart(dir) == au_dbstart(dentry))
au_cpup_attr_timesizes(dir);
inc_nlink(inode);
inode->i_ctime = dir->i_ctime;
d_instantiate(dentry, au_igrab(inode));
if (d_unhashed(a->h_path.dentry))
/* some filesystem calls d_drop() */
d_drop(dentry);
goto out_unpin; /* success */
out_revert:
rerr = vfsub_unlink(au_pinned_h_dir(&a->pin), &a->h_path, /*force*/0);
if (unlikely(rerr)) {
AuIOErr("%.*s reverting failed(%d, %d)\n",
AuDLNPair(dentry), err, rerr);
err = -EIO;
}
au_dtime_revert(&dt);
out_unpin:
au_unpin(&a->pin);
out_wh:
dput(wh_dentry);
out_parent:
di_write_unlock(a->parent);
dput(a->src_parent);
out_unlock:
if (unlikely(err)) {
au_update_dbstart(dentry);
d_drop(dentry);
}
aufs_read_and_write_unlock2(dentry, src_dentry);
out_kfree:
kfree(a);
out:
AuTraceErr(err);
return err;
}
int aufs_mkdir(struct inode *dir, struct dentry *dentry, umode_t mode)
{
int err, rerr;
aufs_bindex_t bindex;
unsigned char diropq;
struct path h_path;
struct dentry *wh_dentry, *parent, *opq_dentry;
struct mutex *h_mtx;
struct super_block *sb;
struct {
struct au_pin pin;
struct au_dtime dt;
} *a; /* reduce the stack usage */
struct au_wr_dir_args wr_dir_args = {
.force_btgt = -1,
.flags = AuWrDir_ADD_ENTRY | AuWrDir_ISDIR
};
IMustLock(dir);
err = -ENOMEM;
a = kmalloc(sizeof(*a), GFP_NOFS);
if (unlikely(!a))
goto out;
err = aufs_read_lock(dentry, AuLock_DW | AuLock_GEN);
if (unlikely(err))
goto out_free;
err = au_d_may_add(dentry);
if (unlikely(err))
goto out_unlock;
parent = dentry->d_parent; /* dir inode is locked */
di_write_lock_parent(parent);
wh_dentry = lock_hdir_lkup_wh(dentry, &a->dt, /*src_dentry*/NULL,
&a->pin, &wr_dir_args);
err = PTR_ERR(wh_dentry);
if (IS_ERR(wh_dentry))
goto out_parent;
sb = dentry->d_sb;
bindex = au_dbstart(dentry);
h_path.dentry = au_h_dptr(dentry, bindex);
h_path.mnt = au_sbr_mnt(sb, bindex);
err = vfsub_mkdir(au_pinned_h_dir(&a->pin), &h_path, mode);
if (unlikely(err))
goto out_unpin;
/* make the dir opaque */
diropq = 0;
h_mtx = &h_path.dentry->d_inode->i_mutex;
if (wh_dentry
|| au_opt_test(au_mntflags(sb), ALWAYS_DIROPQ)) {
mutex_lock_nested(h_mtx, AuLsc_I_CHILD);
opq_dentry = au_diropq_create(dentry, bindex);
mutex_unlock(h_mtx);
err = PTR_ERR(opq_dentry);
if (IS_ERR(opq_dentry))
goto out_dir;
dput(opq_dentry);
diropq = 1;
}
err = epilog(dir, bindex, wh_dentry, dentry);
if (!err) {
inc_nlink(dir);
goto out_unpin; /* success */
}
/* revert */
if (diropq) {
AuLabel(revert opq);
mutex_lock_nested(h_mtx, AuLsc_I_CHILD);
rerr = au_diropq_remove(dentry, bindex);
mutex_unlock(h_mtx);
if (rerr) {
AuIOErr("%.*s reverting diropq failed(%d, %d)\n",
AuDLNPair(dentry), err, rerr);
err = -EIO;
}
}
out_dir:
AuLabel(revert dir);
rerr = vfsub_rmdir(au_pinned_h_dir(&a->pin), &h_path);
if (rerr) {
AuIOErr("%.*s reverting dir failed(%d, %d)\n",
AuDLNPair(dentry), err, rerr);
err = -EIO;
}
au_dtime_revert(&a->dt);
out_unpin:
au_unpin(&a->pin);
dput(wh_dentry);
out_parent:
di_write_unlock(parent);
out_unlock:
if (unlikely(err)) {
au_update_dbstart(dentry);
d_drop(dentry);
}
aufs_read_unlock(dentry, AuLock_DW);
out_free:
kfree(a);
out:
return err;
}