truncate: Fix corruption when plugin changes per-connection size
[nbdkit/ericb.git] / filters / truncate / truncate.c
blob6408c3505f5ea77ea43eef425de07ee12730a660
1 /* nbdkit
2 * Copyright (C) 2018-2019 Red Hat Inc.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions are
6 * met:
8 * * Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
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.
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.
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.
33 #include <config.h>
35 #include <stdio.h>
36 #include <stdlib.h>
37 #include <stdint.h>
38 #include <string.h>
39 #include <limits.h>
40 #include <errno.h>
42 #include <nbdkit-filter.h>
44 #include "cleanup.h"
45 #include "ispowerof2.h"
46 #include "iszero.h"
47 #include "rounding.h"
49 #define THREAD_MODEL NBDKIT_THREAD_MODEL_PARALLEL
51 /* These are the parameters. */
52 static int64_t truncate_size = -1;
53 static unsigned round_up = 0, round_down = 0;
55 static int
56 parse_round_param (const char *key, const char *value, unsigned *ret)
58 int64_t r;
59 unsigned u;
61 /* Parse it as a "size" quantity so we allow round-up=1M and similar. */
62 r = nbdkit_parse_size (value);
63 if (r == -1)
64 return -1;
66 /* Must not be zero or larger than an unsigned int. */
67 if (r == 0) {
68 nbdkit_error ("if set, the %s parameter must be > 0", key);
69 return -1;
71 if (r > UINT_MAX) {
72 nbdkit_error ("the %s parameter is too large", key);
73 return -1;
75 u = r;
77 /* Must be a power of 2. We could relax this in future. */
78 if (!is_power_of_2 (u)) {
79 nbdkit_error ("the %s parameter must be a power of 2", key);
80 return -1;
83 *ret = u;
84 return 0;
87 /* Called for each key=value passed on the command line. */
88 static int
89 truncate_config (nbdkit_next_config *next, void *nxdata,
90 const char *key, const char *value)
92 if (strcmp (key, "truncate") == 0) {
93 truncate_size = nbdkit_parse_size (value);
94 if (truncate_size == -1)
95 return -1;
96 return 0;
98 else if (strcmp (key, "round-up") == 0) {
99 return parse_round_param (key, value, &round_up);
101 else if (strcmp (key, "round-down") == 0) {
102 return parse_round_param (key, value, &round_down);
104 else
105 return next (nxdata, key, value);
108 #define truncate_config_help \
109 "truncate=<SIZE> The new size.\n" \
110 "round-up=<N> Round up to next multiple of N.\n" \
111 "round-down=<N> Round down to multiple of N."
113 /* Per-connection state. Until the NBD protocol gains dynamic resize
114 * support, each connection remembers the size of the underlying
115 * plugin at open (even if that size differs between connections
116 * because the plugin tracks external resize effects).
118 struct handle {
119 /* The real size of the underlying plugin. */
120 uint64_t real_size;
122 /* The calculated size after applying the parameters. */
123 uint64_t size;
126 /* Open a connection. */
127 static void *
128 truncate_open (nbdkit_next_open *next, void *nxdata, int readonly)
130 struct handle *h;
132 if (next (nxdata, readonly) == -1)
133 return NULL;
135 h = malloc (sizeof *h); /* h is populated during .prepare */
136 if (h == NULL) {
137 nbdkit_error ("malloc: %m");
138 return NULL;
141 return h;
144 static void
145 truncate_close (void *handle)
147 struct handle *h = handle;
149 free (h);
152 /* In prepare, force a call to next_ops->get_size in order to set
153 * per-connection real_size & size; these values are not changed
154 * during the life of the connection.
156 static int
157 truncate_prepare (struct nbdkit_next_ops *next_ops, void *nxdata,
158 void *handle)
160 int64_t r;
161 struct handle *h = handle;
163 r = next_ops->get_size (nxdata);
164 if (r == -1)
165 return -1;
167 h->real_size = h->size = r;
169 /* The truncate, round-up and round-down parameters are treated as
170 * separate operations. It's possible to specify more than one,
171 * although perhaps not very useful.
173 if (truncate_size >= 0)
174 h->size = truncate_size;
175 if (round_up > 0)
176 h->size = ROUND_UP (h->size, round_up);
177 if (round_down > 0)
178 h->size = ROUND_DOWN (h->size, round_down);
180 return r >= 0 ? 0 : -1;
183 /* Get the size. */
184 static int64_t
185 truncate_get_size (struct nbdkit_next_ops *next_ops, void *nxdata,
186 void *handle)
188 struct handle *h = handle;
190 /* If the NBD protocol and nbdkit adds dynamic resize, we'll need a
191 * rwlock where get_size holds write lock and all other ops hold
192 * read lock. Until then, NBD sizes are unchanging (even if the
193 * underlying plugin can react to external size changes), so just
194 * returned what we cached at connection open.
196 return h->size;
199 /* Read data. */
200 static int
201 truncate_pread (struct nbdkit_next_ops *next_ops, void *nxdata,
202 void *handle, void *buf, uint32_t count, uint64_t offset,
203 uint32_t flags, int *err)
205 int r;
206 uint32_t n;
207 struct handle *h = handle;
209 if (offset < h->real_size) {
210 if (offset + count <= h->real_size)
211 n = count;
212 else
213 n = h->real_size - offset;
214 r = next_ops->pread (nxdata, buf, n, offset, flags, err);
215 if (r == -1)
216 return -1;
217 count -= n;
218 buf += n;
221 if (count > 0)
222 memset (buf, 0, count);
224 return 0;
227 /* Write data. */
228 static int
229 truncate_pwrite (struct nbdkit_next_ops *next_ops, void *nxdata,
230 void *handle,
231 const void *buf, uint32_t count, uint64_t offset,
232 uint32_t flags, int *err)
234 int r;
235 uint32_t n;
236 struct handle *h = handle;
238 if (offset < h->real_size) {
239 if (offset + count <= h->real_size)
240 n = count;
241 else
242 n = h->real_size - offset;
243 r = next_ops->pwrite (nxdata, buf, n, offset, flags, err);
244 if (r == -1)
245 return -1;
246 count -= n;
247 buf += n;
250 if (count > 0) {
251 /* The caller must be writing zeroes, else it's an error. */
252 if (!is_zero (buf, count)) {
253 nbdkit_error ("truncate: write beyond end of underlying device");
254 *err = ENOSPC;
255 return -1;
259 return 0;
262 /* Trim data. */
263 static int
264 truncate_trim (struct nbdkit_next_ops *next_ops, void *nxdata,
265 void *handle, uint32_t count, uint64_t offset,
266 uint32_t flags, int *err)
268 uint32_t n;
269 struct handle *h = handle;
271 if (offset < h->real_size) {
272 if (offset + count <= h->real_size)
273 n = count;
274 else
275 n = h->real_size - offset;
276 return next_ops->trim (nxdata, n, offset, flags, err);
278 return 0;
281 /* Zero data. */
282 static int
283 truncate_zero (struct nbdkit_next_ops *next_ops, void *nxdata,
284 void *handle, uint32_t count, uint64_t offset,
285 uint32_t flags, int *err)
287 uint32_t n;
288 struct handle *h = handle;
290 if (offset < h->real_size) {
291 if (offset + count <= h->real_size)
292 n = count;
293 else
294 n = h->real_size - offset;
295 return next_ops->zero (nxdata, n, offset, flags, err);
297 return 0;
300 /* Extents. */
301 static int
302 truncate_extents (struct nbdkit_next_ops *next_ops, void *nxdata,
303 void *handle, uint32_t count, uint64_t offset,
304 uint32_t flags, struct nbdkit_extents *extents, int *err)
306 uint32_t n;
307 struct handle *h = handle;
308 CLEANUP_EXTENTS_FREE struct nbdkit_extents *extents2 = NULL;
309 size_t i;
311 /* If the entire request is beyond the end of the underlying plugin
312 * then this is the easy case: return a hole up to the end of the
313 * file.
315 if (offset >= h->real_size) {
316 int r = nbdkit_add_extent (extents,
317 h->real_size, truncate_size - h->real_size,
318 NBDKIT_EXTENT_ZERO|NBDKIT_EXTENT_HOLE);
319 if (r == -1)
320 *err = errno;
321 return r;
324 /* We're asked first for extents information about the plugin, then
325 * possibly (if truncating larger) for the hole after the plugin.
326 * Since we're not required to provide all of this information, the
327 * easiest thing is to only return data from the plugin. We will be
328 * called later about the hole. However we do need to make sure
329 * that the extents array is truncated to the real size, hence we
330 * have to create a new extents array, ask the plugin, then copy the
331 * returned data to the original array.
333 extents2 = nbdkit_extents_new (offset, h->real_size);
334 if (extents2 == NULL) {
335 *err = errno;
336 return -1;
338 if (offset + count <= h->real_size)
339 n = count;
340 else
341 n = h->real_size - offset;
342 if (next_ops->extents (nxdata, n, offset, flags, extents2, err) == -1)
343 return -1;
345 for (i = 0; i < nbdkit_extents_count (extents2); ++i) {
346 struct nbdkit_extent e = nbdkit_get_extent (extents2, i);
348 if (nbdkit_add_extent (extents, e.offset, e.length, e.type) == -1) {
349 *err = errno;
350 return -1;
354 return 0;
357 static struct nbdkit_filter filter = {
358 .name = "truncate",
359 .longname = "nbdkit truncate filter",
360 .version = PACKAGE_VERSION,
361 .config = truncate_config,
362 .config_help = truncate_config_help,
363 .open = truncate_open,
364 .close = truncate_close,
365 .prepare = truncate_prepare,
366 .get_size = truncate_get_size,
367 .pread = truncate_pread,
368 .pwrite = truncate_pwrite,
369 .trim = truncate_trim,
370 .zero = truncate_zero,
371 .extents = truncate_extents,
374 NBDKIT_REGISTER_FILTER(filter)