Use bdget and sb_set_blocksize so loop can see our preferred blocksize
PAGE_CACHE_SIZE.  Use copy_highpage, removed from highmem.h in 2.4.17:
restore it but with kmap_atomics.  Restore (a simple) copy_page to
asm-sparc64/page.h, which alone of all arches discarded it.
--- tmpfs7/Documentation/filesystems/tmpfs.txt	Wed Mar 20 13:04:25 2002
+++ tmpfs8/Documentation/filesystems/tmpfs.txt	Thu Oct 17 22:01:59 2002
@@ -47,10 +47,9 @@
    shared memory)
 
 3) Some people (including me) find it very convenient to mount it
-   e.g. on /tmp and /var/tmp and have a big swap partition. But be
-   aware: loop mounts of tmpfs files do not work due to the internal
-   design. So mkinitrd shipped by most distributions will fail with a
-   tmpfs /tmp.
+   e.g. on /tmp and /var/tmp and have a big swap partition. And now
+   loop mounts of tmpfs files do work, so mkinitrd shipped by most
+   distributions should succeed with a tmpfs /tmp.
 
 4) And probably a lot more I do not know about :-)
 
@@ -90,13 +89,9 @@
    size=50% the tmpfs instance should be able to grow to 50 percent of
    RAM + swap. So the instance should adapt automatically if you add
    or remove swap space.
-2) loop mounts: This is difficult since loop.c relies on the readpage
-   operation. This operation gets a page from the caller to be filled
-   with the content of the file at that position. But tmpfs always has
-   the page and thus cannot copy the content to the given page. So it
-   cannot provide this operation. The VM had to be changed seriously
-   to achieve this.
-3) Show the number of tmpfs RAM pages. (As shared?)
+2) Show the number of tmpfs RAM pages. (As shared?)
 
 Author:
    Christoph Rohland <cr@sap.com>, 1.12.01
+Updated:
+   Hugh Dickins <hugh@veritas.com>, 17 Oct 2002
--- tmpfs7/include/asm-sparc64/page.h	Mon Oct  7 20:37:47 2002
+++ tmpfs8/include/asm-sparc64/page.h	Thu Oct 17 22:01:59 2002
@@ -39,6 +39,7 @@
 #define clear_page(X)	_clear_page((void *)(X))
 struct page;
 extern void clear_user_page(void *addr, unsigned long vaddr, struct page *page);
+#define copy_page(X,Y)	__memcpy((void *)(X), (void *)(Y), PAGE_SIZE)
 extern void copy_user_page(void *to, void *from, unsigned long vaddr, struct page *topage);
 
 /* GROSS, defining this makes gcc pass these types as aggregates,
--- tmpfs7/include/linux/highmem.h	Mon Sep 16 05:51:51 2002
+++ tmpfs8/include/linux/highmem.h	Thu Oct 17 22:01:59 2002
@@ -73,4 +73,15 @@
 	kunmap_atomic(vto, KM_USER1);
 }
 
+static inline void copy_highpage(struct page *to, struct page *from)
+{
+	char *vfrom, *vto;
+
+	vfrom = kmap_atomic(from, KM_USER0);
+	vto = kmap_atomic(to, KM_USER1);
+	copy_page(vto, vfrom);
+	kunmap_atomic(vfrom, KM_USER0);
+	kunmap_atomic(vto, KM_USER1);
+}
+
 #endif /* _LINUX_HIGHMEM_H */
--- tmpfs7/mm/shmem.c	Thu Oct 17 22:01:49 2002
+++ tmpfs8/mm/shmem.c	Thu Oct 17 22:01:59 2002
@@ -735,39 +735,48 @@
 	struct address_space *mapping = inode->i_mapping;
 	struct shmem_inode_info *info = SHMEM_I(inode);
 	struct shmem_sb_info *sbinfo;
-	struct page *page;
+	struct page *filepage = *pagep;
+	struct page *swappage;
 	swp_entry_t *entry;
 	swp_entry_t swap;
-	int error = 0;
+	int error;
 
 	if (idx >= SHMEM_MAX_INDEX)
 		return -EFBIG;
+	/*
+	 * Normally, filepage is NULL on entry, and either found
+	 * uptodate immediately, or allocated and zeroed, or read
+	 * in under swappage, which is then assigned to filepage.
+	 * But shmem_readpage and shmem_prepare_write pass in a locked
+	 * filepage, which may be found not uptodate by other callers
+	 * too, and may need to be copied from the swappage read in.
+	 */
 repeat:
