]> git.ipfire.org Git - thirdparty/libvirt.git/commitdiff
util: Introduce virFileInData
authorMichal Privoznik <mprivozn@redhat.com>
Thu, 16 Jun 2016 08:42:47 +0000 (10:42 +0200)
committerMichal Privoznik <mprivozn@redhat.com>
Thu, 18 May 2017 05:42:13 +0000 (07:42 +0200)
This function takes a FD and determines whether the current
position is in data section or in a hole. In addition to that,
it also determines how much bytes are there remaining till the
current section ends.

Signed-off-by: Michal Privoznik <mprivozn@redhat.com>
src/libvirt_private.syms
src/util/virfile.c
src/util/virfile.h
tests/virfiletest.c

index bbe283529b617cf7c87fe11dd0cbea26e61f7630..4102a002ba3e06dbd8aab89ec71c1826e857951c 100644 (file)
@@ -1628,6 +1628,7 @@ virFileGetHugepageSize;
 virFileGetMountReverseSubtree;
 virFileGetMountSubtree;
 virFileHasSuffix;
+virFileInData;
 virFileIsAbsPath;
 virFileIsDir;
 virFileIsExecutable;
index ea44a647ce7cdb75caa0a20c8e1c3466705f8acd..2f4bc42b63e9b9e1c3ac5f34c31b1483c69db39f 100644 (file)
@@ -3793,6 +3793,114 @@ virFileComparePaths(const char *p1, const char *p2)
  cleanup:
     VIR_FREE(res1);
     VIR_FREE(res2);
+
+    return ret;
+}
+
+
+/**
+ * virFileInData:
+ * @fd: file to check
+ * @inData: true if current position in the @fd is in data section
+ * @length: amount of bytes until the end of the current section
+ *
+ * With sparse files not every extent has to be physically stored on
+ * the disk. This results in so called data or hole sections.  This
+ * function checks whether the current position in the file @fd is
+ * in a data section (@inData = 1) or in a hole (@inData = 0). Also,
+ * it sets @length to match the number of bytes remaining until the
+ * end of the current section.
+ *
+ * As a special case, there is an implicit hole at the end of any
+ * file. In this case, the function sets @inData = 0, @length = 0.
+ *
+ * Upon its return, the position in the @fd is left unchanged, i.e.
+ * despite this function lseek()-ing back and forth it always
+ * restores the original position in the file.
+ *
+ * NB, @length is type of long long because it corresponds to off_t
+ * the best.
+ *
+ * Returns 0 on success,
+ *        -1 otherwise.
+ */
+int
+virFileInData(int fd,
+              int *inData,
+              long long *length)
+{
+    int ret = -1;
+    off_t cur, data, hole, end;
+
+    /* Get current position */
+    cur = lseek(fd, 0, SEEK_CUR);
+    if (cur == (off_t) -1) {
+        virReportSystemError(errno, "%s",
+                             _("Unable to get current position in file"));
+        goto cleanup;
+    }
+
+    /* Now try to get data and hole offsets */
+    data = lseek(fd, cur, SEEK_DATA);
+
+    /* There are four options:
+     * 1) data == cur;  @cur is in data
+     * 2) data > cur; @cur is in a hole, next data at @data
+     * 3) data < 0, errno = ENXIO; either @cur is in trailing hole, or @cur is beyond EOF.
+     * 4) data < 0, errno != ENXIO; we learned nothing
+     */
+
+    if (data == (off_t) -1) {
+        /* cases 3 and 4 */
+        if (errno != ENXIO) {
+            virReportSystemError(errno, "%s",
+                                 _("Unable to seek to data"));
+            goto cleanup;
+        }
+
+        *inData = 0;
+        /* There are two situations now. There is always an
+         * implicit hole at EOF. However, there might be a
+         * trailing hole just before EOF too. If that's the case
+         * report it. */
+        if ((end = lseek(fd, 0, SEEK_END)) == (off_t) -1) {
+            virReportSystemError(errno, "%s",
+                                 _("Unable to seek to EOF"));
+            goto cleanup;
+        }
+        *length = end - cur;
+    } else if (data > cur) {
+        /* case 2 */
+        *inData = 0;
+        *length = data - cur;
+    } else {
+        /* case 1 */
+        *inData = 1;
+
+        /* We don't know where does the next hole start. Let's
+         * find out. Here we get the same 4 possibilities as
+         * described above.*/
+        hole = lseek(fd, data, SEEK_HOLE);
+        if (hole == (off_t) -1 || hole == data) {
+            /* cases 1, 3 and 4 */
+            /* Wait a second. The reason why we are here is
+             * because we are in data. But at the same time we
+             * are in a trailing hole? Wut!? Do the best what we
+             * can do here. */
+            virReportSystemError(errno, "%s",
+                                 _("unable to seek to hole"));
+            goto cleanup;
+        } else {
+            /* case 2 */
+            *length = (hole - data);
+        }
+    }
+
+    ret = 0;
+ cleanup:
+    /* At any rate, reposition back to where we started. */
+    if (cur != (off_t) -1)
+        ignore_value(lseek(fd, cur, SEEK_SET));
     return ret;
 }
 
