[libvirt] [PATCHv5 02/19] util: storagefile: Introduce universal function to canonicalize paths

Peter Krempa pkrempa at redhat.com
Thu Jun 19 13:59:27 UTC 2014


Introduce a common function that will take a callback to resolve links
that will be used to canonicalize paths on various storage systems and
add extensive tests.
---
 src/libvirt_private.syms  |   1 +
 src/util/virstoragefile.c | 195 ++++++++++++++++++++++++++++++++++++++++++++++
 src/util/virstoragefile.h |   7 ++
 tests/virstoragetest.c    | 108 +++++++++++++++++++++++++
 4 files changed, 311 insertions(+)

diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms
index 6bd87e6..4ed08ca 100644
--- a/src/libvirt_private.syms
+++ b/src/libvirt_private.syms
@@ -1873,6 +1873,7 @@ virStorageGenerateQcowPassphrase;


 # util/virstoragefile.h
+virStorageFileCanonicalizePath;
 virStorageFileChainGetBroken;
 virStorageFileChainLookup;
 virStorageFileFeatureTypeFromString;
diff --git a/src/util/virstoragefile.c b/src/util/virstoragefile.c
index 09b5d10..efae692 100644
--- a/src/util/virstoragefile.c
+++ b/src/util/virstoragefile.c
@@ -40,6 +40,7 @@
 #include "virutil.h"
 #include "viruri.h"
 #include "dirname.h"
+#include "virbuffer.h"
 #if HAVE_SYS_SYSCALL_H
 # include <sys/syscall.h>
 #endif
@@ -1935,3 +1936,197 @@ virStorageSourceNewFromBacking(virStorageSourcePtr parent)

     return ret;
 }
