Commit | Line | Data |
---|---|---|
56a17898 DW |
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 | ||
56a17898 DW |
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 | page_seek_hole_data(struct inode *inode, struct page *page, loff_t *lastoff, | |
19 | int whence) | |
20 | { | |
21 | const struct address_space_operations *ops = inode->i_mapping->a_ops; | |
22 | unsigned int bsize = i_blocksize(inode), off; | |
23 | bool seek_data = whence == SEEK_DATA; | |
24 | loff_t poff = page_offset(page); | |
25 | ||
26 | if (WARN_ON_ONCE(*lastoff >= poff + PAGE_SIZE)) | |
27 | return false; | |
28 | ||
29 | if (*lastoff < poff) { | |
30 | /* | |
31 | * Last offset smaller than the start of the page means we found | |
32 | * a hole: | |
33 | */ | |
34 | if (whence == SEEK_HOLE) | |
35 | return true; | |
36 | *lastoff = poff; | |
37 | } | |
38 | ||
39 | /* | |
40 | * Just check the page unless we can and should check block ranges: | |
41 | */ | |
42 | if (bsize == PAGE_SIZE || !ops->is_partially_uptodate) | |
43 | return PageUptodate(page) == seek_data; | |
44 | ||
45 | lock_page(page); | |
46 | if (unlikely(page->mapping != inode->i_mapping)) | |
47 | goto out_unlock_not_found; | |
48 | ||
49 | for (off = 0; off < PAGE_SIZE; off += bsize) { | |
50 | if (offset_in_page(*lastoff) >= off + bsize) | |
51 | continue; | |
52 | if (ops->is_partially_uptodate(page, off, bsize) == seek_data) { | |
53 | unlock_page(page); | |
54 | return true; | |
55 | } | |
56 | *lastoff = poff + off + bsize; | |
57 | } | |
58 | ||
59 | out_unlock_not_found: | |
60 | unlock_page(page); | |
61 | 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 | page_cache_seek_hole_data(struct inode *inode, loff_t offset, loff_t length, | |
75 | int whence) | |
76 | { | |
77 | pgoff_t index = offset >> PAGE_SHIFT; | |
78 | pgoff_t end = DIV_ROUND_UP(offset + length, PAGE_SIZE); | |
79 | loff_t lastoff = offset; | |
80 | struct pagevec pvec; | |
81 | ||
82 | if (length <= 0) | |
83 | return -ENOENT; | |
84 | ||
85 | pagevec_init(&pvec); | |
86 | ||
87 | do { | |
88 | unsigned nr_pages, i; | |
89 | ||
90 | nr_pages = pagevec_lookup_range(&pvec, inode->i_mapping, &index, | |
91 | end - 1); | |
92 | if (nr_pages == 0) | |
93 | break; | |
94 | ||
95 | for (i = 0; i < nr_pages; i++) { | |
96 | struct page *page = pvec.pages[i]; | |
97 | ||
98 | if (page_seek_hole_data(inode, page, &lastoff, whence)) | |
99 | goto check_range; | |
100 | lastoff = page_offset(page) + PAGE_SIZE; | |
101 | } | |
102 | pagevec_release(&pvec); | |
103 | } while (index < end); | |
104 | ||
105 | /* When no page at lastoff and we are not done, we found a hole. */ | |
106 | if (whence != SEEK_HOLE) | |
107 | goto not_found; | |
108 | ||
109 | check_range: | |
110 | if (lastoff < offset + length) | |
111 | goto out; | |
112 | not_found: | |
113 | lastoff = -ENOENT; | |
114 | out: | |
115 | pagevec_release(&pvec); | |
116 | return lastoff; | |
117 | } | |
118 | ||
119 | ||
120 | static loff_t | |
121 | iomap_seek_hole_actor(struct inode *inode, loff_t offset, loff_t length, | |
122 | void *data, struct iomap *iomap) | |
123 | { | |
124 | switch (iomap->type) { | |
125 | case IOMAP_UNWRITTEN: | |
126 | offset = page_cache_seek_hole_data(inode, offset, length, | |
127 | SEEK_HOLE); | |
128 | if (offset < 0) | |
129 | return length; | |
130 | /* fall through */ | |
131 | case IOMAP_HOLE: | |
132 | *(loff_t *)data = offset; | |
133 | return 0; | |
134 | default: | |
135 | return length; | |
136 | } | |
137 | } | |
138 | ||
139 | loff_t | |
140 | iomap_seek_hole(struct inode *inode, loff_t offset, const struct iomap_ops *ops) | |
141 | { | |
142 | loff_t size = i_size_read(inode); | |
143 | loff_t length = size - offset; | |
144 | loff_t ret; | |
145 | ||
146 | /* Nothing to be found before or beyond the end of the file. */ | |
147 | if (offset < 0 || offset >= size) | |
148 | return -ENXIO; | |
149 | ||
150 | while (length > 0) { | |
151 | ret = iomap_apply(inode, offset, length, IOMAP_REPORT, ops, | |
152 | &offset, iomap_seek_hole_actor); | |
153 | if (ret < 0) | |
154 | return ret; | |
155 | if (ret == 0) | |
156 | break; | |
157 | ||
158 | offset += ret; | |
159 | length -= ret; | |
160 | } | |
161 | ||
162 | return offset; | |
163 | } | |
164 | EXPORT_SYMBOL_GPL(iomap_seek_hole); | |
165 | ||
166 | static loff_t | |
167 | iomap_seek_data_actor(struct inode *inode, loff_t offset, loff_t length, | |
168 | void *data, struct iomap *iomap) | |
169 | { | |
170 | switch (iomap->type) { | |
171 | case IOMAP_HOLE: | |
172 | return length; | |
173 | case IOMAP_UNWRITTEN: | |
174 | offset = page_cache_seek_hole_data(inode, offset, length, | |
175 | SEEK_DATA); | |
176 | if (offset < 0) | |
177 | return length; | |
178 | /*FALLTHRU*/ | |
179 | default: | |
180 | *(loff_t *)data = offset; | |
181 | return 0; | |
182 | } | |
183 | } | |
184 | ||
185 | loff_t | |
186 | iomap_seek_data(struct inode *inode, loff_t offset, const struct iomap_ops *ops) | |
187 | { | |
188 | loff_t size = i_size_read(inode); | |
189 | loff_t length = size - offset; | |
190 | loff_t ret; | |
191 | ||
192 | /* Nothing to be found before or beyond the end of the file. */ | |
193 | if (offset < 0 || offset >= size) | |
194 | return -ENXIO; | |
195 | ||
196 | while (length > 0) { | |
197 | ret = iomap_apply(inode, offset, length, IOMAP_REPORT, ops, | |
198 | &offset, iomap_seek_data_actor); | |
199 | if (ret < 0) | |
200 | return ret; | |
201 | if (ret == 0) | |
202 | break; | |
203 | ||
204 | offset += ret; | |
205 | length -= ret; | |
206 | } | |
207 | ||
208 | if (length <= 0) | |
209 | return -ENXIO; | |
210 | return offset; | |
211 | } | |
212 | EXPORT_SYMBOL_GPL(iomap_seek_data); |