mirror of
https://github.com/apple/foundationdb.git
synced 2025-05-14 09:58:50 +08:00
Merge pull request #533 from ajbeamon/fix-parent-directory
Fixes to parentDirectory() and abspath()
This commit is contained in:
commit
09f37cf3d2
@ -52,6 +52,7 @@
|
||||
#include <string>
|
||||
#include <sstream>
|
||||
#include <iterator>
|
||||
#include <functional>
|
||||
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
@ -223,34 +224,117 @@ std::string joinPath(std::string const& directory, std::string const& filename)
|
||||
return d + CANONICAL_PATH_SEPARATOR + f;
|
||||
}
|
||||
|
||||
std::string abspath(std::string const& filename) {
|
||||
// Returns an absolute path canonicalized to use only CANONICAL_PATH_SEPARATOR
|
||||
char result[PATH_MAX];
|
||||
auto r = realpath( filename.c_str(), result );
|
||||
if (!r) {
|
||||
if (errno == ENOENT) {
|
||||
int sep = filename.find_last_of( CANONICAL_PATH_SEPARATOR );
|
||||
if (sep != std::string::npos) {
|
||||
return joinPath( abspath( filename.substr(0, sep) ), filename.substr(sep) );
|
||||
std::string cleanPath(std::string const &path) {
|
||||
std::vector<std::string> finalParts;
|
||||
bool absolute = !path.empty() && path[0] == CANONICAL_PATH_SEPARATOR;
|
||||
|
||||
int i = 0;
|
||||
while(i < path.size()) {
|
||||
int sep = path.find((char)CANONICAL_PATH_SEPARATOR, i);
|
||||
if(sep == path.npos) {
|
||||
sep = path.size();
|
||||
}
|
||||
std::string part = path.substr(i, sep - i);
|
||||
i = sep + 1;
|
||||
if(part.size() == 0 || (part.size() == 1 && part[0] == '.'))
|
||||
continue;
|
||||
if(part == "..") {
|
||||
if(!finalParts.empty() && finalParts.back() != "..") {
|
||||
finalParts.pop_back();
|
||||
continue;
|
||||
}
|
||||
else if (filename.find("~") == std::string::npos) {
|
||||
return joinPath( abspath( "." ), filename );
|
||||
if(absolute) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
finalParts.push_back(part);
|
||||
}
|
||||
|
||||
log_err("realpath", errno, "Unable to get real path for %s", filename.c_str());
|
||||
std::string result;
|
||||
result.reserve(PATH_MAX);
|
||||
if(absolute) {
|
||||
result.append(1, CANONICAL_PATH_SEPARATOR);
|
||||
}
|
||||
|
||||
for(int i = 0; i < finalParts.size(); ++i) {
|
||||
if(i != 0) {
|
||||
result.append(1, CANONICAL_PATH_SEPARATOR);
|
||||
}
|
||||
result.append(finalParts[i]);
|
||||
}
|
||||
|
||||
return result.empty() ? "." : result;
|
||||
}
|
||||
|
||||
// Removes the last component from a path string (if possible) and returns the result with one trailing separator.
|
||||
std::string popPath(const std::string &path) {
|
||||
int i = path.size() - 1;
|
||||
// Skip over any trailing separators
|
||||
while(i >= 0 && path[i] == CANONICAL_PATH_SEPARATOR) {
|
||||
--i;
|
||||
}
|
||||
// Skip over non separators
|
||||
while(i >= 0 && path[i] != CANONICAL_PATH_SEPARATOR) {
|
||||
--i;
|
||||
}
|
||||
// Skip over trailing separators again
|
||||
bool foundSeparator = false;
|
||||
while(i >= 0 && path[i] == CANONICAL_PATH_SEPARATOR) {
|
||||
--i;
|
||||
foundSeparator = true;
|
||||
}
|
||||
|
||||
if(foundSeparator) {
|
||||
++i;
|
||||
}
|
||||
else {
|
||||
// If absolute then we popped off the only path component so return "/"
|
||||
if(!path.empty() && path.front() == CANONICAL_PATH_SEPARATOR) {
|
||||
return "/";
|
||||
}
|
||||
}
|
||||
return path.substr(0, i + 1);
|
||||
}
|
||||
|
||||
std::string abspath( std::string const& path, bool resolveLinks = true) {
|
||||
if(path.empty()) {
|
||||
return "";
|
||||
}
|
||||
|
||||
if(!resolveLinks) {
|
||||
// Treat paths starting with ~ or separator as absolute, meaning they shouldn't be appended to the current working dir
|
||||
bool absolute = !path.empty() && (path[0] == CANONICAL_PATH_SEPARATOR || path[0] == '~');
|
||||
return cleanPath(absolute ? path : joinPath(abspath(".", true), path));
|
||||
}
|
||||
|
||||
char result[PATH_MAX];
|
||||
// Must resolve links, so first try realpath on the whole thing
|
||||
const char *r = realpath( path.c_str(), result );
|
||||
if(r == nullptr) {
|
||||
// If the error was ENOENT and the path doesn't have to exist,
|
||||
// try to resolve symlinks in progressively shorter prefixes of the path
|
||||
if(errno == ENOENT && path != ".") {
|
||||
std::string prefix = popPath(path);
|
||||
std::string suffix = path.substr(prefix.size());
|
||||
if(prefix.empty() && (suffix.empty() || suffix[0] != '~')) {
|
||||
prefix = ".";
|
||||
}
|
||||
if(!prefix.empty()) {
|
||||
std::string abs = abspath(prefix, true);
|
||||
// An empty return from abspath() means there was an error.
|
||||
if(abs.empty())
|
||||
return abs;
|
||||
return cleanPath(joinPath(abs, 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& filename) {
|
||||
size_t sep = filename.find_last_of( CANONICAL_PATH_SEPARATOR );
|
||||
if (sep == std::string::npos) {
|
||||
return "";
|
||||
}
|
||||
|
||||
return filename.substr(0, sep);
|
||||
std::string parentDirectory( std::string const& path, bool resolveLinks = true) {
|
||||
return popPath(abspath(path, resolveLinks));
|
||||
}
|
||||
|
||||
int mkdir(std::string const& directory) {
|
||||
@ -847,7 +931,7 @@ void watch_conf_dir( int kq, int* confd_fd, std::string confdir ) {
|
||||
/* Find the nearest existing ancestor */
|
||||
while( (*confd_fd = open( confdir.c_str(), O_EVTONLY )) < 0 && errno == ENOENT ) {
|
||||
child = confdir;
|
||||
confdir = parentDirectory(confdir);
|
||||
confdir = parentDirectory(confdir, false);
|
||||
}
|
||||
|
||||
if ( *confd_fd >= 0 ) {
|
||||
@ -939,7 +1023,7 @@ std::unordered_map<int, std::unordered_set<std::string>> set_watches(std::string
|
||||
}
|
||||
}
|
||||
|
||||
std::string parent = parentDirectory(subpath);
|
||||
std::string parent = parentDirectory(subpath, false);
|
||||
|
||||
/* Watch the parent directory of the current path for changes */
|
||||
int wd = inotify_add_watch(ifd, parent.c_str(), IN_CREATE | IN_MOVED_TO);
|
||||
@ -950,7 +1034,7 @@ std::unordered_map<int, std::unordered_set<std::string>> set_watches(std::string
|
||||
|
||||
if(exists) {
|
||||
log_msg(SevInfo, "Watching parent directory of symlink %s (%d)\n", subpath.c_str(), wd);
|
||||
additional_watch_wds[wd].insert(subpath.substr(parent.size()+1));
|
||||
additional_watch_wds[wd].insert(subpath.substr(parent.size()));
|
||||
}
|
||||
else {
|
||||
/* If the subpath has appeared since we set the watch, we should cancel it and resume traversing the path */
|
||||
@ -961,7 +1045,7 @@ std::unordered_map<int, std::unordered_set<std::string>> set_watches(std::string
|
||||
}
|
||||
|
||||
log_msg(SevInfo, "Watching parent directory of missing directory %s (%d)\n", subpath.c_str(), wd);
|
||||
additional_watch_wds[wd].insert(subpath.substr(parent.size()+1));
|
||||
additional_watch_wds[wd].insert(subpath.substr(parent.size()));
|
||||
break;
|
||||
}
|
||||
|
||||
@ -987,7 +1071,104 @@ 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
|
||||
int rc;
|
||||
// Ignoring return codes, if symlinks fail tests below will fail
|
||||
rc = unlink("simfdb/backups/four");
|
||||
rc = unlink("simfdb/backups/five");
|
||||
rc = symlink("one/two", "simfdb/backups/four") == 0 ? 0 : 1;
|
||||
rc = symlink("../backups/four", "simfdb/backups/five") ? 0 : 1;
|
||||
|
||||
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) {
|
||||
// testPathOps(); return -1;
|
||||
|
||||
std::string lockfile = "/var/run/fdbmonitor.pid";
|
||||
std::string _confpath = "/etc/foundationdb/foundationdb.conf";
|
||||
|
||||
@ -1048,8 +1229,8 @@ int main(int argc, char** argv) {
|
||||
std::string confpath = p;
|
||||
|
||||
// Will always succeed given an absolute path
|
||||
std::string confdir = parentDirectory(confpath);
|
||||
std::string conffile = confpath.substr(confdir.size()+1);
|
||||
std::string confdir = parentDirectory(confpath, false);
|
||||
std::string conffile = confpath.substr(confdir.size());
|
||||
|
||||
#ifdef __linux__
|
||||
// Setup inotify
|
||||
@ -1113,7 +1294,7 @@ int main(int argc, char** argv) {
|
||||
}
|
||||
|
||||
/* open and lock our lockfile for mutual exclusion */
|
||||
std::string lockfileDir = parentDirectory(abspath(lockfile));
|
||||
std::string lockfileDir = parentDirectory(lockfile, true);
|
||||
if(lockfileDir.size() == 0) {
|
||||
log_msg(SevError, "Unable to determine parent directory of lockfile %s\n", lockfile.c_str());
|
||||
exit(1);
|
||||
@ -1440,7 +1621,7 @@ int main(int argc, char** argv) {
|
||||
confpath = redone_confpath;
|
||||
|
||||
// Will always succeed given an absolute path
|
||||
confdir = parentDirectory(confpath);
|
||||
confdir = parentDirectory(confpath, false);
|
||||
conffile = confpath.substr(confdir.size());
|
||||
|
||||
// Remove all the old watches
|
||||
|
@ -635,6 +635,8 @@ ACTOR Future<Void> workerServer( Reference<ClusterConnectionFile> connFile, Refe
|
||||
|
||||
state WorkerInterface interf( locality );
|
||||
|
||||
folder = abspath(folder);
|
||||
|
||||
if(metricsPrefix.size() > 0) {
|
||||
if( metricsConnFile.size() > 0) {
|
||||
try {
|
||||
@ -725,7 +727,7 @@ ACTOR Future<Void> workerServer( Reference<ClusterConnectionFile> connFile, Refe
|
||||
StringRef optionsString = StringRef(filename).removePrefix(fileVersionedLogDataPrefix).eat("-");
|
||||
logQueueBasename = fileLogQueuePrefix.toString() + optionsString.toString() + "-";
|
||||
}
|
||||
ASSERT_WE_THINK( StringRef( parentDirectory(s.filename) ).endsWith( StringRef(folder) ) );
|
||||
ASSERT_WE_THINK( abspath(parentDirectory(s.filename)) == folder );
|
||||
IKeyValueStore* kv = openKVStore( s.storeType, s.filename, s.storeID, memoryLimit, validateDataFiles );
|
||||
const DiskQueueVersion dqv = s.tLogOptions.version >= TLogVersion::V3 ? DiskQueueVersion::V1 : DiskQueueVersion::V0;
|
||||
const int64_t diskQueueWarnSize = s.tLogOptions.spillType == TLogSpillType::VALUE ? 10*SERVER_KNOBS->TARGET_BYTES_PER_TLOG : -1;
|
||||
|
@ -1633,7 +1633,7 @@ std::string joinPath( std::string const& directory, std::string const& filename
|
||||
while (f.size() && (f[0] == '/' || f[0] == CANONICAL_PATH_SEPARATOR))
|
||||
f = f.substr(1);
|
||||
while (d.size() && (d.back() == '/' || d.back() == CANONICAL_PATH_SEPARATOR))
|
||||
d = d.substr(0, d.size()-1);
|
||||
d.resize(d.size() - 1);
|
||||
return d + CANONICAL_PATH_SEPARATOR + f;
|
||||
}
|
||||
|
||||
@ -1666,7 +1666,7 @@ void atomicReplace( std::string const& path, std::string const& content, bool te
|
||||
try {
|
||||
INJECT_FAULT( io_error, "atomicReplace" );
|
||||
|
||||
std::string tempfilename = parentDirectory(path) + CANONICAL_PATH_SEPARATOR + g_random->randomUniqueID().toString() + ".tmp";
|
||||
std::string tempfilename = joinPath(parentDirectory(path), g_random->randomUniqueID().toString() + ".tmp");
|
||||
f = textmode ? fopen( tempfilename.c_str(), "wt" ) : fopen(tempfilename.c_str(), "wb");
|
||||
if(!f)
|
||||
throw io_error();
|
||||
@ -1835,15 +1835,122 @@ bool createDirectory( std::string const& directory ) {
|
||||
|
||||
}; // namespace platform
|
||||
|
||||
std::string abspath( std::string const& filename ) {
|
||||
const uint8_t separatorChar = CANONICAL_PATH_SEPARATOR;
|
||||
StringRef separator(&separatorChar, 1);
|
||||
StringRef dotdot = LiteralStringRef("..");
|
||||
|
||||
std::string cleanPath(std::string const &path) {
|
||||
std::vector<StringRef> finalParts;
|
||||
bool absolute = !path.empty() && path[0] == CANONICAL_PATH_SEPARATOR;
|
||||
|
||||
StringRef p(path);
|
||||
|
||||
while(p.size() != 0) {
|
||||
StringRef part = p.eat(separator);
|
||||
if(part.size() == 0 || (part.size() == 1 && part[0] == '.'))
|
||||
continue;
|
||||
if(part == dotdot) {
|
||||
if(!finalParts.empty() && finalParts.back() != dotdot) {
|
||||
finalParts.pop_back();
|
||||
continue;
|
||||
}
|
||||
if(absolute) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
finalParts.push_back(part);
|
||||
}
|
||||
|
||||
std::string result;
|
||||
result.reserve(PATH_MAX);
|
||||
if(absolute) {
|
||||
result.append(1, CANONICAL_PATH_SEPARATOR);
|
||||
}
|
||||
|
||||
for(int i = 0; i < finalParts.size(); ++i) {
|
||||
if(i != 0) {
|
||||
result.append(1, CANONICAL_PATH_SEPARATOR);
|
||||
}
|
||||
result.append((const char *)finalParts[i].begin(), finalParts[i].size());
|
||||
}
|
||||
|
||||
return result.empty() ? "." : result;
|
||||
}
|
||||
|
||||
// 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 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
|
||||
// /a/b/
|
||||
// /a/..
|
||||
// /a/../
|
||||
// /a/.
|
||||
// /a/./
|
||||
// /a//..//
|
||||
std::string popPath(const std::string &path) {
|
||||
int i = path.size() - 1;
|
||||
// Skip over any trailing separators
|
||||
while(i >= 0 && path[i] == CANONICAL_PATH_SEPARATOR) {
|
||||
--i;
|
||||
}
|
||||
// Skip over non separators
|
||||
while(i >= 0 && path[i] != CANONICAL_PATH_SEPARATOR) {
|
||||
--i;
|
||||
}
|
||||
// Skip over trailing separators again
|
||||
bool foundSeparator = false;
|
||||
while(i >= 0 && path[i] == CANONICAL_PATH_SEPARATOR) {
|
||||
--i;
|
||||
foundSeparator = true;
|
||||
}
|
||||
|
||||
if(foundSeparator) {
|
||||
++i;
|
||||
}
|
||||
else {
|
||||
// If absolute then we popped off the only path component so return "/"
|
||||
if(!path.empty() && path.front() == CANONICAL_PATH_SEPARATOR) {
|
||||
return "/";
|
||||
}
|
||||
}
|
||||
return path.substr(0, i + 1);
|
||||
}
|
||||
|
||||
std::string abspath( std::string const& path, bool resolveLinks, bool mustExist ) {
|
||||
if(path.empty()) {
|
||||
Error e = platform_error();
|
||||
Severity sev = e.code() == error_code_io_error ? SevError : SevWarnAlways;
|
||||
TraceEvent(sev, "AbsolutePathError").detail("Path", path).error(e);
|
||||
throw e;
|
||||
}
|
||||
|
||||
// Returns an absolute path canonicalized to use only CANONICAL_PATH_SEPARATOR
|
||||
INJECT_FAULT( platform_error, "abspath" );
|
||||
|
||||
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(false);
|
||||
// Treat paths starting with ~ or separator as absolute, meaning they shouldn't be appended to the current working dir
|
||||
bool absolute = !path.empty() && (path[0] == CANONICAL_PATH_SEPARATOR || path[0] == '~');
|
||||
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;
|
||||
TraceEvent(sev, "AbsolutePathError").detail("Path", path).GetLastError().error(e);
|
||||
throw e;
|
||||
}
|
||||
return clean;
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
char nameBuffer[MAX_PATH];
|
||||
if (!GetFullPathName(filename.c_str(), MAX_PATH, nameBuffer, NULL)) {
|
||||
if (!GetFullPathName(path.c_str(), MAX_PATH, nameBuffer, NULL) || (mustExist && !fileExists(nameBuffer)) {
|
||||
Error e = systemErrorCodeToError();
|
||||
TraceEvent(SevError, "AbsolutePathError").detail("Filename", filename).GetLastError().error(e);
|
||||
Severity sev = e.code() == error_code_io_error ? SevError : SevWarnAlways;
|
||||
TraceEvent(sev, "AbsolutePathError").detail("Path", path).GetLastError().error(e);
|
||||
throw e;
|
||||
}
|
||||
// Not totally obvious from the help whether GetFullPathName canonicalizes slashes, so let's do it...
|
||||
@ -1852,20 +1959,26 @@ std::string abspath( std::string const& filename ) {
|
||||
*x = CANONICAL_PATH_SEPARATOR;
|
||||
return nameBuffer;
|
||||
#elif (defined(__linux__) || defined(__APPLE__))
|
||||
|
||||
char result[PATH_MAX];
|
||||
auto r = realpath( filename.c_str(), result );
|
||||
if (!r) {
|
||||
if (errno == ENOENT) {
|
||||
int sep = filename.find_last_of( CANONICAL_PATH_SEPARATOR );
|
||||
if (sep != std::string::npos) {
|
||||
return joinPath( abspath( filename.substr(0, sep) ), filename.substr(sep) );
|
||||
// Must resolve links, so first try realpath on the whole thing
|
||||
const char *r = realpath( path.c_str(), result );
|
||||
if(r == nullptr) {
|
||||
// If the error was ENOENT and the path doesn't have to exist,
|
||||
// try to resolve symlinks in progressively shorter prefixes of the path
|
||||
if(errno == ENOENT && !mustExist) {
|
||||
std::string prefix = popPath(path);
|
||||
std::string suffix = path.substr(prefix.size());
|
||||
if(prefix.empty() && (suffix.empty() || suffix[0] != '~')) {
|
||||
prefix = ".";
|
||||
}
|
||||
else if (filename.find("~") == std::string::npos) {
|
||||
return joinPath( abspath( "." ), filename );
|
||||
if(!prefix.empty()) {
|
||||
return cleanPath(joinPath(abspath(prefix, true, false), suffix));
|
||||
}
|
||||
}
|
||||
Error e = systemErrorCodeToError();
|
||||
TraceEvent(SevError, "AbsolutePathError").detail("Filename", filename).GetLastError().error(e);
|
||||
Severity sev = e.code() == error_code_io_error ? SevError : SevWarnAlways;
|
||||
TraceEvent(sev, "AbsolutePathError").detail("Path", path).GetLastError().error(e);
|
||||
throw e;
|
||||
}
|
||||
return std::string(r);
|
||||
@ -1874,6 +1987,10 @@ std::string abspath( std::string const& filename ) {
|
||||
#endif
|
||||
}
|
||||
|
||||
std::string parentDirectory( std::string const& path, bool resolveLinks, bool mustExist ) {
|
||||
return popPath(abspath(path, resolveLinks, mustExist));
|
||||
}
|
||||
|
||||
std::string basename( std::string const& filename ) {
|
||||
auto abs = abspath(filename);
|
||||
size_t sep = abs.find_last_of( CANONICAL_PATH_SEPARATOR );
|
||||
@ -1881,18 +1998,6 @@ std::string basename( std::string const& filename ) {
|
||||
return abs.substr(sep+1);
|
||||
}
|
||||
|
||||
std::string parentDirectory( std::string const& filename ) {
|
||||
auto abs = abspath(filename);
|
||||
size_t sep = abs.find_last_of( CANONICAL_PATH_SEPARATOR );
|
||||
if (sep == std::string::npos) {
|
||||
TraceEvent(SevError, "GetParentDirectoryOfFile")
|
||||
.detail("File", filename)
|
||||
.GetLastError();
|
||||
throw platform_error();
|
||||
}
|
||||
return abs.substr(0, sep);
|
||||
}
|
||||
|
||||
std::string getUserHomeDirectory() {
|
||||
#if defined(__unixish__)
|
||||
const char* ret = getenv( "HOME" );
|
||||
@ -2877,3 +2982,142 @@ TEST_CASE("/flow/Platform/getMemoryInfo") {
|
||||
return Void();
|
||||
}
|
||||
#endif
|
||||
|
||||
int testPathFunction(const char *name, std::function<std::string(std::string)> fun, std::string a, ErrorOr<std::string> b) {
|
||||
ErrorOr<std::string> result;
|
||||
try { result = fun(a); } 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') -> %s", r ? "PASS" : "FAIL", name, a.c_str(), 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());
|
||||
}
|
||||
printf("\n");
|
||||
return r ? 0 : 1;
|
||||
}
|
||||
|
||||
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, 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(), 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());
|
||||
}
|
||||
printf("\n");
|
||||
return r ? 0 : 1;
|
||||
}
|
||||
|
||||
TEST_CASE("/flow/Platform/directoryOps") {
|
||||
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("popPath", popPath, "/a/b///..//", "/a/b/");
|
||||
|
||||
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, "..", "..");
|
||||
errors += testPathFunction("cleanPath", cleanPath, "../.././", "../..");
|
||||
errors += testPathFunction("cleanPath", cleanPath, "../a/b/..//", "../a");
|
||||
errors += testPathFunction("cleanPath", cleanPath, "a/b/.././../c/./././////./d/..//..", ".");
|
||||
errors += testPathFunction("cleanPath", cleanPath, "/..", "/");
|
||||
errors += testPathFunction("cleanPath", cleanPath, "/../foo/bar///", "/foo/bar");
|
||||
errors += testPathFunction("cleanPath", cleanPath, "/a/b/../.././../", "/");
|
||||
errors += testPathFunction("cleanPath", cleanPath, ".", ".");
|
||||
|
||||
// Creating this directory in backups avoids some sanity checks
|
||||
platform::createDirectory("simfdb/backups/one/two/three");
|
||||
std::string cwd = platform::getWorkingDirectory();
|
||||
|
||||
#ifndef _WIN32
|
||||
// Create some symlinks and test resolution (or non-resolution) of them
|
||||
ASSERT(symlink("one/two", "simfdb/backups/four") == 0);
|
||||
ASSERT(symlink("../backups/four", "simfdb/backups/five") == 0);
|
||||
|
||||
errors += testPathFunction2("abspath", abspath, "simfdb/backups/four/../two", true, true, joinPath(cwd, "simfdb/backups/one/two"));
|
||||
errors += testPathFunction2("abspath", abspath, "simfdb/backups/five/../two", true, true, joinPath(cwd, "simfdb/backups/one/two"));
|
||||
errors += testPathFunction2("abspath", abspath, "simfdb/backups/five/../two", true, false, joinPath(cwd, "simfdb/backups/one/two"));
|
||||
errors += testPathFunction2("abspath", abspath, "simfdb/backups/five/../three", true, true, platform_error());
|
||||
errors += testPathFunction2("abspath", abspath, "simfdb/backups/five/../three", true, false, joinPath(cwd, "simfdb/backups/one/three"));
|
||||
errors += testPathFunction2("abspath", abspath, "simfdb/backups/five/../three/../four", true, false, joinPath(cwd, "simfdb/backups/one/four"));
|
||||
|
||||
errors += testPathFunction2("parentDirectory", parentDirectory, "simfdb/backups/four/../two", true, true, joinPath(cwd, "simfdb/backups/one/"));
|
||||
errors += testPathFunction2("parentDirectory", parentDirectory, "simfdb/backups/five/../two", true, true, joinPath(cwd, "simfdb/backups/one/"));
|
||||
errors += testPathFunction2("parentDirectory", parentDirectory, "simfdb/backups/five/../two", true, false, joinPath(cwd, "simfdb/backups/one/"));
|
||||
errors += testPathFunction2("parentDirectory", parentDirectory, "simfdb/backups/five/../three", true, true, platform_error());
|
||||
errors += testPathFunction2("parentDirectory", parentDirectory, "simfdb/backups/five/../three", true, false, joinPath(cwd, "simfdb/backups/one/"));
|
||||
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);
|
||||
errors += testPathFunction2("abspath", abspath, "/a", true, false, "/a");
|
||||
errors += testPathFunction2("abspath", abspath, "one/two/three/four", false, true, platform_error());
|
||||
errors += testPathFunction2("abspath", abspath, "one/two/three/four", false, false, joinPath(cwd, "one/two/three/four"));
|
||||
errors += testPathFunction2("abspath", abspath, "one/two/three/./four", false, false, joinPath(cwd, "one/two/three/four"));
|
||||
errors += testPathFunction2("abspath", abspath, "one/two/three/./four", false, false, joinPath(cwd, "one/two/three/four"));
|
||||
errors += testPathFunction2("abspath", abspath, "one/two/three/./four/..", false, false, joinPath(cwd, "one/two/three"));
|
||||
errors += testPathFunction2("abspath", abspath, "one/./two/../three/./four", false, false, joinPath(cwd, "one/three/four"));
|
||||
errors += testPathFunction2("abspath", abspath, "one/./two/../three/./four", false, true, platform_error());
|
||||
errors += testPathFunction2("abspath", abspath, "one/two/three/./four", false, true, platform_error());
|
||||
errors += testPathFunction2("abspath", abspath, "simfdb/backups/one/two/three", false, true, joinPath(cwd, "simfdb/backups/one/two/three"));
|
||||
errors += testPathFunction2("abspath", abspath, "simfdb/backups/one/two/threefoo", false, true, platform_error());
|
||||
errors += testPathFunction2("abspath", abspath, "simfdb/backups/four/../two", false, false, joinPath(cwd, "simfdb/backups/two"));
|
||||
errors += testPathFunction2("abspath", abspath, "simfdb/backups/four/../two", false, true, platform_error());
|
||||
errors += testPathFunction2("abspath", abspath, "simfdb/backups/five/../two", false, true, platform_error());
|
||||
errors += testPathFunction2("abspath", abspath, "simfdb/backups/five/../two", false, false, joinPath(cwd, "simfdb/backups/two"));
|
||||
errors += testPathFunction2("abspath", abspath, "foo/./../foo2/./bar//", false, false, joinPath(cwd, "foo2/bar"));
|
||||
errors += testPathFunction2("abspath", abspath, "foo/./../foo2/./bar//", false, true, platform_error());
|
||||
errors += testPathFunction2("abspath", abspath, "foo/./../foo2/./bar//", true, false, joinPath(cwd, "foo2/bar"));
|
||||
errors += testPathFunction2("abspath", abspath, "foo/./../foo2/./bar//", true, true, platform_error());
|
||||
|
||||
errors += testPathFunction2("parentDirectory", parentDirectory, "", true, false, platform_error());
|
||||
errors += testPathFunction2("parentDirectory", parentDirectory, "/", true, false, "/");
|
||||
errors += testPathFunction2("parentDirectory", parentDirectory, "/a", true, false, "/");
|
||||
errors += testPathFunction2("parentDirectory", parentDirectory, ".", false, false, cleanPath(joinPath(cwd, "..")) + "/");
|
||||
errors += testPathFunction2("parentDirectory", parentDirectory, "./foo", false, false, cleanPath(cwd) + "/");
|
||||
errors += testPathFunction2("parentDirectory", parentDirectory, "one/two/three/four", false, true, platform_error());
|
||||
errors += testPathFunction2("parentDirectory", parentDirectory, "one/two/three/four", false, false, joinPath(cwd, "one/two/three/"));
|
||||
errors += testPathFunction2("parentDirectory", parentDirectory, "one/two/three/./four", false, false, joinPath(cwd, "one/two/three/"));
|
||||
errors += testPathFunction2("parentDirectory", parentDirectory, "one/two/three/./four/..", false, false, joinPath(cwd, "one/two/"));
|
||||
errors += testPathFunction2("parentDirectory", parentDirectory, "one/./two/../three/./four", false, false, joinPath(cwd, "one/three/"));
|
||||
errors += testPathFunction2("parentDirectory", parentDirectory, "one/./two/../three/./four", false, true, platform_error());
|
||||
errors += testPathFunction2("parentDirectory", parentDirectory, "one/two/three/./four", false, true, platform_error());
|
||||
errors += testPathFunction2("parentDirectory", parentDirectory, "simfdb/backups/one/two/three", false, true, joinPath(cwd, "simfdb/backups/one/two/"));
|
||||
errors += testPathFunction2("parentDirectory", parentDirectory, "simfdb/backups/one/two/threefoo", false, true, platform_error());
|
||||
errors += testPathFunction2("parentDirectory", parentDirectory, "simfdb/backups/four/../two", false, false, joinPath(cwd, "simfdb/backups/"));
|
||||
errors += testPathFunction2("parentDirectory", parentDirectory, "simfdb/backups/four/../two", false, true, platform_error());
|
||||
errors += testPathFunction2("parentDirectory", parentDirectory, "simfdb/backups/five/../two", false, true, platform_error());
|
||||
errors += testPathFunction2("parentDirectory", parentDirectory, "simfdb/backups/five/../two", false, false, joinPath(cwd, "simfdb/backups/"));
|
||||
errors += testPathFunction2("parentDirectory", parentDirectory, "foo/./../foo2/./bar//", false, false, joinPath(cwd, "foo2/"));
|
||||
errors += testPathFunction2("parentDirectory", parentDirectory, "foo/./../foo2/./bar//", false, true, platform_error());
|
||||
errors += testPathFunction2("parentDirectory", parentDirectory, "foo/./../foo2/./bar//", true, false, joinPath(cwd, "foo2/"));
|
||||
errors += testPathFunction2("parentDirectory", parentDirectory, "foo/./../foo2/./bar//", true, true, platform_error());
|
||||
|
||||
printf("%d errors.\n", errors);
|
||||
|
||||
ASSERT(errors == 0);
|
||||
return Void();
|
||||
}
|
||||
|
@ -320,15 +320,33 @@ void writeFile(std::string const& filename, std::string const& content);
|
||||
|
||||
std::string joinPath( std::string const& directory, std::string const& filename );
|
||||
|
||||
// Returns an absolute path canonicalized to use only CANONICAL_PATH_SEPARATOR
|
||||
std::string abspath( 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. 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 );
|
||||
|
||||
// abspath() resolves the given path to a canonical form.
|
||||
// If path is relative, the result will be based on the current working directory.
|
||||
// If resolveLinks is true then symbolic links will be expanded BEFORE resolving '..' references.
|
||||
// An empty path or a non-existent path when mustExist is true will result in a platform_error() exception.
|
||||
// Upon success, all '..' references will be resolved with the assumption that non-existent components
|
||||
// are NOT symbolic links.
|
||||
// User directory references such as '~' or '~user' are effectively treated as symbolic links which
|
||||
// are impossible to resolve, so resolveLinks=true results in failure and resolveLinks=false results
|
||||
// in the reference being left in-tact prior to resolving '..' references.
|
||||
std::string abspath( std::string const& path, bool resolveLinks = true, bool mustExist = false );
|
||||
|
||||
// parentDirectory() returns the parent directory of the given file or directory in a canonical form,
|
||||
// with a single trailing path separator.
|
||||
// It uses absPath() with the same bool options to initially obtain a canonical form, and upon success
|
||||
// removes the final path component, if present.
|
||||
std::string parentDirectory( std::string const& path, bool resolveLinks = true, bool mustExist = false);
|
||||
|
||||
// Returns the portion of the path following the last path separator (e.g. the filename or directory name)
|
||||
std::string basename( std::string const& filename );
|
||||
|
||||
// Returns the parent directory of the given file or directory
|
||||
std::string parentDirectory( std::string const& filename );
|
||||
|
||||
// Returns the home directory of the current user
|
||||
std::string getUserHomeDirectory();
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user