-	page = find_lock_page(mapping, idx);
-	if (page) {
-		unlock_page(page);
-		*pagep = page;
-		return 0;
-	}
+	if (!filepage)
+		filepage = find_lock_page(mapping, idx);
+	if (filepage && PageUptodate(filepage))
+		goto done;
+	error = 0;
 
 	spin_lock(&info->lock);
 	shmem_recalc_inode(inode);
 	entry = shmem_swp_alloc(info, idx, sgp);
 	if (IS_ERR(entry)) {
 		spin_unlock(&info->lock);
-		return PTR_ERR(entry);
+		error = PTR_ERR(entry);
+		goto failed;
 	}
 	swap = *entry;
 
 	if (swap.val) {
 		/* Look it up and read it in.. */
-		page = lookup_swap_cache(swap);
-		if (!page) {
+		swappage = lookup_swap_cache(swap);
+		if (!swappage) {
 			shmem_swp_unmap(entry);
 			spin_unlock(&info->lock);
 			swapin_readahead(swap);
-			page = read_swap_cache_async(swap);
-			if (!page) {
+			swappage = read_swap_cache_async(swap);
+			if (!swappage) {
 				spin_lock(&info->lock);
 				entry = shmem_swp_alloc(info, idx, sgp);
 				if (IS_ERR(entry))
@@ -779,110 +788,152 @@
 				}
 				spin_unlock(&info->lock);
 				if (error)
-					return error;
+					goto failed;
 				goto repeat;
 			}
-			wait_on_page_locked(page);
-			page_cache_release(page);
+			wait_on_page_locked(swappage);
+			page_cache_release(swappage);
 			goto repeat;
 		}
 
 		/* We have to do this with page locked to prevent races */
-		if (TestSetPageLocked(page)) {
+		if (TestSetPageLocked(swappage)) {
 			shmem_swp_unmap(entry);
 			spin_unlock(&info->lock);
-			wait_on_page_locked(page);
-			page_cache_release(page);
+			wait_on_page_locked(swappage);
+			page_cache_release(swappage);
 			goto repeat;
 		}
-		if (PageWriteback(page)) {
+		if (PageWriteback(swappage)) {
 			shmem_swp_unmap(entry);
 			spin_unlock(&info->lock);
-			wait_on_page_writeback(page);
-			unlock_page(page);
-			page_cache_release(page);
+			wait_on_page_writeback(swappage);
+			unlock_page(swappage);
+			page_cache_release(swappage);
 			goto repeat;
 		}
-
-		error = PageUptodate(page)?
-			move_from_swap_cache(page, idx, mapping): -EIO;
-		if (error) {
+		if (!PageUptodate(swappage)) {
 			shmem_swp_unmap(entry);
 			spin_unlock(&info->lock);
-			unlock_page(page);
-			page_cache_release(page);
-			return error;
+			unlock_page(swappage);
+			page_cache_release(swappage);
+			error = -EIO;
+			goto failed;
 		}
 
-		shmem_swp_set(info, entry, 0);
-		shmem_swp_unmap(entry);
-		spin_unlock(&info->lock);
-		swap_free(swap);
-	} else if (sgp == SGP_READ) {
+		if (filepage) {
+			shmem_swp_set(info, entry, 0);
+			shmem_swp_unmap(entry);
+			delete_from_swap_cache(swappage);
+			spin_unlock(&info->lock);
+			flush_page_to_ram(swappage);
+			copy_highpage(filepage, swappage);
+			unlock_page(swappage);
+			page_cache_release(swappage);
+			flush_dcache_page(filepage);
+			SetPageUptodate(filepage);
+			set_page_dirty(filepage);
+			swap_free(swap);
+		} else if (!(error = move_from_swap_cache(
+				swappage, idx, mapping))) {
+			shmem_swp_set(info, entry, 0);
+			shmem_swp_unmap(entry);
+			spin_unlock(&info->lock);
+			filepage = swappage;
+			swap_free(swap);
+		} else {
+			shmem_swp_unmap(entry);
+			spin_unlock(&info->lock);
+			unlock_page(swappage);
+			page_cache_release(swappage);
+			if (error != -EEXIST)
+				goto failed;
+			goto repeat;
+		}
+	} else if (sgp == SGP_READ && !filepage) {
 		shmem_swp_unmap(entry);
-		page = find_get_page(mapping, idx);
-		if (page && TestSetPageLocked(page)) {
+		filepage = find_get_page(mapping, idx);
+		if (filepage &&
+		    (!PageUptodate(filepage) || TestSetPageLocked(filepage))) {
 			spin_unlock(&info->lock);
-			wait_on_page_locked(page);
-			page_cache_release(page);
+			wait_on_page_locked(filepage);
+			page_cache_release(filepage);
+			filepage = NULL;
 			goto repeat;
 		}
 		spin_unlock(&info->lock);
 	} else {
 		shmem_swp_unmap(entry);
-		spin_unlock(&info->lock);
 		sbinfo = SHMEM_SB(inode->i_sb);
 		spin_lock(&sbinfo->stat_lock);
 		if (sbinfo->free_blocks == 0) {
 			spin_unlock(&sbinfo->stat_lock);
-			return -ENOSPC;
+			spin_unlock(&info->lock);
+			error = -ENOSPC;
+			goto failed;
 		}
 		sbinfo->free_blocks--;
 		inode->i_blocks += BLOCKS_PER_PAGE;
 		spin_unlock(&sbinfo->stat_lock);
 
-		page = page_cache_alloc(mapping);
-		if (!page) {
-			shmem_free_block(inode);
-			return -ENOMEM;
-		}
-
-		spin_lock(&info->lock);
-		entry = shmem_swp_alloc(info, idx, sgp);
-		if (IS_ERR(entry))
-			error = PTR_ERR(entry);
-		else {
-			swap = *entry;
-			shmem_swp_unmap(entry);
-		}
-		if (error || swap.val ||
-		    add_to_page_cache_lru(page, mapping, idx) < 0) {
+		if (!filepage) {
 			spin_unlock(&info->lock);
-			page_cache_release(page);
-			shmem_free_block(inode);
-			if (error)
-				return error;
-			goto repeat;
+			filepage = page_cache_alloc(mapping);
+			if (!filepage) {
+				shmem_free_block(inode);
+				error = -ENOMEM;
+				goto failed;
+			}
+
+			spin_lock(&info->lock);
+			entry = shmem_swp_alloc(info, idx, sgp);
+			if (IS_ERR(entry))
+				error = PTR_ERR(entry);
+			else {
+				swap = *entry;
+				shmem_swp_unmap(entry);
+			}
+			if (error || swap.val ||
+			    (error = add_to_page_cache_lru(
+					filepage, mapping, idx))) {
+				spin_unlock(&info->lock);
+				page_cache_release(filepage);
+				shmem_free_block(inode);
+				filepage = NULL;
+				if (error != -EEXIST)
+					goto failed;
+				goto repeat;
+			}
 		}
+
 		info->alloced++;
 		spin_unlock(&info->lock);
-		clear_highpage(page);
-		flush_dcache_page(page);
-		SetPageUptodate(page);
+		clear_highpage(filepage);
+		flush_dcache_page(filepage);
+		SetPageUptodate(filepage);
+	}
+done:
+	if (!*pagep) {
+		if (filepage) {
+			unlock_page(filepage);
+			*pagep = filepage;
+		} else
+			*pagep = ZERO_PAGE(0);
 	}
-
-	if (page) {
-		unlock_page(page);
-		*pagep = page;
-	} else
-		*pagep = ZERO_PAGE(0);
 	return 0;
+
+failed:
+	if (*pagep != filepage) {
+		unlock_page(filepage);
+		page_cache_release(filepage);
+	}
+	return error;
 }
 
 struct page *shmem_nopage(struct vm_area_struct *vma, unsigned long address, int unused)
 {
 	struct inode *inode = vma->vm_file->f_dentry->d_inode;
-	struct page *page;
+	struct page *page = NULL;
 	unsigned long idx;
 	int error;
 
@@ -1007,13 +1058,33 @@
 static struct inode_operations shmem_symlink_inode_operations;
 static struct inode_operations shmem_symlink_inline_operations;
 
+/*
+ * tmpfs itself makes no use of generic_file_read, generic_file_mmap
+ * or generic_file_write; but shmem_readpage, shmem_prepare_write and
+ * simple_commit_write let a tmpfs file be used below the loop driver.
+ */
+static int
+shmem_readpage(struct file *file, struct page *page)
+{
+	struct inode *inode = page->mapping->host;
+	int error = shmem_getpage(inode, page->index, &page, SGP_CACHE);
+	unlock_page(page);
+	return error;
+}
+
+static int
+shmem_prepare_write(struct file *file, struct page *page, unsigned offset, unsigned to)
+{
+	struct inode *inode = page->mapping->host;
+	return shmem_getpage(inode, page->index, &page, SGP_WRITE);
+}
+
 static ssize_t
 shmem_file_write(struct file *file, const char *buf, size_t count, loff_t *ppos)
 {
 	struct inode	*inode = file->f_dentry->d_inode;
 	unsigned long	limit = current->rlim[RLIMIT_FSIZE].rlim_cur;
 	loff_t		pos;
-	struct page	*page;
 	unsigned long	written;
 	long		status;
 	int		err;
@@ -1110,6 +1181,7 @@
 	}
 
 	while (count) {
+		struct page *page = NULL;
 		unsigned long bytes, index, offset;
 		char *kaddr;
 		int left;
@@ -1188,7 +1260,7 @@
 	offset = *ppos & ~PAGE_CACHE_MASK;
 
 	for (;;) {
-		struct page *page;
+		struct page *page = NULL;
 		unsigned long end_index, nr, ret;
 
 		end_index = inode->i_size >> PAGE_CACHE_SHIFT;
@@ -1416,7 +1488,7 @@
 	int error;
 	int len;
 	struct inode *inode;
-	struct page *page;
+	struct page *page = NULL;
 	char *kaddr;
 	struct shmem_inode_info *info;
 
@@ -1474,7 +1546,7 @@
 
 static int shmem_readlink(struct dentry *dentry, char *buffer, int buflen)
 {
-	struct page *page;
+	struct page *page = NULL;
 	int res = shmem_getpage(dentry->d_inode, 0, &page, SGP_READ);
 	if (res)
 		return res;
@@ -1486,7 +1558,7 @@
 
 static int shmem_follow_link(struct dentry *dentry, struct nameidata *nd)
 {
-	struct page *page;
+	struct page *page = NULL;
 	int res = shmem_getpage(dentry->d_inode, 0, &page, SGP_READ);
 	if (res)
 		return res;
@@ -1592,7 +1664,7 @@
 	gid_t gid = current->fsgid;
 	struct shmem_sb_info *sbinfo;
 	struct sysinfo si;
-	int err;
+	int err = -ENOMEM;
 
 	sbinfo = kmalloc(sizeof(struct shmem_sb_info), GFP_KERNEL);
 	if (!sbinfo)
@@ -1622,28 +1694,29 @@
 	sbinfo->max_inodes = inodes;
 	sbinfo->free_inodes = inodes;
 	sb->s_maxbytes = SHMEM_MAX_BYTES;
-	sb->s_blocksize = PAGE_CACHE_SIZE;
-	sb->s_blocksize_bits = PAGE_CACHE_SHIFT;
+	sb->s_bdev = bdget(sb->s_dev);
+	if (!sb->s_bdev)
+		goto failed;
+	if (!sb_set_blocksize(sb, PAGE_CACHE_SIZE))
+		BUG();
 	sb->s_magic = TMPFS_MAGIC;
 	sb->s_op = &shmem_ops;
 	inode = shmem_get_inode(sb, S_IFDIR | mode, 0);
-	if (!inode) {
-		err = -ENOMEM;
-		goto failed;
-	}
-
+	if (!inode)
+		goto failed_bdput;
 	inode->i_uid = uid;
 	inode->i_gid = gid;
 	root = d_alloc_root(inode);
-	if (!root) {
-		err = -ENOMEM;
+	if (!root)
 		goto failed_iput;
-	}
 	sb->s_root = root;
 	return 0;
 
 failed_iput:
 	iput(inode);
+failed_bdput:
+	bdput(sb->s_bdev);
+	sb->s_bdev = NULL;
 failed:
 	kfree(sbinfo);
 	sb->s_fs_info = NULL;
@@ -1652,6 +1725,8 @@
 
 static void shmem_put_super(struct super_block *sb)
 {
+	bdput(sb->s_bdev);
+	sb->s_bdev = NULL;
 	kfree(sb->s_fs_info);
 	sb->s_fs_info = NULL;
 }
@@ -1704,6 +1779,11 @@
 	.writepages	= shmem_writepages,
 	.vm_writeback	= shmem_vm_writeback,
 	.set_page_dirty	= __set_page_dirty_nobuffers,
+#ifdef CONFIG_TMPFS
+	.readpage	= shmem_readpage,
+	.prepare_write	= shmem_prepare_write,
+	.commit_write	= simple_commit_write,
+#endif
 };
 
 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/