[JFFS2] Improve getdents vs. f_pos handling on NOR flash.
Commit a491486a2087ac3dfc00efb4f838c8d684afaf54 started obliterating
dirents directly on the medium, when jffs2_can_mark_obsolete(). Removing
them immediately from the f->dents list, however, screws up handling of
f_pos within a directory -- because the offset is equivalent to the
number of entries through the list we are, and the existence of
deletion dirents served to provide 'placeholders' for unlinked
entries. Now, 'rm -r' doesn't even manage to unlink everything in the
directory.
Revert to keeping 'deletion' dirents in the list, at least in memory
even though we no longer write anything to the medium.
Spotted, debugged and mostly fixed by Joakim Tjernlund
Signed-off-by: David Woodhouse <dwmw2@infradead.org>
diff --git a/fs/jffs2/write.c b/fs/jffs2/write.c
index 147e2cb..611012f 100644
--- a/fs/jffs2/write.c
+++ b/fs/jffs2/write.c
@@ -582,7 +582,7 @@
jffs2_add_fd_to_list(c, fd, &dir_f->dents);
up(&dir_f->sem);
} else {
- struct jffs2_full_dirent **prev = &dir_f->dents;
+ struct jffs2_full_dirent *fd = dir_f->dents;
uint32_t nhash = full_name_hash(name, namelen);
/* We don't actually want to reserve any space, but we do
@@ -590,18 +590,20 @@
down(&c->alloc_sem);
down(&dir_f->sem);
- while ((*prev) && (*prev)->nhash <= nhash) {
- if ((*prev)->nhash == nhash &&
- !memcmp((*prev)->name, name, namelen) &&
- !(*prev)->name[namelen]) {
- struct jffs2_full_dirent *this = *prev;
+ for (fd = dir_f->dents; fd; fd = fd->next) {
+ if (fd->nhash == nhash &&
+ !memcmp(fd->name, name, namelen) &&
+ !fd->name[namelen]) {
D1(printk(KERN_DEBUG "Marking old dirent node (ino #%u) @%08x obsolete\n",
- this->ino, ref_offset(this->raw)));
-
- *prev = this->next;
- jffs2_mark_node_obsolete(c, (this->raw));
- jffs2_free_full_dirent(this);
+ fd->ino, ref_offset(fd->raw)));
+ jffs2_mark_node_obsolete(c, fd->raw);
+ /* We don't want to remove it from the list immediately,
+ because that screws up getdents()/seek() semantics even
+ more than they're screwed already. Turn it into a
+ node-less deletion dirent instead -- a placeholder */
+ fd->raw = NULL;
+ fd->ino = 0;
break;
}
prev = &((*prev)->next);
@@ -630,7 +632,8 @@
D1(printk(KERN_DEBUG "Removing deletion dirent for \"%s\" from dir ino #%u\n",
fd->name, dead_f->inocache->ino));
}
- jffs2_mark_node_obsolete(c, fd->raw);
+ if (fd->raw)
+ jffs2_mark_node_obsolete(c, fd->raw);
jffs2_free_full_dirent(fd);
}
}