All of lore.kernel.org
 help / color / mirror / Atom feed
From: jblunck@suse.de
To: linux-kernel@vger.kernel.org
Subject: [RFC,PATCH] libfs dcache_readdir() and dcache_dir_lseek() bugfix
Date: Fri, 4 Nov 2005 12:38:51 +0100	[thread overview]
Message-ID: <20051104113851.GA4770@hasse.suse.de> (raw)

[-- Attachment #1: Type: text/plain, Size: 1354 bytes --]

This patch effects all users of libfs' dcache directory implementation.

Old glibc implementations (e.g. glibc-2.2.5) are lseeking after every call to
getdents(), subsequent calls to getdents() are starting to read from a wrong
f_pos, when the directory is modified in between. Therefore not all directory
entries are returned. IMHO this is a bug and it breaks applications, e.g. "rm
-fr" on tmpfs.

SuSV3 only says:
"If a file is removed from or added to the directory after the most recent
call to opendir() or rewinddir(), whether a subsequent call to readdir_r()
returns an entry for that file is unspecified."

Although I tried to provide a small fix, this is a complete rework of the
libfs' dcache_readdir() and dcache_dir_lseek(). This patch use the dentries
name hashes as the offsets for the directory entries. I'm not that sure it is
maybe a problem to use full_name_hash() for that. If hash collisions occur,
readdir() might return duplicate entries after an lseek(). Any ideas?

This patch is compile and boot tested. This patch is against 2.6.14-git of
today.

Comments, please.
	Jan

-- 
Jan Blunck                                               jblunck@suse.de
SuSE LINUX AG - A Novell company
Maxfeldstr. 5                                          +49-911-74053-608
D-90409 Nürnberg                                      http://www.suse.de

[-- Attachment #2: test1.c --]
[-- Type: text/plain, Size: 602 bytes --]

#include <stdio.h>
#include <unistd.h>
#include <dirent.h>

int main(int argc, char *argv[])
{
	DIR *dir;
	struct dirent *entry;
	unsigned int offset;

        if (argc < 2) {
                printf("USAGE:  %s <directory>\n", argv[0]);
                return 1;
        }

	dir = opendir(argv[1]);
	if (!dir)
		return 1;

	while((entry = readdir(dir)) != NULL) {
		seekdir(dir, entry->d_off);
		printf("name=%s\tino=%d off=%d\n", entry->d_name, entry->d_ino,
		       entry->d_off);
		if (*entry->d_name == '.')
			continue;
		if(unlink(entry->d_name) != 0)
			break;
	}

	closedir(dir);
	return 0;
}

[-- Attachment #3: libfs-dcache_readdir_lseek-fix.diff --]
[-- Type: text/plain, Size: 4767 bytes --]

 fs/libfs.c |  140 +++++++++++++++++++++++++++++++++----------------------------
 1 files changed, 76 insertions(+), 64 deletions(-)

Index: linux-2.6/fs/libfs.c
===================================================================
--- linux-2.6.orig/fs/libfs.c
+++ linux-2.6/fs/libfs.c
@@ -72,42 +72,57 @@ int dcache_dir_close(struct inode *inode
 	return 0;
 }
 
-loff_t dcache_dir_lseek(struct file *file, loff_t offset, int origin)
+loff_t dcache_dir_lseek(struct file * file, loff_t offset, int origin)
 {
+	struct dentry * dentry = file->f_dentry;
+	int found = 0;
+
+	/* for directories it doesn't make any sense to seek relative */
+	if (origin != 0)
+		return -EINVAL;
+
 	down(&file->f_dentry->d_inode->i_sem);
-	switch (origin) {
-		case 1:
-			offset += file->f_pos;
-		case 0:
-			if (offset >= 0)
-				break;
-		default:
-			up(&file->f_dentry->d_inode->i_sem);
-			return -EINVAL;
-	}
 	if (offset != file->f_pos) {
-		file->f_pos = offset;
-		if (file->f_pos >= 2) {
-			struct list_head *p;
-			struct dentry *cursor = file->private_data;
-			loff_t n = file->f_pos - 2;
+		struct list_head *p;
+		struct dentry *cursor = file->private_data;
 
+		// Handle the special values
+		if ((offset == 0)
+		    || (offset == full_name_hash(".", 1))
+		    || (offset == full_name_hash("..", 2))) {
+			found = 1;
+			file->f_pos = offset;
 			spin_lock(&dcache_lock);
 			list_del(&cursor->d_child);
-			p = file->f_dentry->d_subdirs.next;
-			while (n && p != &file->f_dentry->d_subdirs) {
-				struct dentry *next;
-				next = list_entry(p, struct dentry, d_child);
-				if (!d_unhashed(next) && next->d_inode)
-					n--;
-				p = p->next;
+			list_add(&cursor->d_child, &dentry->d_subdirs);
+			spin_unlock(&dcache_lock);
+			goto out;
+		}
+
+		spin_lock(&dcache_lock);
+		list_del(&cursor->d_child);
+		p = file->f_dentry->d_subdirs.next;
+		while (p != &file->f_dentry->d_subdirs) {
+			struct dentry *next;
+			next = list_entry(p, struct dentry, d_child);
+			if (!d_unhashed(next) && next->d_inode
+			    && (offset == full_name_hash(next->d_name.name,
+							 next->d_name.len))) {
+				found = 1;
+				break;
 			}
+			p = p->next;
+		}
+
+		if (found) {
 			list_add_tail(&cursor->d_child, p);
-			spin_unlock(&dcache_lock);
+			file->f_pos = offset;
 		}
+		spin_unlock(&dcache_lock);
 	}
+out:
 	up(&file->f_dentry->d_inode->i_sem);
-	return offset;
+	return (found ? offset : -EINVAL);
 }
 
 /* Relationship between i_mode and the DT_xxx types */
@@ -128,47 +143,44 @@ int dcache_readdir(struct file * filp, v
 	struct dentry *cursor = filp->private_data;
 	struct list_head *p, *q = &cursor->d_child;
 	ino_t ino;
-	int i = filp->f_pos;
 
-	switch (i) {
-		case 0:
-			ino = dentry->d_inode->i_ino;
-			if (filldir(dirent, ".", 1, i, ino, DT_DIR) < 0)
-				break;
-			filp->f_pos++;
-			i++;
-			/* fallthrough */
-		case 1:
-			ino = parent_ino(dentry);
-			if (filldir(dirent, "..", 2, i, ino, DT_DIR) < 0)
-				break;
-			filp->f_pos++;
-			i++;
-			/* fallthrough */
-		default:
-			spin_lock(&dcache_lock);
-			if (filp->f_pos == 2) {
-				list_del(q);
-				list_add(q, &dentry->d_subdirs);
-			}
-			for (p=q->next; p != &dentry->d_subdirs; p=p->next) {
-				struct dentry *next;
-				next = list_entry(p, struct dentry, d_child);
-				if (d_unhashed(next) || !next->d_inode)
-					continue;
-
-				spin_unlock(&dcache_lock);
-				if (filldir(dirent, next->d_name.name, next->d_name.len, filp->f_pos, next->d_inode->i_ino, dt_type(next->d_inode)) < 0)
-					return 0;
-				spin_lock(&dcache_lock);
-				/* next is still alive */
-				list_del(q);
-				list_add(q, p);
-				p = q;
-				filp->f_pos++;
-			}
-			spin_unlock(&dcache_lock);
+//	if (IS_ERR(cursor->d_fsdata))
+//		return PTR_ERR(cursor->d_fsdata);
+
+	if (filp->f_pos == 0) {
+		ino = dentry->d_inode->i_ino;
+		if (filldir(dirent, ".", 1, filp->f_pos, ino, DT_DIR) < 0)
+			goto out;
+		filp->f_pos = full_name_hash(".", 1);
 	}
+	if (filp->f_pos == full_name_hash(".", 1)) {
+		ino = parent_ino(dentry);
+		if (filldir(dirent, "..", 2, filp->f_pos, ino, DT_DIR) < 0)
+			goto out;
+		filp->f_pos = full_name_hash("..", 2);
+	}
+	spin_lock(&dcache_lock);
+	for (p=q->next; p != &dentry->d_subdirs; p=p->next) {
+		struct dentry *next;
+		next = list_entry(p, struct dentry, d_child);
+		if (d_unhashed(next) || !next->d_inode)
+			continue;
+		spin_unlock(&dcache_lock);
+		if (filldir(dirent, next->d_name.name,
+			    next->d_name.len, filp->f_pos,
+			    next->d_inode->i_ino,
+			    dt_type(next->d_inode)) < 0)
+			goto out;
+		spin_lock(&dcache_lock);
+		/* next is still alive */
+		list_del(q);
+		list_add(q, p);
+		p = q;
+		filp->f_pos = full_name_hash(next->d_name.name,
+					     next->d_name.len);
+	}
+	spin_unlock(&dcache_lock);
+out:
 	return 0;
 }
 

             reply	other threads:[~2005-11-04 11:38 UTC|newest]

Thread overview: 23+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2005-11-04 11:38 jblunck [this message]
2005-11-04 11:51 ` [RFC,PATCH] libfs dcache_readdir() and dcache_dir_lseek() bugfix Al Viro
2005-11-04 12:20   ` jblunck
2005-11-04 12:56     ` Miklos Szeredi
2005-11-04 13:18       ` jblunck
2005-11-04 13:31         ` Miklos Szeredi
2005-11-04 15:11           ` jblunck
2005-11-04 15:16             ` Jörn Engel
2005-11-04 15:34               ` jblunck
2005-11-04 15:45                 ` Jörn Engel
2005-11-04 15:38               ` Miklos Szeredi
2005-11-04 15:32             ` Miklos Szeredi
2005-11-04 15:46               ` jblunck
2005-11-04 15:55                 ` Miklos Szeredi
2005-11-04 16:04                   ` jblunck
2005-11-04 16:19                     ` Miklos Szeredi
2005-11-07 10:17                       ` jblunck
2005-11-04 16:27                     ` Al Viro
2005-11-04 16:27             ` Trond Myklebust
2005-11-04 16:39               ` Miklos Szeredi
2005-11-04 16:55   ` Rob Landley
2005-11-07 10:06     ` jblunck
2005-11-04 12:52 ` Jörn Engel

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20051104113851.GA4770@hasse.suse.de \
    --to=jblunck@suse.de \
    --cc=linux-kernel@vger.kernel.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.