diff options
Diffstat (limited to 'fs/cachefiles/rdwr.c')
| -rw-r--r-- | fs/cachefiles/rdwr.c | 130 | 
1 files changed, 118 insertions, 12 deletions
| diff --git a/fs/cachefiles/rdwr.c b/fs/cachefiles/rdwr.c index a69787e7dd96..a6c8c6fe8df9 100644 --- a/fs/cachefiles/rdwr.c +++ b/fs/cachefiles/rdwr.c | |||
| @@ -11,6 +11,7 @@ | |||
| 11 | 11 | ||
| 12 | #include <linux/mount.h> | 12 | #include <linux/mount.h> | 
| 13 | #include <linux/file.h> | 13 | #include <linux/file.h> | 
| 14 | #include <linux/ima.h> | ||
| 14 | #include "internal.h" | 15 | #include "internal.h" | 
| 15 | 16 | ||
| 16 | /* | 17 | /* | 
| @@ -40,8 +41,10 @@ static int cachefiles_read_waiter(wait_queue_t *wait, unsigned mode, | |||
| 40 | 41 | ||
| 41 | _debug("--- monitor %p %lx ---", page, page->flags); | 42 | _debug("--- monitor %p %lx ---", page, page->flags); | 
| 42 | 43 | ||
| 43 | if (!PageUptodate(page) && !PageError(page)) | 44 | if (!PageUptodate(page) && !PageError(page)) { | 
| 44 | dump_stack(); | 45 | /* unlocked, not uptodate and not erronous? */ | 
| 46 | _debug("page probably truncated"); | ||
| 47 | } | ||
| 45 | 48 | ||
| 46 | /* remove from the waitqueue */ | 49 | /* remove from the waitqueue */ | 
| 47 | list_del(&wait->task_list); | 50 | list_del(&wait->task_list); | 
| @@ -61,6 +64,84 @@ static int cachefiles_read_waiter(wait_queue_t *wait, unsigned mode, | |||
| 61 | } | 64 | } | 
| 62 | 65 | ||
| 63 | /* | 66 | /* | 
| 67 | * handle a probably truncated page | ||
| 68 | * - check to see if the page is still relevant and reissue the read if | ||
| 69 | * possible | ||
| 70 | * - return -EIO on error, -ENODATA if the page is gone, -EINPROGRESS if we | ||
| 71 | * must wait again and 0 if successful | ||
| 72 | */ | ||
| 73 | static int cachefiles_read_reissue(struct cachefiles_object *object, | ||
| 74 | struct cachefiles_one_read *monitor) | ||
| 75 | { | ||
| 76 | struct address_space *bmapping = object->backer->d_inode->i_mapping; | ||
| 77 | struct page *backpage = monitor->back_page, *backpage2; | ||
| 78 | int ret; | ||
| 79 | |||
| 80 | kenter("{ino=%lx},{%lx,%lx}", | ||
| 81 | object->backer->d_inode->i_ino, | ||
| 82 | backpage->index, backpage->flags); | ||
| 83 | |||
| 84 | /* skip if the page was truncated away completely */ | ||
| 85 | if (backpage->mapping != bmapping) { | ||
| 86 | kleave(" = -ENODATA [mapping]"); | ||
| 87 | return -ENODATA; | ||
| 88 | } | ||
| 89 | |||
| 90 | backpage2 = find_get_page(bmapping, backpage->index); | ||
| 91 | if (!backpage2) { | ||
| 92 | kleave(" = -ENODATA [gone]"); | ||
| 93 | return -ENODATA; | ||
| 94 | } | ||
| 95 | |||
| 96 | if (backpage != backpage2) { | ||
| 97 | put_page(backpage2); | ||
| 98 | kleave(" = -ENODATA [different]"); | ||
| 99 | return -ENODATA; | ||
| 100 | } | ||
| 101 | |||
| 102 | /* the page is still there and we already have a ref on it, so we don't | ||
| 103 | * need a second */ | ||
| 104 | put_page(backpage2); | ||
| 105 | |||
| 106 | INIT_LIST_HEAD(&monitor->op_link); | ||
| 107 | add_page_wait_queue(backpage, &monitor->monitor); | ||
| 108 | |||
| 109 | if (trylock_page(backpage)) { | ||
| 110 | ret = -EIO; | ||
| 111 | if (PageError(backpage)) | ||
| 112 | goto unlock_discard; | ||
| 113 | ret = 0; | ||
| 114 | if (PageUptodate(backpage)) | ||
| 115 | goto unlock_discard; | ||
| 116 | |||
| 117 | kdebug("reissue read"); | ||
| 118 | ret = bmapping->a_ops->readpage(NULL, backpage); | ||
| 119 | if (ret < 0) | ||
| 120 | goto unlock_discard; | ||
| 121 | } | ||
| 122 | |||
| 123 | /* but the page may have been read before the monitor was installed, so | ||
| 124 | * the monitor may miss the event - so we have to ensure that we do get | ||
| 125 | * one in such a case */ | ||
| 126 | if (trylock_page(backpage)) { | ||
| 127 | _debug("jumpstart %p {%lx}", backpage, backpage->flags); | ||
| 128 | unlock_page(backpage); | ||
| 129 | } | ||
| 130 | |||
| 131 | /* it'll reappear on the todo list */ | ||
| 132 | kleave(" = -EINPROGRESS"); | ||
| 133 | return -EINPROGRESS; | ||
| 134 | |||
| 135 | unlock_discard: | ||
| 136 | unlock_page(backpage); | ||
| 137 | spin_lock_irq(&object->work_lock); | ||
| 138 | list_del(&monitor->op_link); | ||
| 139 | spin_unlock_irq(&object->work_lock); | ||
| 140 | kleave(" = %d", ret); | ||
| 141 | return ret; | ||
| 142 | } | ||
| 143 | |||
| 144 | /* | ||
| 64 | * copy data from backing pages to netfs pages to complete a read operation | 145 | * copy data from backing pages to netfs pages to complete a read operation | 
| 65 | * - driven by FS-Cache's thread pool | 146 | * - driven by FS-Cache's thread pool | 
| 66 | */ | 147 | */ | 
| @@ -92,20 +173,26 @@ static void cachefiles_read_copier(struct fscache_operation *_op) | |||
| 92 | 173 | ||
| 93 | _debug("- copy {%lu}", monitor->back_page->index); | 174 | _debug("- copy {%lu}", monitor->back_page->index); | 
| 94 | 175 | ||
| 95 | error = -EIO; | 176 | recheck: | 
| 96 | if (PageUptodate(monitor->back_page)) { | 177 | if (PageUptodate(monitor->back_page)) { | 
| 97 | copy_highpage(monitor->netfs_page, monitor->back_page); | 178 | copy_highpage(monitor->netfs_page, monitor->back_page); | 
| 98 | 179 | ||
| 99 | pagevec_add(&pagevec, monitor->netfs_page); | 180 | pagevec_add(&pagevec, monitor->netfs_page); | 
| 100 | fscache_mark_pages_cached(monitor->op, &pagevec); | 181 | fscache_mark_pages_cached(monitor->op, &pagevec); | 
| 101 | error = 0; | 182 | error = 0; | 
| 102 | } | 183 | } else if (!PageError(monitor->back_page)) { | 
| 103 | 184 | /* the page has probably been truncated */ | |
| 104 | if (error) | 185 | error = cachefiles_read_reissue(object, monitor); | 
| 186 | if (error == -EINPROGRESS) | ||
| 187 | goto next; | ||
| 188 | goto recheck; | ||
| 189 | } else { | ||
| 105 | cachefiles_io_error_obj( | 190 | cachefiles_io_error_obj( | 
| 106 | object, | 191 | object, | 
| 107 | "Readpage failed on backing file %lx", | 192 | "Readpage failed on backing file %lx", | 
| 108 | (unsigned long) monitor->back_page->flags); | 193 | (unsigned long) monitor->back_page->flags); | 
| 194 | error = -EIO; | ||
| 195 | } | ||
| 109 | 196 | ||
| 110 | page_cache_release(monitor->back_page); | 197 | page_cache_release(monitor->back_page); | 
| 111 | 198 | ||
| @@ -114,6 +201,7 @@ static void cachefiles_read_copier(struct fscache_operation *_op) | |||
| 114 | fscache_put_retrieval(op); | 201 | fscache_put_retrieval(op); | 
| 115 | kfree(monitor); | 202 | kfree(monitor); | 
| 116 | 203 | ||
| 204 | next: | ||
| 117 | /* let the thread pool have some air occasionally */ | 205 | /* let the thread pool have some air occasionally */ | 
| 118 | max--; | 206 | max--; | 
| 119 | if (max < 0 || need_resched()) { | 207 | if (max < 0 || need_resched()) { | 
| @@ -333,7 +421,8 @@ int cachefiles_read_or_alloc_page(struct fscache_retrieval *op, | |||
| 333 | 421 | ||
| 334 | shift = PAGE_SHIFT - inode->i_sb->s_blocksize_bits; | 422 | shift = PAGE_SHIFT - inode->i_sb->s_blocksize_bits; | 
| 335 | 423 | ||
| 336 | op->op.flags = FSCACHE_OP_FAST; | 424 | op->op.flags &= FSCACHE_OP_KEEP_FLAGS; | 
| 425 | op->op.flags |= FSCACHE_OP_FAST; | ||
| 337 | op->op.processor = cachefiles_read_copier; | 426 | op->op.processor = cachefiles_read_copier; | 
| 338 | 427 | ||
| 339 | pagevec_init(&pagevec, 0); | 428 | pagevec_init(&pagevec, 0); | 
| @@ -639,7 +728,8 @@ int cachefiles_read_or_alloc_pages(struct fscache_retrieval *op, | |||
| 639 | 728 | ||
| 640 | pagevec_init(&pagevec, 0); | 729 | pagevec_init(&pagevec, 0); | 
| 641 | 730 | ||
| 642 | op->op.flags = FSCACHE_OP_FAST; | 731 | op->op.flags &= FSCACHE_OP_KEEP_FLAGS; | 
| 732 | op->op.flags |= FSCACHE_OP_FAST; | ||
| 643 | op->op.processor = cachefiles_read_copier; | 733 | op->op.processor = cachefiles_read_copier; | 
| 644 | 734 | ||
| 645 | INIT_LIST_HEAD(&backpages); | 735 | INIT_LIST_HEAD(&backpages); | 
| @@ -801,7 +891,8 @@ int cachefiles_write_page(struct fscache_storage *op, struct page *page) | |||
| 801 | struct cachefiles_cache *cache; | 891 | struct cachefiles_cache *cache; | 
| 802 | mm_segment_t old_fs; | 892 | mm_segment_t old_fs; | 
| 803 | struct file *file; | 893 | struct file *file; | 
| 804 | loff_t pos; | 894 | loff_t pos, eof; | 
| 895 | size_t len; | ||
| 805 | void *data; | 896 | void *data; | 
| 806 | int ret; | 897 | int ret; | 
| 807 | 898 | ||
| @@ -832,18 +923,33 @@ int cachefiles_write_page(struct fscache_storage *op, struct page *page) | |||
| 832 | if (IS_ERR(file)) { | 923 | if (IS_ERR(file)) { | 
| 833 | ret = PTR_ERR(file); | 924 | ret = PTR_ERR(file); | 
| 834 | } else { | 925 | } else { | 
| 926 | ima_counts_get(file); | ||
| 835 | ret = -EIO; | 927 | ret = -EIO; | 
| 836 | if (file->f_op->write) { | 928 | if (file->f_op->write) { | 
| 837 | pos = (loff_t) page->index << PAGE_SHIFT; | 929 | pos = (loff_t) page->index << PAGE_SHIFT; | 
| 930 | |||
| 931 | /* we mustn't write more data than we have, so we have | ||
| 932 | * to beware of a partial page at EOF */ | ||
| 933 | eof = object->fscache.store_limit_l; | ||
| 934 | len = PAGE_SIZE; | ||
| 935 | if (eof & ~PAGE_MASK) { | ||
| 936 | ASSERTCMP(pos, <, eof); | ||
| 937 | if (eof - pos < PAGE_SIZE) { | ||
| 938 | _debug("cut short %llx to %llx", | ||
| 939 | pos, eof); | ||
| 940 | len = eof - pos; | ||
| 941 | ASSERTCMP(pos + len, ==, eof); | ||
| 942 | } | ||
| 943 | } | ||
| 944 | |||
| 838 | data = kmap(page); | 945 | data = kmap(page); | 
| 839 | old_fs = get_fs(); | 946 | old_fs = get_fs(); | 
| 840 | set_fs(KERNEL_DS); | 947 | set_fs(KERNEL_DS); | 
| 841 | ret = file->f_op->write( | 948 | ret = file->f_op->write( | 
| 842 | file, (const void __user *) data, PAGE_SIZE, | 949 | file, (const void __user *) data, len, &pos); | 
| 843 | &pos); | ||
| 844 | set_fs(old_fs); | 950 | set_fs(old_fs); | 
| 845 | kunmap(page); | 951 | kunmap(page); | 
| 846 | if (ret != PAGE_SIZE) | 952 | if (ret != len) | 
| 847 | ret = -EIO; | 953 | ret = -EIO; | 
| 848 | } | 954 | } | 
| 849 | fput(file); | 955 | fput(file); | 
