parentDirectory() is now based on popPath(). Bug fix, abspath() would prepend current working directory even when not resolving symlinks. Added more unit tests. Ported path operation unit tests to fdbmonitor() since the path manipulation function implementations are significantly different. Clarified some comments. The flow project version of abspath() does not allow resolveLinks to be false, for now, because behavior of this on Windows is not well thought out or tested.

This commit is contained in:
Stephen Atherton 2019-03-21 16:56:36 -07:00
parent 644a88e8b4
commit c6d96498ca
3 changed files with 116 additions and 27 deletions

View File

@ -233,7 +233,8 @@ std::string cleanPath(std::string const &path) {
if(sep == path.npos) {
sep = path.size();
}
std::string part = path.substr(i + 1, sep - i);
std::string part = path.substr(i, sep - i);
i = sep + 1;
if(part.size() == 0 || (part.size() == 1 && part[0] == '.'))
continue;
if(part == "..") {
@ -300,8 +301,8 @@ std::string abspath( std::string const& path, bool resolveLinks = true) {
}
if(!resolveLinks) {
std::string clean = cleanPath(joinPath(abspath(".", true), path));
return clean;
bool absolute = !path.empty() && path[0] == CANONICAL_PATH_SEPARATOR;
return cleanPath(absolute ? path : joinPath(abspath(".", true), path));
}
char result[PATH_MAX];
@ -318,18 +319,14 @@ std::string abspath( std::string const& path, bool resolveLinks = true) {
}
return cleanPath(joinPath(abspath(prefix, true), suffix));
}
log_err("realpath", errno, "Unable to get real path for %s", path.c_str());
return "";
}
return std::string(r);
}
std::string parentDirectory( std::string const& path, bool resolveLinks = true) {
auto abs = abspath(path, resolveLinks);
size_t sep = abs.find_last_of( CANONICAL_PATH_SEPARATOR );
if (sep == std::string::npos) {
return "";
}
return abs.substr(0, sep + 1);
return popPath(abspath(path, resolveLinks));
}
int mkdir(std::string const& directory) {
@ -1066,6 +1063,97 @@ std::unordered_map<int, std::unordered_set<std::string>> set_watches(std::string
}
#endif
int testPathFunction(const char *name, std::function<std::string(std::string)> fun, std::string a, std::string b) {
std::string o = fun(a);
bool r = b == o;
printf("%s: %s(%s) = %s expected %s\n", r ? "PASS" : "FAIL", name, a.c_str(), o.c_str(), b.c_str());
return r ? 0 : 1;
}
int testPathFunction2(const char *name, std::function<std::string(std::string, bool)> fun, std::string a, bool x, std::string b) {
std::string o = fun(a, x);
bool r = b == o;
printf("%s: %s(%s, %d) => %s expected %s\n", r ? "PASS" : "FAIL", name, a.c_str(), x, o.c_str(), b.c_str());
return r ? 0 : 1;
}
void testPathOps() {
int errors = 0;
errors += testPathFunction("popPath", popPath, "a", "");
errors += testPathFunction("popPath", popPath, "a/", "");
errors += testPathFunction("popPath", popPath, "a///", "");
errors += testPathFunction("popPath", popPath, "a///..", "a/");
errors += testPathFunction("popPath", popPath, "a///../", "a/");
errors += testPathFunction("popPath", popPath, "a///..//", "a/");
errors += testPathFunction("popPath", popPath, "/", "/");
errors += testPathFunction("popPath", popPath, "/a", "/");
errors += testPathFunction("popPath", popPath, "/a/b", "/a/");
errors += testPathFunction("popPath", popPath, "/a/b/", "/a/");
errors += testPathFunction("popPath", popPath, "/a/b/..", "/a/b/");
errors += testPathFunction("cleanPath", cleanPath, "/", "/");
errors += testPathFunction("cleanPath", cleanPath, "..", "..");
errors += testPathFunction("cleanPath", cleanPath, "../.././", "../..");
errors += testPathFunction("cleanPath", cleanPath, "///.///", "/");
errors += testPathFunction("cleanPath", cleanPath, "/a/b/.././../c/./././////./d/..//", "/c");
errors += testPathFunction("cleanPath", cleanPath, "a/b/.././../c/./././////./d/..//", "c");
errors += testPathFunction("cleanPath", cleanPath, "a/b/.././../c/./././////./d/..//..", ".");
errors += testPathFunction("cleanPath", cleanPath, "a/b/.././../c/./././////./d/..//../..", "..");
errors += testPathFunction("cleanPath", cleanPath, "../a/b/..//", "../a");
errors += testPathFunction("cleanPath", cleanPath, "/..", "/");
errors += testPathFunction("cleanPath", cleanPath, "/../foo/bar///", "/foo/bar");
errors += testPathFunction("cleanPath", cleanPath, "/a/b/../.././../", "/");
errors += testPathFunction("cleanPath", cleanPath, ".", ".");
mkdir("simfdb/backups/one/two/three");
std::string cwd = abspath(".", true);
// Create some symlinks and test resolution (or non-resolution) of them
symlink("one/two", "simfdb/backups/four");
symlink("../backups/four", "simfdb/backups/five");
errors += testPathFunction2("abspath", abspath, "simfdb/backups/five/../two", true, joinPath(cwd, "simfdb/backups/one/two"));
errors += testPathFunction2("abspath", abspath, "simfdb/backups/five/../three", true, joinPath(cwd, "simfdb/backups/one/three"));
errors += testPathFunction2("abspath", abspath, "simfdb/backups/five/../three/../four", true, joinPath(cwd, "simfdb/backups/one/four"));
errors += testPathFunction2("parentDirectory", parentDirectory, "simfdb/backups/five/../two", true, joinPath(cwd, "simfdb/backups/one/"));
errors += testPathFunction2("parentDirectory", parentDirectory, "simfdb/backups/five/../three", true, joinPath(cwd, "simfdb/backups/one/"));
errors += testPathFunction2("abspath", abspath, "/", false, "/");
errors += testPathFunction2("abspath", abspath, "/foo//bar//baz/.././", false, "/foo/bar");
errors += testPathFunction2("abspath", abspath, "/", true, "/");
errors += testPathFunction2("abspath", abspath, "", true, "");
errors += testPathFunction2("abspath", abspath, ".", true, cwd);
errors += testPathFunction2("abspath", abspath, "/a", true, "/a");
errors += testPathFunction2("abspath", abspath, "one/two/three/four", false, joinPath(cwd, "one/two/three/four"));
errors += testPathFunction2("abspath", abspath, "one/two/three/./four", false, joinPath(cwd, "one/two/three/four"));
errors += testPathFunction2("abspath", abspath, "one/two/three/./four/..", false, joinPath(cwd, "one/two/three"));
errors += testPathFunction2("abspath", abspath, "one/./two/../three/./four", false, joinPath(cwd, "one/three/four"));
errors += testPathFunction2("abspath", abspath, "simfdb/backups/four/../two", false, joinPath(cwd, "simfdb/backups/two"));
errors += testPathFunction2("abspath", abspath, "simfdb/backups/five/../two", false, joinPath(cwd, "simfdb/backups/two"));
errors += testPathFunction2("abspath", abspath, "foo/./../foo2/./bar//", false, joinPath(cwd, "foo2/bar"));
errors += testPathFunction2("abspath", abspath, "foo/./../foo2/./bar//", true, joinPath(cwd, "foo2/bar"));
errors += testPathFunction2("parentDirectory", parentDirectory, "", true, "");
errors += testPathFunction2("parentDirectory", parentDirectory, "/", true, "/");
errors += testPathFunction2("parentDirectory", parentDirectory, "/a", true, "/");
errors += testPathFunction2("parentDirectory", parentDirectory, ".", false, cleanPath(joinPath(cwd, "..")) + "/");
errors += testPathFunction2("parentDirectory", parentDirectory, "./foo", false, cleanPath(cwd) + "/");
errors += testPathFunction2("parentDirectory", parentDirectory, "one/two/three/four", false, joinPath(cwd, "one/two/three/"));
errors += testPathFunction2("parentDirectory", parentDirectory, "one/two/three/./four", false, joinPath(cwd, "one/two/three/"));
errors += testPathFunction2("parentDirectory", parentDirectory, "one/two/three/./four/..", false, joinPath(cwd, "one/two/"));
errors += testPathFunction2("parentDirectory", parentDirectory, "one/./two/../three/./four", false, joinPath(cwd, "one/three/"));
errors += testPathFunction2("parentDirectory", parentDirectory, "simfdb/backups/four/../two", false, joinPath(cwd, "simfdb/backups/"));
errors += testPathFunction2("parentDirectory", parentDirectory, "simfdb/backups/five/../two", false, joinPath(cwd, "simfdb/backups/"));
errors += testPathFunction2("parentDirectory", parentDirectory, "foo/./../foo2/./bar//", false, joinPath(cwd, "foo2/"));
errors += testPathFunction2("parentDirectory", parentDirectory, "foo/./../foo2/./bar//", true, joinPath(cwd, "foo2/"));
printf("%d errors.\n", errors);
if(errors)
exit(-1);
}
int main(int argc, char** argv) {
std::string lockfile = "/var/run/fdbmonitor.pid";
std::string _confpath = "/etc/foundationdb/foundationdb.conf";

View File

@ -1840,7 +1840,7 @@ std::string cleanPath(std::string const &path) {
// Removes the last component from a path string (if possible) and returns the result with one trailing separator.
// If there is only one path component, the result will be "" for relative paths and "/" for absolute paths.
// Note that this is NOT the same as getting the parant of path, as the final component could be ".."
// Note that this is NOT the same as getting the parent of path, as the final component could be ".."
// or "." and it would still be simply removed.
// ALL of the following inputs will yield the result "/a/"
// /a/b
@ -1893,8 +1893,9 @@ std::string abspath( std::string const& path, bool resolveLinks, bool mustExist
if(!resolveLinks) {
// TODO: Not resolving symbolic links does not yet behave well on Windows because of drive letters
// and network names, so it's not currently allowed here (but it is allowed in fdbmonitor which is unix-only)
ASSERT(g_network->isSimulated());
std::string clean = cleanPath(joinPath(platform::getWorkingDirectory(), path));
ASSERT(false);
bool absolute = !path.empty() && path[0] == CANONICAL_PATH_SEPARATOR;
std::string clean = cleanPath(absolute ? path : joinPath(platform::getWorkingDirectory(), path));
if(mustExist && !fileExists(clean)) {
Error e = systemErrorCodeToError();
Severity sev = e.code() == error_code_io_error ? SevError : SevWarnAlways;
@ -1945,16 +1946,7 @@ std::string abspath( std::string const& path, bool resolveLinks, bool mustExist
}
std::string parentDirectory( std::string const& path, bool resolveLinks, bool mustExist ) {
auto abs = abspath(path, resolveLinks, mustExist);
size_t sep = abs.find_last_of( CANONICAL_PATH_SEPARATOR );
if (sep == std::string::npos) {
Error e = platform_error();
TraceEvent(SevWarnAlways, "GetParentDirectory")
.detail("File", path)
.error(e);
throw e;
}
return abs.substr(0, sep + 1);
return popPath(abspath(path, resolveLinks, mustExist));
}
std::string basename( std::string const& filename ) {
@ -2961,12 +2953,18 @@ int testPathFunction(const char *name, std::function<std::string(std::string)> f
return r ? 0 : 1;
}
int testPathFunction2(const char *name, std::function<std::string(std::string, bool, bool)> fun, std::string a, bool x, bool y, ErrorOr<std::string> b) {
int testPathFunction2(const char *name, std::function<std::string(std::string, bool, bool)> fun, std::string a, bool resolveLinks, bool mustExist, ErrorOr<std::string> b) {
// Skip tests with resolveLinks set to false as the implementation is not complete
if(resolveLinks == false) {
printf("SKIPPED: %s('%s', %d, %d)\n", name, a.c_str(), resolveLinks, mustExist);
return 0;
}
ErrorOr<std::string> result;
try { result = fun(a, x, y); } catch(Error &e) { result = e; }
try { result = fun(a, resolveLinks, mustExist); } catch(Error &e) { result = e; }
bool r = result.isError() == b.isError() && (b.isError() || b.get() == result.get()) && (!b.isError() || b.getError().code() == result.getError().code());
printf("%s: %s('%s', %d, %d) -> %s", r ? "PASS" : "FAIL", name, a.c_str(), x, y, result.isError() ? result.getError().what() : format("'%s'", result.get().c_str()).c_str());
printf("%s: %s('%s', %d, %d) -> %s", r ? "PASS" : "FAIL", name, a.c_str(), resolveLinks, mustExist, result.isError() ? result.getError().what() : format("'%s'", result.get().c_str()).c_str());
if(!r) {
printf(" *ERROR* expected %s", b.isError() ? b.getError().what() : format("'%s'", b.get().c_str()).c_str());
}
@ -3027,6 +3025,8 @@ TEST_CASE("/flow/Platform/directoryOps") {
errors += testPathFunction2("parentDirectory", parentDirectory, "simfdb/backups/five/../three/../four", true, false, joinPath(cwd, "simfdb/backups/one/"));
#endif
errors += testPathFunction2("abspath", abspath, "/", false, false, "/");
errors += testPathFunction2("abspath", abspath, "/foo//bar//baz/.././", false, false, "/foo/bar");
errors += testPathFunction2("abspath", abspath, "/", true, false, "/");
errors += testPathFunction2("abspath", abspath, "", true, false, platform_error());
errors += testPathFunction2("abspath", abspath, ".", true, false, cwd);

View File

@ -321,8 +321,9 @@ void writeFile(std::string const& filename, std::string const& content);
std::string joinPath( std::string const& directory, std::string const& filename );
// cleanPath() does a 'logical' resolution of the given path string to a canonical form *without*
// following symbolic links or verifying the existence of any path components, removing redundant
// "." references and duplicate separators, and resolving any ".." references.
// following symbolic links or verifying the existence of any path components. It removes redundant
// "." references and duplicate separators, and resolves any ".." references that can be resolved
// using the preceding path components.
// Relative paths remain relative and are NOT rebased on the current working directory.
std::string cleanPath( std::string const& path );