public inbox for linux-kernel@vger.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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox