Update Red Hat Copyright Notices
[nbdkit.git] / plugins / split / split.c
blobf9828d8402aa00a1ed1b8362f739da791ba4c559
1 /* nbdkit
2 * Copyright Red Hat
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 <inttypes.h>
39 #include <string.h>
40 #include <unistd.h>
41 #include <fcntl.h>
42 #include <errno.h>
43 #include <sys/types.h>
44 #include <sys/stat.h>
45 #include <stdbool.h>
46 #include <pthread.h>
48 #include <nbdkit-plugin.h>
50 #include "cleanup.h"
51 #include "pread.h"
52 #include "pwrite.h"
53 #include "string-vector.h"
54 #include "windows-compat.h"
56 /* The files. */
57 static string_vector filenames = empty_vector;
59 #ifdef SEEK_HOLE
60 /* Any callbacks using lseek must be protected by this lock. */
61 static pthread_mutex_t lseek_lock = PTHREAD_MUTEX_INITIALIZER;
62 #endif
64 static void
65 split_unload (void)
67 string_vector_empty (&filenames);
70 static int
71 split_config (const char *key, const char *value)
73 char *s;
75 if (strcmp (key, "file") == 0) {
76 s = nbdkit_realpath (value);
77 if (s == NULL)
78 return -1;
79 if (string_vector_append (&filenames, s) == -1) {
80 nbdkit_error ("realloc: %m");
81 return -1;
84 else {
85 nbdkit_error ("unknown parameter '%s'", key);
86 return -1;
89 return 0;
92 #define split_config_help \
93 "file=<FILENAME> (required) File(s) to serve."
95 /* The per-connection handle. */
96 struct handle {
97 struct file *files;
98 uint64_t size; /* Total concatenated size. */
101 struct file {
102 uint64_t offset, size;
103 int fd;
104 bool can_extents;
107 /* Create the per-connection handle. */
108 static void *
109 split_open (int readonly)
111 struct handle *h;
112 int flags;
113 size_t i;
114 uint64_t offset;
115 struct stat statbuf;
116 #ifdef SEEK_HOLE
117 off_t r;
118 #endif
120 h = malloc (sizeof *h);
121 if (h == NULL) {
122 nbdkit_error ("malloc: %m");
123 return NULL;
126 h->files = malloc (filenames.len * sizeof (struct file));
127 if (h->files == NULL) {
128 nbdkit_error ("malloc: %m");
129 free (h);
130 return NULL;
132 for (i = 0; i < filenames.len; ++i)
133 h->files[i].fd = -1;
135 /* Open the files. */
136 flags = O_CLOEXEC|O_NOCTTY;
137 if (readonly)
138 flags |= O_RDONLY;
139 else
140 flags |= O_RDWR;
142 for (i = 0; i < filenames.len; ++i) {
143 h->files[i].fd = open (filenames.ptr[i], flags);
144 if (h->files[i].fd == -1) {
145 nbdkit_error ("open: %s: %m", filenames.ptr[i]);
146 goto err;
150 offset = 0;
151 for (i = 0; i < filenames.len; ++i) {
152 h->files[i].offset = offset;
154 if (fstat (h->files[i].fd, &statbuf) == -1) {
155 nbdkit_error ("stat: %s: %m", filenames.ptr[i]);
156 goto err;
158 h->files[i].size = statbuf.st_size;
159 offset += statbuf.st_size;
161 nbdkit_debug ("file[%zu]=%s: offset=%" PRIu64 ", size=%" PRIu64,
162 i, filenames.ptr[i], h->files[i].offset, h->files[i].size);
164 #ifdef SEEK_HOLE
165 /* Test if this file supports extents. */
166 ACQUIRE_LOCK_FOR_CURRENT_SCOPE (&lseek_lock);
167 r = lseek (h->files[i].fd, 0, SEEK_DATA);
168 if (r == -1 && errno != ENXIO) {
169 nbdkit_debug ("disabling extents: lseek on %s: %m", filenames.ptr[i]);
170 h->files[i].can_extents = false;
172 else
173 h->files[i].can_extents = true;
174 #else
175 h->files[i].can_extents = false;
176 #endif
178 h->size = offset;
179 nbdkit_debug ("total size=%" PRIu64, h->size);
181 return h;
183 err:
184 for (i = 0; i < filenames.len; ++i) {
185 if (h->files[i].fd >= 0)
186 close (h->files[i].fd);
188 free (h->files);
189 free (h);
190 return NULL;
193 /* Free up the per-connection handle. */
194 static void
195 split_close (void *handle)
197 struct handle *h = handle;
198 size_t i;
200 for (i = 0; i < filenames.len; ++i)
201 close (h->files[i].fd);
202 free (h->files);
203 free (h);
206 #define THREAD_MODEL NBDKIT_THREAD_MODEL_SERIALIZE_REQUESTS
208 /* Get the disk size. */
209 static int64_t
210 split_get_size (void *handle)
212 struct handle *h = handle;
214 return (int64_t) h->size;
217 static int
218 split_can_cache (void *handle)
220 /* Prefer posix_fadvise(), but letting nbdkit call .pread on our
221 * behalf also tends to work well for the local file system
222 * cache.
224 #if HAVE_POSIX_FADVISE
225 return NBDKIT_FUA_NATIVE;
226 #else
227 return NBDKIT_FUA_EMULATE;
228 #endif
231 /* Helper function to map the offset to the correct file. */
232 static int
233 compare_offset (const void *offsetp, const void *filep)
235 const uint64_t offset = *(uint64_t *)offsetp;
236 const struct file *file = (struct file *)filep;
238 if (offset < file->offset) return -1;
239 if (offset >= file->offset + file->size) return 1;
240 return 0;
243 static struct file *
244 get_file (struct handle *h, uint64_t offset)
246 return bsearch (&offset, h->files,
247 filenames.len, sizeof (struct file),
248 compare_offset);
251 /* Read data. */
252 static int
253 split_pread (void *handle, void *buf, uint32_t count, uint64_t offset)
255 struct handle *h = handle;
257 while (count > 0) {
258 struct file *file = get_file (h, offset);
259 uint64_t foffs = offset - file->offset;
260 uint64_t max;
261 ssize_t r;
263 max = file->size - foffs;
264 if (max > count)
265 max = count;
267 r = pread (file->fd, buf, max, foffs);
268 if (r == -1) {
269 nbdkit_error ("pread: %m");
270 return -1;
272 if (r == 0) {
273 nbdkit_error ("pread: unexpected end of file");
274 return -1;
276 buf += r;
277 count -= r;
278 offset += r;
281 return 0;
284 /* Write data to the file. */
285 static int
286 split_pwrite (void *handle, const void *buf, uint32_t count, uint64_t offset)
288 struct handle *h = handle;
290 while (count > 0) {
291 struct file *file = get_file (h, offset);
292 uint64_t foffs = offset - file->offset;
293 uint64_t max;
294 ssize_t r;
296 max = file->size - foffs;
297 if (max > count)
298 max = count;
300 r = pwrite (file->fd, buf, max, offset);
301 if (r == -1) {
302 nbdkit_error ("pwrite: %m");
303 return -1;
305 buf += r;
306 count -= r;
307 offset += r;
310 return 0;
313 #if HAVE_POSIX_FADVISE
314 /* Caching. */
315 static int
316 split_cache (void *handle, uint32_t count, uint64_t offset, uint32_t flags)
318 struct handle *h = handle;
320 /* Cache is advisory, we don't care if this fails */
321 while (count > 0) {
322 struct file *file = get_file (h, offset);
323 uint64_t foffs = offset - file->offset;
324 uint64_t max;
325 int r;
327 max = file->size - foffs;
328 if (max > count)
329 max = count;
331 r = posix_fadvise (file->fd, offset, max, POSIX_FADV_WILLNEED);
332 if (r) {
333 errno = r;
334 nbdkit_error ("posix_fadvise: %m");
335 return -1;
337 count -= r;
338 offset += r;
341 return 0;
343 #endif /* HAVE_POSIX_FADVISE */
345 #ifdef SEEK_HOLE
346 static int64_t
347 do_extents (struct file *file, uint32_t count, uint64_t offset,
348 bool req_one, struct nbdkit_extents *extents)
350 int64_t r = 0;
351 uint64_t end = offset + count;
353 do {
354 off_t pos;
356 pos = lseek (file->fd, offset, SEEK_DATA);
357 if (pos == -1) {
358 if (errno == ENXIO) {
359 /* The current man page does not describe this situation well,
360 * but a proposed change to POSIX adds these words for ENXIO:
361 * "or the whence argument is SEEK_DATA and the offset falls
362 * within the final hole of the file."
364 pos = end;
366 else {
367 nbdkit_error ("lseek: SEEK_DATA: %" PRIu64 ": %m", offset);
368 return -1;
372 /* We know there is a hole from offset to pos-1. */
373 if (pos > offset) {
374 if (nbdkit_add_extent (extents, offset + file->offset, pos - offset,
375 NBDKIT_EXTENT_HOLE | NBDKIT_EXTENT_ZERO) == -1)
376 return -1;
377 r += pos - offset;
378 if (req_one)
379 break;
382 offset = pos;
383 if (offset >= end)
384 break;
386 pos = lseek (file->fd, offset, SEEK_HOLE);
387 if (pos == -1) {
388 nbdkit_error ("lseek: SEEK_HOLE: %" PRIu64 ": %m", offset);
389 return -1;
392 /* We know there is data from offset to pos-1. */
393 if (pos > offset) {
394 if (nbdkit_add_extent (extents, offset + file->offset, pos - offset,
395 0 /* allocated data */) == -1)
396 return -1;
397 r += pos - offset;
398 if (req_one)
399 break;
402 offset = pos;
403 } while (offset < end);
405 return r;
408 static int
409 split_extents (void *handle, uint32_t count, uint64_t offset,
410 uint32_t flags, struct nbdkit_extents *extents)
412 struct handle *h = handle;
413 const bool req_one = flags & NBDKIT_FLAG_REQ_ONE;
415 while (count > 0) {
416 struct file *file = get_file (h, offset);
417 uint64_t foffs = offset - file->offset;
418 uint64_t max;
419 int64_t r;
421 max = file->size - foffs;
422 if (max > count)
423 max = count;
425 if (file->can_extents) {
426 ACQUIRE_LOCK_FOR_CURRENT_SCOPE (&lseek_lock);
427 max = r = do_extents (file, max, foffs, req_one, extents);
429 else
430 r = nbdkit_add_extent (extents, offset, max, 0 /* allocated data */);
431 if (r == -1)
432 return -1;
433 count -= max;
434 offset += max;
435 if (req_one)
436 break;
439 return 0;
441 #endif /* SEEK_HOLE */
443 static struct nbdkit_plugin plugin = {
444 .name = "split",
445 .version = PACKAGE_VERSION,
446 .unload = split_unload,
447 .config = split_config,
448 .config_help = split_config_help,
449 .magic_config_key = "file",
450 .open = split_open,
451 .close = split_close,
452 .get_size = split_get_size,
453 .can_cache = split_can_cache,
454 .pread = split_pread,
455 .pwrite = split_pwrite,
456 #if HAVE_POSIX_FADVISE
457 .cache = split_cache,
458 #endif
459 #ifdef SEEK_HOLE
460 .extents = split_extents,
461 #endif
462 /* In this plugin, errno is preserved properly along error return
463 * paths from failed system calls.
465 .errno_is_preserved = 1,
468 NBDKIT_REGISTER_PLUGIN (plugin)