Commit bc962de7 authored by J. R. Okajima's avatar J. R. Okajima
Browse files

aufs: ioctl, mvdown 1/2, body



Another ioctl feature, move-down.
The behaviour is, as you can guess, the opposite of copy-up.
The feature called FHSM (file-based hierarchical storage management, in
later commit) uses this ioctl aggressively.

See also the document in this commit.
Signed-off-by: default avatarJ. R. Okajima <hooanon05g@gmail.com>
parent 8ea77cf9
...@@ -217,3 +217,29 @@ When user modifies a file on a readonly branch, aufs operate "copy-up" ...@@ -217,3 +217,29 @@ When user modifies a file on a readonly branch, aufs operate "copy-up"
internally and makes change to the new file on the upper writable branch. internally and makes change to the new file on the upper writable branch.
When the trigger systemcall does not update the timestamps of the parent When the trigger systemcall does not update the timestamps of the parent
dir, aufs reverts it after copy-up. dir, aufs reverts it after copy-up.
Move-down (aufs3.9 and later)
----------------------------------------------------------------------
"Copy-up" is one of the essential feature in aufs. It copies a file from
the lower readonly branch to the upper writable branch when a user
changes something about the file.
"Move-down" is an opposite action of copy-up. Basically this action is
ran manually instead of automatically and internally.
For desgin and implementation, aufs has to consider these issues.
- whiteout for the file may exist on the lower branch.
- ancestor directories may not exist on the lower branch.
- diropq for the ancestor directories may exist on the upper branch.
- free space on the lower branch will reduce.
- another access to the file may happen during moving-down, including
UDBA (see "Revalidate Dentry and UDBA").
- the file should not be hard-linked nor pseudo-linked. they should be
handled by auplink utility later.
Sometimes users want to move-down a file from the upper writable branch
to the lower readonly or writable branch. For instance,
- the free space of the upper writable branch is going to run out.
- create a new intermediate branch between the upper and lower branch.
- etc.
For this purpose, use "aumvdown" command in aufs-util.git.
...@@ -18,7 +18,7 @@ aufs-y := module.o sbinfo.o super.o branch.o xino.o sysaufs.o opts.o \ ...@@ -18,7 +18,7 @@ aufs-y := module.o sbinfo.o super.o branch.o xino.o sysaufs.o opts.o \
finfo.o file.o f_op.o \ finfo.o file.o f_op.o \
dir.o vdir.o \ dir.o vdir.o \
iinfo.o inode.o i_op.o i_op_add.o i_op_del.o i_op_ren.o \ iinfo.o inode.o i_op.o i_op_add.o i_op_del.o i_op_ren.o \
ioctl.o mvdown.o ioctl.o
# all are boolean # all are boolean
aufs-$(CONFIG_PROC_FS) += procfs.o plink.o aufs-$(CONFIG_PROC_FS) += procfs.o plink.o
......
...@@ -1354,3 +1354,21 @@ out: ...@@ -1354,3 +1354,21 @@ out:
AuTraceErr(err); AuTraceErr(err);
return err; return err;
} }
/* ---------------------------------------------------------------------- */
int au_br_stfs(struct au_branch *br, struct aufs_stfs *stfs)
{
int err;
struct kstatfs kstfs;
err = vfs_statfs(&br->br_path, &kstfs);
if (!err) {
stfs->f_blocks = kstfs.f_blocks;
stfs->f_bavail = kstfs.f_bavail;
stfs->f_files = kstfs.f_files;
stfs->f_ffree = kstfs.f_ffree;
}
return err;
}
...@@ -194,6 +194,8 @@ long au_ibusy_compat_ioctl(struct file *file, unsigned long arg); ...@@ -194,6 +194,8 @@ long au_ibusy_compat_ioctl(struct file *file, unsigned long arg);
struct au_opt_mod; struct au_opt_mod;
int au_br_mod(struct super_block *sb, struct au_opt_mod *mod, int remount, int au_br_mod(struct super_block *sb, struct au_opt_mod *mod, int remount,
int *do_refresh); int *do_refresh);
struct aufs_stfs;
int au_br_stfs(struct au_branch *br, struct aufs_stfs *stfs);
/* xino.c */ /* xino.c */
static const loff_t au_loff_max = LLONG_MAX; static const loff_t au_loff_max = LLONG_MAX;
......
...@@ -1121,6 +1121,12 @@ int au_sio_cpup_simple(struct au_cp_generic *cpg) ...@@ -1121,6 +1121,12 @@ int au_sio_cpup_simple(struct au_cp_generic *cpg)
return au_do_sio_cpup_simple(cpg); return au_do_sio_cpup_simple(cpg);
} }
int au_sio_cpdown_simple(struct au_cp_generic *cpg)
{
AuDebugOn(cpg->bdst <= cpg->bsrc);
return au_do_sio_cpup_simple(cpg);
}
/* ---------------------------------------------------------------------- */ /* ---------------------------------------------------------------------- */
/* /*
......
...@@ -40,6 +40,10 @@ struct au_cp_generic { ...@@ -40,6 +40,10 @@ struct au_cp_generic {
#define AuCpup_KEEPLINO (1 << 1) /* do not clear the lower xino, #define AuCpup_KEEPLINO (1 << 1) /* do not clear the lower xino,
for link(2) */ for link(2) */
#define AuCpup_RENAME (1 << 2) /* rename after cpup */ #define AuCpup_RENAME (1 << 2) /* rename after cpup */
#define AuCpup_OVERWRITE (1 << 4) /* allow overwriting the
existing entry */
#define AuCpup_RWDST (1 << 5) /* force write target even if
the branch is marked as RO */
#define au_ftest_cpup(flags, name) ((flags) & AuCpup_##name) #define au_ftest_cpup(flags, name) ((flags) & AuCpup_##name)
#define au_fset_cpup(flags, name) \ #define au_fset_cpup(flags, name) \
...@@ -49,6 +53,7 @@ struct au_cp_generic { ...@@ -49,6 +53,7 @@ struct au_cp_generic {
int au_copy_file(struct file *dst, struct file *src, loff_t len); int au_copy_file(struct file *dst, struct file *src, loff_t len);
int au_sio_cpup_simple(struct au_cp_generic *cpg); int au_sio_cpup_simple(struct au_cp_generic *cpg);
int au_sio_cpdown_simple(struct au_cp_generic *cpg);
int au_sio_cpup_wh(struct au_cp_generic *cpg, struct file *file); int au_sio_cpup_wh(struct au_cp_generic *cpg, struct file *file);
int au_cp_dirs(struct dentry *dentry, aufs_bindex_t bdst, int au_cp_dirs(struct dentry *dentry, aufs_bindex_t bdst,
......
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2011-2019 Junjiro R. Okajima
*/
/*
* move-down, opposite of copy-up
*/
#include "aufs.h"
struct au_mvd_args {
struct {
struct super_block *h_sb;
struct dentry *h_parent;
struct au_hinode *hdir;
struct inode *h_dir, *h_inode;
struct au_pin pin;
} info[AUFS_MVDOWN_NARRAY];
struct aufs_mvdown mvdown;
struct dentry *dentry, *parent;
struct inode *inode, *dir;
struct super_block *sb;
aufs_bindex_t bopq, bwh, bfound;
unsigned char rename_lock;
};
#define mvd_errno mvdown.au_errno
#define mvd_bsrc mvdown.stbr[AUFS_MVDOWN_UPPER].bindex
#define mvd_src_brid mvdown.stbr[AUFS_MVDOWN_UPPER].brid
#define mvd_bdst mvdown.stbr[AUFS_MVDOWN_LOWER].bindex
#define mvd_dst_brid mvdown.stbr[AUFS_MVDOWN_LOWER].brid
#define mvd_h_src_sb info[AUFS_MVDOWN_UPPER].h_sb
#define mvd_h_src_parent info[AUFS_MVDOWN_UPPER].h_parent
#define mvd_hdir_src info[AUFS_MVDOWN_UPPER].hdir
#define mvd_h_src_dir info[AUFS_MVDOWN_UPPER].h_dir
#define mvd_h_src_inode info[AUFS_MVDOWN_UPPER].h_inode
#define mvd_pin_src info[AUFS_MVDOWN_UPPER].pin
#define mvd_h_dst_sb info[AUFS_MVDOWN_LOWER].h_sb
#define mvd_h_dst_parent info[AUFS_MVDOWN_LOWER].h_parent
#define mvd_hdir_dst info[AUFS_MVDOWN_LOWER].hdir
#define mvd_h_dst_dir info[AUFS_MVDOWN_LOWER].h_dir
#define mvd_h_dst_inode info[AUFS_MVDOWN_LOWER].h_inode
#define mvd_pin_dst info[AUFS_MVDOWN_LOWER].pin
#define AU_MVD_PR(flag, ...) do { \
if (flag) \
pr_err(__VA_ARGS__); \
} while (0)
static int find_lower_writable(struct au_mvd_args *a)
{
struct super_block *sb;
aufs_bindex_t bindex, bbot;
struct au_branch *br;
sb = a->sb;
bindex = a->mvd_bsrc;
bbot = au_sbbot(sb);
if (a->mvdown.flags & AUFS_MVDOWN_FHSM_LOWER)
; /* re-commit later */
else if (!(a->mvdown.flags & AUFS_MVDOWN_ROLOWER))
for (bindex++; bindex <= bbot; bindex++) {
br = au_sbr(sb, bindex);
if (!au_br_rdonly(br))
return bindex;
}
else
for (bindex++; bindex <= bbot; bindex++) {
br = au_sbr(sb, bindex);
if (!sb_rdonly(au_br_sb(br))) {
if (au_br_rdonly(br))
a->mvdown.flags
|= AUFS_MVDOWN_ROLOWER_R;
return bindex;
}
}
return -1;
}
/* make the parent dir on bdst */
static int au_do_mkdir(const unsigned char dmsg, struct au_mvd_args *a)
{
int err;
err = 0;
a->mvd_hdir_src = au_hi(a->dir, a->mvd_bsrc);
a->mvd_hdir_dst = au_hi(a->dir, a->mvd_bdst);
a->mvd_h_src_parent = au_h_dptr(a->parent, a->mvd_bsrc);
a->mvd_h_dst_parent = NULL;
if (au_dbbot(a->parent) >= a->mvd_bdst)
a->mvd_h_dst_parent = au_h_dptr(a->parent, a->mvd_bdst);
if (!a->mvd_h_dst_parent) {
err = au_cpdown_dirs(a->dentry, a->mvd_bdst);
if (unlikely(err)) {
AU_MVD_PR(dmsg, "cpdown_dirs failed\n");
goto out;
}
a->mvd_h_dst_parent = au_h_dptr(a->parent, a->mvd_bdst);
}
out:
AuTraceErr(err);
return err;
}
/* lock them all */
static int au_do_lock(const unsigned char dmsg, struct au_mvd_args *a)
{
int err;
struct dentry *h_trap;
a->mvd_h_src_sb = au_sbr_sb(a->sb, a->mvd_bsrc);
a->mvd_h_dst_sb = au_sbr_sb(a->sb, a->mvd_bdst);
err = au_pin(&a->mvd_pin_dst, a->dentry, a->mvd_bdst,
au_opt_udba(a->sb),
AuPin_MNT_WRITE | AuPin_DI_LOCKED);
AuTraceErr(err);
if (unlikely(err)) {
AU_MVD_PR(dmsg, "pin_dst failed\n");
goto out;
}
if (a->mvd_h_src_sb != a->mvd_h_dst_sb) {
a->rename_lock = 0;
au_pin_init(&a->mvd_pin_src, a->dentry, a->mvd_bsrc,
AuLsc_DI_PARENT, AuLsc_I_PARENT3,
au_opt_udba(a->sb),
AuPin_MNT_WRITE | AuPin_DI_LOCKED);
err = au_do_pin(&a->mvd_pin_src);
AuTraceErr(err);
a->mvd_h_src_dir = d_inode(a->mvd_h_src_parent);
if (unlikely(err)) {
AU_MVD_PR(dmsg, "pin_src failed\n");
goto out_dst;
}
goto out; /* success */
}
a->rename_lock = 1;
au_pin_hdir_unlock(&a->mvd_pin_dst);
err = au_pin(&a->mvd_pin_src, a->dentry, a->mvd_bsrc,
au_opt_udba(a->sb),
AuPin_MNT_WRITE | AuPin_DI_LOCKED);
AuTraceErr(err);
a->mvd_h_src_dir = d_inode(a->mvd_h_src_parent);
if (unlikely(err)) {
AU_MVD_PR(dmsg, "pin_src failed\n");
au_pin_hdir_lock(&a->mvd_pin_dst);
goto out_dst;
}
au_pin_hdir_unlock(&a->mvd_pin_src);
h_trap = vfsub_lock_rename(a->mvd_h_src_parent, a->mvd_hdir_src,
a->mvd_h_dst_parent, a->mvd_hdir_dst);
if (h_trap) {
err = (h_trap != a->mvd_h_src_parent);
if (err)
err = (h_trap != a->mvd_h_dst_parent);
}
BUG_ON(err); /* it should never happen */
if (unlikely(a->mvd_h_src_dir != au_pinned_h_dir(&a->mvd_pin_src))) {
err = -EBUSY;
AuTraceErr(err);
vfsub_unlock_rename(a->mvd_h_src_parent, a->mvd_hdir_src,
a->mvd_h_dst_parent, a->mvd_hdir_dst);
au_pin_hdir_lock(&a->mvd_pin_src);
au_unpin(&a->mvd_pin_src);
au_pin_hdir_lock(&a->mvd_pin_dst);
goto out_dst;
}
goto out; /* success */
out_dst:
au_unpin(&a->mvd_pin_dst);
out:
AuTraceErr(err);
return err;
}
static void au_do_unlock(const unsigned char dmsg, struct au_mvd_args *a)
{
if (!a->rename_lock)
au_unpin(&a->mvd_pin_src);
else {
vfsub_unlock_rename(a->mvd_h_src_parent, a->mvd_hdir_src,
a->mvd_h_dst_parent, a->mvd_hdir_dst);
au_pin_hdir_lock(&a->mvd_pin_src);
au_unpin(&a->mvd_pin_src);
au_pin_hdir_lock(&a->mvd_pin_dst);
}
au_unpin(&a->mvd_pin_dst);
}
/* copy-down the file */
static int au_do_cpdown(const unsigned char dmsg, struct au_mvd_args *a)
{
int err;
struct au_cp_generic cpg = {
.dentry = a->dentry,
.bdst = a->mvd_bdst,
.bsrc = a->mvd_bsrc,
.len = -1,
.pin = &a->mvd_pin_dst,
.flags = AuCpup_DTIME
};
AuDbg("b%d, b%d\n", cpg.bsrc, cpg.bdst);
if (a->mvdown.flags & AUFS_MVDOWN_OWLOWER)
au_fset_cpup(cpg.flags, OVERWRITE);
if (a->mvdown.flags & AUFS_MVDOWN_ROLOWER)
au_fset_cpup(cpg.flags, RWDST);
err = au_sio_cpdown_simple(&cpg);
if (unlikely(err))
AU_MVD_PR(dmsg, "cpdown failed\n");
AuTraceErr(err);
return err;
}
/*
* unlink the whiteout on bdst if exist which may be created by UDBA while we
* were sleeping
*/
static int au_do_unlink_wh(const unsigned char dmsg, struct au_mvd_args *a)
{
int err;
struct path h_path;
struct au_branch *br;
struct inode *delegated;
br = au_sbr(a->sb, a->mvd_bdst);
h_path.dentry = au_wh_lkup(a->mvd_h_dst_parent, &a->dentry->d_name, br);
err = PTR_ERR(h_path.dentry);
if (IS_ERR(h_path.dentry)) {
AU_MVD_PR(dmsg, "wh_lkup failed\n");
goto out;
}
err = 0;
if (d_is_positive(h_path.dentry)) {
h_path.mnt = au_br_mnt(br);
delegated = NULL;
err = vfsub_unlink(d_inode(a->mvd_h_dst_parent), &h_path,
&delegated, /*force*/0);
if (unlikely(err == -EWOULDBLOCK)) {
pr_warn("cannot retry for NFSv4 delegation"
" for an internal unlink\n");
iput(delegated);
}
if (unlikely(err))
AU_MVD_PR(dmsg, "wh_unlink failed\n");
}
dput(h_path.dentry);
out:
AuTraceErr(err);
return err;
}
/*
* unlink the topmost h_dentry
*/
static int au_do_unlink(const unsigned char dmsg, struct au_mvd_args *a)
{
int err;
struct path h_path;
struct inode *delegated;
h_path.mnt = au_sbr_mnt(a->sb, a->mvd_bsrc);
h_path.dentry = au_h_dptr(a->dentry, a->mvd_bsrc);
delegated = NULL;
err = vfsub_unlink(a->mvd_h_src_dir, &h_path, &delegated, /*force*/0);
if (unlikely(err == -EWOULDBLOCK)) {
pr_warn("cannot retry for NFSv4 delegation"
" for an internal unlink\n");
iput(delegated);
}
if (unlikely(err))
AU_MVD_PR(dmsg, "unlink failed\n");
AuTraceErr(err);
return err;
}
/* Since mvdown succeeded, we ignore an error of this function */
static void au_do_stfs(const unsigned char dmsg, struct au_mvd_args *a)
{
int err;
struct au_branch *br;
a->mvdown.flags |= AUFS_MVDOWN_STFS_FAILED;
br = au_sbr(a->sb, a->mvd_bsrc);
err = au_br_stfs(br, &a->mvdown.stbr[AUFS_MVDOWN_UPPER].stfs);
if (!err) {
br = au_sbr(a->sb, a->mvd_bdst);
a->mvdown.stbr[AUFS_MVDOWN_LOWER].brid = br->br_id;
err = au_br_stfs(br, &a->mvdown.stbr[AUFS_MVDOWN_LOWER].stfs);
}
if (!err)
a->mvdown.flags &= ~AUFS_MVDOWN_STFS_FAILED;
else
AU_MVD_PR(dmsg, "statfs failed (%d), ignored\n", err);
}
/*
* copy-down the file and unlink the bsrc file.
* - unlink the bdst whout if exist
* - copy-down the file (with whtmp name and rename)
* - unlink the bsrc file
*/
static int au_do_mvdown(const unsigned char dmsg, struct au_mvd_args *a)
{
int err;
err = au_do_mkdir(dmsg, a);
if (!err)
err = au_do_lock(dmsg, a);
if (unlikely(err))
goto out;
/*
* do not revert the activities we made on bdst since they should be
* harmless in aufs.
*/
err = au_do_cpdown(dmsg, a);
if (!err)
err = au_do_unlink_wh(dmsg, a);
if (!err && !(a->mvdown.flags & AUFS_MVDOWN_KUPPER))
err = au_do_unlink(dmsg, a);
if (unlikely(err))
goto out_unlock;
AuDbg("%pd2, 0x%x, %d --> %d\n",
a->dentry, a->mvdown.flags, a->mvd_bsrc, a->mvd_bdst);
if (find_lower_writable(a) < 0)
a->mvdown.flags |= AUFS_MVDOWN_BOTTOM;
if (a->mvdown.flags & AUFS_MVDOWN_STFS)
au_do_stfs(dmsg, a);
/* maintain internal array */
if (!(a->mvdown.flags & AUFS_MVDOWN_KUPPER)) {
au_set_h_dptr(a->dentry, a->mvd_bsrc, NULL);
au_set_dbtop(a->dentry, a->mvd_bdst);
au_set_h_iptr(a->inode, a->mvd_bsrc, NULL, /*flags*/0);
au_set_ibtop(a->inode, a->mvd_bdst);
} else {
/* hide the lower */
au_set_h_dptr(a->dentry, a->mvd_bdst, NULL);
au_set_dbbot(a->dentry, a->mvd_bsrc);
au_set_h_iptr(a->inode, a->mvd_bdst, NULL, /*flags*/0);
au_set_ibbot(a->inode, a->mvd_bsrc);
}
if (au_dbbot(a->dentry) < a->mvd_bdst)
au_set_dbbot(a->dentry, a->mvd_bdst);
if (au_ibbot(a->inode) < a->mvd_bdst)
au_set_ibbot(a->inode, a->mvd_bdst);
out_unlock:
au_do_unlock(dmsg, a);
out:
AuTraceErr(err);
return err;
}
/* ---------------------------------------------------------------------- */
/* make sure the file is idle */
static int au_mvd_args_busy(const unsigned char dmsg, struct au_mvd_args *a)
{
int err, plinked;
err = 0;
plinked = !!au_opt_test(au_mntflags(a->sb), PLINK);
if (au_dbtop(a->dentry) == a->mvd_bsrc
&& au_dcount(a->dentry) == 1
&& atomic_read(&a->inode->i_count) == 1
/* && a->mvd_h_src_inode->i_nlink == 1 */
&& (!plinked || !au_plink_test(a->inode))
&& a->inode->i_nlink == 1)
goto out;
err = -EBUSY;
AU_MVD_PR(dmsg,
"b%d, d{b%d, c%d?}, i{c%d?, l%u}, hi{l%u}, p{%d, %d}\n",
a->mvd_bsrc, au_dbtop(a->dentry), au_dcount(a->dentry),
atomic_read(&a->inode->i_count), a->inode->i_nlink,
a->mvd_h_src_inode->i_nlink,
plinked, plinked ? au_plink_test(a->inode) : 0);
out:
AuTraceErr(err);
return err;
}
/* make sure the parent dir is fine */
static int au_mvd_args_parent(const unsigned char dmsg,
struct au_mvd_args *a)
{
int err;
aufs_bindex_t bindex;
err = 0;
if (unlikely(au_alive_dir(a->parent))) {
err = -ENOENT;
AU_MVD_PR(dmsg, "parent dir is dead\n");
goto out;
}
a->bopq = au_dbdiropq(a->parent);
bindex = au_wbr_nonopq(a->dentry, a->mvd_bdst);
AuDbg("b%d\n", bindex);
if (unlikely((bindex >= 0 && bindex < a->mvd_bdst)
|| (a->bopq != -1 && a->bopq < a->mvd_bdst))) {
err = -EINVAL;
a->mvd_errno = EAU_MVDOWN_OPAQUE;
AU_MVD_PR(dmsg, "ancestor is opaque b%d, b%d\n",
a->bopq, a->mvd_bdst);
}
out:
AuTraceErr(err);
return err;
}
static int au_mvd_args_intermediate(const unsigned char dmsg,
struct au_mvd_args *a)
{
int err;
struct au_dinfo *dinfo, *tmp;
/* lookup the next lower positive entry */
err = -ENOMEM;
tmp = au_di_alloc(a->sb, AuLsc_DI_TMP);
if (unlikely(!tmp))
goto out;
a->bfound = -1;
a->bwh = -1;
dinfo = au_di(a->dentry);
au_di_cp(tmp, dinfo);
au_di_swap(tmp, dinfo);
/* returns the number of positive dentries */
err = au_lkup_dentry(a->dentry, a->mvd_bsrc + 1,
/* AuLkup_IGNORE_PERM */ 0);
if (!err)
a->bwh = au_dbwh(a->dentry);
else if (err > 0)
a->bfound = au_dbtop(a->dentry);
au_di_swap(tmp, dinfo);
au_rw_write_unlock(&tmp->di_rwsem);
au_di_free(tmp);
if (unlikely(err < 0))
AU_MVD_PR(dmsg, "failed look-up lower\n");
/*
* here, we have these cases.
* bfound == -1
* no positive dentry under bsrc. there are more sub-cases.
* bwh < 0
* there no whiteout, we can safely move-down.
* bwh <= bsrc
* impossible
* bsrc < bwh && bwh < bdst
* there is a whiteout on RO branch. cannot proceed.
* bwh == bdst
* there is a whiteout on the RW target branch. it should
* be removed.
* bdst < bwh
* there is a whiteout somewhere unrelated branch.
* -1 < bfound && bfound <= bsrc
* impossible.
* bfound < bdst
* found, but it is on RO branch between bsrc and bdst. cannot
* proceed.
* bfound == bdst
* found, replace it if AUFS_MVDOWN_FORCE is set. otherwise return
* error.
* bdst < bfound
* found, after we create the file on bdst, it will be hidden.
*/
AuDebugOn(a->bfound == -1
&& a->bwh != -1
&& a->bwh <= a->mvd_bsrc);
AuDebugOn(-1 < a->bfound
&& a->bfound <= a->mvd_bsrc);
err = -EINVAL;
if (a->bfound == -1
&& a->mvd_bsrc < a->bwh
&& a->bwh != -1
&& a->bwh < a->mvd_bdst) {
a->mvd_errno = EAU_MVDOWN_WHITEOUT;
AU_MVD_PR(dmsg, "bsrc %d, bdst %d, bfound %d, bwh %d\n",
a->mvd_bsrc, a->mvd_bdst, a->bfound, a->bwh);
goto out;
} else if (a->bfound != -1 && a->bfound < a->mvd_bdst) {
a->mvd_errno = EAU_MVDOWN_UPPER;
AU_MVD_PR(dmsg, "bdst %d, bfound %d\n",
a->mvd_bdst, a->bfound);
goto out;
}
err = 0; /* success */
out:
AuTraceErr(err);
return err;
}
static int au_mvd_args_exist(const unsigned char dmsg, struct au_mvd_args *a)
{
int err;
err = 0;
if (!(a->mvdown.flags & AUFS_MVDOWN_OWLOWER)
&& a->bfound == a->mvd_bdst)
err = -EEXIST;
AuTraceErr(err);
return err;
}
static int au_mvd_args(const unsigned char dmsg, struct au_mvd_args *a)
{
int err;
struct au_branch *br;
err = -EISDIR;
if (unlikely(S_ISDIR(a->inode->i_mode)))
goto out;
err = -EINVAL;
if (!(a->mvdown.flags & AUFS_MVDOWN_BRID_UPPER))
a->mvd_bsrc = au_ibtop(a->inode);
else {
a->mvd_bsrc = au_br_index(a->sb, a->mvd_src_brid);
if (unlikely(a->mvd_bsrc < 0
|| (a->mvd_bsrc < au_dbtop(a->dentry)
|| au_dbbot(a->dentry) < a->mvd_bsrc
|| !au_h_dptr(a->dentry, a->mvd_bsrc))
|| (a->mvd_bsrc < au_ibtop(a->inode)
|| au_ibbot(a->inode) < a->mvd_bsrc
|| !au_h_iptr(a->inode, a->mvd_bsrc)))) {
a->mvd_errno = EAU_MVDOWN_NOUPPER;
AU_MVD_PR(dmsg, "no upper\n");
goto out;
}
}
if (unlikely(a->mvd_bsrc == au_sbbot(a->sb))) {
a->mvd_errno = EAU_MVDOWN_BOTTOM;
AU_MVD_PR(dmsg, "on the bottom\n");
goto out;
}
a->mvd_h_src_inode = au_h_iptr(a->inode, a->mvd_bsrc);
br = au_sbr(a->sb, a->mvd_bsrc);
err = au_br_rdonly(br);
if (!(a->mvdown.flags & AUFS_MVDOWN_ROUPPER)) {
if (unlikely(err))
goto out;
} else if (!(vfsub_native_ro(a->mvd_h_src_inode)
|| IS_APPEND(a->mvd_h_src_inode))) {
if (err)
a->mvdown.flags |= AUFS_MVDOWN_ROUPPER_R;
/* go on */
} else
goto out;
err = -EINVAL;
if (!(a->mvdown.flags & AUFS_MVDOWN_BRID_LOWER)) {
a->mvd_bdst = find_lower_writable(a);
if (unlikely(a->mvd_bdst < 0)) {
a->mvd_errno = EAU_MVDOWN_BOTTOM;
AU_MVD_PR(dmsg, "no writable lower branch\n");
goto out;
}
} else {
a->mvd_bdst = au_br_index(a->sb, a->mvd_dst_brid);
if (unlikely(a->mvd_bdst < 0
|| au_sbbot(a->sb) < a->mvd_bdst)) {
a->mvd_errno = EAU_MVDOWN_NOLOWERBR;
AU_MVD_PR(dmsg, "no lower brid\n");
goto out;
}
}
err = au_mvd_args_busy(dmsg, a);
if (!err)
err = au_mvd_args_parent(dmsg, a);
if (!err)
err = au_mvd_args_intermediate(dmsg, a);
if (!err)
err = au_mvd_args_exist(dmsg, a);
if (!err)
AuDbg("b%d, b%d\n", a->mvd_bsrc, a->mvd_bdst);
out:
AuTraceErr(err);
return err;
}
int au_mvdown(struct dentry *dentry, struct aufs_mvdown __user *uarg)
{
int err, e;
unsigned char dmsg;
struct au_mvd_args *args;
struct inode *inode;
inode = d_inode(dentry);
err = -EPERM;
if (unlikely(!capable(CAP_SYS_ADMIN)))
goto out;
err = -ENOMEM;
args = kmalloc(sizeof(*args), GFP_NOFS);
if (unlikely(!args))
goto out;
err = copy_from_user(&args->mvdown, uarg, sizeof(args->mvdown));
if (!err)
/* VERIFY_WRITE */
err = !access_ok(uarg, sizeof(*uarg));
if (unlikely(err)) {
err = -EFAULT;
AuTraceErr(err);
goto out_free;
}
AuDbg("flags 0x%x\n", args->mvdown.flags);
args->mvdown.flags &= ~(AUFS_MVDOWN_ROLOWER_R | AUFS_MVDOWN_ROUPPER_R);
args->mvdown.au_errno = 0;
args->dentry = dentry;
args->inode = inode;
args->sb = dentry->d_sb;
err = -ENOENT;
dmsg = !!(args->mvdown.flags & AUFS_MVDOWN_DMSG);
args->parent = dget_parent(dentry);
args->dir = d_inode(args->parent);
inode_lock_nested(args->dir, I_MUTEX_PARENT);
dput(args->parent);
if (unlikely(args->parent != dentry->d_parent)) {
AU_MVD_PR(dmsg, "parent dir is moved\n");
goto out_dir;
}
inode_lock_nested(inode, I_MUTEX_CHILD);
err = aufs_read_lock(dentry, AuLock_DW | AuLock_FLUSH | AuLock_NOPLMW);
if (unlikely(err))
goto out_inode;
di_write_lock_parent(args->parent);
err = au_mvd_args(dmsg, args);
if (unlikely(err))
goto out_parent;
err = au_do_mvdown(dmsg, args);
if (unlikely(err))
goto out_parent;
au_cpup_attr_timesizes(args->dir);
au_cpup_attr_timesizes(inode);
if (!(args->mvdown.flags & AUFS_MVDOWN_KUPPER))
au_cpup_igen(inode, au_h_iptr(inode, args->mvd_bdst));
/* au_digen_dec(dentry); */
out_parent:
di_write_unlock(args->parent);
aufs_read_unlock(dentry, AuLock_DW);
out_inode:
inode_unlock(inode);
out_dir:
inode_unlock(args->dir);
out_free:
e = copy_to_user(uarg, &args->mvdown, sizeof(args->mvdown));
if (unlikely(e))
err = -EFAULT;
au_kfree_rcu(args);
out:
AuTraceErr(err);
return err;
}
...@@ -260,6 +260,9 @@ int au_cpdown_dirs(struct dentry *dentry, aufs_bindex_t bdst); ...@@ -260,6 +260,9 @@ int au_cpdown_dirs(struct dentry *dentry, aufs_bindex_t bdst);
int au_wbr_nonopq(struct dentry *dentry, aufs_bindex_t bindex); int au_wbr_nonopq(struct dentry *dentry, aufs_bindex_t bindex);
int au_wbr_do_copyup_bu(struct dentry *dentry, aufs_bindex_t btop); int au_wbr_do_copyup_bu(struct dentry *dentry, aufs_bindex_t btop);
/* mvdown.c */
int au_mvdown(struct dentry *dentry, struct aufs_mvdown __user *arg);
/* ---------------------------------------------------------------------- */ /* ---------------------------------------------------------------------- */
static inline struct au_sbinfo *au_sbi(struct super_block *sb) static inline struct au_sbinfo *au_sbi(struct super_block *sb)
......
...@@ -57,6 +57,14 @@ static inline void vfsub_dead_dir(struct inode *inode) ...@@ -57,6 +57,14 @@ static inline void vfsub_dead_dir(struct inode *inode)
clear_nlink(inode); clear_nlink(inode);
} }
static inline int vfsub_native_ro(struct inode *inode)
{
return sb_rdonly(inode->i_sb)
|| IS_RDONLY(inode)
/* || IS_APPEND(inode) */
|| IS_IMMUTABLE(inode);
}
int vfsub_sync_filesystem(struct super_block *h_sb, int wait); int vfsub_sync_filesystem(struct super_block *h_sb, int wait);
/* ---------------------------------------------------------------------- */ /* ---------------------------------------------------------------------- */
......
...@@ -312,6 +312,66 @@ struct aufs_ibusy { ...@@ -312,6 +312,66 @@ struct aufs_ibusy {
/* ---------------------------------------------------------------------- */ /* ---------------------------------------------------------------------- */
/* error code for move-down */
/* the actual message strings are implemented in aufs-util.git */
enum {
EAU_MVDOWN_OPAQUE = 1,
EAU_MVDOWN_WHITEOUT,
EAU_MVDOWN_UPPER,
EAU_MVDOWN_BOTTOM,
EAU_MVDOWN_NOUPPER,
EAU_MVDOWN_NOLOWERBR,
EAU_Last
};
/* flags for move-down */
#define AUFS_MVDOWN_DMSG 1
#define AUFS_MVDOWN_OWLOWER (1 << 1) /* overwrite lower */
#define AUFS_MVDOWN_KUPPER (1 << 2) /* keep upper */
#define AUFS_MVDOWN_ROLOWER (1 << 3) /* do even if lower is RO */
#define AUFS_MVDOWN_ROLOWER_R (1 << 4) /* did on lower RO */
#define AUFS_MVDOWN_ROUPPER (1 << 5) /* do even if upper is RO */
#define AUFS_MVDOWN_ROUPPER_R (1 << 6) /* did on upper RO */
#define AUFS_MVDOWN_BRID_UPPER (1 << 7) /* upper brid */
#define AUFS_MVDOWN_BRID_LOWER (1 << 8) /* lower brid */
#define AUFS_MVDOWN_FHSM_LOWER (1 << 9) /* find fhsm attr for lower */
#define AUFS_MVDOWN_STFS (1 << 10) /* req. stfs */
#define AUFS_MVDOWN_STFS_FAILED (1 << 11) /* output: stfs is unusable */
#define AUFS_MVDOWN_BOTTOM (1 << 12) /* output: no more lowers */
/* index for move-down */
enum {
AUFS_MVDOWN_UPPER,
AUFS_MVDOWN_LOWER,
AUFS_MVDOWN_NARRAY
};
/*
* additional info of move-down
* number of free blocks and inodes.
* subset of struct kstatfs, but smaller and always 64bit.
*/
struct aufs_stfs {
uint64_t f_blocks;
uint64_t f_bavail;
uint64_t f_files;
uint64_t f_ffree;
};
struct aufs_stbr {
int16_t brid; /* optional input */
int16_t bindex; /* output */
struct aufs_stfs stfs; /* output when AUFS_MVDOWN_STFS set */
} __aligned(8);
struct aufs_mvdown {
uint32_t flags; /* input/output */
struct aufs_stbr stbr[AUFS_MVDOWN_NARRAY]; /* input/output */
int8_t au_errno; /* output */
} __aligned(8);
/* ---------------------------------------------------------------------- */
union aufs_brinfo { union aufs_brinfo {
/* PATH_MAX may differ between kernel-space and user-space */ /* PATH_MAX may differ between kernel-space and user-space */
char _spacer[4096]; char _spacer[4096];
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment