[patch 14/16] tmpfs bugfixes

From: Andrew Morton (akpm@zip.com.au)
Date: Sat Jun 01 2002 - 03:44:03 EST


A patch from Hugh Dickins which fixes a couple of error-path leaks
related to tmpfs (I think).

Also fixes a yield()-inside-spinlock bug.

It also includes code to clear the final page outside i_size on
truncate. tmpfs should be returning zeroes when a truncated file is
later expanded and it currently is not.

Hugh is taking care of the 2.4 fix for this.

=====================================

--- 2.5.19/mm/shmem.c~hugh Sat Jun 1 01:18:13 2002
+++ 2.5.19-akpm/mm/shmem.c Sat Jun 1 01:18:13 2002
@@ -52,6 +52,8 @@ LIST_HEAD (shmem_inodes);
 static spinlock_t shmem_ilock = SPIN_LOCK_UNLOCKED;
 atomic_t shmem_nrpages = ATOMIC_INIT(0); /* Not used right now */
 
+static struct page *shmem_getpage_locked(struct shmem_inode_info *, struct inode *, unsigned long);
+
 #define BLOCKS_PER_PAGE (PAGE_CACHE_SIZE/512)
 
 /*
@@ -317,6 +319,7 @@ shmem_truncate_indirect(struct shmem_ino
 static void shmem_truncate (struct inode * inode)
 {
         unsigned long index;
+ unsigned long partial;
         unsigned long freed = 0;
         struct shmem_inode_info * info = SHMEM_I(inode);
 
@@ -324,6 +327,28 @@ static void shmem_truncate (struct inode
         inode->i_ctime = inode->i_mtime = CURRENT_TIME;
         spin_lock (&info->lock);
         index = (inode->i_size + PAGE_CACHE_SIZE - 1) >> PAGE_CACHE_SHIFT;
+ partial = inode->i_size & ~PAGE_CACHE_MASK;
+
+ if (partial) {
+ swp_entry_t *entry = shmem_swp_entry(info, index-1, 0);
+ struct page *page;
+ /*
+ * This check is racy: it's faintly possible that page
+ * was assigned to swap during truncate_inode_pages,
+ * and now assigned to file; but better than nothing.
+ */
+ if (!IS_ERR(entry) && entry->val) {
+ spin_unlock(&info->lock);
+ page = shmem_getpage_locked(info, inode, index-1);
+ if (!IS_ERR(page)) {
+ memclear_highpage_flush(page, partial,
+ PAGE_CACHE_SIZE - partial);
+ unlock_page(page);
+ page_cache_release(page);
+ }
+ spin_lock(&info->lock);
+ }
+ }
 
         while (index < info->next_index)
                 freed += shmem_truncate_indirect(info, index);
@@ -369,10 +394,10 @@ static int shmem_unuse_inode (struct shm
         swp_entry_t *ptr;
         unsigned long idx;
         int offset;
+ struct inode *inode = NULL;
 
- spin_lock (&info->lock);
-repeat:
         idx = 0;
+ spin_lock (&info->lock);
         offset = shmem_clear_swp (entry, info->i_direct, SHMEM_NR_DIRECT);
         if (offset >= 0)
                 goto found;
@@ -389,20 +414,36 @@ repeat:
         spin_unlock (&info->lock);
         return 0;
 found:
- if (!move_from_swap_cache (page, offset+idx, info->vfs_inode.i_mapping)) {
- info->swapped--;
- SetPageUptodate (page);
- spin_unlock (&info->lock);
- return 1;
+ idx += offset;
+ inode = igrab(&info->vfs_inode);
+ spin_unlock(&shmem_ilock);
+
+ while (inode && move_from_swap_cache(page, idx, inode->i_mapping)) {
+ /*
+ * Yield for kswapd, and try again - but we're still
+ * holding the page lock - ugh! fix this up later on.
+ * Beware of inode being unlinked or truncated: just
+ * leave try_to_unuse to delete_from_swap_cache if so.
+ */
+ spin_unlock(&info->lock);
+ yield();
+ spin_lock(&info->lock);
+ ptr = shmem_swp_entry(info, idx, 0);
+ if (IS_ERR(ptr))
+ break;
         }
 
- /* Yield for kswapd, and try again */
- yield();
- goto repeat;
+ info->swapped--;
+ SetPageUptodate(page);
+ spin_unlock(&info->lock);
+ if (inode)
+ iput(inode);
+ return 1;
 }
 
 /*
- * unuse_shmem() search for an eventually swapped out shmem page.
+ * shmem_unuse() search for an eventually swapped out shmem page.
+ * Note shmem_unuse_inode drops shmem_ilock itself if successful.
  */
 void shmem_unuse(swp_entry_t entry, struct page *page)
 {
@@ -414,7 +455,7 @@ void shmem_unuse(swp_entry_t entry, stru
                 info = list_entry(p, struct shmem_inode_info, list);
 
                 if (shmem_unuse_inode(info, entry, page))
- break;
+ return;
         }
         spin_unlock (&shmem_ilock);
 }
@@ -551,6 +592,7 @@ repeat:
                 error = move_from_swap_cache(page, idx, mapping);
                 if (error < 0) {
                         unlock_page(page);
+ page_cache_release(page);
                         return ERR_PTR(error);
                 }
 
@@ -576,11 +618,11 @@ repeat:
                  * is enough to make this atomic. */
                 page = page_cache_alloc(mapping);
                 if (!page)
- return ERR_PTR(-ENOMEM);
+ goto no_mem;
                 error = add_to_page_cache(page, mapping, idx);
                 if (error < 0) {
                         page_cache_release(page);
- return ERR_PTR(-ENOMEM);
+ goto no_mem;
                 }
                 clear_highpage(page);
                 inode->i_blocks += BLOCKS_PER_PAGE;
@@ -589,6 +631,13 @@ repeat:
         /* We have the page */
         SetPageUptodate(page);
         return page;
+
+no_mem:
+ spin_lock(&sbinfo->stat_lock);
+ sbinfo->free_blocks++;
+ spin_unlock(&sbinfo->stat_lock);
+ return ERR_PTR(-ENOMEM);
+
 no_space:
         spin_unlock (&sbinfo->stat_lock);
         return ERR_PTR(-ENOSPC);
@@ -1396,6 +1445,7 @@ static void destroy_inodecache(void)
 
 static struct address_space_operations shmem_aops = {
         writepage: shmem_writepage,
+ set_page_dirty: __set_page_dirty_nobuffers,
 };
 
 static struct file_operations shmem_file_operations = {

-
-
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/



This archive was generated by hypermail 2b29 : Fri Jun 07 2002 - 22:00:10 EST