mirror of
https://github.com/linux-sunxi/meta-sunxi.git
synced 2025-04-08 09:56:47 +02:00
516 lines
12 KiB
C
516 lines
12 KiB
C
/*
|
|
* Copyright (C) 2005-2012 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
|
|
*/
|
|
|
|
/*
|
|
* pseudo-link
|
|
*/
|
|
|
|
#include "aufs.h"
|
|
|
|
/*
|
|
* the pseudo-link maintenance mode.
|
|
* during a user process maintains the pseudo-links,
|
|
* prohibit adding a new plink and branch manipulation.
|
|
*
|
|
* Flags
|
|
* NOPLM:
|
|
* For entry functions which will handle plink, and i_mutex is already held
|
|
* in VFS.
|
|
* They cannot wait and should return an error at once.
|
|
* Callers has to check the error.
|
|
* NOPLMW:
|
|
* For entry functions which will handle plink, but i_mutex is not held
|
|
* in VFS.
|
|
* They can wait the plink maintenance mode to finish.
|
|
*
|
|
* They behave like F_SETLK and F_SETLKW.
|
|
* If the caller never handle plink, then both flags are unnecessary.
|
|
*/
|
|
|
|
int au_plink_maint(struct super_block *sb, int flags)
|
|
{
|
|
int err;
|
|
pid_t pid, ppid;
|
|
struct au_sbinfo *sbi;
|
|
|
|
SiMustAnyLock(sb);
|
|
|
|
err = 0;
|
|
if (!au_opt_test(au_mntflags(sb), PLINK))
|
|
goto out;
|
|
|
|
sbi = au_sbi(sb);
|
|
pid = sbi->si_plink_maint_pid;
|
|
if (!pid || pid == current->pid)
|
|
goto out;
|
|
|
|
/* todo: it highly depends upon /sbin/mount.aufs */
|
|
rcu_read_lock();
|
|
ppid = task_pid_vnr(rcu_dereference(current->real_parent));
|
|
rcu_read_unlock();
|
|
if (pid == ppid)
|
|
goto out;
|
|
|
|
if (au_ftest_lock(flags, NOPLMW)) {
|
|
/* if there is no i_mutex lock in VFS, we don't need to wait */
|
|
/* AuDebugOn(!lockdep_depth(current)); */
|
|
while (sbi->si_plink_maint_pid) {
|
|
si_read_unlock(sb);
|
|
/* gave up wake_up_bit() */
|
|
wait_event(sbi->si_plink_wq, !sbi->si_plink_maint_pid);
|
|
|
|
if (au_ftest_lock(flags, FLUSH))
|
|
au_nwt_flush(&sbi->si_nowait);
|
|
si_noflush_read_lock(sb);
|
|
}
|
|
} else if (au_ftest_lock(flags, NOPLM)) {
|
|
AuDbg("ppid %d, pid %d\n", ppid, pid);
|
|
err = -EAGAIN;
|
|
}
|
|
|
|
out:
|
|
return err;
|
|
}
|
|
|
|
void au_plink_maint_leave(struct au_sbinfo *sbinfo)
|
|
{
|
|
spin_lock(&sbinfo->si_plink_maint_lock);
|
|
sbinfo->si_plink_maint_pid = 0;
|
|
spin_unlock(&sbinfo->si_plink_maint_lock);
|
|
wake_up_all(&sbinfo->si_plink_wq);
|
|
}
|
|
|
|
int au_plink_maint_enter(struct super_block *sb)
|
|
{
|
|
int err;
|
|
struct au_sbinfo *sbinfo;
|
|
|
|
err = 0;
|
|
sbinfo = au_sbi(sb);
|
|
/* make sure i am the only one in this fs */
|
|
si_write_lock(sb, AuLock_FLUSH);
|
|
if (au_opt_test(au_mntflags(sb), PLINK)) {
|
|
spin_lock(&sbinfo->si_plink_maint_lock);
|
|
if (!sbinfo->si_plink_maint_pid)
|
|
sbinfo->si_plink_maint_pid = current->pid;
|
|
else
|
|
err = -EBUSY;
|
|
spin_unlock(&sbinfo->si_plink_maint_lock);
|
|
}
|
|
si_write_unlock(sb);
|
|
|
|
return err;
|
|
}
|
|
|
|
/* ---------------------------------------------------------------------- */
|
|
|
|
struct pseudo_link {
|
|
union {
|
|
struct list_head list;
|
|
struct rcu_head rcu;
|
|
};
|
|
struct inode *inode;
|
|
};
|
|
|
|
#ifdef CONFIG_AUFS_DEBUG
|
|
void au_plink_list(struct super_block *sb)
|
|
{
|
|
struct au_sbinfo *sbinfo;
|
|
struct list_head *plink_list;
|
|
struct pseudo_link *plink;
|
|
|
|
SiMustAnyLock(sb);
|
|
|
|
sbinfo = au_sbi(sb);
|
|
AuDebugOn(!au_opt_test(au_mntflags(sb), PLINK));
|
|
AuDebugOn(au_plink_maint(sb, AuLock_NOPLM));
|
|
|
|
plink_list = &sbinfo->si_plink.head;
|
|
rcu_read_lock();
|
|
list_for_each_entry_rcu(plink, plink_list, list)
|
|
AuDbg("%lu\n", plink->inode->i_ino);
|
|
rcu_read_unlock();
|
|
}
|
|
#endif
|
|
|
|
/* is the inode pseudo-linked? */
|
|
int au_plink_test(struct inode *inode)
|
|
{
|
|
int found;
|
|
struct au_sbinfo *sbinfo;
|
|
struct list_head *plink_list;
|
|
struct pseudo_link *plink;
|
|
|
|
sbinfo = au_sbi(inode->i_sb);
|
|
AuRwMustAnyLock(&sbinfo->si_rwsem);
|
|
AuDebugOn(!au_opt_test(au_mntflags(inode->i_sb), PLINK));
|
|
AuDebugOn(au_plink_maint(inode->i_sb, AuLock_NOPLM));
|
|
|
|
found = 0;
|
|
plink_list = &sbinfo->si_plink.head;
|
|
rcu_read_lock();
|
|
list_for_each_entry_rcu(plink, plink_list, list)
|
|
if (plink->inode == inode) {
|
|
found = 1;
|
|
break;
|
|
}
|
|
rcu_read_unlock();
|
|
return found;
|
|
}
|
|
|
|
/* ---------------------------------------------------------------------- */
|
|
|
|
/*
|
|
* generate a name for plink.
|
|
* the file will be stored under AUFS_WH_PLINKDIR.
|
|
*/
|
|
/* 20 is max digits length of ulong 64 */
|
|
#define PLINK_NAME_LEN ((20 + 1) * 2)
|
|
|
|
static int plink_name(char *name, int len, struct inode *inode,
|
|
aufs_bindex_t bindex)
|
|
{
|
|
int rlen;
|
|
struct inode *h_inode;
|
|
|
|
h_inode = au_h_iptr(inode, bindex);
|
|
rlen = snprintf(name, len, "%lu.%lu", inode->i_ino, h_inode->i_ino);
|
|
return rlen;
|
|
}
|
|
|
|
struct au_do_plink_lkup_args {
|
|
struct dentry **errp;
|
|
struct qstr *tgtname;
|
|
struct dentry *h_parent;
|
|
struct au_branch *br;
|
|
};
|
|
|
|
static struct dentry *au_do_plink_lkup(struct qstr *tgtname,
|
|
struct dentry *h_parent,
|
|
struct au_branch *br)
|
|
{
|
|
struct dentry *h_dentry;
|
|
struct mutex *h_mtx;
|
|
|
|
h_mtx = &h_parent->d_inode->i_mutex;
|
|
mutex_lock_nested(h_mtx, AuLsc_I_CHILD2);
|
|
h_dentry = au_lkup_one(tgtname, h_parent, br, /*nd*/NULL);
|
|
mutex_unlock(h_mtx);
|
|
return h_dentry;
|
|
}
|
|
|
|
static void au_call_do_plink_lkup(void *args)
|
|
{
|
|
struct au_do_plink_lkup_args *a = args;
|
|
*a->errp = au_do_plink_lkup(a->tgtname, a->h_parent, a->br);
|
|
}
|
|
|
|
/* lookup the plink-ed @inode under the branch at @bindex */
|
|
struct dentry *au_plink_lkup(struct inode *inode, aufs_bindex_t bindex)
|
|
{
|
|
struct dentry *h_dentry, *h_parent;
|
|
struct au_branch *br;
|
|
struct inode *h_dir;
|
|
int wkq_err;
|
|
char a[PLINK_NAME_LEN];
|
|
struct qstr tgtname = {
|
|
.name = a
|
|
};
|
|
|
|
AuDebugOn(au_plink_maint(inode->i_sb, AuLock_NOPLM));
|
|
|
|
br = au_sbr(inode->i_sb, bindex);
|
|
h_parent = br->br_wbr->wbr_plink;
|
|
h_dir = h_parent->d_inode;
|
|
tgtname.len = plink_name(a, sizeof(a), inode, bindex);
|
|
|
|
if (current_fsuid()) {
|
|
struct au_do_plink_lkup_args args = {
|
|
.errp = &h_dentry,
|
|
.tgtname = &tgtname,
|
|
.h_parent = h_parent,
|
|
.br = br
|
|
};
|
|
|
|
wkq_err = au_wkq_wait(au_call_do_plink_lkup, &args);
|
|
if (unlikely(wkq_err))
|
|
h_dentry = ERR_PTR(wkq_err);
|
|
} else
|
|
h_dentry = au_do_plink_lkup(&tgtname, h_parent, br);
|
|
|
|
return h_dentry;
|
|
}
|
|
|
|
/* create a pseudo-link */
|
|
static int do_whplink(struct qstr *tgt, struct dentry *h_parent,
|
|
struct dentry *h_dentry, struct au_branch *br)
|
|
{
|
|
int err;
|
|
struct path h_path = {
|
|
.mnt = br->br_mnt
|
|
};
|
|
struct inode *h_dir;
|
|
|
|
h_dir = h_parent->d_inode;
|
|
mutex_lock_nested(&h_dir->i_mutex, AuLsc_I_CHILD2);
|
|
again:
|
|
h_path.dentry = au_lkup_one(tgt, h_parent, br, /*nd*/NULL);
|
|
err = PTR_ERR(h_path.dentry);
|
|
if (IS_ERR(h_path.dentry))
|
|
goto out;
|
|
|
|
err = 0;
|
|
/* wh.plink dir is not monitored */
|
|
/* todo: is it really safe? */
|
|
if (h_path.dentry->d_inode
|
|
&& h_path.dentry->d_inode != h_dentry->d_inode) {
|
|
err = vfsub_unlink(h_dir, &h_path, /*force*/0);
|
|
dput(h_path.dentry);
|
|
h_path.dentry = NULL;
|
|
if (!err)
|
|
goto again;
|
|
}
|
|
if (!err && !h_path.dentry->d_inode)
|
|
err = vfsub_link(h_dentry, h_dir, &h_path);
|
|
dput(h_path.dentry);
|
|
|
|
out:
|
|
mutex_unlock(&h_dir->i_mutex);
|
|
return err;
|
|
}
|
|
|
|
struct do_whplink_args {
|
|
int *errp;
|
|
struct qstr *tgt;
|
|
struct dentry *h_parent;
|
|
struct dentry *h_dentry;
|
|
struct au_branch *br;
|
|
};
|
|
|
|
static void call_do_whplink(void *args)
|
|
{
|
|
struct do_whplink_args *a = args;
|
|
*a->errp = do_whplink(a->tgt, a->h_parent, a->h_dentry, a->br);
|
|
}
|
|
|
|
static int whplink(struct dentry *h_dentry, struct inode *inode,
|
|
aufs_bindex_t bindex, struct au_branch *br)
|
|
{
|
|
int err, wkq_err;
|
|
struct au_wbr *wbr;
|
|
struct dentry *h_parent;
|
|
struct inode *h_dir;
|
|
char a[PLINK_NAME_LEN];
|
|
struct qstr tgtname = {
|
|
.name = a
|
|
};
|
|
|
|
wbr = au_sbr(inode->i_sb, bindex)->br_wbr;
|
|
h_parent = wbr->wbr_plink;
|
|
h_dir = h_parent->d_inode;
|
|
tgtname.len = plink_name(a, sizeof(a), inode, bindex);
|
|
|
|
/* always superio. */
|
|
if (current_fsuid()) {
|
|
struct do_whplink_args args = {
|
|
.errp = &err,
|
|
.tgt = &tgtname,
|
|
.h_parent = h_parent,
|
|
.h_dentry = h_dentry,
|
|
.br = br
|
|
};
|
|
wkq_err = au_wkq_wait(call_do_whplink, &args);
|
|
if (unlikely(wkq_err))
|
|
err = wkq_err;
|
|
} else
|
|
err = do_whplink(&tgtname, h_parent, h_dentry, br);
|
|
|
|
return err;
|
|
}
|
|
|
|
/* free a single plink */
|
|
static void do_put_plink(struct pseudo_link *plink, int do_del)
|
|
{
|
|
if (do_del)
|
|
list_del(&plink->list);
|
|
iput(plink->inode);
|
|
kfree(plink);
|
|
}
|
|
|
|
static void do_put_plink_rcu(struct rcu_head *rcu)
|
|
{
|
|
struct pseudo_link *plink;
|
|
|
|
plink = container_of(rcu, struct pseudo_link, rcu);
|
|
iput(plink->inode);
|
|
kfree(plink);
|
|
}
|
|
|
|
/*
|
|
* create a new pseudo-link for @h_dentry on @bindex.
|
|
* the linked inode is held in aufs @inode.
|
|
*/
|
|
void au_plink_append(struct inode *inode, aufs_bindex_t bindex,
|
|
struct dentry *h_dentry)
|
|
{
|
|
struct super_block *sb;
|
|
struct au_sbinfo *sbinfo;
|
|
struct list_head *plink_list;
|
|
struct pseudo_link *plink, *tmp;
|
|
int found, err, cnt;
|
|
|
|
sb = inode->i_sb;
|
|
sbinfo = au_sbi(sb);
|
|
AuDebugOn(!au_opt_test(au_mntflags(sb), PLINK));
|
|
AuDebugOn(au_plink_maint(sb, AuLock_NOPLM));
|
|
|
|
cnt = 0;
|
|
found = 0;
|
|
plink_list = &sbinfo->si_plink.head;
|
|
rcu_read_lock();
|
|
list_for_each_entry_rcu(plink, plink_list, list) {
|
|
cnt++;
|
|
if (plink->inode == inode) {
|
|
found = 1;
|
|
break;
|
|
}
|
|
}
|
|
rcu_read_unlock();
|
|
if (found)
|
|
return;
|
|
|
|
tmp = kmalloc(sizeof(*plink), GFP_NOFS);
|
|
if (tmp)
|
|
tmp->inode = au_igrab(inode);
|
|
else {
|
|
err = -ENOMEM;
|
|
goto out;
|
|
}
|
|
|
|
spin_lock(&sbinfo->si_plink.spin);
|
|
list_for_each_entry(plink, plink_list, list) {
|
|
if (plink->inode == inode) {
|
|
found = 1;
|
|
break;
|
|
}
|
|
}
|
|
if (!found)
|
|
list_add_rcu(&tmp->list, plink_list);
|
|
spin_unlock(&sbinfo->si_plink.spin);
|
|
if (!found) {
|
|
cnt++;
|
|
WARN_ONCE(cnt > AUFS_PLINK_WARN,
|
|
"unexpectedly many pseudo links, %d\n", cnt);
|
|
err = whplink(h_dentry, inode, bindex, au_sbr(sb, bindex));
|
|
} else {
|
|
do_put_plink(tmp, 0);
|
|
return;
|
|
}
|
|
|
|
out:
|
|
if (unlikely(err)) {
|
|
pr_warn("err %d, damaged pseudo link.\n", err);
|
|
if (tmp) {
|
|
au_spl_del_rcu(&tmp->list, &sbinfo->si_plink);
|
|
call_rcu(&tmp->rcu, do_put_plink_rcu);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* free all plinks */
|
|
void au_plink_put(struct super_block *sb, int verbose)
|
|
{
|
|
struct au_sbinfo *sbinfo;
|
|
struct list_head *plink_list;
|
|
struct pseudo_link *plink, *tmp;
|
|
|
|
SiMustWriteLock(sb);
|
|
|
|
sbinfo = au_sbi(sb);
|
|
AuDebugOn(!au_opt_test(au_mntflags(sb), PLINK));
|
|
AuDebugOn(au_plink_maint(sb, AuLock_NOPLM));
|
|
|
|
plink_list = &sbinfo->si_plink.head;
|
|
/* no spin_lock since sbinfo is write-locked */
|
|
WARN(verbose && !list_empty(plink_list), "pseudo-link is not flushed");
|
|
list_for_each_entry_safe(plink, tmp, plink_list, list)
|
|
do_put_plink(plink, 0);
|
|
INIT_LIST_HEAD(plink_list);
|
|
}
|
|
|
|
void au_plink_clean(struct super_block *sb, int verbose)
|
|
{
|
|
struct dentry *root;
|
|
|
|
root = sb->s_root;
|
|
aufs_write_lock(root);
|
|
if (au_opt_test(au_mntflags(sb), PLINK))
|
|
au_plink_put(sb, verbose);
|
|
aufs_write_unlock(root);
|
|
}
|
|
|
|
/* free the plinks on a branch specified by @br_id */
|
|
void au_plink_half_refresh(struct super_block *sb, aufs_bindex_t br_id)
|
|
{
|
|
struct au_sbinfo *sbinfo;
|
|
struct list_head *plink_list;
|
|
struct pseudo_link *plink, *tmp;
|
|
struct inode *inode;
|
|
aufs_bindex_t bstart, bend, bindex;
|
|
unsigned char do_put;
|
|
|
|
SiMustWriteLock(sb);
|
|
|
|
sbinfo = au_sbi(sb);
|
|
AuDebugOn(!au_opt_test(au_mntflags(sb), PLINK));
|
|
AuDebugOn(au_plink_maint(sb, AuLock_NOPLM));
|
|
|
|
plink_list = &sbinfo->si_plink.head;
|
|
/* no spin_lock since sbinfo is write-locked */
|
|
list_for_each_entry_safe(plink, tmp, plink_list, list) {
|
|
do_put = 0;
|
|
inode = au_igrab(plink->inode);
|
|
ii_write_lock_child(inode);
|
|
bstart = au_ibstart(inode);
|
|
bend = au_ibend(inode);
|
|
if (bstart >= 0) {
|
|
for (bindex = bstart; bindex <= bend; bindex++) {
|
|
if (!au_h_iptr(inode, bindex)
|
|
|| au_ii_br_id(inode, bindex) != br_id)
|
|
continue;
|
|
au_set_h_iptr(inode, bindex, NULL, 0);
|
|
do_put = 1;
|
|
break;
|
|
}
|
|
} else
|
|
do_put_plink(plink, 1);
|
|
|
|
if (do_put) {
|
|
for (bindex = bstart; bindex <= bend; bindex++)
|
|
if (au_h_iptr(inode, bindex)) {
|
|
do_put = 0;
|
|
break;
|
|
}
|
|
if (do_put)
|
|
do_put_plink(plink, 1);
|
|
}
|
|
ii_write_unlock(inode);
|
|
iput(inode);
|
|
}
|
|
}
|