seek.c 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205
  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. * Seek for SEEK_DATA / SEEK_HOLE within @page, starting at @lastoff.
  14. * Returns true if found and updates @lastoff to the offset in file.
  15. */
  16. static bool
  17. page_seek_hole_data(struct inode *inode, struct page *page, loff_t *lastoff,
  18. int whence)
  19. {
  20. const struct address_space_operations *ops = inode->i_mapping->a_ops;
  21. unsigned int bsize = i_blocksize(inode), off;
  22. bool seek_data = whence == SEEK_DATA;
  23. loff_t poff = page_offset(page);
  24. if (WARN_ON_ONCE(*lastoff >= poff + PAGE_SIZE))
  25. return false;
  26. if (*lastoff < poff) {
  27. /*
  28. * Last offset smaller than the start of the page means we found
  29. * a hole:
  30. */
  31. if (whence == SEEK_HOLE)
  32. return true;
  33. *lastoff = poff;
  34. }
  35. /*
  36. * Just check the page unless we can and should check block ranges:
  37. */
  38. if (bsize == PAGE_SIZE || !ops->is_partially_uptodate)
  39. return PageUptodate(page) == seek_data;
  40. lock_page(page);
  41. if (unlikely(page->mapping != inode->i_mapping))
  42. goto out_unlock_not_found;
  43. for (off = 0; off < PAGE_SIZE; off += bsize) {
  44. if (offset_in_page(*lastoff) >= off + bsize)
  45. continue;
  46. if (ops->is_partially_uptodate(page, off, bsize) == seek_data) {
  47. unlock_page(page);
  48. return true;
  49. }
  50. *lastoff = poff + off + bsize;
  51. }
  52. out_unlock_not_found:
  53. unlock_page(page);
  54. return false;
  55. }
  56. /*
  57. * Seek for SEEK_DATA / SEEK_HOLE in the page cache.
  58. *
  59. * Within unwritten extents, the page cache determines which parts are holes
  60. * and which are data: uptodate buffer heads count as data; everything else
  61. * counts as a hole.
  62. *
  63. * Returns the resulting offset on successs, and -ENOENT otherwise.
  64. */
  65. static loff_t
  66. page_cache_seek_hole_data(struct inode *inode, loff_t offset, loff_t length,
  67. int whence)
  68. {
  69. pgoff_t index = offset >> PAGE_SHIFT;
  70. pgoff_t end = DIV_ROUND_UP(offset + length, PAGE_SIZE);
  71. loff_t lastoff = offset;
  72. struct pagevec pvec;
  73. if (length <= 0)
  74. return -ENOENT;
  75. pagevec_init(&pvec);
  76. do {
  77. unsigned nr_pages, i;
  78. nr_pages = pagevec_lookup_range(&pvec, inode->i_mapping, &index,
  79. end - 1);
  80. if (nr_pages == 0)
  81. break;
  82. for (i = 0; i < nr_pages; i++) {
  83. struct page *page = pvec.pages[i];
  84. if (page_seek_hole_data(inode, page, &lastoff, whence))
  85. goto check_range;
  86. lastoff = page_offset(page) + PAGE_SIZE;
  87. }
  88. pagevec_release(&pvec);
  89. } while (index < end);
  90. /* When no page at lastoff and we are not done, we found a hole. */
  91. if (whence != SEEK_HOLE)
  92. goto not_found;
  93. check_range:
  94. if (lastoff < offset + length)
  95. goto out;
  96. not_found:
  97. lastoff = -ENOENT;
  98. out:
  99. pagevec_release(&pvec);
  100. return lastoff;
  101. }
  102. static loff_t
  103. iomap_seek_hole_actor(struct inode *inode, loff_t offset, loff_t length,
  104. void *data, struct iomap *iomap, struct iomap *srcmap)
  105. {
  106. switch (iomap->type) {
  107. case IOMAP_UNWRITTEN:
  108. offset = page_cache_seek_hole_data(inode, offset, length,
  109. SEEK_HOLE);
  110. if (offset < 0)
  111. return length;
  112. fallthrough;
  113. case IOMAP_HOLE:
  114. *(loff_t *)data = offset;
  115. return 0;
  116. default:
  117. return length;
  118. }
  119. }
  120. loff_t
  121. iomap_seek_hole(struct inode *inode, loff_t offset, const struct iomap_ops *ops)
  122. {
  123. loff_t size = i_size_read(inode);
  124. loff_t ret;
  125. /* Nothing to be found before or beyond the end of the file. */
  126. if (offset < 0 || offset >= size)
  127. return -ENXIO;
  128. while (offset < size) {
  129. ret = iomap_apply(inode, offset, size - offset, IOMAP_REPORT,
  130. ops, &offset, iomap_seek_hole_actor);
  131. if (ret < 0)
  132. return ret;
  133. if (ret == 0)
  134. break;
  135. offset += ret;
  136. }
  137. return offset;
  138. }
  139. EXPORT_SYMBOL_GPL(iomap_seek_hole);
  140. static loff_t
  141. iomap_seek_data_actor(struct inode *inode, loff_t offset, loff_t length,
  142. void *data, struct iomap *iomap, struct iomap *srcmap)
  143. {
  144. switch (iomap->type) {
  145. case IOMAP_HOLE:
  146. return length;
  147. case IOMAP_UNWRITTEN:
  148. offset = page_cache_seek_hole_data(inode, offset, length,
  149. SEEK_DATA);
  150. if (offset < 0)
  151. return length;
  152. fallthrough;
  153. default:
  154. *(loff_t *)data = offset;
  155. return 0;
  156. }
  157. }
  158. loff_t
  159. iomap_seek_data(struct inode *inode, loff_t offset, const struct iomap_ops *ops)
  160. {
  161. loff_t size = i_size_read(inode);
  162. loff_t ret;
  163. /* Nothing to be found before or beyond the end of the file. */
  164. if (offset < 0 || offset >= size)
  165. return -ENXIO;
  166. while (offset < size) {
  167. ret = iomap_apply(inode, offset, size - offset, IOMAP_REPORT,
  168. ops, &offset, iomap_seek_data_actor);
  169. if (ret < 0)
  170. return ret;
  171. if (ret == 0)
  172. return offset;
  173. offset += ret;
  174. }
  175. /* We've reached the end of the file without finding data */
  176. return -ENXIO;
  177. }
  178. EXPORT_SYMBOL_GPL(iomap_seek_data);