+
+
+static char *
+virStorageFileCanonicalizeFormatPath(char **components,
+                                     size_t ncomponents,
+                                     bool beginSlash,
+                                     bool beginDoubleSlash)
+{
+    virBuffer buf = VIR_BUFFER_INITIALIZER;
+    size_t i;
+    char *ret = NULL;
+
+    if (beginSlash)
+        virBufferAddLit(&buf, "/");
+
+    if (beginDoubleSlash)
+        virBufferAddLit(&buf, "/");
+
+    for (i = 0; i < ncomponents; i++) {
+        if (i != 0)
+            virBufferAddLit(&buf, "/");
+
+        virBufferAdd(&buf, components[i], -1);
+    }
+
+    if (virBufferError(&buf) != 0) {
+        virReportOOMError();
+        return NULL;
+    }
+
+    /* if the output string is empty just return an empty string */
+    if (!(ret = virBufferContentAndReset(&buf)))
+        ignore_value(VIR_STRDUP(ret, ""));
+
+    return ret;
+}
+
+
+static int
+virStorageFileCanonicalizeInjectSymlink(const char *path,
+                                        size_t at,
+                                        char ***components,
+                                        size_t *ncomponents)
+{
+    char **tmp = NULL;
+    char **next;
+    size_t ntmp = 0;
+    int ret = -1;
+
+    if (!(tmp = virStringSplitCount(path, "/", 0, &ntmp)))
+        goto cleanup;
+
+    /* prepend */
+    for (next = tmp; *next; next++) {
+        if (VIR_INSERT_ELEMENT(*components, at, *ncomponents, *next) < 0)
+            goto cleanup;
+
+        at++;
+    }
+
+    ret = 0;
+
+ cleanup:
+    virStringFreeListCount(tmp, ntmp);
+    return ret;
+}
+
+
+char *
+virStorageFileCanonicalizePath(const char *path,
+                               virStorageFileSimplifyPathReadlinkCallback cb,
+                               void *cbdata)
+{
+    virHashTablePtr cycle = NULL;
+    bool beginSlash = false;
+    bool beginDoubleSlash = false;
+    char **components = NULL;
+    size_t ncomponents = 0;
+    char *linkpath = NULL;
+    char *currentpath = NULL;
+    size_t i = 0;
+    int rc;
+    char *ret = NULL;
+
+    if (path[0] == '/') {
+        beginSlash = true;
+
+        if (path[1] == '/' && path[2] != '/')
+            beginDoubleSlash = true;
+    }
+
+    if (!(cycle = virHashCreate(10, NULL)))
+        goto cleanup;
+
+    if (!(components = virStringSplitCount(path, "/", 0, &ncomponents)))
+        goto cleanup;
+
+    while (i < ncomponents) {
+        /* skip slashes and '.'s */
+        if (STREQ(components[i], "") ||
+            STREQ(components[i], ".")) {
+            VIR_FREE(components[i]);
+            VIR_DELETE_ELEMENT(components, i, ncomponents);
+            continue;
+        }
+
+        /* resolve changes to parent directory */
+        if (STREQ(components[i], "..")) {
+            if (!beginSlash &&
+                (i == 0 || STREQ(components[i - 1], ".."))) {
+                i++;
+                continue;
+            }
+
+            VIR_FREE(components[i]);
+            VIR_DELETE_ELEMENT(components, i, ncomponents);
+
+            if (i != 0) {
+                VIR_FREE(components[i - 1]);
+                VIR_DELETE_ELEMENT(components, i - 1, ncomponents);
+                i--;
+            }
+
+            continue;
+        }
+
+        /* check if the actual path isn't resulting into a symlink */
+        if (!(currentpath = virStorageFileCanonicalizeFormatPath(components,
+                                                                 i + 1,
+                                                                 beginSlash,
+                                                                 beginDoubleSlash)))
+            goto cleanup;
+
+        if ((rc = cb(currentpath, &linkpath, cbdata)) < 0)
+            goto cleanup;
+
+        if (rc == 0) {
+            if (virHashLookup(cycle, currentpath)) {
+                virReportSystemError(ELOOP,
+                                     _("Failed to canonicalize path '%s'"), path);
+                goto cleanup;
+            }
+
+            if (virHashAddEntry(cycle, currentpath, (void *) 1) < 0)
+                goto cleanup;
+
+            if (linkpath[0] == '/') {
+                /* kill everything from the beginning including the actual component */
+                i++;
+                while (i--) {
+                    VIR_FREE(components[0]);
+                    VIR_DELETE_ELEMENT(components, 0, ncomponents);
+                }
+                beginSlash = true;
+
+                if (linkpath[1] == '/' && linkpath[2] != '/')
+                    beginDoubleSlash = true;
+                else
+                    beginDoubleSlash = false;
+
+                i = 0;
+            } else {
+                VIR_FREE(components[i]);
+                VIR_DELETE_ELEMENT(components, i, ncomponents);
+            }
+
+            if (virStorageFileCanonicalizeInjectSymlink(linkpath,
+                                                        i,
+                                                        &components,
+                                                        &ncomponents) < 0)
+                goto cleanup;
+
+            VIR_FREE(linkpath);
+            VIR_FREE(currentpath);
+
+            continue;
+        }
+
+        VIR_FREE(currentpath);
+
+        i++;
+    }
+
+    ret = virStorageFileCanonicalizeFormatPath(components, ncomponents,
+                                               beginSlash, beginDoubleSlash);
+
+ cleanup:
+    virHashFree(cycle);
+    virStringFreeListCount(components, ncomponents);
+    VIR_FREE(linkpath);
+    VIR_FREE(currentpath);
+
+    return ret;
+}
diff --git a/src/util/virstoragefile.h b/src/util/virstoragefile.h
index 34b3625..fd5c89e 100644
--- a/src/util/virstoragefile.h
+++ b/src/util/virstoragefile.h
@@ -325,5 +325,12 @@ void virStorageSourceFree(virStorageSourcePtr def);
 void virStorageSourceClearBackingStore(virStorageSourcePtr def);
 virStorageSourcePtr virStorageSourceNewFromBacking(virStorageSourcePtr parent);

+typedef int
+(*virStorageFileSimplifyPathReadlinkCallback)(const char *path,
+                                              char **link,
+                                              void *data);
+char *virStorageFileCanonicalizePath(const char *path,
+                                     virStorageFileSimplifyPathReadlinkCallback cb,
+                                     void *cbdata);

 #endif /* __VIR_STORAGE_FILE_H__ */
diff --git a/tests/virstoragetest.c b/tests/virstoragetest.c
index e15578c..0bc4a42 100644
--- a/tests/virstoragetest.c
+++ b/tests/virstoragetest.c
@@ -525,6 +525,71 @@ testStorageLookup(const void *args)
     return ret;
 }

