Branch data Line data Source code
1 : : // SPDX-License-Identifier: GPL-2.0 2 : : /* 3 : : * Copyright (C) 2017 Red Hat, Inc. 4 : : * Copyright (c) 2018 Christoph Hellwig. 5 : : */ 6 : : #include <linux/module.h> 7 : : #include <linux/compiler.h> 8 : : #include <linux/fs.h> 9 : : #include <linux/iomap.h> 10 : : #include <linux/pagemap.h> 11 : : #include <linux/pagevec.h> 12 : : 13 : : /* 14 : : * Seek for SEEK_DATA / SEEK_HOLE within @page, starting at @lastoff. 15 : : * Returns true if found and updates @lastoff to the offset in file. 16 : : */ 17 : : static bool 18 : 0 : page_seek_hole_data(struct inode *inode, struct page *page, loff_t *lastoff, 19 : : int whence) 20 : : { 21 : 0 : const struct address_space_operations *ops = inode->i_mapping->a_ops; 22 : : unsigned int bsize = i_blocksize(inode), off; 23 : 0 : bool seek_data = whence == SEEK_DATA; 24 : : loff_t poff = page_offset(page); 25 : : 26 : 0 : if (WARN_ON_ONCE(*lastoff >= poff + PAGE_SIZE)) 27 : : return false; 28 : : 29 : 0 : if (*lastoff < poff) { 30 : : /* 31 : : * Last offset smaller than the start of the page means we found 32 : : * a hole: 33 : : */ 34 : 0 : if (whence == SEEK_HOLE) 35 : : return true; 36 : 0 : *lastoff = poff; 37 : : } 38 : : 39 : : /* 40 : : * Just check the page unless we can and should check block ranges: 41 : : */ 42 : 0 : if (bsize == PAGE_SIZE || !ops->is_partially_uptodate) 43 : 0 : return PageUptodate(page) == seek_data; 44 : : 45 : 0 : lock_page(page); 46 : 0 : if (unlikely(page->mapping != inode->i_mapping)) 47 : : goto out_unlock_not_found; 48 : : 49 : 0 : for (off = 0; off < PAGE_SIZE; off += bsize) { 50 : 0 : if (offset_in_page(*lastoff) >= off + bsize) 51 : 0 : continue; 52 : 0 : if (ops->is_partially_uptodate(page, off, bsize) == seek_data) { 53 : 0 : unlock_page(page); 54 : 0 : return true; 55 : : } 56 : 0 : *lastoff = poff + off + bsize; 57 : : } 58 : : 59 : : out_unlock_not_found: 60 : 0 : unlock_page(page); 61 : 0 : return false; 62 : : } 63 : : 64 : : /* 65 : : * Seek for SEEK_DATA / SEEK_HOLE in the page cache. 66 : : * 67 : : * Within unwritten extents, the page cache determines which parts are holes 68 : : * and which are data: uptodate buffer heads count as data; everything else 69 : : * counts as a hole. 70 : : * 71 : : * Returns the resulting offset on successs, and -ENOENT otherwise. 72 : : */ 73 : : static loff_t 74 : 0 : page_cache_seek_hole_data(struct inode *inode, loff_t offset, loff_t length, 75 : : int whence) 76 : : { 77 : 0 : pgoff_t index = offset >> PAGE_SHIFT; 78 : 0 : pgoff_t end = DIV_ROUND_UP(offset + length, PAGE_SIZE); 79 : 0 : loff_t lastoff = offset; 80 : : struct pagevec pvec; 81 : : 82 : 0 : if (length <= 0) 83 : : return -ENOENT; 84 : : 85 : : pagevec_init(&pvec); 86 : : 87 : : do { 88 : : unsigned nr_pages, i; 89 : : 90 : 0 : nr_pages = pagevec_lookup_range(&pvec, inode->i_mapping, &index, 91 : : end - 1); 92 : 0 : if (nr_pages == 0) 93 : : break; 94 : : 95 : 0 : for (i = 0; i < nr_pages; i++) { 96 : 0 : struct page *page = pvec.pages[i]; 97 : : 98 : 0 : if (page_seek_hole_data(inode, page, &lastoff, whence)) 99 : : goto check_range; 100 : 0 : lastoff = page_offset(page) + PAGE_SIZE; 101 : : } 102 : : pagevec_release(&pvec); 103 : 0 : } while (index < end); 104 : : 105 : : /* When no page at lastoff and we are not done, we found a hole. */ 106 : 0 : if (whence != SEEK_HOLE) 107 : : goto not_found; 108 : : 109 : : check_range: 110 : 0 : if (lastoff < offset + length) 111 : : goto out; 112 : : not_found: 113 : 0 : lastoff = -ENOENT; 114 : : out: 115 : : pagevec_release(&pvec); 116 : 0 : return lastoff; 117 : : } 118 : : 119 : : 120 : : static loff_t 121 : 0 : iomap_seek_hole_actor(struct inode *inode, loff_t offset, loff_t length, 122 : : void *data, struct iomap *iomap) 123 : : { 124 : 0 : switch (iomap->type) { 125 : : case IOMAP_UNWRITTEN: 126 : 0 : offset = page_cache_seek_hole_data(inode, offset, length, 127 : : SEEK_HOLE); 128 : 0 : if (offset < 0) 129 : : return length; 130 : : /* fall through */ 131 : : case IOMAP_HOLE: 132 : 0 : *(loff_t *)data = offset; 133 : 0 : return 0; 134 : : default: 135 : : return length; 136 : : } 137 : : } 138 : : 139 : : loff_t 140 : 0 : iomap_seek_hole(struct inode *inode, loff_t offset, const struct iomap_ops *ops) 141 : : { 142 : : loff_t size = i_size_read(inode); 143 : 0 : loff_t length = size - offset; 144 : : loff_t ret; 145 : : 146 : : /* Nothing to be found before or beyond the end of the file. */ 147 : 0 : if (offset < 0 || offset >= size) 148 : : return -ENXIO; 149 : : 150 : 0 : while (length > 0) { 151 : 0 : ret = iomap_apply(inode, offset, length, IOMAP_REPORT, ops, 152 : : &offset, iomap_seek_hole_actor); 153 : 0 : if (ret < 0) 154 : 0 : return ret; 155 : 0 : if (ret == 0) 156 : : break; 157 : : 158 : 0 : offset += ret; 159 : 0 : length -= ret; 160 : : } 161 : : 162 : 0 : return offset; 163 : : } 164 : : EXPORT_SYMBOL_GPL(iomap_seek_hole); 165 : : 166 : : static loff_t 167 : 0 : iomap_seek_data_actor(struct inode *inode, loff_t offset, loff_t length, 168 : : void *data, struct iomap *iomap) 169 : : { 170 : 0 : switch (iomap->type) { 171 : : case IOMAP_HOLE: 172 : : return length; 173 : : case IOMAP_UNWRITTEN: 174 : 0 : offset = page_cache_seek_hole_data(inode, offset, length, 175 : : SEEK_DATA); 176 : 0 : if (offset < 0) 177 : : return length; 178 : : /*FALLTHRU*/ 179 : : default: 180 : 0 : *(loff_t *)data = offset; 181 : 0 : return 0; 182 : : } 183 : : } 184 : : 185 : : loff_t 186 : 0 : iomap_seek_data(struct inode *inode, loff_t offset, const struct iomap_ops *ops) 187 : : { 188 : : loff_t size = i_size_read(inode); 189 : 0 : loff_t length = size - offset; 190 : : loff_t ret; 191 : : 192 : : /* Nothing to be found before or beyond the end of the file. */ 193 : 0 : if (offset < 0 || offset >= size) 194 : : return -ENXIO; 195 : : 196 : 0 : while (length > 0) { 197 : 0 : ret = iomap_apply(inode, offset, length, IOMAP_REPORT, ops, 198 : : &offset, iomap_seek_data_actor); 199 : 0 : if (ret < 0) 200 : 0 : return ret; 201 : 0 : if (ret == 0) 202 : : break; 203 : : 204 : 0 : offset += ret; 205 : 0 : length -= ret; 206 : : } 207 : : 208 : 0 : if (length <= 0) 209 : : return -ENXIO; 210 : 0 : return offset; 211 : : } 212 : : EXPORT_SYMBOL_GPL(iomap_seek_data);