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