#include <linux/fsnotify.h>
#include <linux/slab.h>
#include <linux/init.h>
-#include <linux/smp_lock.h>
#include <linux/hash.h>
#include <linux/cache.h>
#include <linux/module.h>
}
}
+/**
+ * d_kill - kill dentry and return parent
+ * @dentry: dentry to kill
+ *
+ * Called with dcache_lock and d_lock, releases both. The dentry must
+ * already be unhashed and removed from the LRU.
+ *
+ * If this is the root of the dentry tree, return NULL.
+ */
+static struct dentry *d_kill(struct dentry *dentry)
+{
+ struct dentry *parent;
+
+ list_del(&dentry->d_u.d_child);
+ dentry_stat.nr_dentry--; /* For d_free, below */
+ /*drops the locks, at that point nobody can reach this dentry */
+ dentry_iput(dentry);
+ parent = dentry->d_parent;
+ d_free(dentry);
+ return dentry == parent ? NULL : parent;
+}
+
/*
* This is dput
*
unhash_it:
__d_drop(dentry);
-
-kill_it: {
- struct dentry *parent;
-
- /* If dentry was on d_lru list
- * delete it from there
- */
- if (!list_empty(&dentry->d_lru)) {
- list_del(&dentry->d_lru);
- dentry_stat.nr_unused--;
- }
- list_del(&dentry->d_u.d_child);
- dentry_stat.nr_dentry--; /* For d_free, below */
- /*drops the locks, at that point nobody can reach this dentry */
- dentry_iput(dentry);
- parent = dentry->d_parent;
- d_free(dentry);
- if (dentry == parent)
- return;
- dentry = parent;
- goto repeat;
+kill_it:
+ /* If dentry was on d_lru list
+ * delete it from there
+ */
+ if (!list_empty(&dentry->d_lru)) {
+ list_del(&dentry->d_lru);
+ dentry_stat.nr_unused--;
}
+ dentry = d_kill(dentry);
+ if (dentry)
+ goto repeat;
}
/**
* Throw away a dentry - free the inode, dput the parent. This requires that
* the LRU list has already been removed.
*
+ * If prune_parents is true, try to prune ancestors as well.
+ *
* Called with dcache_lock, drops it and then regains.
* Called with dentry->d_lock held, drops it.
*/
-static void prune_one_dentry(struct dentry * dentry)
+static void prune_one_dentry(struct dentry * dentry, int prune_parents)
{
- struct dentry * parent;
-
__d_drop(dentry);
- list_del(&dentry->d_u.d_child);
- dentry_stat.nr_dentry--; /* For d_free, below */
- dentry_iput(dentry);
- parent = dentry->d_parent;
- d_free(dentry);
- if (parent != dentry)
- dput(parent);
+ dentry = d_kill(dentry);
+ if (!prune_parents) {
+ dput(dentry);
+ spin_lock(&dcache_lock);
+ return;
+ }
+
+ /*
+ * Prune ancestors. Locking is simpler than in dput(),
+ * because dcache_lock needs to be taken anyway.
+ */
spin_lock(&dcache_lock);
+ while (dentry) {
+ if (!atomic_dec_and_lock(&dentry->d_count, &dentry->d_lock))
+ return;
+
+ if (dentry->d_op && dentry->d_op->d_delete)
+ dentry->d_op->d_delete(dentry);
+ if (!list_empty(&dentry->d_lru)) {
+ list_del(&dentry->d_lru);
+ dentry_stat.nr_unused--;
+ }
+ __d_drop(dentry);
+ dentry = d_kill(dentry);
+ spin_lock(&dcache_lock);
+ }
}
/**
* @count: number of entries to try and free
* @sb: if given, ignore dentries for other superblocks
* which are being unmounted.
+ * @prune_parents: if true, try to prune ancestors as well in one go
*
* Shrink the dcache. This is done when we need
* more memory, or simply when we need to unmount
* all the dentries are in use.
*/
-static void prune_dcache(int count, struct super_block *sb)
+static void prune_dcache(int count, struct super_block *sb, int prune_parents)
{
spin_lock(&dcache_lock);
for (; count ; count--) {
* without taking the s_umount lock (I already hold it).
*/
if (sb && dentry->d_sb == sb) {
- prune_one_dentry(dentry);
+ prune_one_dentry(dentry, prune_parents);
continue;
}
/*
s_umount = &dentry->d_sb->s_umount;
if (down_read_trylock(s_umount)) {
if (dentry->d_sb->s_root != NULL) {
- prune_one_dentry(dentry);
+ prune_one_dentry(dentry, prune_parents);
up_read(s_umount);
continue;
}
spin_unlock(&dentry->d_lock);
continue;
}
- prune_one_dentry(dentry);
+ prune_one_dentry(dentry, 1);
cond_resched_lock(&dcache_lock);
goto repeat;
}
int found;
while ((found = select_parent(parent)) != 0)
- prune_dcache(found, parent->d_sb);
+ prune_dcache(found, parent->d_sb, 1);
}
/*
if (nr) {
if (!(gfp_mask & __GFP_FS))
return -1;
- prune_dcache(nr, NULL);
+ prune_dcache(nr, NULL, 1);
}
return (dentry_stat.nr_unused / 100) * sysctl_vfs_cache_pressure;
}
* @rootmnt: vfsmnt to which the root dentry belongs
* @buffer: buffer to return value in
* @buflen: buffer length
- * @fail_deleted: what to return for deleted files
*
- * Convert a dentry into an ASCII path name. If the entry has been deleted,
- * then if @fail_deleted is true, ERR_PTR(-ENOENT) is returned. Otherwise,
- * the the string " (deleted)" is appended. Note that this is ambiguous.
+ * Convert a dentry into an ASCII path name. If the entry has been deleted
+ * the string " (deleted)" is appended. Note that this is ambiguous.
+ *
+ * Returns the buffer or an error code if the path was too long.
*
- * Returns the buffer or an error code.
+ * "buflen" should be positive. Caller holds the dcache_lock.
*/
-static char *__d_path(struct dentry *dentry, struct vfsmount *vfsmnt,
- struct dentry *root, struct vfsmount *rootmnt,
- char *buffer, int buflen, int fail_deleted)
+static char * __d_path( struct dentry *dentry, struct vfsmount *vfsmnt,
+ struct dentry *root, struct vfsmount *rootmnt,
+ char *buffer, int buflen)
{
- int namelen, is_slash;
+ char * end = buffer+buflen;
+ char * retval;
+ int namelen;
- if (buflen < 2)
- return ERR_PTR(-ENAMETOOLONG);
- buffer += --buflen;
- *buffer = '\0';
-
- spin_lock(&dcache_lock);
+ *--end = '\0';
+ buflen--;
if (!IS_ROOT(dentry) && d_unhashed(dentry)) {
- if (fail_deleted) {
- buffer = ERR_PTR(-ENOENT);
- goto out;
- }
- if (buflen < 10)
- goto Elong;
buflen -= 10;
- buffer -= 10;
- memcpy(buffer, " (deleted)", 10);
+ end -= 10;
+ if (buflen < 0)
+ goto Elong;
+ memcpy(end, " (deleted)", 10);
}
- while (dentry != root || vfsmnt != rootmnt) {
+
+ if (buflen < 1)
+ goto Elong;
+ /* Get '/' right */
+ retval = end-1;
+ *retval = '/';
+
+ for (;;) {
struct dentry * parent;
+ if (dentry == root && vfsmnt == rootmnt)
+ break;
if (dentry == vfsmnt->mnt_root || IS_ROOT(dentry)) {
+ /* Global root? */
spin_lock(&vfsmount_lock);
if (vfsmnt->mnt_parent == vfsmnt) {
spin_unlock(&vfsmount_lock);
parent = dentry->d_parent;
prefetch(parent);
namelen = dentry->d_name.len;
- if (buflen <= namelen)
- goto Elong;
buflen -= namelen + 1;
- buffer -= namelen;
- memcpy(buffer, dentry->d_name.name, namelen);
- *--buffer = '/';
+ if (buflen < 0)
+ goto Elong;
+ end -= namelen;
+ memcpy(end, dentry->d_name.name, namelen);
+ *--end = '/';
+ retval = end;
dentry = parent;
}
- /* Get '/' right */
- if (*buffer != '/')
- *--buffer = '/';
-out:
- spin_unlock(&dcache_lock);
- return buffer;
+ return retval;
global_root:
- /*
- * We went past the (vfsmount, dentry) we were looking for and have
- * either hit a root dentry, a lazily unmounted dentry, an
- * unconnected dentry, or the file is on a pseudo filesystem.
- */
namelen = dentry->d_name.len;
- is_slash = (namelen == 1 && *dentry->d_name.name == '/');
- if (is_slash || (dentry->d_sb->s_flags & MS_NOUSER)) {
- /*
- * Make sure we won't return a pathname starting with '/'.
- *
- * Historically, we also glue together the root dentry and
- * remaining name for pseudo filesystems like pipefs, which
- * have the MS_NOUSER flag set. This results in pathnames
- * like "pipe:[439336]".
- */
- if (*buffer == '/') {
- buffer++;
- buflen++;
- }
- if (is_slash)
- goto out;
- }
- if (buflen < namelen)
+ buflen -= namelen;
+ if (buflen < 0)
goto Elong;
- buffer -= namelen;
- memcpy(buffer, dentry->d_name.name, namelen);
- goto out;
-
+ retval -= namelen-1; /* hit the slash */
+ memcpy(retval, dentry->d_name.name, namelen);
+ return retval;
Elong:
- buffer = ERR_PTR(-ENAMETOOLONG);
- goto out;
+ return ERR_PTR(-ENAMETOOLONG);
}
/* write full pathname into buffer and return start of pathname */
-char *d_path(struct dentry *dentry, struct vfsmount *vfsmnt, char *buf,
- int buflen)
+char * d_path(struct dentry *dentry, struct vfsmount *vfsmnt,
+ char *buf, int buflen)
{
char *res;
struct vfsmount *rootmnt;
struct dentry *root;
+ /*
+ * We have various synthetic filesystems that never get mounted. On
+ * these filesystems dentries are never used for lookup purposes, and
+ * thus don't need to be hashed. They also don't need a name until a
+ * user wants to identify the object in /proc/pid/fd/. The little hack
+ * below allows us to generate a name for these objects on demand:
+ */
+ if (dentry->d_op && dentry->d_op->d_dname)
+ return dentry->d_op->d_dname(dentry, buf, buflen);
+
read_lock(¤t->fs->lock);
rootmnt = mntget(current->fs->rootmnt);
root = dget(current->fs->root);
read_unlock(¤t->fs->lock);
- res = __d_path(dentry, vfsmnt, root, rootmnt, buf, buflen, 0);
+ spin_lock(&dcache_lock);
+ res = __d_path(dentry, vfsmnt, root, rootmnt, buf, buflen);
+ spin_unlock(&dcache_lock);
dput(root);
mntput(rootmnt);
return res;
}
+/*
+ * Helper function for dentry_operations.d_dname() members
+ */
+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);
+}
+
/*
* NOTE! The user-level library version returns a
* character pointer. The kernel system call just
*/
asmlinkage long sys_getcwd(char __user *buf, unsigned long size)
{
- int error, len;
+ int error;
struct vfsmount *pwdmnt, *rootmnt;
struct dentry *pwd, *root;
- char *page = (char *) __get_free_page(GFP_USER), *cwd;
+ char *page = (char *) __get_free_page(GFP_USER);
if (!page)
return -ENOMEM;
root = dget(current->fs->root);
read_unlock(¤t->fs->lock);
- cwd = __d_path(pwd, pwdmnt, root, rootmnt, page, PAGE_SIZE, 1);
- error = PTR_ERR(cwd);
- if (IS_ERR(cwd))
- goto out;
+ error = -ENOENT;
+ /* Has the current directory has been unlinked? */
+ spin_lock(&dcache_lock);
+ if (pwd->d_parent == pwd || !d_unhashed(pwd)) {
+ unsigned long len;
+ char * cwd;
- error = -ERANGE;
- len = PAGE_SIZE + page - cwd;
- if (len <= size) {
- error = len;
- if (copy_to_user(buf, cwd, len))
- error = -EFAULT;
- }
+ cwd = __d_path(pwd, pwdmnt, root, rootmnt, page, PAGE_SIZE);
+ spin_unlock(&dcache_lock);
+
+ error = PTR_ERR(cwd);
+ if (IS_ERR(cwd))
+ goto out;
+
+ error = -ERANGE;
+ len = PAGE_SIZE + page - cwd;
+ if (len <= size) {
+ error = len;
+ if (copy_to_user(buf, cwd, len))
+ error = -EFAULT;
+ }
+ } else
+ spin_unlock(&dcache_lock);
out:
dput(pwd);
* but it is probably not worth it because of the cache nature
* of the dcache.
*/
- dentry_cache = kmem_cache_create("dentry_cache",
- sizeof(struct dentry),
- 0,
- (SLAB_RECLAIM_ACCOUNT|SLAB_PANIC|
- SLAB_MEM_SPREAD),
- NULL, NULL);
+ dentry_cache = KMEM_CACHE(dentry,
+ SLAB_RECLAIM_ACCOUNT|SLAB_PANIC|SLAB_MEM_SPREAD);
set_shrinker(DEFAULT_SEEKS, shrink_dcache_memory);