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