mirror of
https://gitlab.com/libvirt/libvirt.git
synced 2025-03-07 17:28:15 +00:00
util: storagefile: Introduce universal function to canonicalize paths
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.
This commit is contained in:
parent
5d4a482584
commit
08aa22ec1d
@ -1878,6 +1878,7 @@ virStorageGenerateQcowPassphrase;
|
|||||||
|
|
||||||
|
|
||||||
# util/virstoragefile.h
|
# util/virstoragefile.h
|
||||||
|
virStorageFileCanonicalizePath;
|
||||||
virStorageFileChainGetBroken;
|
virStorageFileChainGetBroken;
|
||||||
virStorageFileChainLookup;
|
virStorageFileChainLookup;
|
||||||
virStorageFileFeatureTypeFromString;
|
virStorageFileFeatureTypeFromString;
|
||||||
|
@ -40,6 +40,7 @@
|
|||||||
#include "virutil.h"
|
#include "virutil.h"
|
||||||
#include "viruri.h"
|
#include "viruri.h"
|
||||||
#include "dirname.h"
|
#include "dirname.h"
|
||||||
|
#include "virbuffer.h"
|
||||||
#if HAVE_SYS_SYSCALL_H
|
#if HAVE_SYS_SYSCALL_H
|
||||||
# include <sys/syscall.h>
|
# include <sys/syscall.h>
|
||||||
#endif
|
#endif
|
||||||
@ -1958,3 +1959,220 @@ virStorageSourceNewFromBacking(virStorageSourcePtr parent)
|
|||||||
|
|
||||||
return ret;
|
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;
|
||||||
|
}
|
||||||
|
@ -329,5 +329,12 @@ void virStorageSourceFree(virStorageSourcePtr def);
|
|||||||
void virStorageSourceClearBackingStore(virStorageSourcePtr def);
|
void virStorageSourceClearBackingStore(virStorageSourcePtr def);
|
||||||
virStorageSourcePtr virStorageSourceNewFromBacking(virStorageSourcePtr parent);
|
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__ */
|
#endif /* __VIR_STORAGE_FILE_H__ */
|
||||||
|
@ -525,6 +525,71 @@ testStorageLookup(const void *args)
|
|||||||
return ret;
|
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
|
static int
|
||||||
mymain(void)
|
mymain(void)
|
||||||
{
|
{
|
||||||
@ -532,6 +597,7 @@ mymain(void)
|
|||||||
virCommandPtr cmd = NULL;
|
virCommandPtr cmd = NULL;
|
||||||
struct testChainData data;
|
struct testChainData data;
|
||||||
struct testLookupData data2;
|
struct testLookupData data2;
|
||||||
|
struct testPathCanonicalizeData data3;
|
||||||
virStorageSourcePtr chain = NULL;
|
virStorageSourcePtr chain = NULL;
|
||||||
virStorageSourcePtr chain2; /* short for chain->backingStore */
|
virStorageSourcePtr chain2; /* short for chain->backingStore */
|
||||||
virStorageSourcePtr chain3; /* short for chain2->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(80, "vda", chain3, "vda[2]", 2, NULL, NULL, NULL);
|
||||||
TEST_LOOKUP_TARGET(81, "vda", NULL, "vda[3]", 3, 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:
|
cleanup:
|
||||||
/* Final cleanup */
|
/* Final cleanup */
|
||||||
virStorageSourceFree(chain);
|
virStorageSourceFree(chain);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user