This patch uses an alternative approach to dealing with deferred unlink
of in-use files, suggested yesterday by Hirofumi. We allow d_delete to
unhash the in-use dentry as it is fond of doing, but clone a new,
negative dentry to take its place to make the file appear to be deleted
as expected, while the filesystem has not removed the dentry yet.
Unlike my previous attempt, this approach avoids making any changes to
other filesystems. There are four changes to core vfs:
1) Add a new flag DCACHE_HIDDEN that is cleared in dcache.c wherever
a dentry is changed from negative to positive (i.e., when an inode
is attached).
2) Add a d_negative inline that checks the HIDDEN flag as well as the
traditional null inode condition for negative dentry. Use the new
inline throughout fs/namei.c (arguably this should have been done
long ago for readability).
3) Add a ->hide method to d_delete that allows a filesystem to add
its own handling to dentry hiding.
4) Reserve two dentry state bits for use by the filesystem. These
are used to decide what kind of deferred processing needs to be
done on a dentry.
while a new method had to be added to d_delete, no new method was needed
for dentry instantiation, because that is already left in the hands of
the filesystem. This apparent asymmetry actually makes sense: at
instantiation time, only one task knows about the new dentry, whereas
at unlink time (d_delete, which should have been called d_unlink)
multiple tasks on multiple cpus can be accessing the dentry, so all the
work has to be done very carefully under locks.
The ext2-specific hide method provided in the patch is:
static int ext2_hide_dentry(struct dentry *dentry)
{
dentry->d_flags |= DCACHE_HIDDEN;
if (!(dentry->d_flags & DCACHE_BACKED)) {
/* converting unbacked to negative */
dput(dentry); /* Cancel dget from deferred create */
return 0;
}
if (atomic_read(&dentry->d_count) == 1) {
BUG_ON(!dentry->d_inode);
dentry->d_flags &= ~DCACHE_BACKED;
dentry->d_flags |= DCACHE_STALE;
dget(dentry);
return 1;
}
spin_unlock(&dentry->d_lock);
spin_unlock(&dcache_lock);
dentry = d_alloc(dentry->d_parent, &dentry->d_name);
dentry->d_flags |= DCACHE_STALE;
d_instantiate(dentry, NULL);
d_rehash(dentry);
spin_lock(&dentry->d_lock);
spin_lock(&dcache_lock);
return 0;
}
A zero return means d_delete should process the dentry normally, which
means unhashing any busy dentries, or directly unlinking if not busy.
A return of one tells d_delete to allow for an extra use count due to
deferring the unlink, but go ahead and unlink the dentry from the inode
now. In the case of a busy dentry that requires deferred processing,
we add a new, negative dentry to the dentry cache, which is marked
STALE so that our directory sync operation will remove the dirent. We
allow d_delete to unhash the original dentry.
This approach is not quite as efficient as the previous version because
it clones a dentry on every unlink of a busy dentry, whereas the more
invasive patch only clones when a busy, unlinked dentry is re-opened.
But the clone case is still rare. I think this new approach will work
out well.
Now I will move on to the finishing details:
1) Handle deferred inode creation
2) Handle rename
Regards,
Daniel
diff --git a/Makefile b/Makefile
index 56fb747..985a7ce 100644
--- a/Makefile
+++ b/Makefile
@@ -538,7 +538,7 @@ NOSTDINC_FLAGS += -nostdinc -isystem $(shell $(CC) -print-file-name=include)
CHECKFLAGS += $(NOSTDINC_FLAGS)
# warn about C99 declaration after statement
-KBUILD_CFLAGS += $(call cc-option,-Wdeclaration-after-statement,)
+#KBUILD_CFLAGS += $(call cc-option,-Wdeclaration-after-statement,)
# disable pointer signed / unsigned warnings in gcc 4.0
KBUILD_CFLAGS += $(call cc-option,-Wno-pointer-sign,)
diff --git a/fs/dcache.c b/fs/dcache.c
index 6068c25..038bb83 100644
--- a/fs/dcache.c
+++ b/fs/dcache.c
@@ -272,7 +272,7 @@ int d_invalidate(struct dentry * dentry)
*/
spin_lock(&dentry->d_lock);
if (atomic_read(&dentry->d_count) > 1) {
- if (dentry->d_inode && S_ISDIR(dentry->d_inode->i_mode)) {
+ if (dentry->d_inode && S_ISDIR(dentry->d_inode->i_mode)) { // what to do here?
spin_unlock(&dentry->d_lock);
spin_unlock(&dcache_lock);
return -EBUSY;
@@ -970,9 +970,11 @@ void d_instantiate(struct dentry *entry, struct inode * inode)
{
BUG_ON(!list_empty(&entry->d_alias));
spin_lock(&dcache_lock);
+ /* d_attach_locked(dentry, inode) */
if (inode)
list_add(&entry->d_alias, &inode->i_dentry);
entry->d_inode = inode;
+ entry->d_flags &= ~DCACHE_HIDDEN;
fsnotify_d_instantiate(entry, inode);
spin_unlock(&dcache_lock);
security_d_instantiate(entry, inode);
@@ -1022,8 +1024,10 @@ static struct dentry *__d_instantiate_unique(struct dentry *entry,
return alias;
}
+ /* d_attach_locked(dentry, inode) */
list_add(&entry->d_alias, &inode->i_dentry);
entry->d_inode = inode;
+ entry->d_flags &= ~DCACHE_HIDDEN;
fsnotify_d_instantiate(entry, inode);
return NULL;
}
@@ -1182,8 +1186,10 @@ struct dentry *d_splice_alias(struct inode *inode, struct dentry *dentry)
iput(inode);
} else {
/* d_instantiate takes dcache_lock, so we do it by hand */
+ /* d_attach_locked(dentry, inode) */
list_add(&dentry->d_alias, &inode->i_dentry);
dentry->d_inode = inode;
+ dentry->d_flags &= ~DCACHE_HIDDEN;
fsnotify_d_instantiate(dentry, inode);
spin_unlock(&dcache_lock);
security_d_instantiate(dentry, inode);
@@ -1387,20 +1393,22 @@ out:
void d_delete(struct dentry * dentry)
{
- int isdir = 0;
+ int isdir = 0, hidden;
/*
* Are we the only user?
*/
spin_lock(&dcache_lock);
spin_lock(&dentry->d_lock);
isdir = S_ISDIR(dentry->d_inode->i_mode);
- if (atomic_read(&dentry->d_count) == 1) {
+ hidden = dentry->d_op && dentry->d_op->d_hide && dentry->d_op->d_hide(dentry);
+
+ if (atomic_read(&dentry->d_count) == 1 + hidden) {
dentry_iput(dentry);
fsnotify_nameremove(dentry, isdir);
return;
}
- if (!d_unhashed(dentry))
+ if (!d_unhashed(dentry) && !hidden)
__d_drop(dentry);
spin_unlock(&dentry->d_lock);
@@ -1514,7 +1522,7 @@ static void d_move_locked(struct dentry * dentry, struct dentry * target)
{
struct hlist_head *list;
- if (!dentry->d_inode)
+ if (d_negative(dentry))
printk(KERN_WARNING "VFS: moving negative dcache entry\n");
write_seqlock(&rename_lock);
@@ -2103,7 +2111,7 @@ ino_t find_inode_number(struct dentry *dir, struct qstr *name)
dentry = d_hash_and_lookup(dir, name);
if (dentry) {
- if (dentry->d_inode)
+ if (!d_negative(dentry))
ino = dentry->d_inode->i_ino;
dput(dentry);
}
diff --git a/fs/ext2/dir.c b/fs/ext2/dir.c
index a78c6b4..b7bb8a8 100644
--- a/fs/ext2/dir.c
+++ b/fs/ext2/dir.c
@@ -270,6 +270,61 @@ static inline void ext2_set_de_type(ext2_dirent *de, struct inode *inode)
de->file_type = 0;
}
+int real_unlink(struct inode *dir, struct dentry *dentry);
+void dentry_iput(struct dentry *dentry);
+
+int ext2_flush_dir(struct dentry *dir)
+{
+ printk(">>> ext2_sync_dir %p \"%.*s\"\n", dir, dir->d_name.len, dir->d_name.name);
+ struct list_head *next;
+ spin_lock(&dcache_lock);
+ for (next = dir->d_subdirs.next; next != &dir->d_subdirs;) {
+ struct dentry *dentry = list_entry(next, struct dentry, d_u.d_child);
+ next = next->next;
+ show_dentry("dentry", dentry);
+ if (d_unhashed(dentry))
+ continue;
+ spin_lock(&dentry->d_lock);
+ if (d_negative(dentry)) {
+ if ((dentry->d_flags & DCACHE_STALE)) {
+ dentry->d_flags &= ~DCACHE_STALE;
+ show_dentry("deferred unlink", dentry);
+ spin_unlock(&dentry->d_lock);
+ spin_unlock(&dcache_lock);
+ real_unlink(dir->d_inode, dentry);
+ if (dentry->d_inode)
+ d_delete(dentry);
+ dput(dentry);
+ spin_lock(&dcache_lock);
+ continue;
+ }
+ } else {
+ if (!(dentry->d_flags & DCACHE_BACKED)) {
+ int stale = dentry->d_flags & DCACHE_STALE;
+ show_dentry("deferred link", dentry);
+ dentry->d_flags &= ~DCACHE_STALE;
+ dentry->d_flags |= DCACHE_BACKED;
+ spin_unlock(&dentry->d_lock);
+ spin_unlock(&dcache_lock);
+ if (stale)
+ real_unlink(dir->d_inode, dentry);
+ struct inode *inode = dentry->d_inode;
+ int err = ext2_add_link(dentry, inode);
+ if (err) {
+ inode_dec_link_count(inode);
+ iput(inode);
+ }
+ dput(dentry);
+ spin_lock(&dcache_lock);
+ continue;
+ }
+ }
+ spin_unlock(&dentry->d_lock);
+ }
+ spin_unlock(&dcache_lock);
+ return 0; // really??
+}
+
static int
ext2_readdir (struct file * filp, void * dirent, filldir_t filldir)
{
@@ -283,6 +338,8 @@ ext2_readdir (struct file * filp, void * dirent, filldir_t filldir)
unsigned char *types = NULL;
int need_revalidate = filp->f_version != inode->i_version;
+ ext2_flush_dir(filp->f_path.dentry);
+
if (pos > inode->i_size - EXT2_DIR_REC_LEN(1))
return 0;
@@ -699,6 +756,12 @@ not_empty:
return 0;
}
+int ext2_sync_dir(struct file *file, struct dentry *dir, int datasync)
+{
+ ext2_flush_dir(dir);
+ return ext2_sync_file(file, dir, datasync);
+}
+
const struct file_operations ext2_dir_operations = {
.llseek = generic_file_llseek,
.read = generic_read_dir,
@@ -707,5 +770,5 @@ const struct file_operations ext2_dir_operations = {
#ifdef CONFIG_COMPAT
.compat_ioctl = ext2_compat_ioctl,
#endif
- .fsync = ext2_sync_file,
+ .fsync = ext2_sync_dir,
};
diff --git a/fs/ext2/inode.c b/fs/ext2/inode.c
index 384fc0d..a9ce89b 100644
--- a/fs/ext2/inode.c
+++ b/fs/ext2/inode.c
@@ -58,6 +58,7 @@ static inline int ext2_inode_is_fast_symlink(struct inode *inode)
*/
void ext2_delete_inode (struct inode * inode)
{
+ printk(">>> ext2_delete_inode\n");
truncate_inode_pages(&inode->i_data, 0);
if (is_bad_inode(inode))
diff --git a/fs/ext2/namei.c b/fs/ext2/namei.c
index 80c97fd..a90bd96 100644
--- a/fs/ext2/namei.c
+++ b/fs/ext2/namei.c
@@ -36,16 +36,54 @@
#include "acl.h"
#include "xip.h"
-static inline int ext2_add_nondir(struct dentry *dentry, struct inode *inode)
+static int ext2_hide_dentry(struct dentry *dentry)
{
- int err = ext2_add_link(dentry, inode);
- if (!err) {
- d_instantiate(dentry, inode);
+ dentry->d_flags |= DCACHE_HIDDEN;
+ if (!(dentry->d_flags & DCACHE_BACKED)) {
+ /* converting unbacked to negative */
+ dput(dentry); /* Cancel dget from deferred create */
return 0;
}
- inode_dec_link_count(inode);
- iput(inode);
- return err;
+ if (atomic_read(&dentry->d_count) == 1) {
+ BUG_ON(!dentry->d_inode);
+ show_dentry("hide dentry", dentry);
+ dentry->d_flags &= ~DCACHE_BACKED;
+ dentry->d_flags |= DCACHE_STALE;
+ dget(dentry);
+ return 1;
+ }
+ spin_unlock(&dentry->d_lock);
+ spin_unlock(&dcache_lock);
+ dentry = d_alloc(dentry->d_parent, &dentry->d_name);
+ dentry->d_flags |= DCACHE_STALE;
+ d_instantiate(dentry, NULL);
+ d_rehash(dentry);
+ spin_lock(&dentry->d_lock);
+ spin_lock(&dcache_lock);
+ return 0;
+}
+
+static struct dentry_operations ext2_dentry_operations = {
+ .d_hide = ext2_hide_dentry,
+};
+
+static int ext2_unlink(struct inode *dir, struct dentry *dentry)
+{
+ show_dentry("defer unlink", dentry);
+ dentry->d_inode->i_ctime = dir->i_ctime;
+ inode_dec_link_count(dentry->d_inode);
+ return 0;
+}
+
+static int ext2_add_nondir(struct dentry *dentry, struct inode *inode)
+{
+ show_dentry("defer create", dentry);
+ if (!(dentry->d_flags & DCACHE_STALE)) /* already deferred? */
+ dget(dentry);
+ dentry->d_op = &ext2_dentry_operations;
+ d_instantiate(dentry, inode);
+ show_dentry("instantiated", dentry);
+ return 0;
}
/*
@@ -66,7 +104,10 @@ static struct dentry *ext2_lookup(struct inode * dir, struct dentry *dentry, str
inode = ext2_iget(dir->i_sb, ino);
if (IS_ERR(inode))
return ERR_CAST(inode);
+ dentry->d_flags |= DCACHE_BACKED;
+ show_dentry("found real dirent", dentry);
}
+ dentry->d_op = &ext2_dentry_operations;
return d_splice_alias(inode, dentry);
}
@@ -237,6 +278,7 @@ static int ext2_mkdir(struct inode * dir, struct dentry * dentry, int mode)
if (err)
goto out_fail;
+ dentry->d_op = &ext2_dentry_operations;
d_instantiate(dentry, inode);
out:
return err;
@@ -250,26 +292,12 @@ out_dir:
goto out;
}
-static int ext2_unlink(struct inode * dir, struct dentry *dentry)
+int real_unlink(struct inode *dir, struct dentry *dentry)
{
- struct inode * inode = dentry->d_inode;
- struct ext2_dir_entry_2 * de;
- struct page * page;
- int err = -ENOENT;
-
- de = ext2_find_entry (dir, dentry, &page);
- if (!de)
- goto out;
-
- err = ext2_delete_entry (de, page);
- if (err)
- goto out;
-
- inode->i_ctime = dir->i_ctime;
- inode_dec_link_count(inode);
- err = 0;
-out:
- return err;
+ struct page *page;
+ struct ext2_dir_entry_2 *de = ext2_find_entry(dir, dentry, &page);
+ show_dentry("ext2_unlink", dentry);
+ return de ? ext2_delete_entry(de, page) : -ENOENT;
}
static int ext2_rmdir (struct inode * dir, struct dentry *dentry)
diff --git a/fs/ext2/super.c b/fs/ext2/super.c
index ef50cbc..e8066b3 100644
--- a/fs/ext2/super.c
+++ b/fs/ext2/super.c
@@ -295,17 +295,52 @@ static ssize_t ext2_quota_read(struct super_block *sb, int type, char *data, siz
static ssize_t ext2_quota_write(struct super_block *sb, int type, const char *data, size_t len, loff_t off);
#endif
+extern spinlock_t inode_lock;
+
+static inline void show_inode(char *tag, struct inode *inode)
+{
+ printk(">>> %s: %p/%i %x\n", tag, inode,
+ atomic_read(&inode->i_count), inode->i_flags);
+}
+
+static void defer_drop_inode(struct inode *inode)
+{
+ if (inode->i_nlink) {
+ generic_drop_inode(inode);
+ return;
+ }
+ show_inode("defer inode delete", inode);
+ inode->i_state |= I_DIRTY;
+ list_move(&inode->i_list, &EXT2_SB(inode->i_sb)->delete);
+ spin_unlock(&inode_lock);
+}
+
+extern struct list_head inode_unused;
+
+static int ext2_sync_fs(struct super_block *sb, int wait)
+{
+ while (!list_empty(&EXT2_SB(sb)->delete)) {
+ struct inode *inode = list_entry(EXT2_SB(sb)->delete.next, struct inode, i_list);
+ show_inode("delete deferred inode", inode);
+ spin_lock(&inode_lock);
+ generic_delete_inode(inode); /* removes from list and drops lock */
+ }
+ return 0;
+}
+
static const struct super_operations ext2_sops = {
.alloc_inode = ext2_alloc_inode,
.destroy_inode = ext2_destroy_inode,
.write_inode = ext2_write_inode,
.delete_inode = ext2_delete_inode,
+ .drop_inode = defer_drop_inode,
.put_super = ext2_put_super,
.write_super = ext2_write_super,
.statfs = ext2_statfs,
.remount_fs = ext2_remount,
.clear_inode = ext2_clear_inode,
.show_options = ext2_show_options,
+ .sync_fs = ext2_sync_fs,
#ifdef CONFIG_QUOTA
.quota_read = ext2_quota_read,
.quota_write = ext2_quota_write,
@@ -614,6 +649,7 @@ static int ext2_setup_super (struct super_block * sb,
EXT2_BLOCKS_PER_GROUP(sb),
EXT2_INODES_PER_GROUP(sb),
sbi->s_mount_opt);
+ INIT_LIST_HEAD(&sbi->delete);
return res;
}
diff --git a/fs/namei.c b/fs/namei.c
index 3b26a24..59e6fdc 100644
--- a/fs/namei.c
+++ b/fs/namei.c
@@ -955,9 +955,9 @@ static int __link_path_walk(const char *name, struct nameidata *nd)
break;
err = -ENOENT;
- inode = next.dentry->d_inode;
- if (!inode)
+ if (d_negative(next.dentry))
goto out_dput;
+ inode = next.dentry->d_inode;
err = -ENOTDIR;
if (!inode->i_op)
goto out_dput;
@@ -967,9 +967,9 @@ static int __link_path_walk(const char *name, struct nameidata *nd)
if (err)
goto return_err;
err = -ENOENT;
- inode = nd->path.dentry->d_inode;
- if (!inode)
+ if (d_negative(nd->path.dentry))
break;
+ inode = nd->path.dentry->d_inode;
err = -ENOTDIR;
if (!inode->i_op)
break;
@@ -1077,7 +1077,7 @@ static int __emul_lookup_dentry(const char *name, struct nameidata *nd)
if (path_walk(name, nd))
return 0; /* something went wrong... */
- if (!nd->path.dentry->d_inode ||
+ if (d_negative(nd->path.dentry) ||
S_ISDIR(nd->path.dentry->d_inode->i_mode)) {
struct path old_path = nd->path;
struct qstr last = nd->last;
@@ -1094,7 +1094,7 @@ static int __emul_lookup_dentry(const char *name, struct nameidata *nd)
path_get(&fs->root);
read_unlock(&fs->lock);
if (path_walk(name, nd) == 0) {
- if (nd->path.dentry->d_inode) {
+ if (!d_negative(nd->path.dentry)) {
path_put(&old_path);
return 1;
}
@@ -1487,7 +1487,7 @@ static int may_delete(struct inode *dir,struct dentry *victim,int isdir)
{
int error;
- if (!victim->d_inode)
+ if (d_negative(victim))
return -ENOENT;
BUG_ON(victim->d_parent->d_inode != dir);
@@ -1526,7 +1526,7 @@ static int may_delete(struct inode *dir,struct dentry *victim,int isdir)
static inline int may_create(struct inode *dir, struct dentry *child,
struct nameidata *nd)
{
- if (child->d_inode)
+ if (!d_negative(child))
return -EEXIST;
if (IS_DEADDIR(dir))
return -ENOENT;
@@ -1618,12 +1618,14 @@ int vfs_create(struct inode *dir, struct dentry *dentry, int mode,
int may_open(struct nameidata *nd, int acc_mode, int flag)
{
struct dentry *dentry = nd->path.dentry;
- struct inode *inode = dentry->d_inode;
+ struct inode *inode;
int error;
- if (!inode)
+ if (d_negative(dentry))
return -ENOENT;
+ inode = dentry->d_inode;
+
if (S_ISLNK(inode->i_mode))
return -ELOOP;
@@ -1828,7 +1830,7 @@ do_last:
}
/* Negative dentry, just create the file */
- if (!path.dentry->d_inode) {
+ if (d_negative(path.dentry)) {
/*
* This write is needed to ensure that a
* ro->rw transition does not occur between
@@ -1866,14 +1868,14 @@ do_last:
}
error = -ENOENT;
- if (!path.dentry->d_inode)
+ if (d_negative(path.dentry))
goto exit_dput;
if (path.dentry->d_inode->i_op && path.dentry->d_inode->i_op->follow_link)
goto do_link;
path_to_nameidata(&path, &nd);
error = -EISDIR;
- if (path.dentry->d_inode && S_ISDIR(path.dentry->d_inode->i_mode))
+ if (!d_negative(path.dentry) && S_ISDIR(path.dentry->d_inode->i_mode))
goto exit;
ok:
/*
@@ -2017,7 +2019,7 @@ struct dentry *lookup_create(struct nameidata *nd, int is_dir)
if (IS_ERR(dentry))
goto fail;
- if (dentry->d_inode)
+ if (!d_negative(dentry))
goto eexist;
/*
* Special case - lookup gave negative, but... we had foo/bar/
@@ -2381,7 +2383,7 @@ static long do_unlinkat(int dfd, const char __user *pathname)
/* Why not before? Because we want correct error value */
if (nd.last.name[nd.last.len])
goto slashes;
- inode = dentry->d_inode;
+ inode = dentry->d_inode; // what to do here?
if (inode)
atomic_inc(&inode->i_count);
error = mnt_want_write(nd.path.mnt);
@@ -2402,7 +2404,7 @@ exit:
return error;
slashes:
- error = !dentry->d_inode ? -ENOENT :
+ error = d_negative(dentry) ? -ENOENT :
S_ISDIR(dentry->d_inode->i_mode) ? -EISDIR : -ENOTDIR;
goto exit2;
}
@@ -2680,7 +2682,7 @@ static int vfs_rename_other(struct inode *old_dir, struct dentry *old_dentry,
dget(new_dentry);
target = new_dentry->d_inode;
- if (target)
+ if (target) // what to do here?
mutex_lock(&target->i_mutex);
if (d_mountpoint(old_dentry)||d_mountpoint(new_dentry))
error = -EBUSY;
@@ -2710,7 +2712,7 @@ int vfs_rename(struct inode *old_dir, struct dentry *old_dentry,
if (error)
return error;
- if (!new_dentry->d_inode)
+ if (d_negative(new_dentry))
error = may_create(new_dir, new_dentry, NULL);
else
error = may_delete(new_dir, new_dentry, is_dir);
@@ -2777,7 +2779,7 @@ static int do_rename(int olddfd, const char *oldname,
goto exit3;
/* source must exist */
error = -ENOENT;
- if (!old_dentry->d_inode)
+ if (d_negative(old_dentry))
goto exit4;
/* unless the source is a directory trailing slashes give -ENOTDIR */
if (!S_ISDIR(old_dentry->d_inode->i_mode)) {
diff --git a/include/linux/dcache.h b/include/linux/dcache.h
index d982eb8..dde5d1e 100644
--- a/include/linux/dcache.h
+++ b/include/linux/dcache.h
@@ -133,6 +133,7 @@ struct dentry_operations {
void (*d_release)(struct dentry *);
void (*d_iput)(struct dentry *, struct inode *);
char *(*d_dname)(struct dentry *, char *, int);
+ int (*d_hide)(struct dentry *);
};
/* the dentry parameter passed to d_hash and d_compare is the parent
@@ -176,6 +177,10 @@ d_iput: no no no yes
#define DCACHE_INOTIFY_PARENT_WATCHED 0x0020 /* Parent inode is watched */
+#define DCACHE_HIDDEN 0x0040 /* Unlinked but referenced by an open file */
+#define DCACHE_BACKED 0x0080 /* FS has dirent */
+#define DCACHE_STALE 0x0100 /* FS has wrong dirent */
+
extern spinlock_t dcache_lock;
extern seqlock_t rename_lock;
@@ -341,6 +346,11 @@ static inline int d_unhashed(struct dentry *dentry)
return (dentry->d_flags & DCACHE_UNHASHED);
}
+static inline int d_negative(struct dentry *dentry)
+{
+ return !dentry->d_inode || (dentry->d_flags & DCACHE_HIDDEN);
+}
+
static inline struct dentry *dget_parent(struct dentry *dentry)
{
struct dentry *ret;
@@ -363,4 +373,11 @@ extern struct dentry *lookup_create(struct nameidata *nd, int is_dir);
extern int sysctl_vfs_cache_pressure;
+static inline void show_dentry(char *tag, struct dentry *dentry)
+{
+ printk(">>> %s: %p/%i %x %p \"%.*s\"\n", tag, dentry,
+ atomic_read(&dentry->d_count), dentry->d_flags, dentry->d_inode,
+ dentry->d_name.len, dentry->d_name.name);
+}
+
#endif /* __LINUX_DCACHE_H */
diff --git a/include/linux/ext2_fs_sb.h b/include/linux/ext2_fs_sb.h
index f273415..54ef185 100644
--- a/include/linux/ext2_fs_sb.h
+++ b/include/linux/ext2_fs_sb.h
@@ -106,6 +106,7 @@ struct ext2_sb_info {
spinlock_t s_rsv_window_lock;
struct rb_root s_rsv_window_root;
struct ext2_reserve_window_node s_rsv_window_head;
+ struct list_head delete;
};
#endif /* _LINUX_EXT2_FS_SB */
_______________________________________________
Tux3 mailing list
[email protected]
http://mailman.tux3.org/cgi-bin/mailman/listinfo/tux3