4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions are
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
43 #include <sys/types.h>
48 #include <nbdkit-plugin.h>
53 #include "string-vector.h"
54 #include "windows-compat.h"
57 static string_vector filenames
= empty_vector
;
60 /* Any callbacks using lseek must be protected by this lock. */
61 static pthread_mutex_t lseek_lock
= PTHREAD_MUTEX_INITIALIZER
;
67 string_vector_empty (&filenames
);
71 split_config (const char *key
, const char *value
)
75 if (strcmp (key
, "file") == 0) {
76 s
= nbdkit_realpath (value
);
79 if (string_vector_append (&filenames
, s
) == -1) {
80 nbdkit_error ("realloc: %m");
85 nbdkit_error ("unknown parameter '%s'", key
);
92 #define split_config_help \
93 "file=<FILENAME> (required) File(s) to serve."
95 /* The per-connection handle. */
98 uint64_t size
; /* Total concatenated size. */
102 uint64_t offset
, size
;
107 /* Create the per-connection handle. */
109 split_open (int readonly
)
120 h
= malloc (sizeof *h
);
122 nbdkit_error ("malloc: %m");
126 h
->files
= malloc (filenames
.len
* sizeof (struct file
));
127 if (h
->files
== NULL
) {
128 nbdkit_error ("malloc: %m");
132 for (i
= 0; i
< filenames
.len
; ++i
)
135 /* Open the files. */
136 flags
= O_CLOEXEC
|O_NOCTTY
;
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
]);
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
]);
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
);
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;
173 h
->files
[i
].can_extents
= true;
175 h
->files
[i
].can_extents
= false;
179 nbdkit_debug ("total size=%" PRIu64
, h
->size
);
184 for (i
= 0; i
< filenames
.len
; ++i
) {
185 if (h
->files
[i
].fd
>= 0)
186 close (h
->files
[i
].fd
);
193 /* Free up the per-connection handle. */
195 split_close (void *handle
)
197 struct handle
*h
= handle
;
200 for (i
= 0; i
< filenames
.len
; ++i
)
201 close (h
->files
[i
].fd
);
206 #define THREAD_MODEL NBDKIT_THREAD_MODEL_SERIALIZE_REQUESTS
208 /* Get the disk size. */
210 split_get_size (void *handle
)
212 struct handle
*h
= handle
;
214 return (int64_t) h
->size
;
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
224 #if HAVE_POSIX_FADVISE
225 return NBDKIT_FUA_NATIVE
;
227 return NBDKIT_FUA_EMULATE
;
231 /* Helper function to map the offset to the correct file. */
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;
244 get_file (struct handle
*h
, uint64_t offset
)
246 return bsearch (&offset
, h
->files
,
247 filenames
.len
, sizeof (struct file
),
253 split_pread (void *handle
, void *buf
, uint32_t count
, uint64_t offset
)
255 struct handle
*h
= handle
;
258 struct file
*file
= get_file (h
, offset
);
259 uint64_t foffs
= offset
- file
->offset
;
263 max
= file
->size
- foffs
;
267 r
= pread (file
->fd
, buf
, max
, foffs
);
269 nbdkit_error ("pread: %m");
273 nbdkit_error ("pread: unexpected end of file");
284 /* Write data to the file. */
286 split_pwrite (void *handle
, const void *buf
, uint32_t count
, uint64_t offset
)
288 struct handle
*h
= handle
;
291 struct file
*file
= get_file (h
, offset
);
292 uint64_t foffs
= offset
- file
->offset
;
296 max
= file
->size
- foffs
;
300 r
= pwrite (file
->fd
, buf
, max
, offset
);
302 nbdkit_error ("pwrite: %m");
313 #if HAVE_POSIX_FADVISE
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 */
322 struct file
*file
= get_file (h
, offset
);
323 uint64_t foffs
= offset
- file
->offset
;
327 max
= file
->size
- foffs
;
331 r
= posix_fadvise (file
->fd
, offset
, max
, POSIX_FADV_WILLNEED
);
334 nbdkit_error ("posix_fadvise: %m");
343 #endif /* HAVE_POSIX_FADVISE */
347 do_extents (struct file
*file
, uint32_t count
, uint64_t offset
,
348 bool req_one
, struct nbdkit_extents
*extents
)
351 uint64_t end
= offset
+ count
;
356 pos
= lseek (file
->fd
, offset
, SEEK_DATA
);
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."
367 nbdkit_error ("lseek: SEEK_DATA: %" PRIu64
": %m", offset
);
372 /* We know there is a hole from offset to pos-1. */
374 if (nbdkit_add_extent (extents
, offset
+ file
->offset
, pos
- offset
,
375 NBDKIT_EXTENT_HOLE
| NBDKIT_EXTENT_ZERO
) == -1)
386 pos
= lseek (file
->fd
, offset
, SEEK_HOLE
);
388 nbdkit_error ("lseek: SEEK_HOLE: %" PRIu64
": %m", offset
);
392 /* We know there is data from offset to pos-1. */
394 if (nbdkit_add_extent (extents
, offset
+ file
->offset
, pos
- offset
,
395 0 /* allocated data */) == -1)
403 } while (offset
< end
);
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
;
416 struct file
*file
= get_file (h
, offset
);
417 uint64_t foffs
= offset
- file
->offset
;
421 max
= file
->size
- foffs
;
425 if (file
->can_extents
) {
426 ACQUIRE_LOCK_FOR_CURRENT_SCOPE (&lseek_lock
);
427 max
= r
= do_extents (file
, max
, foffs
, req_one
, extents
);
430 r
= nbdkit_add_extent (extents
, offset
, max
, 0 /* allocated data */);
441 #endif /* SEEK_HOLE */
443 static struct nbdkit_plugin plugin
= {
445 .version
= PACKAGE_VERSION
,
446 .unload
= split_unload
,
447 .config
= split_config
,
448 .config_help
= split_config_help
,
449 .magic_config_key
= "file",
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
,
460 .extents
= split_extents
,
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
)