As gnulib's canonicalize_filename_mode isn't LGPL and relative snapshot
resolution code will need to clean paths with relative path components
this patch adds a libvirt's own implementation of that functionality and
tests to make sure everything works well.
---
src/util/virstoragefile.c | 95 +++++++++++++++++++++++++++++++++++++++++++++++
src/util/virstoragefile.h | 2 +
tests/virstoragetest.c | 83 +++++++++++++++++++++++++++++++++++++++++
3 files changed, 180 insertions(+)
diff --git a/src/util/virstoragefile.c b/src/util/virstoragefile.c
index db71cf3..21d71c4 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
@@ -1924,3 +1925,97 @@ virStorageSourceNewFromBacking(virStorageSourcePtr parent)
return ret;
}
+
+
+static char *
+virStorageFileExportPath(char **components,
+ size_t ncomponents,
+ bool beginSlash)
+{
+ virBuffer buf = VIR_BUFFER_INITIALIZER;
+ size_t i;
+ char *ret = NULL;
+
+ if (beginSlash)
+ 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 ... wel just return an empty string */
+ if (!(ret = virBufferContentAndReset(&buf)))
+ ignore_value(VIR_STRDUP(ret, ""));
+
+ return ret;
+}
+
+
+char *
+virStorageFileSimplifyPath(const char *path,
+ bool allow_relative)
+{
+ bool beginSlash = false;
+ char **components = NULL;
+ size_t ncomponents = 0;
+ size_t i;
+ char *ret = NULL;
+
+ /* special cases are "" and "//", return them as they are */
+ if (STREQ(path, "") || STREQ(path, "//")) {
+ ignore_value(VIR_STRDUP(ret, path));
+ goto cleanup;
+ }
+
+ if (path[0] == '/')
+ beginSlash = true;
+
+ if (!(components = virStringSplitCount(path, "/", 0, &ncomponents)))
+ goto cleanup;
+
+ i = 0;
+ while (i < ncomponents) {
+ if (STREQ(components[i], "") ||
+ STREQ(components[i], ".")) {
+ VIR_FREE(components[i]);
+ VIR_DELETE_ELEMENT(components, i, ncomponents);
+ continue;
+ }
+
+ if (STREQ(components[i], "..")) {
+ if (allow_relative && !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;
+ }
+
+ i++;
+ }
+
+ ret = virStorageFileExportPath(components, ncomponents, beginSlash);
+
+ cleanup:
+ virStringFreeListCount(components, ncomponents);
+
+ return ret;
+}
diff --git a/src/util/virstoragefile.h b/src/util/virstoragefile.h
index 0d0f958..3708c5e 100644
--- a/src/util/virstoragefile.h
+++ b/src/util/virstoragefile.h
@@ -324,5 +324,7 @@ void virStorageSourceFree(virStorageSourcePtr def);
void virStorageSourceClearBackingStore(virStorageSourcePtr def);
virStorageSourcePtr virStorageSourceNewFromBacking(virStorageSourcePtr parent);
+char *virStorageFileSimplifyPath(const char *path,
+ bool allow_relative);
#endif /* __VIR_STORAGE_FILE_H__ */
diff --git a/tests/virstoragetest.c b/tests/virstoragetest.c
index 29297ef..57f16ca 100644
--- a/tests/virstoragetest.c
+++ b/tests/virstoragetest.c
@@ -512,12 +512,61 @@ testStorageLookup(const void *args)
return ret;
}
+
+struct testPathSimplifyData
+{
+ const char *path;
+ const char *exp_abs;
+ const char *exp_rel;
+};
+
+
+static int
+testPathSimplify(const void *args)
+{
+ const struct testPathSimplifyData *data = args;
+ char *simple;
+ int ret = -1;
+
+ if (!(simple = virStorageFileSimplifyPath(data->path, false))) {
+ fprintf(stderr, "path simplification returned NULL\n");
+ goto cleanup;
+ }
+
+ if (STRNEQ(simple, data->exp_abs)) {
+ fprintf(stderr, "simplify path (absolute) '%s': expected:
'%s', got '%s'\n",
+ data->path, data->exp_abs, simple);
+ goto cleanup;
+ }
+
+ VIR_FREE(simple);
+
+ if (!(simple = virStorageFileSimplifyPath(data->path, true))) {
+ fprintf(stderr, "path simplification returned NULL\n");
+ goto cleanup;
+ }
+
+ if (STRNEQ(simple, data->exp_rel)) {
+ fprintf(stderr, "simplify path (relative) '%s': expected:
'%s', got '%s'\n",
+ data->path, data->exp_rel, simple);
+ goto cleanup;
+ }
+
+ ret = 0;
+
+ cleanup:
+ VIR_FREE(simple);
+ return ret;
+}
+
+
static int
mymain(void)
{
int ret;
virCommandPtr cmd = NULL;
struct testChainData data;
+ struct testPathSimplifyData data3;
virStorageSourcePtr chain = NULL;
/* Prep some files with qemu-img; if that is not found on PATH, or
@@ -1016,6 +1065,40 @@ mymain(void)
chain->backingStore->path);
TEST_LOOKUP_TARGET(33, "vda", "vda[3]", 3, NULL, NULL, NULL);
+#define TEST_SIMPLIFY(id, PATH, EXP_ABS, EXP_REL) \
+ do { \
+ data3.path = PATH; \
+ data3.exp_abs = EXP_ABS; \
+ data3.exp_rel = EXP_REL; \
+ if (virtTestRun("Path simplify " #id, \
+ testPathSimplify, &data3) < 0) \
+ ret = -1; \
+ } while (0)
+
+ /* PATH, absolute simplification, relative simplification */
+ TEST_SIMPLIFY(1, "/", "/", "/");
+ TEST_SIMPLIFY(2, "/path", "/path", "/path");
+ TEST_SIMPLIFY(3, "/path/to/blah", "/path/to/blah",
"/path/to/blah");
+ TEST_SIMPLIFY(4, "/path/", "/path", "/path");
+ TEST_SIMPLIFY(5, "///////", "/", "/");
+ TEST_SIMPLIFY(6, "//", "//", "//");
+ TEST_SIMPLIFY(7, "", "", "");
+ TEST_SIMPLIFY(8, "../", "", "..");
+ TEST_SIMPLIFY(9, "../../", "", "../..");
+ TEST_SIMPLIFY(10, "../../blah", "blah", "../../blah");
+ TEST_SIMPLIFY(11, "/./././blah", "/blah", "/blah");
+ TEST_SIMPLIFY(12, ".././../././../blah", "blah",
"../../../blah");
+ TEST_SIMPLIFY(13, "/././", "/", "/");
+ TEST_SIMPLIFY(14, "./././", "", "");
+ TEST_SIMPLIFY(15, "blah/../foo", "foo", "foo");
+ TEST_SIMPLIFY(16, "foo/bar/../blah", "foo/blah",
"foo/blah");
+ TEST_SIMPLIFY(17, "foo/bar/.././blah", "foo/blah",
"foo/blah");
+ TEST_SIMPLIFY(18, "/path/to/foo/bar/../../../../../../../../baz",
"/baz", "/baz");
+ TEST_SIMPLIFY(19, "path/to/foo/bar/../../../../../../../../baz",
"baz", "../../../../baz");
+ TEST_SIMPLIFY(20, "path/to/foo/bar", "path/to/foo/bar",
"path/to/foo/bar");
+ TEST_SIMPLIFY(21,
"some/path/to/image.qcow/../image2.qcow/../image3.qcow/",
+ "some/path/to/image3.qcow",
"some/path/to/image3.qcow");
+
cleanup:
/* Final cleanup */
virStorageSourceFree(chain);
--
1.9.3