1 /* nbdkit
2 * Copyright (C) 2019-2020 Red Hat Inc.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions are
6 * met:
7 *
8 * * Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 *
11 * * Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in the
13 * documentation and/or other materials provided with the distribution.
14 *
15 * * Neither the name of Red Hat nor the names of its contributors may be
16 * used to endorse or promote products derived from this software without
17 * specific prior written permission.
18 *
19 * THIS SOFTWARE IS PROVIDED BY RED HAT AND CONTRIBUTORS ''AS IS'' AND
20 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
21 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
22 * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL RED HAT OR
23 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
26 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
27 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
28 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
29 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
30 * SUCH DAMAGE.
31 */
32
33 #include <config.h>
34
35 #include <stdio.h>
36 #include <stdlib.h>
37 #include <stdint.h>
38 #include <inttypes.h>
39 #include <string.h>
40 #include <errno.h>
41 #include <assert.h>
42
43 #include <nbdkit-filter.h>
44
45 #include "cleanup.h"
46 #include "minmax.h"
47 #include "vector.h"
48
49 #define HOLE (NBDKIT_EXTENT_HOLE|NBDKIT_EXTENT_ZERO)
50
51 static const char *extentlist;
52
53 /* List of extents. Once we've finally parsed them this will be
54 * ordered, non-overlapping and have no gaps.
55 */
56 struct extent {
57 uint64_t offset, length;
58 uint32_t type;
59 };
60 DEFINE_VECTOR_TYPE(extent_list, struct extent);
61 static extent_list extents;
62
63 static void
extentlist_unload(void)64 extentlist_unload (void)
65 {
66 free (extents.ptr);
67 }
68
69 /* Called for each key=value passed on the command line. */
70 static int
extentlist_config(nbdkit_next_config * next,void * nxdata,const char * key,const char * value)71 extentlist_config (nbdkit_next_config *next, void *nxdata,
72 const char *key, const char *value)
73 {
74 if (strcmp (key, "extentlist") == 0) {
75 if (extentlist != NULL) {
76 nbdkit_error ("extentlist cannot appear twice");
77 exit (EXIT_FAILURE);
78 }
79 extentlist = value;
80 return 0;
81 }
82 else
83 return next (nxdata, key, value);
84 }
85
86 static int
extentlist_config_complete(nbdkit_next_config_complete * next,void * nxdata)87 extentlist_config_complete (nbdkit_next_config_complete *next, void *nxdata)
88 {
89 if (extentlist == NULL) {
90 nbdkit_error ("you must supply the extentlist parameter "
91 "on the command line");
92 return -1;
93 }
94
95 return next (nxdata);
96 }
97
98 static int
compare_offsets(const void * ev1,const void * ev2)99 compare_offsets (const void *ev1, const void *ev2)
100 {
101 const struct extent *e1 = ev1;
102 const struct extent *e2 = ev2;
103
104 if (e1->offset < e2->offset)
105 return -1;
106 else if (e1->offset > e2->offset)
107 return 1;
108 else
109 return 0;
110 }
111
112 static int
compare_ranges(const void * ev1,const void * ev2)113 compare_ranges (const void *ev1, const void *ev2)
114 {
115 const struct extent *e1 = ev1;
116 const struct extent *e2 = ev2;
117
118 if (e1->offset < e2->offset)
119 return -1;
120 else if (e1->offset >= e2->offset + e2->length)
121 return 1;
122 else
123 return 0;
124 }
125
126 /* Similar to parse_extents in plugins/sh/methods.c */
127 static void
parse_extentlist(void)128 parse_extentlist (void)
129 {
130 FILE *fp;
131 CLEANUP_FREE char *line = NULL;
132 size_t linelen = 0;
133 ssize_t len;
134 size_t i;
135 uint64_t end;
136
137 assert (extentlist != NULL);
138 assert (extents.ptr == NULL);
139 assert (extents.size == 0);
140
141 fp = fopen (extentlist, "r");
142 if (!fp) {
143 nbdkit_error ("open: %s: %m", extentlist);
144 exit (EXIT_FAILURE);
145 }
146
147 while ((len = getline (&line, &linelen, fp)) != -1) {
148 const char *delim = " \t";
149 char *sp, *p;
150 int64_t offset, length;
151 uint32_t type;
152
153 if (len > 0 && line[len-1] == '\n') {
154 line[len-1] = '\0';
155 len--;
156 }
157
158 if ((p = strtok_r (line, delim, &sp)) == NULL) {
159 parse_error:
160 nbdkit_error ("%s: cannot parse %s", extentlist, line);
161 exit (EXIT_FAILURE);
162 }
163 offset = nbdkit_parse_size (p);
164 if (offset == -1)
165 exit (EXIT_FAILURE);
166
167 if ((p = strtok_r (NULL, delim, &sp)) == NULL)
168 goto parse_error;
169 length = nbdkit_parse_size (p);
170 if (length == -1)
171 exit (EXIT_FAILURE);
172
173 /* Skip zero length extents. Makes the rest of the code easier. */
174 if (length == 0)
175 continue;
176
177 if ((p = strtok_r (NULL, delim, &sp)) == NULL)
178 /* empty type field means allocated data (0) */
179 type = 0;
180 else if (sscanf (p, "%" SCNu32, &type) == 1)
181 ;
182 else {
183 type = 0;
184 if (strstr (p, "hole") != NULL)
185 type |= NBDKIT_EXTENT_HOLE;
186 if (strstr (p, "zero") != NULL)
187 type |= NBDKIT_EXTENT_ZERO;
188 }
189
190 if (extent_list_append (&extents,
191 (struct extent){.offset = offset, .length=length,
192 .type=type}) == -1) {
193 nbdkit_error ("realloc: %m");
194 exit (EXIT_FAILURE);
195 }
196 }
197
198 fclose (fp);
199
200 /* Sort the extents by offset. */
201 qsort (extents.ptr, extents.size, sizeof (struct extent), compare_offsets);
202
203 /* There must not be overlaps at this point. */
204 end = 0;
205 for (i = 0; i < extents.size; ++i) {
206 if (extents.ptr[i].offset < end ||
207 extents.ptr[i].offset + extents.ptr[i].length < extents.ptr[i].offset) {
208 nbdkit_error ("extents in the extent list are overlapping");
209 exit (EXIT_FAILURE);
210 }
211 end = extents.ptr[i].offset + extents.ptr[i].length;
212 }
213
214 /* If there's a gap at the beginning, insert a hole|zero extent. */
215 if (extents.size == 0 || extents.ptr[0].offset > 0) {
216 end = extents.size == 0 ? UINT64_MAX : extents.ptr[0].offset;
217 if (extent_list_insert (&extents,
218 (struct extent){.offset = 0, .length = end,
219 .type = HOLE},
220 0) == -1) {
221 nbdkit_error ("realloc: %m");
222 exit (EXIT_FAILURE);
223 }
224 }
225
226 /* Now insert hole|zero extents after every extent where there
227 * is a gap between that extent and the next one.
228 */
229 for (i = 0; i < extents.size-1; ++i) {
230 end = extents.ptr[i].offset + extents.ptr[i].length;
231 if (end < extents.ptr[i+1].offset)
232 if (extent_list_insert (&extents,
233 (struct extent){.offset = end,
234 .length = extents.ptr[i+1].offset - end,
235 .type = HOLE},
236 i+1) == -1) {
237 nbdkit_error ("realloc: %m");
238 exit (EXIT_FAILURE);
239 }
240 }
241
242 /* If there's a gap at the end, insert a hole|zero extent. */
243 end = extents.ptr[extents.size-1].offset + extents.ptr[extents.size-1].length;
244 if (end < UINT64_MAX) {
245 if (extent_list_append (&extents,
246 (struct extent){.offset = end,
247 .length = UINT64_MAX-end,
248 .type = HOLE}) == -1) {
249 nbdkit_error ("realloc: %m");
250 exit (EXIT_FAILURE);
251 }
252 }
253
254 /* Debug the final list. */
255 for (i = 0; i < extents.size; ++i) {
256 nbdkit_debug ("extentlist: "
257 "extent[%zu] = %" PRIu64 "-%" PRIu64 " (length %" PRIu64 ")"
258 " type %" PRIu32,
259 i, extents.ptr[i].offset,
260 extents.ptr[i].offset + extents.ptr[i].length - 1,
261 extents.ptr[i].length,
262 extents.ptr[i].type);
263 }
264 }
265
266 static int
extentlist_get_ready(nbdkit_next_get_ready * next,void * nxdata)267 extentlist_get_ready (nbdkit_next_get_ready *next, void *nxdata)
268 {
269 parse_extentlist ();
270
271 return next (nxdata);
272 }
273
274 static int
extentlist_can_extents(struct nbdkit_next_ops * next_ops,void * nxdata,void * handle)275 extentlist_can_extents (struct nbdkit_next_ops *next_ops, void *nxdata,
276 void *handle)
277 {
278 return 1;
279 }
280
281 /* Use ‘-D extentlist.lookup=1’ to debug the function below. */
282 int extentlist_debug_lookup = 0;
283
284 /* Read extents. */
285 static int
extentlist_extents(struct nbdkit_next_ops * next_ops,void * nxdata,void * handle,uint32_t count,uint64_t offset,uint32_t flags,struct nbdkit_extents * ret_extents,int * err)286 extentlist_extents (struct nbdkit_next_ops *next_ops, void *nxdata,
287 void *handle, uint32_t count, uint64_t offset,
288 uint32_t flags,
289 struct nbdkit_extents *ret_extents,
290 int *err)
291 {
292 const struct extent eoffset = { .offset = offset };
293 struct extent *p;
294 ssize_t i;
295 uint64_t end;
296
297 /* Find the starting point in the extents list. */
298 p = bsearch (&eoffset, extents.ptr,
299 extents.size, sizeof (struct extent), compare_ranges);
300 assert (p != NULL);
301 i = p - extents.ptr;
302
303 /* Add extents to the output. */
304 while (count > 0) {
305 if (extentlist_debug_lookup)
306 nbdkit_debug ("extentlist lookup: "
307 "loop i=%zd count=%" PRIu32 " offset=%" PRIu64,
308 i, count, offset);
309
310 end = extents.ptr[i].offset + extents.ptr[i].length;
311 if (nbdkit_add_extent (ret_extents, offset, end - offset,
312 extents.ptr[i].type) == -1)
313 return -1;
314
315 count -= MIN (count, end-offset);
316 offset = end;
317 i++;
318 }
319
320 return 0;
321 }
322
323 static struct nbdkit_filter filter = {
324 .name = "extentlist",
325 .longname = "nbdkit extentlist filter",
326 .unload = extentlist_unload,
327 .config = extentlist_config,
328 .config_complete = extentlist_config_complete,
329 .get_ready = extentlist_get_ready,
330 .can_extents = extentlist_can_extents,
331 .extents = extentlist_extents,
332 };
333
334 NBDKIT_REGISTER_FILTER(filter)
335