Merge branch 'master' of master.kernel.org:/pub/scm/linux/kernel/git/davem/net-2.6
[safe/jmp/linux-2.6] / fs / cachefiles / rdwr.c
index a69787e..a6c8c6f 100644 (file)
@@ -11,6 +11,7 @@
 
 #include <linux/mount.h>
 #include <linux/file.h>
+#include <linux/ima.h>
 #include "internal.h"
 
 /*
@@ -40,8 +41,10 @@ static int cachefiles_read_waiter(wait_queue_t *wait, unsigned mode,
 
        _debug("--- monitor %p %lx ---", page, page->flags);
 
-       if (!PageUptodate(page) && !PageError(page))
-               dump_stack();
+       if (!PageUptodate(page) && !PageError(page)) {
+               /* unlocked, not uptodate and not erronous? */
+               _debug("page probably truncated");
+       }
 
        /* remove from the waitqueue */
        list_del(&wait->task_list);
@@ -61,6 +64,84 @@ static int cachefiles_read_waiter(wait_queue_t *wait, unsigned mode,
 }
 
 /*
+ * handle a probably truncated page
+ * - check to see if the page is still relevant and reissue the read if
+ *   possible
+ * - return -EIO on error, -ENODATA if the page is gone, -EINPROGRESS if we
+ *   must wait again and 0 if successful
+ */
+static int cachefiles_read_reissue(struct cachefiles_object *object,
+                                  struct cachefiles_one_read *monitor)
+{
+       struct address_space *bmapping = object->backer->d_inode->i_mapping;
+       struct page *backpage = monitor->back_page, *backpage2;
+       int ret;
+
+       kenter("{ino=%lx},{%lx,%lx}",
+              object->backer->d_inode->i_ino,
+              backpage->index, backpage->flags);
+
+       /* skip if the page was truncated away completely */
+       if (backpage->mapping != bmapping) {
+               kleave(" = -ENODATA [mapping]");
+               return -ENODATA;
+       }
+
+       backpage2 = find_get_page(bmapping, backpage->index);
+       if (!backpage2) {
+               kleave(" = -ENODATA [gone]");
+               return -ENODATA;
+       }
+
+       if (backpage != backpage2) {
+               put_page(backpage2);
+               kleave(" = -ENODATA [different]");
+               return -ENODATA;
+       }
+
+       /* the page is still there and we already have a ref on it, so we don't
+        * need a second */
+       put_page(backpage2);
+
+       INIT_LIST_HEAD(&monitor->op_link);
+       add_page_wait_queue(backpage, &monitor->monitor);
+
+       if (trylock_page(backpage)) {
+               ret = -EIO;
+               if (PageError(backpage))
+                       goto unlock_discard;
+               ret = 0;
+               if (PageUptodate(backpage))
+                       goto unlock_discard;
+
+               kdebug("reissue read");
+               ret = bmapping->a_ops->readpage(NULL, backpage);
+               if (ret < 0)
+                       goto unlock_discard;
+       }
+
+       /* but the page may have been read before the monitor was installed, so
+        * the monitor may miss the event - so we have to ensure that we do get
+        * one in such a case */
+       if (trylock_page(backpage)) {
+               _debug("jumpstart %p {%lx}", backpage, backpage->flags);
+               unlock_page(backpage);
+       }
+
+       /* it'll reappear on the todo list */
+       kleave(" = -EINPROGRESS");
+       return -EINPROGRESS;
+
+unlock_discard:
+       unlock_page(backpage);
+       spin_lock_irq(&object->work_lock);
+       list_del(&monitor->op_link);
+       spin_unlock_irq(&object->work_lock);
+       kleave(" = %d", ret);
+       return ret;
+}
+
+/*
  * copy data from backing pages to netfs pages to complete a read operation
  * - driven by FS-Cache's thread pool
  */
@@ -92,20 +173,26 @@ static void cachefiles_read_copier(struct fscache_operation *_op)
 
                _debug("- copy {%lu}", monitor->back_page->index);
 