index 38e938f87f12f47fb0286d7c4c8819b295375d38..57ceb807210c01358c7f057a3e37117c200db355 100644 (file)
@@ -348,4 +348,8 @@ int virFileReadValueString(char **value, const char *format, ...)
  ATTRIBUTE_FMT_PRINTF(2, 3);
 
 
+int virFileInData(int fd,
+                  int *inData,
+                  long long *length);
+
 #endif /* __VIR_FILE_H */
index 702a76a501c96fcb27c55b1e0f51b1ace8f678d0..a93bee01ab8b03deb28c11616b51c6bb86a49c22 100644 (file)
@@ -21,6 +21,7 @@
 #include <config.h>
 
 #include <stdlib.h>
+#include <fcntl.h>
 
 #include "testutils.h"
 #include "virfile.h"
@@ -118,6 +119,190 @@ testFileSanitizePath(const void *opaque)
 }
 
 
+static int
+makeSparseFile(const off_t offsets[],
+               const bool startData);
+
+#ifdef __linux__
+/* Create a sparse file. @offsets in KiB. */
+static int
+makeSparseFile(const off_t offsets[],
+               const bool startData)
+{
+    int fd = -1;
+    char path[] = abs_builddir "fileInData.XXXXXX";
+    off_t len = 0;
+    size_t i;
+
+    if ((fd = mkostemp(path,  O_CLOEXEC|O_RDWR)) < 0)
+        goto error;
+
+    if (unlink(path) < 0)
+        goto error;
+
+    for (i = 0; offsets[i] != (off_t) -1; i++)
+        len += offsets[i] * 1024;
+
+    while (len) {
+        const char buf[] = "abcdefghijklmnopqrstuvwxyz";
+        off_t toWrite = sizeof(buf);
+
+        if (toWrite > len)
+            toWrite = len;
+
+        if (safewrite(fd, buf, toWrite) < 0) {
+            fprintf(stderr, "unable to write to %s (errno=%d)\n", path, errno);
+            goto error;
+        }
+
+        len -= toWrite;
+    }
+
+    len = 0;
+    for (i = 0; offsets[i] != (off_t) -1; i++) {
+        bool inData = startData;
+
+        if (i % 2)
+            inData = !inData;
+
+        if (!inData &&
+            fallocate(fd,
+                      FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE,
+                      len, offsets[i] * 1024) < 0) {
+            fprintf(stderr, "unable to punch a hole at offset %lld length %lld\n",
+                    (long long) len, (long long) offsets[i]);
+            goto error;
+        }
+
+        len += offsets[i] * 1024;
+    }
+
+    if (lseek(fd, 0, SEEK_SET) == (off_t) -1) {
+        fprintf(stderr, "unable to lseek (errno=%d)\n", errno);
+        goto error;
+    }
+
+    return fd;
+ error:
+    VIR_FORCE_CLOSE(fd);
+    return -1;
+}
+
+#else /* !__linux__ */
+
+static int
+makeSparseFile(const off_t offsets[] ATTRIBUTE_UNUSED,
+               const bool startData ATTRIBUTE_UNUSED)
+{
+    return -1;
+}
+
+#endif /* !__linux__ */
+
+
+#define EXTENT 4
+static bool
+holesSupported(void)
+{
+    off_t offsets[] = {EXTENT, EXTENT, EXTENT, -1};
+    off_t tmp;
+    int fd;
+    bool ret = false;
+
+    if ((fd = makeSparseFile(offsets, true)) < 0)
+        goto cleanup;
+
+    /* The way this works is: there are 4K of data followed by 4K hole followed
+     * by 4K hole again. Check if the filesystem we are running the test suite
+     * on supports holes. */
+    if ((tmp = lseek(fd, 0, SEEK_DATA)) == (off_t) -1)
+        goto cleanup;
+
+    if (tmp != 0)
+        goto cleanup;
+
+    if ((tmp = lseek(fd, tmp, SEEK_HOLE)) == (off_t) -1)
+        goto cleanup;
+
+    if (tmp != EXTENT * 1024)
+        goto cleanup;
+
+    if ((tmp = lseek(fd, tmp, SEEK_DATA)) == (off_t) -1)
+        goto cleanup;
+
+    if (tmp != 2 * EXTENT * 1024)
+        goto cleanup;
+
+    if ((tmp = lseek(fd, tmp, SEEK_HOLE)) == (off_t) -1)
+        goto cleanup;
+
+    if (tmp != 3 * EXTENT * 1024)
+        goto cleanup;
+
+    ret = true;
+ cleanup:
+    VIR_FORCE_CLOSE(fd);
+    return ret;
+}
+
+
+struct testFileInData {
+    bool startData;     /* whether the list of offsets starts with data section */
+    off_t *offsets;
+};
+
+
+static int
+testFileInData(const void *opaque)
+{
+    const struct testFileInData *data = opaque;
+    int fd = -1;
+    int ret = -1;
+    size_t i;
+
+    if ((fd = makeSparseFile(data->offsets, data->startData)) < 0)
+        goto cleanup;
+
+    for (i = 0; data->offsets[i] != (off_t) -1; i++) {
+        bool shouldInData = data->startData;
+        int realInData;
+        long long shouldLen;
+        long long realLen;
+
+        if (i % 2)
+            shouldInData = !shouldInData;
+
+        if (virFileInData(fd, &realInData, &realLen) < 0)
+            goto cleanup;
+
+        if (realInData != shouldInData) {
+            fprintf(stderr, "Unexpected data/hole. Expected %s got %s\n",
+                    shouldInData ? "data" : "hole",
+                    realInData ? "data" : "hole");
+            goto cleanup;
+        }
+
+        shouldLen = data->offsets[i] * 1024;
+        if (realLen != shouldLen) {
+            fprintf(stderr, "Unexpected section length. Expected %lld got %lld\n",
+                    shouldLen, realLen);
+            goto cleanup;
+        }
+
+        if (lseek(fd, shouldLen, SEEK_CUR) < 0) {
+            fprintf(stderr, "Unable to seek\n");
+            goto cleanup;
+        }
+    }
+
+    ret = 0;
+
+ cleanup:
+    VIR_FORCE_CLOSE(fd);
+    return ret;
+}
+
+
 static int
 mymain(void)
 {
@@ -186,6 +371,24 @@ mymain(void)
     DO_TEST_SANITIZE_PATH_SAME("gluster://bar.baz/fooo//hoo");
     DO_TEST_SANITIZE_PATH_SAME("gluster://bar.baz/fooo///////hoo");
 
+#define DO_TEST_IN_DATA(inData, ...)                                        \
+    do {                                                                    \
+        off_t offsets[] = {__VA_ARGS__, -1};                                \
+        struct testFileInData data = {                                      \
+            .startData = inData, .offsets = offsets,                        \
+        };                                                                  \
+        if (virTestRun(virTestCounterNext(), testFileInData, &data) < 0)    \
+            ret = -1;                                                       \
+    } while (0)
+
+    if (holesSupported()) {
+        DO_TEST_IN_DATA(true, 4, 4, 4);
+        DO_TEST_IN_DATA(false, 4, 4, 4);
+        DO_TEST_IN_DATA(true, 8, 8, 8);
+        DO_TEST_IN_DATA(false, 8, 8, 8);
+        DO_TEST_IN_DATA(true, 8, 16, 32, 64, 128, 256, 512);
+        DO_TEST_IN_DATA(false, 8, 16, 32, 64, 128, 256, 512);
+    }
     return ret != 0 ? EXIT_FAILURE : EXIT_SUCCESS;
 }