]> git.ipfire.org Git - thirdparty/libvirt.git/commitdiff
util: storagefile: Introduce universal function to canonicalize paths
authorPeter Krempa <pkrempa@redhat.com>
Fri, 2 May 2014 17:22:17 +0000 (19:22 +0200)
committerPeter Krempa <pkrempa@redhat.com>
Tue, 24 Jun 2014 08:45:43 +0000 (10:45 +0200)
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
src/util/virstoragefile.c
src/util/virstoragefile.h
tests/virstoragetest.c

index a5f9ede81016a6a6c04ed3dbf4bf25d27857068f..fd0191307238d491de61af688a6709e7d65eee73 100644 (file)
@@ -1878,6 +1878,7 @@ virStorageGenerateQcowPassphrase;
 
 
 # util/virstoragefile.h
+virStorageFileCanonicalizePath;
 virStorageFileChainGetBroken;
 virStorageFileChainLookup;
 virStorageFileFeatureTypeFromString;
index db2b4019c2e5d70c5d169169593a8f344c07df55..613ba3cf49268d327c69892976a545b4916323c5 100644 (file)
@@ -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
@@ -1958,3 +1959,220 @@ 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;
+    size_t j = 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;
+
+    j = 0;
+    while (j < ncomponents) {
+        /* skip slashes */
+        if (STREQ(components[j], "")) {
+            VIR_FREE(components[j]);
+            VIR_DELETE_ELEMENT(components, j, ncomponents);
+            continue;
+        }
+        j++;
+    }
+
+    while (i < ncomponents) {
+        /* skip '.'s unless it's the last one remaining */
+        if (STREQ(components[i], ".") &&
+            (beginSlash || ncomponents  > 1)) {
+            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;
+
+            j = 0;
+            while (j < ncomponents) {
+                /* skip slashes */
+                if (STREQ(components[j], "")) {
+                    VIR_FREE(components[j]);
+                    VIR_DELETE_ELEMENT(components, j, ncomponents);
+                    continue;
+                }
+                j++;
+            }
+
+            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;
+}
index 35ac6182d9f4dd8da352564ee2c0610e2026e5d7..ff53a860c9bf35e75dda66f8485b21520ddd4f5d 100644 (file)
@@ -329,5 +329,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__ */
index e15578cbf0664cbbd50376669480f773884f66a2..f86d25cf2dc2bd305478564ed299368156f84c3d 100644 (file)
@@ -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,49 @@ 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);
+    TEST_PATH_CANONICALIZE(31, "///", "/");
+
  cleanup:
     /* Final cleanup */
     virStorageSourceFree(chain);