diff options
Diffstat (limited to 'libstdc++-v3/src/filesystem/ops.cc')
-rw-r--r-- | libstdc++-v3/src/filesystem/ops.cc | 99 |
1 files changed, 87 insertions, 12 deletions
diff --git a/libstdc++-v3/src/filesystem/ops.cc b/libstdc++-v3/src/filesystem/ops.cc index cefb927c6a50..b5c8eb9e1118 100644 --- a/libstdc++-v3/src/filesystem/ops.cc +++ b/libstdc++-v3/src/filesystem/ops.cc @@ -96,23 +96,98 @@ namespace fs::path fs::canonical(const path& p, const path& base, error_code& ec) { - path can; + const path pa = absolute(p, base); + path result; #ifdef _GLIBCXX_USE_REALPATH - char* buffer = nullptr; -#if defined(__SunOS_5_10) && defined(PATH_MAX) - buffer = (char*)::malloc(PATH_MAX); -#endif - if (char_ptr rp = char_ptr{::realpath(absolute(p, base).c_str(), buffer)}) + char_ptr buf{ nullptr }; +# if _XOPEN_VERSION < 700 + // Not safe to call realpath(path, NULL) + buf.reset( (char*)::malloc(PATH_MAX) ); +# endif + if (char* rp = ::realpath(pa.c_str(), buf.get())) { - can.assign(rp.get()); + if (buf == nullptr) + buf.reset(rp); + result.assign(rp); ec.clear(); + return result; + } + if (errno != ENAMETOOLONG) + { + ec.assign(errno, std::generic_category()); + return result; } - else - ec.assign(errno, std::generic_category()); -#else - ec = std::make_error_code(std::errc::not_supported); #endif - return can; + + auto fail = [&ec, &result](int e) mutable { + if (!ec.value()) + ec.assign(e, std::generic_category()); + result.clear(); + }; + + if (!exists(pa, ec)) + { + fail(ENOENT); + return result; + } + // else we can assume no unresolvable symlink loops + + result = pa.root_path(); + + deque<path> cmpts; + for (auto& f : pa.relative_path()) + cmpts.push_back(f); + + while (!cmpts.empty()) + { + path f = std::move(cmpts.front()); + cmpts.pop_front(); + + if (f.compare(".") == 0) + { + if (!is_directory(result, ec)) + { + fail(ENOTDIR); + break; + } + } + else if (f.compare("..") == 0) + { + auto parent = result.parent_path(); + if (parent.empty()) + result = pa.root_path(); + else + result.swap(parent); + } + else + { + result /= f; + + if (is_symlink(result, ec)) + { + path link = read_symlink(result, ec); + if (!ec.value()) + { + if (link.is_absolute()) + { + result = link.root_path(); + link = link.relative_path(); + } + else + result.remove_filename(); + + cmpts.insert(cmpts.begin(), link.begin(), link.end()); + } + } + + if (ec.value() || !exists(result, ec)) + { + fail(ENOENT); + break; + } + } + } + return result; } fs::path |