From: Andrew Morton <akpm@zip.com.au>
To: lkml <linux-kernel@vger.kernel.org>
Cc: ext2-devel@lists.sourceforge.net
Subject: [patch] ext2 directory handling
Date: Wed, 10 Apr 2002 03:53:47 -0700 [thread overview]
Message-ID: <3CB419BB.D7649D52@zip.com.au> (raw)
ext2's directory parsing relies on the preservation of the
data outside i_size in the last page of the directory mapping.
But with the stuff I'm doing, ext2 directory pages are marked
dirty, and can be written out at any time via writepage.
And block_write_full_page zaps the data outside i_size, which
causes the ext2 directory scan code to go into an infinite
loop when it hits rec_len == 0.
The patch teaches ext2 to not look at anything outside
i_size when the backing page is not locked.
It also adds lots of checks for rec_len == 0, which fixes the
lockup. In a perfect world, those checks are not needed, because
the checking was already performed in ext2_check_page. But
these checks do help to catch problems which have a tendency to
arise with the stuff I'm doing.
I'd really appreciate it if someone could double-check this
patch, please.
Patch against
2.5.8-pre3+ratcache+readahead+pageprivate+pdflush+pdflush_inodes+pdflush_buffers
--- 2.5.8-pre3/fs/ext2/dir.c~dallocbase-55-ext2_dir Wed Apr 10 00:42:47 2002
+++ 2.5.8-pre3-akpm/fs/ext2/dir.c Wed Apr 10 02:44:30 2002
@@ -46,6 +46,21 @@ static inline unsigned long dir_pages(st
return (inode->i_size+PAGE_CACHE_SIZE-1)>>PAGE_CACHE_SHIFT;
}
+/*
+ * Return the offset into page `page_nr' of the last valid
+ * byte in that page, plus one.
+ */
+static unsigned
+ext2_last_byte(struct inode *inode, unsigned long page_nr)
+{
+ unsigned last_byte = inode->i_size;
+
+ last_byte -= page_nr << PAGE_CACHE_SHIFT;
+ if (last_byte > PAGE_CACHE_SIZE)
+ last_byte = PAGE_CACHE_SIZE;
+ return last_byte;
+}
+
static int ext2_commit_chunk(struct page *page, unsigned from, unsigned to)
{
struct inode *dir = page->mapping->host;
@@ -78,10 +93,6 @@ static void ext2_check_page(struct page
limit = dir->i_size & ~PAGE_CACHE_MASK;
if (limit & (chunk_size - 1))
goto Ebadsize;
- for (offs = limit; offs<PAGE_CACHE_SIZE; offs += chunk_size) {
- ext2_dirent *p = (ext2_dirent*)(kaddr + offs);
- p->rec_len = cpu_to_le16(chunk_size);
- }
if (!limit)
goto out;
}
@@ -197,8 +208,11 @@ ext2_validate_entry(char *base, unsigned
{
ext2_dirent *de = (ext2_dirent*)(base + offset);
ext2_dirent *p = (ext2_dirent*)(base + (offset&mask));
- while ((char*)p < (char*)de)
+ while ((char*)p < (char*)de) {
+ if (p->rec_len == 0)
+ break;
p = ext2_next_entry(p);
+ }
return (char *)p - base;
}
@@ -245,6 +259,7 @@ ext2_readdir (struct file * filp, void *
unsigned chunk_mask = ~(ext2_chunk_size(inode)-1);
unsigned char *types = NULL;
int need_revalidate = (filp->f_version != inode->i_version);
+ int ret = 0;
if (pos > inode->i_size - EXT2_DIR_REC_LEN(1))
goto done;
@@ -265,8 +280,15 @@ ext2_readdir (struct file * filp, void *
need_revalidate = 0;
}
de = (ext2_dirent *)(kaddr+offset);
- limit = kaddr + PAGE_CACHE_SIZE - EXT2_DIR_REC_LEN(1);
- for ( ;(char*)de <= limit; de = ext2_next_entry(de))
+ limit = kaddr + ext2_last_byte(inode, n) - EXT2_DIR_REC_LEN(1);
+ for ( ;(char*)de <= limit; de = ext2_next_entry(de)) {
+ if (de->rec_len == 0) {
+ ext2_error(sb, __FUNCTION__,
+ "zero-length directory entry");
+ ret = -EIO;
+ ext2_put_page(page);
+ goto done;
+ }
if (de->inode) {
int over;
unsigned char d_type = DT_UNKNOWN;
@@ -283,6 +305,7 @@ ext2_readdir (struct file * filp, void *
goto done;
}
}
+ }
ext2_put_page(page);
}
@@ -326,10 +349,16 @@ struct ext2_dir_entry_2 * ext2_find_entr
if (!IS_ERR(page)) {
kaddr = page_address(page);
de = (ext2_dirent *) kaddr;
- kaddr += PAGE_CACHE_SIZE - reclen;
+ kaddr += ext2_last_byte(dir, n) - reclen;
while ((char *) de <= kaddr) {
if (ext2_match (namelen, name, de))
goto found;
+ if (de->rec_len == 0) {
+ ext2_error(dir->i_sb, __FUNCTION__,
+ "zero-length directory entry");
+ ext2_put_page(page);
+ goto out;
+ }
de = ext2_next_entry(de);
}
ext2_put_page(page);
@@ -337,6 +366,7 @@ struct ext2_dir_entry_2 * ext2_find_entr
if (++n >= npages)
n = 0;
} while (n != start);
+out:
return NULL;
found:
@@ -401,6 +431,7 @@ int ext2_add_link (struct dentry *dentry
struct inode *dir = dentry->d_parent->d_inode;
const char *name = dentry->d_name.name;
int namelen = dentry->d_name.len;
+ unsigned chunk_size = ext2_chunk_size(dir);
unsigned reclen = EXT2_DIR_REC_LEN(namelen);
unsigned short rec_len, name_len;
struct page *page = NULL;
@@ -411,27 +442,50 @@ int ext2_add_link (struct dentry *dentry
unsigned from, to;
int err;
- /* We take care of directory expansion in the same loop */
+ /*
+ * We take care of directory expansion in the same loop.
+ * This code plays outside i_size, so it locks the page
+ * to protect that region.
+ */
for (n = 0; n <= npages; n++) {
+ char *dir_end;
+
page = ext2_get_page(dir, n);
err = PTR_ERR(page);
if (IS_ERR(page))
goto out;
+ lock_page(page);
kaddr = page_address(page);
+ dir_end = kaddr + ext2_last_byte(dir, n);
de = (ext2_dirent *)kaddr;
kaddr += PAGE_CACHE_SIZE - reclen;
while ((char *)de <= kaddr) {
err = -EEXIST;
if (ext2_match (namelen, name, de))
- goto out_page;
+ goto out_unlock;
+ if ((char *)de == dir_end) {
+ /* We hit i_size */
+ name_len = 0;
+ rec_len = chunk_size;
+ de->rec_len = cpu_to_le16(chunk_size);
+ de->inode = 0;
+ goto got_it;
+ }
name_len = EXT2_DIR_REC_LEN(de->name_len);
rec_len = le16_to_cpu(de->rec_len);
if (!de->inode && rec_len >= reclen)
goto got_it;
if (rec_len >= name_len + reclen)
goto got_it;
+ if (de->rec_len == 0) {
+ ext2_error(dir->i_sb, __FUNCTION__,
+ "zero-length directory entry");
+ err = -EIO;
+ goto out_unlock;
+ }
de = (ext2_dirent *) ((char *) de + rec_len);
}
+ unlock_page(page);
ext2_put_page(page);
}
BUG();
@@ -440,7 +494,6 @@ int ext2_add_link (struct dentry *dentry
got_it:
from = (char*)de - (char*)page_address(page);
to = from + rec_len;
- lock_page(page);
err = page->mapping->a_ops->prepare_write(NULL, page, from, to);
if (err)
goto out_unlock;
@@ -460,7 +513,6 @@ got_it:
/* OFFSET_CACHE */
out_unlock:
UnlockPage(page);
-out_page:
ext2_put_page(page);
out:
return err;
@@ -484,6 +536,12 @@ int ext2_delete_entry (struct ext2_dir_e
while ((char*)de < (char*)dir) {
pde = de;
de = ext2_next_entry(de);
+ if (de->rec_len == 0) {
+ ext2_error(inode->i_sb, __FUNCTION__,
+ "zero-length directory entry");
+ err = -EIO;
+ goto out;
+ }
}
if (pde)
from = (char*)pde - (char*)page_address(page);
@@ -496,9 +554,10 @@ int ext2_delete_entry (struct ext2_dir_e
dir->inode = 0;
err = ext2_commit_chunk(page, from, to);
UnlockPage(page);
- ext2_put_page(page);
inode->i_ctime = inode->i_mtime = CURRENT_TIME;
mark_inode_dirty(inode);
+out:
+ ext2_put_page(page);
return err;
}
@@ -550,7 +609,7 @@ int ext2_empty_dir (struct inode * inode
{
struct page *page = NULL;
unsigned long i, npages = dir_pages(inode);
-
+
for (i = 0; i < npages; i++) {
char *kaddr;
ext2_dirent * de;
@@ -561,7 +620,7 @@ int ext2_empty_dir (struct inode * inode
kaddr = page_address(page);
de = (ext2_dirent *)kaddr;
- kaddr += PAGE_CACHE_SIZE-EXT2_DIR_REC_LEN(1);
+ kaddr += ext2_last_byte(inode, i) - EXT2_DIR_REC_LEN(1);
while ((char *)de <= kaddr) {
if (de->inode != 0) {
@@ -578,6 +637,11 @@ int ext2_empty_dir (struct inode * inode
goto not_empty;
}
de = ext2_next_entry(de);
+ if (de->rec_len == 0) {
+ ext2_error(inode->i_sb, __FUNCTION__,
+ "zero-length directory entry");
+ goto not_empty;
+ }
}
ext2_put_page(page);
}
-
next reply other threads:[~2002-04-10 10:53 UTC|newest]
Thread overview: 2+ messages / expand[flat|nested] mbox.gz Atom feed top
2002-04-10 10:53 Andrew Morton [this message]
2002-04-10 11:27 ` [patch] ext2 directory handling Andrew Morton
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=3CB419BB.D7649D52@zip.com.au \
--to=akpm@zip.com.au \
--cc=ext2-devel@lists.sourceforge.net \
--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