Gitweb:     
http://git.kernel.org/git/?p=linux/kernel/git/torvalds/linux-2.6.git;a=commit;h=a2646d1e6c8d2239d8054a7d342eb9775a1d273a
Commit:     a2646d1e6c8d2239d8054a7d342eb9775a1d273a
Parent:     96fac9dc95b91fc198bfbf4ba90263b06eff023d
Author:     Hugh Dickins <[EMAIL PROTECTED]>
AuthorDate: Thu Mar 29 01:20:35 2007 -0700
Committer:  Linus Torvalds <[EMAIL PROTECTED]>
CommitDate: Thu Mar 29 08:22:25 2007 -0700

    [PATCH] holepunch: fix shmem_truncate_range punching too far
    
    Miklos Szeredi observes BUG_ON(!entry) in shmem_writepage() triggered in 
rare
    circumstances, because shmem_truncate_range() erroneously removes partially
    truncated directory pages at the end of the range: later reclaim on pages
    pointing to these removed directories triggers the BUG.  Indeed, and it can
    also cause data loss beyond the hole.
    
    Fix this as in the patch proposed by Miklos, but distinguish between "limit"
    (how far we need to search: ignore truncation's next_index optimization in 
the
    holepunch case - if there are races it's more consistent to act on the whole
    range specified) and "upper_limit" (how far we can free directory pages:
    generally we must be careful to keep partially punched pages, but can relax 
at
    end of file - i_size being held stable by i_mutex).
    
    Signed-off-by: Hugh Dickins <[EMAIL PROTECTED]>
    Cc: Miklos Szeredi <[EMAIL PROTECTED]>
    Cc: Badari Pulavarty <[EMAIL PROTECTED]>
    Signed-off-by: Andrew Morton <[EMAIL PROTECTED]>
    Signed-off-by: Linus Torvalds <[EMAIL PROTECTED]>
---
 mm/shmem.c |   32 +++++++++++++++++++++-----------
 1 files changed, 21 insertions(+), 11 deletions(-)

diff --git a/mm/shmem.c b/mm/shmem.c
index b8c429a..1077b1d 100644
--- a/mm/shmem.c
+++ b/mm/shmem.c
@@ -481,7 +481,8 @@ static void shmem_truncate_range(struct inode *inode, 
loff_t start, loff_t end)
        long nr_swaps_freed = 0;
        int offset;
        int freed;
-       int punch_hole = 0;
+       int punch_hole;
+       unsigned long upper_limit;
 
        inode->i_ctime = inode->i_mtime = CURRENT_TIME;
        idx = (start + PAGE_CACHE_SIZE - 1) >> PAGE_CACHE_SHIFT;
@@ -492,11 +493,18 @@ static void shmem_truncate_range(struct inode *inode, 
loff_t start, loff_t end)
        info->flags |= SHMEM_TRUNCATE;
        if (likely(end == (loff_t) -1)) {
                limit = info->next_index;
+               upper_limit = SHMEM_MAX_INDEX;
                info->next_index = idx;
+               punch_hole = 0;
        } else {
-               limit = (end + PAGE_CACHE_SIZE - 1) >> PAGE_CACHE_SHIFT;
-               if (limit > info->next_index)
-                       limit = info->next_index;
+               if (end + 1 >= inode->i_size) { /* we may free a little more */
+                       limit = (inode->i_size + PAGE_CACHE_SIZE - 1) >>
+                                                       PAGE_CACHE_SHIFT;
+                       upper_limit = SHMEM_MAX_INDEX;
+               } else {
+                       limit = (end + 1) >> PAGE_CACHE_SHIFT;
+                       upper_limit = limit;
+               }
                punch_hole = 1;
        }
 
@@ -520,10 +528,10 @@ static void shmem_truncate_range(struct inode *inode, 
loff_t start, loff_t end)
         * If there are no indirect blocks or we are punching a hole
         * below indirect blocks, nothing to be done.
         */
-       if (!topdir || (punch_hole && (limit <= SHMEM_NR_DIRECT)))
+       if (!topdir || limit <= SHMEM_NR_DIRECT)
                goto done2;
 
-       BUG_ON(limit <= SHMEM_NR_DIRECT);
+       upper_limit -= SHMEM_NR_DIRECT;
        limit -= SHMEM_NR_DIRECT;
        idx = (idx > SHMEM_NR_DIRECT)? (idx - SHMEM_NR_DIRECT): 0;
        offset = idx % ENTRIES_PER_PAGE;
@@ -543,7 +551,7 @@ static void shmem_truncate_range(struct inode *inode, 
loff_t start, loff_t end)
                if (*dir) {
                        diroff = ((idx - ENTRIES_PER_PAGEPAGE/2) %
                                ENTRIES_PER_PAGEPAGE) / ENTRIES_PER_PAGE;
-                       if (!diroff && !offset) {
+                       if (!diroff && !offset && upper_limit >= stage) {
                                *dir = NULL;
                                nr_pages_to_free++;
                                list_add(&middir->lru, &pages_to_free);
@@ -570,9 +578,11 @@ static void shmem_truncate_range(struct inode *inode, 
loff_t start, loff_t end)
                        }
                        stage = idx + ENTRIES_PER_PAGEPAGE;
                        middir = *dir;
-                       *dir = NULL;
-                       nr_pages_to_free++;
-                       list_add(&middir->lru, &pages_to_free);
+                       if (upper_limit >= stage) {
+                               *dir = NULL;
+                               nr_pages_to_free++;
+                               list_add(&middir->lru, &pages_to_free);
+                       }
                        shmem_dir_unmap(dir);
                        cond_resched();
                        dir = shmem_dir_map(middir);
@@ -598,7 +608,7 @@ static void shmem_truncate_range(struct inode *inode, 
loff_t start, loff_t end)
                }
                if (offset)
                        offset = 0;
-               else if (subdir && !page_private(subdir)) {
+               else if (subdir && upper_limit - idx >= ENTRIES_PER_PAGE) {
                        dir[diroff] = NULL;
                        nr_pages_to_free++;
                        list_add(&subdir->lru, &pages_to_free);
-
To unsubscribe from this list: send the line "unsubscribe git-commits-head" in
the body of a message to [EMAIL PROTECTED]
More majordomo info at  http://vger.kernel.org/majordomo-info.html

Reply via email to