]> git.kernelconcepts.de Git - karo-tx-linux.git/blobdiff - mm/shmem.c
holepunch: fix shmem_truncate_range punching too far
[karo-tx-linux.git] / mm / shmem.c
index e25bceda7dd0a4d3d54ff07b4a08f1d93c3bd884..f80e050330befe7e91324733501c76ce887ba315 100644 (file)
@@ -476,7 +476,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;
@@ -487,11 +488,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;
        }
 
@@ -515,10 +523,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;
@@ -538,7 +546,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);
@@ -565,9 +573,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);
@@ -593,7 +603,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);