This uses lseek SEEK_DATA/SEEK_HOLE to search for allocated data and
holes in the underlying file.
---
plugins/file/file.c | 142 ++++++++++++++++++++++++++++++++++++++++----
1 file changed, 132 insertions(+), 10 deletions(-)
diff --git a/plugins/file/file.c b/plugins/file/file.c
index 628f8fb..704deaa 100644
--- a/plugins/file/file.c
+++ b/plugins/file/file.c
@@ -1,5 +1,5 @@
/* nbdkit
- * Copyright (C) 2013-2018 Red Hat Inc.
+ * Copyright (C) 2013-2019 Red Hat Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
@@ -37,6 +37,7 @@
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
+#include <inttypes.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/types.h>
@@ -44,6 +45,8 @@
#include <sys/ioctl.h>
#include <errno.h>
+#include <pthread.h>
+
#if defined(__linux__) && !defined(FALLOC_FL_PUNCH_HOLE)
#include <linux/falloc.h> /* For FALLOC_FL_*, glibc < 2.18 */
#endif
@@ -68,7 +71,11 @@
static char *filename = NULL;
-int file_debug_zero; /* to enable: -D file.zero=1 */
+/* Any callbacks using lseek must be protected by this lock. */
+static pthread_mutex_t lseek_lock = PTHREAD_MUTEX_INITIALIZER;
+
+/* to enable: -D file.zero=1 */
+int file_debug_zero;
static void
file_unload (void)
@@ -220,6 +227,21 @@ file_close (void *handle)
#define THREAD_MODEL NBDKIT_THREAD_MODEL_PARALLEL
+/* For block devices, stat->st_size is not the true size. */
+static int64_t
+block_device_size (int fd)
+{
+ off_t size;
+
+ size = lseek (fd, 0, SEEK_END);
+ if (size == -1) {
+ nbdkit_error ("lseek (to find device size): %m");
+ return -1;
+ }
+
+ return size;
+}
+
/* Get the file size. */
static int64_t
file_get_size (void *handle)
@@ -227,15 +249,11 @@ file_get_size (void *handle)
struct handle *h = handle;
if (h->is_block_device) {
- /* Block device, so st_size will not be the true size. */
- off_t size;
-
- size = lseek (h->fd, 0, SEEK_END);
- if (size == -1) {
- nbdkit_error ("lseek (to find device size): %m");
- return -1;
- }
+ int64_t size;
+ pthread_mutex_lock (&lseek_lock);
+ size = block_device_size (h->fd);
+ pthread_mutex_unlock (&lseek_lock);
return size;
} else {
/* Regular file. */
@@ -501,6 +519,106 @@ file_trim (void *handle, uint32_t count, uint64_t offset, uint32_t
flags)
return 0;
}
+#ifdef SEEK_HOLE
+/* Extents. */
+
+static int
+file_can_extents (void *handle)
+{
+ struct handle *h = handle;
+ off_t r;
+
+ /* A simple test to see whether SEEK_HOLE etc is likely to work on
+ * the current filesystem.
+ */
+ pthread_mutex_lock (&lseek_lock);
+ r = lseek (h->fd, 0, SEEK_HOLE);
+ pthread_mutex_unlock (&lseek_lock);
+ if (r == -1) {
+ nbdkit_debug ("extents disabled: lseek: SEEK_HOLE: %m");
+ return 0;
+ }
+ return 1;
+}
+
+static int
+do_extents (void *handle, uint32_t count, uint64_t offset,
+ uint32_t flags, struct nbdkit_extents_map *extents_map)
+{
+ struct handle *h = handle;
+ const bool req_one = flags & NBDKIT_FLAG_REQ_ONE;
+ size_t nr_extents = 0;
+ uint64_t end = offset + count;
+
+ do {
+ off_t pos;
+
+ pos = lseek (h->fd, offset, SEEK_DATA);
+ if (pos == -1) {
+ if (errno == ENXIO) {
+ /* The current man page does not describe this situation well,
+ * but a proposed change to POSIX adds these words for ENXIO:
+ * "or the whence argument is SEEK_DATA and the offset falls
+ * within the final hole of the file."
+ */
+ pos = end;
+ }
+ else {
+ nbdkit_error ("lseek: SEEK_DATA: %" PRIu64 ": %m", offset);
+ return -1;
+ }
+ }
+
+ /* We know there is a hole from offset to pos-1. */
+ if (pos > offset) {
+ if (nbdkit_extent_add (extents_map, offset, pos - offset,
+ NBDKIT_EXTENT_HOLE | NBDKIT_EXTENT_ZERO) == -1)
+ return -1;
+ nr_extents++;
+ }
+
+ offset = pos;
+ if (offset >= end)
+ break;
+
+ pos = lseek (h->fd, offset, SEEK_HOLE);
+ if (pos == -1) {
+ nbdkit_error ("lseek: SEEK_HOLE: %" PRIu64 ": %m", offset);
+ return -1;
+ }
+
+ /* We know there is data from offset to pos-1, but we don't have
+ * to add this to the map because that is the default.
+ */
+ if (pos > offset)
+ nr_extents++;
+
+ offset = pos;
+
+ /* If the req_one flag is set, we must want until TWO extents have
+ * been added so that we have the limit of the first extent.
+ */
+ if (req_one && nr_extents >= 2)
+ break;
+ } while (offset < end);
+
+ return 0;
+}
+
+static int
+file_extents (void *handle, uint32_t count, uint64_t offset,
+ uint32_t flags, struct nbdkit_extents_map *extents_map)
+{
+ int r;
+
+ pthread_mutex_lock (&lseek_lock);
+ r = do_extents (handle, count, offset, flags, extents_map);
+ pthread_mutex_unlock (&lseek_lock);
+
+ return r;
+}
+#endif /* SEEK_HOLE */
+
static struct nbdkit_plugin plugin = {
.name = "file",
.longname = "nbdkit file plugin",
@@ -522,6 +640,10 @@ static struct nbdkit_plugin plugin = {
.flush = file_flush,
.trim = file_trim,
.zero = file_zero,
+#ifdef SEEK_HOLE
+ .can_extents = file_can_extents,
+ .extents = file_extents,
+#endif
.errno_is_preserved = 1,
};
--
2.20.1