+
+struct testPathCanonicalizeData
+{
+    const char *path;
+    const char *expect;
+};
+
+static const char *testPathCanonicalizeSymlinks[][2] =
+{
+    {"/path/blah", "/other/path/huzah"},
+    {"/path/to/relative/symlink", "../../actual/file"},
+    {"/cycle", "/cycle"},
+    {"/cycle2/link", "./link"},
+};
+
+static int
+testPathCanonicalizeReadlink(const char *path,
+                             char **link,
+                             void *data ATTRIBUTE_UNUSED)
+{
+    size_t i;
+
+    *link = NULL;
+
+    for (i = 0; i < ARRAY_CARDINALITY(testPathCanonicalizeSymlinks); i++) {
+        if (STREQ(path, testPathCanonicalizeSymlinks[i][0])) {
+            if (VIR_STRDUP(*link, testPathCanonicalizeSymlinks[i][1]) < 0)
+                return -1;
+
+            return 0;
+        }
+    }
+
+    return 1;
+}
+
+
+static int
+testPathCanonicalize(const void *args)
+{
+    const struct testPathCanonicalizeData *data = args;
+    char *canon = NULL;
+    int ret = -1;
+
+    canon = virStorageFileCanonicalizePath(data->path,
+                                           testPathCanonicalizeReadlink,
+                                           NULL);
+
+    if (STRNEQ_NULLABLE(data->expect, canon)) {
+        fprintf(stderr,
+                "path canonicalization of '%s' failed: expected '%s' got '%s'\n",
+                data->path, NULLSTR(data->expect), NULLSTR(canon));
+
+        goto cleanup;
+    }
+
+    ret = 0;
+
+ cleanup:
+    VIR_FREE(canon);
+
+    return ret;
+}
+
+
 static int
 mymain(void)
 {
@@ -532,6 +597,7 @@ mymain(void)
     virCommandPtr cmd = NULL;
     struct testChainData data;
     struct testLookupData data2;
+    struct testPathCanonicalizeData data3;
     virStorageSourcePtr chain = NULL;
     virStorageSourcePtr chain2; /* short for chain->backingStore */
     virStorageSourcePtr chain3; /* short for chain2->backingStore */
@@ -1072,6 +1138,48 @@ mymain(void)
     TEST_LOOKUP_TARGET(80, "vda", chain3, "vda[2]", 2, NULL, NULL, NULL);
     TEST_LOOKUP_TARGET(81, "vda", NULL, "vda[3]", 3, NULL, NULL, NULL);

+#define TEST_PATH_CANONICALIZE(id, PATH, EXPECT)                            \
+    do {                                                                    \
+        data3.path = PATH;                                                  \
+        data3.expect = EXPECT;                                              \
+        if (virtTestRun("Path canonicalize " #id,                           \
+                        testPathCanonicalize, &data3) < 0)                  \
+            ret = -1;                                                       \
+    } while (0)
+
+    TEST_PATH_CANONICALIZE(1, "/", "/");
+    TEST_PATH_CANONICALIZE(2, "/path", "/path");
+    TEST_PATH_CANONICALIZE(3, "/path/to/blah", "/path/to/blah");
+    TEST_PATH_CANONICALIZE(4, "/path/", "/path");
+    TEST_PATH_CANONICALIZE(5, "///////", "/");
+    TEST_PATH_CANONICALIZE(6, "//", "//");
+    TEST_PATH_CANONICALIZE(7, "", "");
+    TEST_PATH_CANONICALIZE(8, ".", "");
+    TEST_PATH_CANONICALIZE(9, "../", "..");
+    TEST_PATH_CANONICALIZE(10, "../../", "../..");
+    TEST_PATH_CANONICALIZE(11, "../../blah", "../../blah");
+    TEST_PATH_CANONICALIZE(12, "/./././blah", "/blah");
+    TEST_PATH_CANONICALIZE(13, ".././../././../blah", "../../../blah");
+    TEST_PATH_CANONICALIZE(14, "/././", "/");
+    TEST_PATH_CANONICALIZE(15, "./././", "");
+    TEST_PATH_CANONICALIZE(16, "blah/../foo", "foo");
+    TEST_PATH_CANONICALIZE(17, "foo/bar/../blah", "foo/blah");
+    TEST_PATH_CANONICALIZE(18, "foo/bar/.././blah", "foo/blah");
+    TEST_PATH_CANONICALIZE(19, "/path/to/foo/bar/../../../../../../../../baz", "/baz");
+    TEST_PATH_CANONICALIZE(20, "path/to/foo/bar/../../../../../../../../baz", "../../../../baz");
+    TEST_PATH_CANONICALIZE(21, "path/to/foo/bar", "path/to/foo/bar");
+    TEST_PATH_CANONICALIZE(22, "//foo//bar", "//foo/bar");
+    TEST_PATH_CANONICALIZE(23, "/bar//foo", "/bar/foo");
+    TEST_PATH_CANONICALIZE(24, "//../blah", "//blah");
+
+    /* test paths with symlinks */
+    TEST_PATH_CANONICALIZE(25, "/path/blah", "/other/path/huzah");
+    TEST_PATH_CANONICALIZE(26, "/path/to/relative/symlink", "/path/actual/file");
+    TEST_PATH_CANONICALIZE(27, "/path/to/relative/symlink/blah", "/path/actual/file/blah");
+    TEST_PATH_CANONICALIZE(28, "/path/blah/yippee", "/other/path/huzah/yippee");
+    TEST_PATH_CANONICALIZE(29, "/cycle", NULL);
+    TEST_PATH_CANONICALIZE(30, "/cycle2/link", NULL);
+
  cleanup:
     /* Final cleanup */
     virStorageSourceFree(chain);
-- 
1.9.3




More information about the libvir-list mailing list