-               error = -EIO;
+       recheck:
                if (PageUptodate(monitor->back_page)) {
                        copy_highpage(monitor->netfs_page, monitor->back_page);
 
                        pagevec_add(&pagevec, monitor->netfs_page);
                        fscache_mark_pages_cached(monitor->op, &pagevec);
                        error = 0;
-               }
-
-               if (error)
+               } else if (!PageError(monitor->back_page)) {
+                       /* the page has probably been truncated */
+                       error = cachefiles_read_reissue(object, monitor);
+                       if (error == -EINPROGRESS)
+                               goto next;
+                       goto recheck;
+               } else {
                        cachefiles_io_error_obj(
                                object,
                                "Readpage failed on backing file %lx",
                                (unsigned long) monitor->back_page->flags);
+                       error = -EIO;
+               }
 
                page_cache_release(monitor->back_page);
 
@@ -114,6 +201,7 @@ static void cachefiles_read_copier(struct fscache_operation *_op)
                fscache_put_retrieval(op);
                kfree(monitor);
 
+       next:
                /* let the thread pool have some air occasionally */
                max--;
                if (max < 0 || need_resched()) {
@@ -333,7 +421,8 @@ int cachefiles_read_or_alloc_page(struct fscache_retrieval *op,
 
        shift = PAGE_SHIFT - inode->i_sb->s_blocksize_bits;
 
-       op->op.flags = FSCACHE_OP_FAST;
+       op->op.flags &= FSCACHE_OP_KEEP_FLAGS;
+       op->op.flags |= FSCACHE_OP_FAST;
        op->op.processor = cachefiles_read_copier;
 
        pagevec_init(&pagevec, 0);
@@ -639,7 +728,8 @@ int cachefiles_read_or_alloc_pages(struct fscache_retrieval *op,
 
        pagevec_init(&pagevec, 0);
 
-       op->op.flags = FSCACHE_OP_FAST;
+       op->op.flags &= FSCACHE_OP_KEEP_FLAGS;
+       op->op.flags |= FSCACHE_OP_FAST;
        op->op.processor = cachefiles_read_copier;
 
        INIT_LIST_HEAD(&backpages);
@@ -801,7 +891,8 @@ int cachefiles_write_page(struct fscache_storage *op, struct page *page)
        struct cachefiles_cache *cache;
        mm_segment_t old_fs;
        struct file *file;
-       loff_t pos;
+       loff_t pos, eof;
+       size_t len;
        void *data;
        int ret;
 
@@ -832,18 +923,33 @@ int cachefiles_write_page(struct fscache_storage *op, struct page *page)
        if (IS_ERR(file)) {
                ret = PTR_ERR(file);
        } else {
+               ima_counts_get(file);
                ret = -EIO;
                if (file->f_op->write) {
                        pos = (loff_t) page->index << PAGE_SHIFT;
+
+                       /* we mustn't write more data than we have, so we have
+                        * to beware of a partial page at EOF */
+                       eof = object->fscache.store_limit_l;
+                       len = PAGE_SIZE;
+                       if (eof & ~PAGE_MASK) {
+                               ASSERTCMP(pos, <, eof);
+                               if (eof - pos < PAGE_SIZE) {
+                                       _debug("cut short %llx to %llx",
+                                              pos, eof);
+                                       len = eof - pos;
+                                       ASSERTCMP(pos + len, ==, eof);
+                               }
+                       }
+
                        data = kmap(page);
                        old_fs = get_fs();
                        set_fs(KERNEL_DS);
                        ret = file->f_op->write(
-                               file, (const void __user *) data, PAGE_SIZE,
-                               &pos);
+                               file, (const void __user *) data, len, &pos);
                        set_fs(old_fs);
                        kunmap(page);
-                       if (ret != PAGE_SIZE)
+                       if (ret != len)
                                ret = -EIO;
                }
                fput(file);