#include <linux/syscalls.h>
#include <linux/string.h>
#include <linux/mm.h>
#include <linux/fs.h>
#include <linux/fsnotify.h>
#include <linux/slab.h>
#include <linux/init.h>
#include <linux/hash.h>
#include <linux/cache.h>
#include <linux/module.h>
#include <linux/mount.h>
#include <linux/file.h>
#include <asm/uaccess.h>
#include <linux/security.h>
#include <linux/seqlock.h>
#include <linux/swap.h>
#include <linux/bootmem.h>
#include <linux/fs_struct.h>
#include <linux/hardirq.h>
#include <linux/bit_spinlock.h>
#include <linux/rculist_bl.h>
#include <linux/prefetch.h>
#include "internal.h"
int sysctl_vfs_cache_pressure __read_mostly = 100;
EXPORT_SYMBOL_GPL(sysctl_vfs_cache_pressure);
static __cacheline_aligned_in_smp DEFINE_SPINLOCK(dcache_lru_lock);
__cacheline_aligned_in_smp DEFINE_SEQLOCK(rename_lock);
EXPORT_SYMBOL(rename_lock);
static struct kmem_cache *dentry_cache __read_mostly;
#define D_HASHBITS d_hash_shift
#define D_HASHMASK d_hash_mask
static unsigned int d_hash_mask __read_mostly;
static unsigned int d_hash_shift __read_mostly;
static struct hlist_bl_head *dentry_hashtable __read_mostly;
static inline struct hlist_bl_head *d_hash(struct dentry *parent,
unsigned long hash)
{
hash += ((unsigned long) parent ^ GOLDEN_RATIO_PRIME) / L1_CACHE_BYTES;
hash = hash ^ ((hash ^ GOLDEN_RATIO_PRIME) >> D_HASHBITS);
return dentry_hashtable + (hash & D_HASHMASK);
}
struct dentry_stat_t dentry_stat = {
.age_limit = 45,
};
static DEFINE_PER_CPU(unsigned int, nr_dentry);
#if defined(CONFIG_SYSCTL) && defined(CONFIG_PROC_FS)
static int get_nr_dentry(void)
{
int i;
int sum = 0;
for_each_possible_cpu(i)
sum += per_cpu(nr_dentry, i);
return sum < 0 ? 0 : sum;
}
int proc_nr_dentry(ctl_table *table, int write, void __user *buffer,
size_t *lenp, loff_t *ppos)
{
dentry_stat.nr_dentry = get_nr_dentry();
return proc_dointvec(table, write, buffer, lenp, ppos);
}
#endif
static void __d_free(struct rcu_head *head)
{
struct dentry *dentry = container_of(head, struct dentry, d_u.d_rcu);
WARN_ON(!list_empty(&dentry->d_alias));
if (dname_external(dentry))
kfree(dentry->d_name.name);
kmem_cache_free(dentry_cache, dentry);
}
static void d_free(struct dentry *dentry)
{
BUG_ON(dentry->d_count);
this_cpu_dec(nr_dentry);
if (dentry->d_op && dentry->d_op->d_release)
dentry->d_op->d_release(dentry);
if (!(dentry->d_flags & DCACHE_RCUACCESS))
__d_free(&dentry->d_u.d_rcu);
else
call_rcu(&dentry->d_u.d_rcu, __d_free);
}
static inline void dentry_rcuwalk_barrier(struct dentry *dentry)
{
assert_spin_locked(&dentry->d_lock);
write_seqcount_barrier(&dentry->d_seq);
}
static void dentry_iput(struct dentry * dentry)
__releases(dentry->d_lock)
__releases(dentry->d_inode->i_lock)
{
struct inode *inode = dentry->d_inode;
if (inode) {
dentry->d_inode = NULL;
list_del_init(&dentry->d_alias);
spin_unlock(&dentry->d_lock);
spin_unlock(&inode->i_lock);
if (!inode->i_nlink)
fsnotify_inoderemove(inode);
if (dentry->d_op && dentry->d_op->d_iput)
dentry->d_op->d_iput(dentry, inode);
else
iput(inode);
} else {
spin_unlock(&dentry->d_lock);
}
}
static void dentry_unlink_inode(struct dentry * dentry)
__releases(dentry->d_lock)
__releases(dentry->d_inode->i_lock)
{
struct inode *inode = dentry->d_inode;
dentry->d_inode = NULL;
list_del_init(&dentry->d_alias);
dentry_rcuwalk_barrier(dentry);
spin_unlock(&dentry->d_lock);
spin_unlock(&inode->i_lock);
if (!inode->i_nlink)
fsnotify_inoderemove(inode);
if (dentry->d_op && dentry->d_op->d_iput)
dentry->d_op->d_iput(dentry, inode);
else
iput(inode);
}
static void dentry_lru_add(struct dentry *dentry)
{
if (list_empty(&dentry->d_lru)) {
spin_lock(&dcache_lru_lock);
list_add(&dentry->d_lru, &dentry->d_sb->s_dentry_lru);
dentry->d_sb->s_nr_dentry_unused++;
dentry_stat.nr_unused++;
spin_unlock(&dcache_lru_lock);
}
}
static void __dentry_lru_del(struct dentry *dentry)
{
list_del_init(&dentry->d_lru);
dentry->d_sb->s_nr_dentry_unused--;
dentry_stat.nr_unused--;
}
static void dentry_lru_del(struct dentry *dentry)
{
if (!list_empty(&dentry->d_lru)) {
spin_lock(&dcache_lru_lock);
__dentry_lru_del(dentry);
spin_unlock(&dcache_lru_lock);
}
}
static void dentry_lru_move_tail(struct dentry *dentry)
{
spin_lock(&dcache_lru_lock);
if (list_empty(&dentry->d_lru)) {
list_add_tail(&dentry->d_lru, &dentry->d_sb->s_dentry_lru);
dentry->d_sb->s_nr_dentry_unused++;
dentry_stat.nr_unused++;
} else {
list_move_tail(&dentry->d_lru, &dentry->d_sb->s_dentry_lru);
}
spin_unlock(&dcache_lru_lock);
}
static struct dentry *d_kill(struct dentry *dentry, struct dentry *parent)
__releases(dentry->d_lock)
__releases(parent->d_lock)
__releases(dentry->d_inode->i_lock)
{
list_del(&dentry->d_u.d_child);
dentry->d_flags |= DCACHE_DISCONNECTED;
if (parent)
spin_unlock(&parent->d_lock);
dentry_iput(dentry);
d_free(dentry);
return parent;
}
void __d_drop(struct dentry *dentry)
{
if (!d_unhashed(dentry)) {
struct hlist_bl_head *b;
if (unlikely(dentry->d_flags & DCACHE_DISCONNECTED))
b = &dentry->d_sb->s_anon;
else
b = d_hash(dentry->d_parent, dentry->d_name.hash);
hlist_bl_lock(b);
__hlist_bl_del(&dentry->d_hash);
dentry->d_hash.pprev = NULL;
hlist_bl_unlock(b);
dentry_rcuwalk_barrier(dentry);
}
}
EXPORT_SYMBOL(__d_drop);
void d_drop(struct dentry *dentry)
{
spin_lock(&dentry->d_lock);
__d_drop(dentry);
spin_unlock(&dentry->d_lock);
}
EXPORT_SYMBOL(d_drop);
static inline struct dentry *dentry_kill(struct dentry *dentry, int ref)
__releases(dentry->d_lock)
{
struct inode *inode;
struct dentry *parent;
inode = dentry->d_inode;
if (inode && !spin_trylock(&inode->i_lock)) {
relock:
spin_unlock(&dentry->d_lock);
cpu_relax();
return dentry;
}
if (IS_ROOT(dentry))
parent = NULL;
else
parent = dentry->d_parent;
if (parent && !spin_trylock(&parent->d_lock)) {
if (inode)
spin_unlock(&inode->i_lock);
goto relock;
}
if (ref)
dentry->d_count--;
dentry_lru_del(dentry);
__d_drop(dentry);
return d_kill(dentry, parent);
}
void dput(struct dentry *dentry)
{
if (!dentry)
return;
repeat:
if (dentry->d_count == 1)
might_sleep();
spin_lock(&dentry->d_lock);
BUG_ON(!dentry->d_count);
if (dentry->d_count > 1) {
dentry->d_count--;
spin_unlock(&dentry->d_lock);
return;
}
if (dentry->d_flags & DCACHE_OP_DELETE) {
if (dentry->d_op->d_delete(dentry))
goto kill_it;
}
if (d_unhashed(dentry))
goto kill_it;
dentry->d_flags |= DCACHE_REFERENCED;
dentry_lru_add(dentry);
dentry->d_count--;
spin_unlock(&dentry->d_lock);
return;
kill_it:
dentry = dentry_kill(dentry, 1);
if (dentry)
goto repeat;
}
EXPORT_SYMBOL(dput);
int d_invalidate(struct dentry * dentry)
{
spin_lock(&dentry->d_lock);
if (d_unhashed(dentry)) {
spin_unlock(&dentry->d_lock);
return 0;
}
if (!list_empty(&dentry->d_subdirs)) {
spin_unlock(&dentry->d_lock);
shrink_dcache_parent(dentry);
spin_lock(&dentry->d_lock);
}
if (dentry->d_count > 1) {
if (dentry->d_inode && S_ISDIR(dentry->d_inode->i_mode)) {
spin_unlock(&dentry->d_lock);
return -EBUSY;
}
}
__d_drop(dentry);
spin_unlock(&dentry->d_lock);
return 0;
}
EXPORT_SYMBOL(d_invalidate);
static inline void __dget_dlock(struct dentry *dentry)
{
dentry->d_count++;
}
static inline void __dget(struct dentry *dentry)
{
spin_lock(&dentry->d_lock);
__dget_dlock(dentry);
spin_unlock(&dentry->d_lock);
}
struct dentry *dget_parent(struct dentry *dentry)
{
struct dentry *ret;
repeat:
rcu_read_lock();
ret = dentry->d_parent;
if (!ret) {
rcu_read_unlock();
goto out;
}
spin_lock(&ret->d_lock);
if (unlikely(ret != dentry->d_parent)) {
spin_unlock(&ret->d_lock);
rcu_read_unlock();
goto repeat;
}
rcu_read_unlock();
BUG_ON(!ret->d_count);
ret->d_count++;
spin_unlock(&ret->d_lock);
out:
return ret;
}
EXPORT_SYMBOL(dget_parent);
static struct dentry *__d_find_alias(struct inode *inode, int want_discon)
{
struct dentry *alias, *discon_alias;
again:
discon_alias = NULL;
list_for_each_entry(alias, &inode->i_dentry, d_alias) {
spin_lock(&alias->d_lock);
if (S_ISDIR(inode->i_mode) || !d_unhashed(alias)) {
if (IS_ROOT(alias) &&
(alias->d_flags & DCACHE_DISCONNECTED)) {
discon_alias = alias;
} else if (!want_discon) {
__dget_dlock(alias);
spin_unlock(&alias->d_lock);
return alias;
}
}
spin_unlock(&alias->d_lock);
}
if (discon_alias) {
alias = discon_alias;
spin_lock(&alias->d_lock);
if (S_ISDIR(inode->i_mode) || !d_unhashed(alias)) {
if (IS_ROOT(alias) &&
(alias->d_flags & DCACHE_DISCONNECTED)) {
__dget_dlock(alias);
spin_unlock(&alias->d_lock);
return alias;
}
}
spin_unlock(&alias->d_lock);
goto again;
}
return NULL;
}
struct dentry *d_find_alias(struct inode *inode)
{
struct dentry *de = NULL;
if (!list_empty(&inode->i_dentry)) {
spin_lock(&inode->i_lock);
de = __d_find_alias(inode, 0);
spin_unlock(&inode->i_lock);
}
return de;
}
EXPORT_SYMBOL(d_find_alias);
void d_prune_aliases(struct inode *inode)
{
struct dentry *dentry;
restart:
spin_lock(&inode->i_lock);
list_for_each_entry(dentry, &inode->i_dentry, d_alias) {
spin_lock(&dentry->d_lock);
if (!dentry->d_count) {
__dget_dlock(dentry);
__d_drop(dentry);
spin_unlock(&dentry->d_lock);
spin_unlock(&inode->i_lock);
dput(dentry);
goto restart;
}
spin_unlock(&dentry->d_lock);
}
spin_unlock(&inode->i_lock);
}
EXPORT_SYMBOL(d_prune_aliases);
static void try_prune_one_dentry(struct dentry *dentry)
__releases(dentry->d_lock)
{
struct dentry *parent;
parent = dentry_kill(dentry, 0);
if (!parent)
return;
if (parent == dentry)
return;
dentry = parent;
while (dentry) {
spin_lock(&dentry->d_lock);
if (dentry->d_count > 1) {
dentry->d_count--;
spin_unlock(&dentry->d_lock);
return;
}
dentry = dentry_kill(dentry, 1);
}
}
static void shrink_dentry_list(struct list_head *list)
{
struct dentry *dentry;
rcu_read_lock();
for (;;) {
dentry = list_entry_rcu(list->prev, struct dentry, d_lru);
if (&dentry->d_lru == list)
break;
spin_lock(&dentry->d_lock);
if (dentry != list_entry(list->prev, struct dentry, d_lru)) {
spin_unlock(&dentry->d_lock);
continue;
}
if (dentry->d_count) {
dentry_lru_del(dentry);
spin_unlock(&dentry->d_lock);
continue;
}
rcu_read_unlock();
try_prune_one_dentry(dentry);
rcu_read_lock();
}
rcu_read_unlock();
}
static void __shrink_dcache_sb(struct super_block *sb, int *count, int flags)
{
struct dentry *dentry;
LIST_HEAD(referenced);
LIST_HEAD(tmp);
int cnt = *count;
relock:
spin_lock(&dcache_lru_lock);
while (!list_empty(&sb->s_dentry_lru)) {
dentry = list_entry(sb->s_dentry_lru.prev,
struct dentry, d_lru);
BUG_ON(dentry->d_sb != sb);
if (!spin_trylock(&dentry->d_lock)) {
spin_unlock(&dcache_lru_lock);
cpu_relax();
goto relock;
}
if (flags & DCACHE_REFERENCED &&
dentry->d_flags & DCACHE_REFERENCED) {
dentry->d_flags &= ~DCACHE_REFERENCED;
list_move(&dentry->d_lru, &referenced);
spin_unlock(&dentry->d_lock);
} else {
list_move_tail(&dentry->d_lru, &tmp);
spin_unlock(&dentry->d_lock);
if (!--cnt)
break;
}
cond_resched_lock(&dcache_lru_lock);
}
if (!list_empty(&referenced))
list_splice(&referenced, &sb->s_dentry_lru);
spin_unlock(&dcache_lru_lock);
shrink_dentry_list(&tmp);
*count = cnt;
}
static void prune_dcache(int count)
{
struct super_block *sb, *p = NULL;
int w_count;
int unused = dentry_stat.nr_unused;
int prune_ratio;
int pruned;
if (unused == 0 || count == 0)
return;
if (count >= unused)
prune_ratio = 1;
else
prune_ratio = unused / count;
spin_lock(&sb_lock);
list_for_each_entry(sb, &super_blocks, s_list) {
if (list_empty(&sb->s_instances))
continue;
if (sb->s_nr_dentry_unused == 0)
continue;
sb->s_count++;
spin_unlock(&sb_lock);
if (prune_ratio != 1)
w_count = (sb->s_nr_dentry_unused / prune_ratio) + 1;
else
w_count = sb->s_nr_dentry_unused;
pruned = w_count;
if (down_read_trylock(&sb->s_umount)) {
if ((sb->s_root != NULL) &&
(!list_empty(&sb->s_dentry_lru))) {
__shrink_dcache_sb(sb, &w_count,
DCACHE_REFERENCED);
pruned -= w_count;
}
up_read(&sb->s_umount);
}
spin_lock(&sb_lock);
if (p)
__put_super(p);
count -= pruned;
p = sb;
if (count <= 0)
break;
}
if (p)
__put_super(p);
spin_unlock(&sb_lock);
}
void shrink_dcache_sb(struct super_block *sb)
{
LIST_HEAD(tmp);
spin_lock(&dcache_lru_lock);
while (!list_empty(&sb->s_dentry_lru)) {
list_splice_init(&sb->s_dentry_lru, &tmp);
spin_unlock(&dcache_lru_lock);
shrink_dentry_list(&tmp);
spin_lock(&dcache_lru_lock);
}
spin_unlock(&dcache_lru_lock);
}
EXPORT_SYMBOL(shrink_dcache_sb);
static void shrink_dcache_for_umount_subtree(struct dentry *dentry)
{
struct dentry *parent;
unsigned detached = 0;
BUG_ON(!IS_ROOT(dentry));
spin_lock(&dentry->d_lock);
dentry_lru_del(dentry);
__d_drop(dentry);
spin_unlock(&dentry->d_lock);
for (;;) {
while (!list_empty(&dentry->d_subdirs)) {
struct dentry *loop;
spin_lock(&dentry->d_lock);
list_for_each_entry(loop, &dentry->d_subdirs,
d_u.d_child) {
spin_lock_nested(&loop->d_lock,
DENTRY_D_LOCK_NESTED);
dentry_lru_del(loop);
__d_drop(loop);
spin_unlock(&loop->d_lock);
}
spin_unlock(&dentry->d_lock);
dentry = list_entry(dentry->d_subdirs.next,
struct dentry, d_u.d_child);
}
do {
struct inode *inode;
if (dentry->d_count != 0) {
printk(KERN_ERR
"BUG: Dentry %p{i=%lx,n=%s}"
" still in use (%d)"
" [unmount of %s %s]\n",
dentry,
dentry->d_inode ?
dentry->d_inode->i_ino : 0UL,
dentry->d_name.name,
dentry->d_count,
dentry->d_sb->s_type->name,
dentry->d_sb->s_id);
BUG();
}
if (IS_ROOT(dentry)) {
parent = NULL;
list_del(&dentry->d_u.d_child);
} else {
parent = dentry->d_parent;
spin_lock(&parent->d_lock);
parent->d_count--;
list_del(&dentry->d_u.d_child);
spin_unlock(&parent->d_lock);
}
detached++;
inode = dentry->d_inode;
if (inode) {
dentry->d_inode = NULL;
list_del_init(&dentry->d_alias);
if (dentry->d_op && dentry->d_op->d_iput)
dentry->d_op->d_iput(dentry, inode);
else
iput(inode);
}
d_free(dentry);
if (!parent)
return;
dentry = parent;
} while (list_empty(&dentry->d_subdirs));
dentry = list_entry(dentry->d_subdirs.next,
struct dentry, d_u.d_child);
}
}
void shrink_dcache_for_umount(struct super_block *sb)
{
struct dentry *dentry;
if (down_read_trylock(&sb->s_umount))
BUG();
dentry = sb->s_root;
sb->s_root = NULL;
spin_lock(&dentry->d_lock);
dentry->d_count--;
spin_unlock(&dentry->d_lock);
shrink_dcache_for_umount_subtree(dentry);
while (!hlist_bl_empty(&sb->s_anon)) {
dentry = hlist_bl_entry(hlist_bl_first(&sb->s_anon), struct dentry, d_hash);
shrink_dcache_for_umount_subtree(dentry);
}
}
static struct dentry *try_to_ascend(struct dentry *old, int locked, unsigned seq)
{
struct dentry *new = old->d_parent;
rcu_read_lock();
spin_unlock(&old->d_lock);
spin_lock(&new->d_lock);
if (new != old->d_parent ||
(old->d_flags & DCACHE_DISCONNECTED) ||
(!locked && read_seqretry(&rename_lock, seq))) {
spin_unlock(&new->d_lock);
new = NULL;
}
rcu_read_unlock();
return new;
}
int have_submounts(struct dentry *parent)
{
struct dentry *this_parent;
struct list_head *next;
unsigned seq;
int locked = 0;
seq = read_seqbegin(&rename_lock);
again:
this_parent = parent;
if (d_mountpoint(parent))
goto positive;
spin_lock(&this_parent->d_lock);
repeat:
next = this_parent->d_subdirs.next;
resume:
while (next != &this_parent->d_subdirs) {
struct list_head *tmp = next;
struct dentry *dentry = list_entry(tmp, struct dentry, d_u.d_child);
next = tmp->next;
spin_lock_nested(&dentry->d_lock, DENTRY_D_LOCK_NESTED);
if (d_mountpoint(dentry)) {
spin_unlock(&dentry->d_lock);
spin_unlock(&this_parent->d_lock);
goto positive;
}
if (!list_empty(&dentry->d_subdirs)) {
spin_unlock(&this_parent->d_lock);
spin_release(&dentry->d_lock.dep_map, 1, _RET_IP_);
this_parent = dentry;
spin_acquire(&this_parent->d_lock.dep_map, 0, 1, _RET_IP_);
goto repeat;
}
spin_unlock(&dentry->d_lock);
}
if (this_parent != parent) {
struct dentry *child = this_parent;
this_parent = try_to_ascend(this_parent, locked, seq);
if (!this_parent)
goto rename_retry;
next = child->d_u.d_child.next;
goto resume;
}
spin_unlock(&this_parent->d_lock);
if (!locked && read_seqretry(&rename_lock, seq))
goto rename_retry;
if (locked)
write_sequnlock(&rename_lock);
return 0;
positive:
if (!locked && read_seqretry(&rename_lock, seq))
goto rename_retry;
if (locked)
write_sequnlock(&rename_lock);
return 1;
rename_retry:
locked = 1;
write_seqlock(&rename_lock);
goto again;
}
EXPORT_SYMBOL(have_submounts);
static int select_parent(struct dentry * parent)
{
struct dentry *this_parent;
struct list_head *next;
unsigned seq;
int found = 0;
int locked = 0;
seq = read_seqbegin(&rename_lock);
again:
this_parent = parent;
spin_lock(&this_parent->d_lock);
repeat:
next = this_parent->d_subdirs.next;
resume:
while (next != &this_parent->d_subdirs) {
struct list_head *tmp = next;
struct dentry *dentry = list_entry(tmp, struct dentry, d_u.d_child);
next = tmp->next;
spin_lock_nested(&dentry->d_lock, DENTRY_D_LOCK_NESTED);
if (!dentry->d_count) {
dentry_lru_move_tail(dentry);
found++;
} else {
dentry_lru_del(dentry);
}
if (found && need_resched()) {
spin_unlock(&dentry->d_lock);
goto out;
}
if (!list_empty(&dentry->d_subdirs)) {
spin_unlock(&this_parent->d_lock);
spin_release(&dentry->d_lock.dep_map, 1, _RET_IP_);
this_parent = dentry;
spin_acquire(&this_parent->d_lock.dep_map, 0, 1, _RET_IP_);
goto repeat;
}
spin_unlock(&dentry->d_lock);
}
if (this_parent != parent) {
struct dentry *child = this_parent;
this_parent = try_to_ascend(this_parent, locked, seq);
if (!this_parent)
goto rename_retry;
next = child->d_u.d_child.next;
goto resume;
}
out:
spin_unlock(&this_parent->d_lock);
if (!locked && read_seqretry(&rename_lock, seq))
goto rename_retry;
if (locked)
write_sequnlock(&rename_lock);
return found;
rename_retry:
if (found)
return found;
locked = 1;
write_seqlock(&rename_lock);
goto again;
}
void shrink_dcache_parent(struct dentry * parent)
{
struct super_block *sb = parent->d_sb;
int found;
while ((found = select_parent(parent)) != 0)
__shrink_dcache_sb(sb, &found, 0);
}
EXPORT_SYMBOL(shrink_dcache_parent);
static int shrink_dcache_memory(struct shrinker *shrink,
struct shrink_control *sc)
{
int nr = sc->nr_to_scan;
gfp_t gfp_mask = sc->gfp_mask;
if (nr) {
if (!(gfp_mask & __GFP_FS))
return -1;
prune_dcache(nr);
}
return (dentry_stat.nr_unused / 100) * sysctl_vfs_cache_pressure;
}
static struct shrinker dcache_shrinker = {
.shrink = shrink_dcache_memory,
.seeks = DEFAULT_SEEKS,
};
struct dentry *d_alloc(struct dentry * parent, const struct qstr *name)
{
struct dentry *dentry;
char *dname;
dentry = kmem_cache_alloc(dentry_cache, GFP_KERNEL);
if (!dentry)
return NULL;
if (name->len > DNAME_INLINE_LEN-1) {
dname = kmalloc(name->len + 1, GFP_KERNEL);
if (!dname) {
kmem_cache_free(dentry_cache, dentry);
return NULL;
}
} else {
dname = dentry->d_iname;
}
dentry->d_name.name = dname;
dentry->d_name.len = name->len;
dentry->d_name.hash = name->hash;
memcpy(dname, name->name, name->len);
dname[name->len] = 0;
dentry->d_count = 1;
dentry->d_flags = 0;
spin_lock_init(&dentry->d_lock);
seqcount_init(&dentry->d_seq);
dentry->d_inode = NULL;
dentry->d_parent = NULL;
dentry->d_sb = NULL;
dentry->d_op = NULL;
dentry->d_fsdata = NULL;
INIT_HLIST_BL_NODE(&dentry->d_hash);
INIT_LIST_HEAD(&dentry->d_lru);
INIT_LIST_HEAD(&dentry->d_subdirs);
INIT_LIST_HEAD(&dentry->d_alias);
INIT_LIST_HEAD(&dentry->d_u.d_child);
if (parent) {
spin_lock(&parent->d_lock);
__dget_dlock(parent);
dentry->d_parent = parent;
dentry->d_sb = parent->d_sb;
d_set_d_op(dentry, dentry->d_sb->s_d_op);
list_add(&dentry->d_u.d_child, &parent->d_subdirs);
spin_unlock(&parent->d_lock);
}
this_cpu_inc(nr_dentry);
return dentry;
}
EXPORT_SYMBOL(d_alloc);
struct dentry *d_alloc_pseudo(struct super_block *sb, const struct qstr *name)
{
struct dentry *dentry = d_alloc(NULL, name);
if (dentry) {
dentry->d_sb = sb;
d_set_d_op(dentry, dentry->d_sb->s_d_op);
dentry->d_parent = dentry;
dentry->d_flags |= DCACHE_DISCONNECTED;
}
return dentry;
}
EXPORT_SYMBOL(d_alloc_pseudo);
struct dentry *d_alloc_name(struct dentry *parent, const char *name)
{
struct qstr q;
q.name = name;
q.len = strlen(name);
q.hash = full_name_hash(q.name, q.len);
return d_alloc(parent, &q);
}
EXPORT_SYMBOL(d_alloc_name);
void d_set_d_op(struct dentry *dentry, const struct dentry_operations *op)
{
WARN_ON_ONCE(dentry->d_op);
WARN_ON_ONCE(dentry->d_flags & (DCACHE_OP_HASH |
DCACHE_OP_COMPARE |
DCACHE_OP_REVALIDATE |
DCACHE_OP_DELETE ));
dentry->d_op = op;
if (!op)
return;
if (op->d_hash)
dentry->d_flags |= DCACHE_OP_HASH;
if (op->d_compare)
dentry->d_flags |= DCACHE_OP_COMPARE;
if (op->d_revalidate)
dentry->d_flags |= DCACHE_OP_REVALIDATE;
if (op->d_delete)
dentry->d_flags |= DCACHE_OP_DELETE;
}
EXPORT_SYMBOL(d_set_d_op);
static void __d_instantiate(struct dentry *dentry, struct inode *inode)
{
spin_lock(&dentry->d_lock);
if (inode) {
if (unlikely(IS_AUTOMOUNT(inode)))
dentry->d_flags |= DCACHE_NEED_AUTOMOUNT;
list_add(&dentry->d_alias, &inode->i_dentry);
}
dentry->d_inode = inode;
dentry_rcuwalk_barrier(dentry);
spin_unlock(&dentry->d_lock);
fsnotify_d_instantiate(dentry, inode);
}
void d_instantiate(struct dentry *entry, struct inode * inode)
{
BUG_ON(!list_empty(&entry->d_alias));
if (inode)
spin_lock(&inode->i_lock);
__d_instantiate(entry, inode);
if (inode)
spin_unlock(&inode->i_lock);
security_d_instantiate(entry, inode);
}
EXPORT_SYMBOL(d_instantiate);
static struct dentry *__d_instantiate_unique(struct dentry *entry,
struct inode *inode)
{
struct dentry *alias;
int len = entry->d_name.len;
const char *name = entry->d_name.name;
unsigned int hash = entry->d_name.hash;
if (!inode) {
__d_instantiate(entry, NULL);
return NULL;
}
list_for_each_entry(alias, &inode->i_dentry, d_alias) {
struct qstr *qstr = &alias->d_name;
if (qstr->hash != hash)
continue;
if (alias->d_parent != entry->d_parent)
continue;
if (dentry_cmp(qstr->name, qstr->len, name, len))
continue;
__dget(alias);
return alias;
}
__d_instantiate(entry, inode);
return NULL;
}
struct dentry *d_instantiate_unique(struct dentry *entry, struct inode *inode)
{
struct dentry *result;
BUG_ON(!list_empty(&entry->d_alias));
if (inode)
spin_lock(&inode->i_lock);
result = __d_instantiate_unique(entry, inode);
if (inode)
spin_unlock(&inode->i_lock);
if (!result) {
security_d_instantiate(entry, inode);
return NULL;
}
BUG_ON(!d_unhashed(result));
iput(inode);
return result;
}
EXPORT_SYMBOL(d_instantiate_unique);
struct dentry * d_alloc_root(struct inode * root_inode)
{
struct dentry *res = NULL;
if (root_inode) {
static const struct qstr name = { .name = "/", .len = 1 };
res = d_alloc(NULL, &name);
if (res) {
res->d_sb = root_inode->i_sb;
d_set_d_op(res, res->d_sb->s_d_op);
res->d_parent = res;
d_instantiate(res, root_inode);
}
}
return res;
}
EXPORT_SYMBOL(d_alloc_root);
static struct dentry * __d_find_any_alias(struct inode *inode)
{
struct dentry *alias;
if (list_empty(&inode->i_dentry))
return NULL;
alias = list_first_entry(&inode->i_dentry, struct dentry, d_alias);
__dget(alias);
return alias;
}
static struct dentry * d_find_any_alias(struct inode *inode)
{
struct dentry *de;
spin_lock(&inode->i_lock);
de = __d_find_any_alias(inode);
spin_unlock(&inode->i_lock);
return de;
}
struct dentry *d_obtain_alias(struct inode *inode)
{
static const struct qstr anonstring = { .name = "" };
struct dentry *tmp;
struct dentry *res;
if (!inode)
return ERR_PTR(-ESTALE);
if (IS_ERR(inode))
return ERR_CAST(inode);
res = d_find_any_alias(inode);
if (res)
goto out_iput;
tmp = d_alloc(NULL, &anonstring);
if (!tmp) {
res = ERR_PTR(-ENOMEM);
goto out_iput;
}
tmp->d_parent = tmp;
spin_lock(&inode->i_lock);
res = __d_find_any_alias(inode);
if (res) {
spin_unlock(&inode->i_lock);
dput(tmp);
goto out_iput;
}
spin_lock(&tmp->d_lock);
tmp->d_sb = inode->i_sb;
d_set_d_op(tmp, tmp->d_sb->s_d_op);
tmp->d_inode = inode;
tmp->d_flags |= DCACHE_DISCONNECTED;
list_add(&tmp->d_alias, &inode->i_dentry);
hlist_bl_lock(&tmp->d_sb->s_anon);
hlist_bl_add_head(&tmp->d_hash, &tmp->d_sb->s_anon);
hlist_bl_unlock(&tmp->d_sb->s_anon);
spin_unlock(&tmp->d_lock);
spin_unlock(&inode->i_lock);
security_d_instantiate(tmp, inode);
return tmp;
out_iput:
if (res && !IS_ERR(res))
security_d_instantiate(res, inode);
iput(inode);
return res;
}
EXPORT_SYMBOL(d_obtain_alias);
struct dentry *d_splice_alias(struct inode *inode, struct dentry *dentry)
{
struct dentry *new = NULL;
if (inode && S_ISDIR(inode->i_mode)) {
spin_lock(&inode->i_lock);
new = __d_find_alias(inode, 1);
if (new) {
BUG_ON(!(new->d_flags & DCACHE_DISCONNECTED));
spin_unlock(&inode->i_lock);
security_d_instantiate(new, inode);
d_move(new, dentry);
iput(inode);
} else {
__d_instantiate(dentry, inode);
spin_unlock(&inode->i_lock);
security_d_instantiate(dentry, inode);
d_rehash(dentry);
}
} else
d_add(dentry, inode);
return new;
}
EXPORT_SYMBOL(d_splice_alias);
struct dentry *d_add_ci(struct dentry *dentry, struct inode *inode,
struct qstr *name)
{
int error;
struct dentry *found;
struct dentry *new;
found = d_hash_and_lookup(dentry->d_parent, name);
if (!found) {
new = d_alloc(dentry->d_parent, name);
if (!new) {
error = -ENOMEM;
goto err_out;
}
found = d_splice_alias(inode, new);
if (found) {
dput(new);
return found;
}
return new;
}
if (found->d_inode) {
if (unlikely(found->d_inode != inode)) {
BUG_ON(!is_bad_inode(inode));
BUG_ON(!is_bad_inode(found->d_inode));
}
iput(inode);
return found;
}
spin_lock(&inode->i_lock);
if (!S_ISDIR(inode->i_mode) || list_empty(&inode->i_dentry)) {
__d_instantiate(found, inode);
spin_unlock(&inode->i_lock);
security_d_instantiate(found, inode);
return found;
}
new = list_entry(inode->i_dentry.next, struct dentry, d_alias);
__dget(new);
spin_unlock(&inode->i_lock);
security_d_instantiate(found, inode);
d_move(new, found);
iput(inode);
dput(found);
return new;
err_out:
iput(inode);
return ERR_PTR(error);
}
EXPORT_SYMBOL(d_add_ci);
struct dentry *__d_lookup_rcu(struct dentry *parent, struct qstr *name,
unsigned *seq, struct inode **inode)
{
unsigned int len = name->len;
unsigned int hash = name->hash;
const unsigned char *str = name->name;
struct hlist_bl_head *b = d_hash(parent, hash);
struct hlist_bl_node *node;
struct dentry *dentry;
hlist_bl_for_each_entry_rcu(dentry, node, b, d_hash) {
struct inode *i;
const char *tname;
int tlen;
if (dentry->d_name.hash != hash)
continue;
seqretry:
*seq = read_seqcount_begin(&dentry->d_seq);
if (dentry->d_parent != parent)
continue;
if (d_unhashed(dentry))
continue;
tlen = dentry->d_name.len;
tname = dentry->d_name.name;
i = dentry->d_inode;
prefetch(tname);
if (read_seqcount_retry(&dentry->d_seq, *seq))
goto seqretry;
if (parent->d_flags & DCACHE_OP_COMPARE) {
if (parent->d_op->d_compare(parent, *inode,
dentry, i,
tlen, tname, name))
continue;
} else {
if (dentry_cmp(tname, tlen, str, len))
continue;
}
*inode = i;
return dentry;
}
return NULL;
}
struct dentry *d_lookup(struct dentry *parent, struct qstr *name)
{
struct dentry *dentry;
unsigned seq;
do {
seq = read_seqbegin(&rename_lock);
dentry = __d_lookup(parent, name);
if (dentry)
break;
} while (read_seqretry(&rename_lock, seq));
return dentry;
}
EXPORT_SYMBOL(d_lookup);
struct dentry *__d_lookup(struct dentry *parent, struct qstr *name)
{
unsigned int len = name->len;
unsigned int hash = name->hash;
const unsigned char *str = name->name;
struct hlist_bl_head *b = d_hash(parent, hash);
struct hlist_bl_node *node;
struct dentry *found = NULL;
struct dentry *dentry;
rcu_read_lock();
hlist_bl_for_each_entry_rcu(dentry, node, b, d_hash) {
const char *tname;
int tlen;
if (dentry->d_name.hash != hash)
continue;
spin_lock(&dentry->d_lock);
if (dentry->d_parent != parent)
goto next;
if (d_unhashed(dentry))
goto next;
tlen = dentry->d_name.len;
tname = dentry->d_name.name;
if (parent->d_flags & DCACHE_OP_COMPARE) {
if (parent->d_op->d_compare(parent, parent->d_inode,
dentry, dentry->d_inode,
tlen, tname, name))
goto next;
} else {
if (dentry_cmp(tname, tlen, str, len))
goto next;
}
dentry->d_count++;
found = dentry;
spin_unlock(&dentry->d_lock);
break;
next:
spin_unlock(&dentry->d_lock);
}
rcu_read_unlock();
return found;
}
struct dentry *d_hash_and_lookup(struct dentry *dir, struct qstr *name)
{
struct dentry *dentry = NULL;
name->hash = full_name_hash(name->name, name->len);
if (dir->d_flags & DCACHE_OP_HASH) {
if (dir->d_op->d_hash(dir, dir->d_inode, name) < 0)
goto out;
}
dentry = d_lookup(dir, name);
out:
return dentry;
}
int d_validate(struct dentry *dentry, struct dentry *dparent)
{
struct dentry *child;
spin_lock(&dparent->d_lock);
list_for_each_entry(child, &dparent->d_subdirs, d_u.d_child) {
if (dentry == child) {
spin_lock_nested(&dentry->d_lock, DENTRY_D_LOCK_NESTED);
__dget_dlock(dentry);
spin_unlock(&dentry->d_lock);
spin_unlock(&dparent->d_lock);
return 1;
}
}
spin_unlock(&dparent->d_lock);
return 0;
}
EXPORT_SYMBOL(d_validate);
void d_delete(struct dentry * dentry)
{
struct inode *inode;
int isdir = 0;
again:
spin_lock(&dentry->d_lock);
inode = dentry->d_inode;
isdir = S_ISDIR(inode->i_mode);
if (dentry->d_count == 1) {
if (inode && !spin_trylock(&inode->i_lock)) {
spin_unlock(&dentry->d_lock);
cpu_relax();
goto again;
}
dentry->d_flags &= ~DCACHE_CANT_MOUNT;
dentry_unlink_inode(dentry);
fsnotify_nameremove(dentry, isdir);
return;
}
if (!d_unhashed(dentry))
__d_drop(dentry);
spin_unlock(&dentry->d_lock);
fsnotify_nameremove(dentry, isdir);
}
EXPORT_SYMBOL(d_delete);
static void __d_rehash(struct dentry * entry, struct hlist_bl_head *b)
{
BUG_ON(!d_unhashed(entry));
hlist_bl_lock(b);
entry->d_flags |= DCACHE_RCUACCESS;
hlist_bl_add_head_rcu(&entry->d_hash, b);
hlist_bl_unlock(b);
}
static void _d_rehash(struct dentry * entry)
{
__d_rehash(entry, d_hash(entry->d_parent, entry->d_name.hash));
}
void d_rehash(struct dentry * entry)
{
spin_lock(&entry->d_lock);
_d_rehash(entry);
spin_unlock(&entry->d_lock);
}
EXPORT_SYMBOL(d_rehash);
void dentry_update_name_case(struct dentry *dentry, struct qstr *name)
{
BUG_ON(!mutex_is_locked(&dentry->d_parent->d_inode->i_mutex));
BUG_ON(dentry->d_name.len != name->len);
spin_lock(&dentry->d_lock);
write_seqcount_begin(&dentry->d_seq);
memcpy((unsigned char *)dentry->d_name.name, name->name, name->len);
write_seqcount_end(&dentry->d_seq);
spin_unlock(&dentry->d_lock);
}
EXPORT_SYMBOL(dentry_update_name_case);
static void switch_names(struct dentry *dentry, struct dentry *target)
{
if (dname_external(target)) {
if (dname_external(dentry)) {
swap(target->d_name.name, dentry->d_name.name);
} else {
memcpy(target->d_iname, dentry->d_name.name,
dentry->d_name.len + 1);
dentry->d_name.name = target->d_name.name;
target->d_name.name = target->d_iname;
}
} else {
if (dname_external(dentry)) {
memcpy(dentry->d_iname, target->d_name.name,
target->d_name.len + 1);
target->d_name.name = dentry->d_name.name;
dentry->d_name.name = dentry->d_iname;
} else {
memcpy(dentry->d_iname, target->d_name.name,
target->d_name.len + 1);
dentry->d_name.len = target->d_name.len;
return;
}
}
swap(dentry->d_name.len, target->d_name.len);
}
static void dentry_lock_for_move(struct dentry *dentry, struct dentry *target)
{
if (IS_ROOT(dentry) || dentry->d_parent == target->d_parent)
spin_lock(&target->d_parent->d_lock);
else {
if (d_ancestor(dentry->d_parent, target->d_parent)) {
spin_lock(&dentry->d_parent->d_lock);
spin_lock_nested(&target->d_parent->d_lock,
DENTRY_D_LOCK_NESTED);
} else {
spin_lock(&target->d_parent->d_lock);
spin_lock_nested(&dentry->d_parent->d_lock,
DENTRY_D_LOCK_NESTED);
}
}
if (target < dentry) {
spin_lock_nested(&target->d_lock, 2);
spin_lock_nested(&dentry->d_lock, 3);
} else {
spin_lock_nested(&dentry->d_lock, 2);
spin_lock_nested(&target->d_lock, 3);
}
}
static void dentry_unlock_parents_for_move(struct dentry *dentry,
struct dentry *target)
{
if (target->d_parent != dentry->d_parent)
spin_unlock(&dentry->d_parent->d_lock);
if (target->d_parent != target)
spin_unlock(&target->d_parent->d_lock);
}
static void __d_move(struct dentry * dentry, struct dentry * target)
{
if (!dentry->d_inode)
printk(KERN_WARNING "VFS: moving negative dcache entry\n");
BUG_ON(d_ancestor(dentry, target));
BUG_ON(d_ancestor(target, dentry));
dentry_lock_for_move(dentry, target);
write_seqcount_begin(&dentry->d_seq);
write_seqcount_begin(&target->d_seq);
__d_drop(dentry);
__d_rehash(dentry, d_hash(target->d_parent, target->d_name.hash));
__d_drop(target);
list_del(&dentry->d_u.d_child);
list_del(&target->d_u.d_child);
switch_names(dentry, target);
swap(dentry->d_name.hash, target->d_name.hash);
if (IS_ROOT(dentry)) {
dentry->d_parent = target->d_parent;
target->d_parent = target;
INIT_LIST_HEAD(&target->d_u.d_child);
} else {
swap(dentry->d_parent, target->d_parent);
list_add(&target->d_u.d_child, &target->d_parent->d_subdirs);
}
list_add(&dentry->d_u.d_child, &dentry->d_parent->d_subdirs);
write_seqcount_end(&target->d_seq);
write_seqcount_end(&dentry->d_seq);
dentry_unlock_parents_for_move(dentry, target);
spin_unlock(&target->d_lock);
fsnotify_d_move(dentry);
spin_unlock(&dentry->d_lock);
}
void d_move(struct dentry *dentry, struct dentry *target)
{
write_seqlock(&rename_lock);
__d_move(dentry, target);
write_sequnlock(&rename_lock);
}
EXPORT_SYMBOL(d_move);
struct dentry *d_ancestor(struct dentry *p1, struct dentry *p2)
{
struct dentry *p;
for (p = p2; !IS_ROOT(p); p = p->d_parent) {
if (p->d_parent == p1)
return p;
}
return NULL;
}
static struct dentry *__d_unalias(struct inode *inode,
struct dentry *dentry, struct dentry *alias)
{
struct mutex *m1 = NULL, *m2 = NULL;
struct dentry *ret;
if (alias->d_parent == dentry->d_parent)
goto out_unalias;
ret = ERR_PTR(-EBUSY);
if (!mutex_trylock(&dentry->d_sb->s_vfs_rename_mutex))
goto out_err;
m1 = &dentry->d_sb->s_vfs_rename_mutex;
if (!mutex_trylock(&alias->d_parent->d_inode->i_mutex))
goto out_err;
m2 = &alias->d_parent->d_inode->i_mutex;
out_unalias:
__d_move(alias, dentry);
ret = alias;
out_err:
spin_unlock(&inode->i_lock);
if (m2)
mutex_unlock(m2);
if (m1)
mutex_unlock(m1);
return ret;
}
static void __d_materialise_dentry(struct dentry *dentry, struct dentry *anon)
{
struct dentry *dparent, *aparent;
dentry_lock_for_move(anon, dentry);
write_seqcount_begin(&dentry->d_seq);
write_seqcount_begin(&anon->d_seq);
dparent = dentry->d_parent;
aparent = anon->d_parent;
switch_names(dentry, anon);
swap(dentry->d_name.hash, anon->d_name.hash);
dentry->d_parent = (aparent == anon) ? dentry : aparent;
list_del(&dentry->d_u.d_child);
if (!IS_ROOT(dentry))
list_add(&dentry->d_u.d_child, &dentry->d_parent->d_subdirs);
else
INIT_LIST_HEAD(&dentry->d_u.d_child);
anon->d_parent = (dparent == dentry) ? anon : dparent;
list_del(&anon->d_u.d_child);
if (!IS_ROOT(anon))
list_add(&anon->d_u.d_child, &anon->d_parent->d_subdirs);
else
INIT_LIST_HEAD(&anon->d_u.d_child);
write_seqcount_end(&dentry->d_seq);
write_seqcount_end(&anon->d_seq);
dentry_unlock_parents_for_move(anon, dentry);
spin_unlock(&dentry->d_lock);
anon->d_flags &= ~DCACHE_DISCONNECTED;
}
struct dentry *d_materialise_unique(struct dentry *dentry, struct inode *inode)
{
struct dentry *actual;
BUG_ON(!d_unhashed(dentry));
if (!inode) {
actual = dentry;
__d_instantiate(dentry, NULL);
d_rehash(actual);
goto out_nolock;
}
spin_lock(&inode->i_lock);
if (S_ISDIR(inode->i_mode)) {
struct dentry *alias;
alias = __d_find_alias(inode, 0);
if (alias) {
actual = alias;
write_seqlock(&rename_lock);
if (d_ancestor(alias, dentry)) {
actual = ERR_PTR(-ELOOP);
} else if (IS_ROOT(alias)) {
__d_materialise_dentry(dentry, alias);
write_sequnlock(&rename_lock);
__d_drop(alias);
goto found;
} else {
actual = __d_unalias(inode, dentry, alias);
}
write_sequnlock(&rename_lock);
if (IS_ERR(actual))
dput(alias);
goto out_nolock;
}
}
actual = __d_instantiate_unique(dentry, inode);
if (!actual)
actual = dentry;
else
BUG_ON(!d_unhashed(actual));
spin_lock(&actual->d_lock);
found:
_d_rehash(actual);
spin_unlock(&actual->d_lock);
spin_unlock(&inode->i_lock);
out_nolock:
if (actual == dentry) {
security_d_instantiate(dentry, inode);
return NULL;
}
iput(inode);
return actual;
}
EXPORT_SYMBOL_GPL(d_materialise_unique);
static int prepend(char **buffer, int *buflen, const char *str, int namelen)
{
*buflen -= namelen;
if (*buflen < 0)
return -ENAMETOOLONG;
*buffer -= namelen;
memcpy(*buffer, str, namelen);
return 0;
}
static int prepend_name(char **buffer, int *buflen, struct qstr *name)
{
return prepend(buffer, buflen, name->name, name->len);
}
static int prepend_path(const struct path *path, struct path *root,
char **buffer, int *buflen)
{
struct dentry *dentry = path->dentry;
struct vfsmount *vfsmnt = path->mnt;
bool slash = false;
int error = 0;
br_read_lock(vfsmount_lock);
while (dentry != root->dentry || vfsmnt != root->mnt) {
struct dentry * parent;
if (dentry == vfsmnt->mnt_root || IS_ROOT(dentry)) {
if (vfsmnt->mnt_parent == vfsmnt) {
goto global_root;
}
dentry = vfsmnt->mnt_mountpoint;
vfsmnt = vfsmnt->mnt_parent;
continue;
}
parent = dentry->d_parent;
prefetch(parent);
spin_lock(&dentry->d_lock);
error = prepend_name(buffer, buflen, &dentry->d_name);
spin_unlock(&dentry->d_lock);
if (!error)
error = prepend(buffer, buflen, "/", 1);
if (error)
break;
slash = true;
dentry = parent;
}
out:
if (!error && !slash)
error = prepend(buffer, buflen, "/", 1);
br_read_unlock(vfsmount_lock);
return error;
global_root:
if (IS_ROOT(dentry) &&
(dentry->d_name.len != 1 || dentry->d_name.name[0] != '/')) {
WARN(1, "Root dentry has weird name <%.*s>\n",
(int) dentry->d_name.len, dentry->d_name.name);
}
root->mnt = vfsmnt;
root->dentry = dentry;
goto out;
}
char *__d_path(const struct path *path, struct path *root,
char *buf, int buflen)
{
char *res = buf + buflen;
int error;
prepend(&res, &buflen, "\0", 1);
write_seqlock(&rename_lock);
error = prepend_path(path, root, &res, &buflen);
write_sequnlock(&rename_lock);
if (error)
return ERR_PTR(error);
return res;
}
static int path_with_deleted(const struct path *path, struct path *root,
char **buf, int *buflen)
{
prepend(buf, buflen, "\0", 1);
if (d_unlinked(path->dentry)) {
int error = prepend(buf, buflen, " (deleted)", 10);
if (error)
return error;
}
return prepend_path(path, root, buf, buflen);
}
static int prepend_unreachable(char **buffer, int *buflen)
{
return prepend(buffer, buflen, "(unreachable)", 13);
}
char *d_path(const struct path *path, char *buf, int buflen)
{
char *res = buf + buflen;
struct path root;
struct path tmp;
int error;
if (path->dentry->d_op && path->dentry->d_op->d_dname)
return path->dentry->d_op->d_dname(path->dentry, buf, buflen);
get_fs_root(current->fs, &root);
write_seqlock(&rename_lock);
tmp = root;
error = path_with_deleted(path, &tmp, &res, &buflen);
if (error)
res = ERR_PTR(error);
write_sequnlock(&rename_lock);
path_put(&root);
return res;
}
EXPORT_SYMBOL(d_path);
char *d_path_with_unreachable(const struct path *path, char *buf, int buflen)
{
char *res = buf + buflen;
struct path root;
struct path tmp;
int error;
if (path->dentry->d_op && path->dentry->d_op->d_dname)
return path->dentry->d_op->d_dname(path->dentry, buf, buflen);
get_fs_root(current->fs, &root);
write_seqlock(&rename_lock);
tmp = root;
error = path_with_deleted(path, &tmp, &res, &buflen);
if (!error && !path_equal(&tmp, &root))
error = prepend_unreachable(&res, &buflen);
write_sequnlock(&rename_lock);
path_put(&root);
if (error)
res = ERR_PTR(error);
return res;
}
char *dynamic_dname(struct dentry *dentry, char *buffer, int buflen,
const char *fmt, ...)
{
va_list args;
char temp[64];
int sz;
va_start(args, fmt);
sz = vsnprintf(temp, sizeof(temp), fmt, args) + 1;
va_end(args);
if (sz > sizeof(temp) || sz > buflen)
return ERR_PTR(-ENAMETOOLONG);
buffer += buflen - sz;
return memcpy(buffer, temp, sz);
}
static char *__dentry_path(struct dentry *dentry, char *buf, int buflen)
{
char *end = buf + buflen;
char *retval;
prepend(&end, &buflen, "\0", 1);
if (buflen < 1)
goto Elong;
retval = end-1;
*retval = '/';
while (!IS_ROOT(dentry)) {
struct dentry *parent = dentry->d_parent;
int error;
prefetch(parent);
spin_lock(&dentry->d_lock);
error = prepend_name(&end, &buflen, &dentry->d_name);
spin_unlock(&dentry->d_lock);
if (error != 0 || prepend(&end, &buflen, "/", 1) != 0)
goto Elong;
retval = end;
dentry = parent;
}
return retval;
Elong:
return ERR_PTR(-ENAMETOOLONG);
}
char *dentry_path_raw(struct dentry *dentry, char *buf, int buflen)
{
char *retval;
write_seqlock(&rename_lock);
retval = __dentry_path(dentry, buf, buflen);
write_sequnlock(&rename_lock);
return retval;
}
EXPORT_SYMBOL(dentry_path_raw);
char *dentry_path(struct dentry *dentry, char *buf, int buflen)
{
char *p = NULL;
char *retval;
write_seqlock(&rename_lock);
if (d_unlinked(dentry)) {
p = buf + buflen;
if (prepend(&p, &buflen, "//deleted", 10) != 0)
goto Elong;
buflen++;
}
retval = __dentry_path(dentry, buf, buflen);
write_sequnlock(&rename_lock);
if (!IS_ERR(retval) && p)
*p = '/';
return retval;
Elong:
return ERR_PTR(-ENAMETOOLONG);
}
SYSCALL_DEFINE2(getcwd, char __user *, buf, unsigned long, size)
{
int error;
struct path pwd, root;
char *page = (char *) __get_free_page(GFP_USER);
if (!page)
return -ENOMEM;
get_fs_root_and_pwd(current->fs, &root, &pwd);
error = -ENOENT;
write_seqlock(&rename_lock);
if (!d_unlinked(pwd.dentry)) {
unsigned long len;
struct path tmp = root;
char *cwd = page + PAGE_SIZE;
int buflen = PAGE_SIZE;
prepend(&cwd, &buflen, "\0", 1);
error = prepend_path(&pwd, &tmp, &cwd, &buflen);
write_sequnlock(&rename_lock);
if (error)
goto out;
if (!path_equal(&tmp, &root)) {
error = prepend_unreachable(&cwd, &buflen);
if (error)
goto out;
}
error = -ERANGE;
len = PAGE_SIZE + page - cwd;
if (len <= size) {
error = len;
if (copy_to_user(buf, cwd, len))
error = -EFAULT;
}
} else {
write_sequnlock(&rename_lock);
}
out:
path_put(&pwd);
path_put(&root);
free_page((unsigned long) page);
return error;
}
int is_subdir(struct dentry *new_dentry, struct dentry *old_dentry)
{
int result;
unsigned seq;
if (new_dentry == old_dentry)
return 1;
do {
seq = read_seqbegin(&rename_lock);
rcu_read_lock();
if (d_ancestor(old_dentry, new_dentry))
result = 1;
else
result = 0;
rcu_read_unlock();
} while (read_seqretry(&rename_lock, seq));
return result;
}
int path_is_under(struct path *path1, struct path *path2)
{
struct vfsmount *mnt = path1->mnt;
struct dentry *dentry = path1->dentry;
int res;
br_read_lock(vfsmount_lock);
if (mnt != path2->mnt) {
for (;;) {
if (mnt->mnt_parent == mnt) {
br_read_unlock(vfsmount_lock);
return 0;
}
if (mnt->mnt_parent == path2->mnt)
break;
mnt = mnt->mnt_parent;
}
dentry = mnt->mnt_mountpoint;
}
res = is_subdir(dentry, path2->dentry);
br_read_unlock(vfsmount_lock);
return res;
}
EXPORT_SYMBOL(path_is_under);
void d_genocide(struct dentry *root)
{
struct dentry *this_parent;
struct list_head *next;
unsigned seq;
int locked = 0;
seq = read_seqbegin(&rename_lock);
again:
this_parent = root;
spin_lock(&this_parent->d_lock);
repeat:
next = this_parent->d_subdirs.next;
resume:
while (next != &this_parent->d_subdirs) {
struct list_head *tmp = next;
struct dentry *dentry = list_entry(tmp, struct dentry, d_u.d_child);
next = tmp->next;
spin_lock_nested(&dentry->d_lock, DENTRY_D_LOCK_NESTED);
if (d_unhashed(dentry) || !dentry->d_inode) {
spin_unlock(&dentry->d_lock);
continue;
}
if (!list_empty(&dentry->d_subdirs)) {
spin_unlock(&this_parent->d_lock);
spin_release(&dentry->d_lock.dep_map, 1, _RET_IP_);
this_parent = dentry;
spin_acquire(&this_parent->d_lock.dep_map, 0, 1, _RET_IP_);
goto repeat;
}
if (!(dentry->d_flags & DCACHE_GENOCIDE)) {
dentry->d_flags |= DCACHE_GENOCIDE;
dentry->d_count--;
}
spin_unlock(&dentry->d_lock);
}
if (this_parent != root) {
struct dentry *child = this_parent;
if (!(this_parent->d_flags & DCACHE_GENOCIDE)) {
this_parent->d_flags |= DCACHE_GENOCIDE;
this_parent->d_count--;
}
this_parent = try_to_ascend(this_parent, locked, seq);
if (!this_parent)
goto rename_retry;
next = child->d_u.d_child.next;
goto resume;
}
spin_unlock(&this_parent->d_lock);
if (!locked && read_seqretry(&rename_lock, seq))
goto rename_retry;
if (locked)
write_sequnlock(&rename_lock);
return;
rename_retry:
locked = 1;
write_seqlock(&rename_lock);
goto again;
}
ino_t find_inode_number(struct dentry *dir, struct qstr *name)
{
struct dentry * dentry;
ino_t ino = 0;
dentry = d_hash_and_lookup(dir, name);
if (dentry) {
if (dentry->d_inode)
ino = dentry->d_inode->i_ino;
dput(dentry);
}
return ino;
}
EXPORT_SYMBOL(find_inode_number);
static __initdata unsigned long dhash_entries;
static int __init set_dhash_entries(char *str)
{
if (!str)
return 0;
dhash_entries = simple_strtoul(str, &str, 0);
return 1;
}
__setup("dhash_entries=", set_dhash_entries);
static void __init dcache_init_early(void)
{
int loop;
if (hashdist)
return;
dentry_hashtable =
alloc_large_system_hash("Dentry cache",
sizeof(struct hlist_bl_head),
dhash_entries,
13,
HASH_EARLY,
&d_hash_shift,
&d_hash_mask,
0);
for (loop = 0; loop < (1 << d_hash_shift); loop++)
INIT_HLIST_BL_HEAD(dentry_hashtable + loop);
}
static void __init dcache_init(void)
{
int loop;
dentry_cache = KMEM_CACHE(dentry,
SLAB_RECLAIM_ACCOUNT|SLAB_PANIC|SLAB_MEM_SPREAD);
register_shrinker(&dcache_shrinker);
if (!hashdist)
return;
dentry_hashtable =
alloc_large_system_hash("Dentry cache",
sizeof(struct hlist_bl_head),
dhash_entries,
13,
0,
&d_hash_shift,
&d_hash_mask,
0);
for (loop = 0; loop < (1 << d_hash_shift); loop++)
INIT_HLIST_BL_HEAD(dentry_hashtable + loop);
}
struct kmem_cache *names_cachep __read_mostly;
EXPORT_SYMBOL(names_cachep);
EXPORT_SYMBOL(d_genocide);
void __init vfs_caches_init_early(void)
{
dcache_init_early();
inode_init_early();
}
void __init vfs_caches_init(unsigned long mempages)
{
unsigned long reserve;
reserve = min((mempages - nr_free_pages()) * 3/2, mempages - 1);
mempages -= reserve;
names_cachep = kmem_cache_create("names_cache", PATH_MAX, 0,
SLAB_HWCACHE_ALIGN|SLAB_PANIC, NULL);
dcache_init();
inode_init();
files_init(mempages);
mnt_init();
bdev_cache_init();
chrdev_init();
}