commit e79ad2dbcb14d1e66f6edead4ff87b62e575a8e7 Author: Jonathan Wakely Date: Wed Sep 16 14:07:54 2015 +0100 Implement filesystem::canonical() without realpath PR libstdc++/67173 * acinclude.m4 (GLIBCXX_CHECK_FILESYSTEM_DEPS): Check _XOPEN_VERSION and PATH_MAX for _GLIBCXX_USE_REALPATH. * config.h.in: Regenerate. * configure: Regenerate. * src/filesystem/dir.cc: Define _XOPEN_VERSION. * src/filesystem/ops.cc: Likewise. (canonical) [!_GLIBCXX_USE_REALPATH]: Add alternative implementation. * testsuite/experimental/filesystem/operations/canonical.cc: New. diff --git a/libstdc++-v3/acinclude.m4 b/libstdc++-v3/acinclude.m4 index 64c9b7e..364a7d2 100644 --- a/libstdc++-v3/acinclude.m4 +++ b/libstdc++-v3/acinclude.m4 @@ -3926,7 +3926,7 @@ dnl AC_LANG_SAVE AC_LANG_CPLUSPLUS ac_save_CXXFLAGS="$CXXFLAGS" - CXXFLAGS="$CXXFLAGS -fno-exceptions" + CXXFLAGS="$CXXFLAGS -fno-exceptions -D_XOPEN_SOURCE=700" dnl AC_MSG_CHECKING([for struct dirent.d_type]) AC_CACHE_VAL(glibcxx_cv_dirent_d_type, [dnl @@ -3947,13 +3947,24 @@ dnl AC_MSG_CHECKING([for realpath]) AC_CACHE_VAL(glibcxx_cv_realpath, [dnl GCC_TRY_COMPILE_OR_LINK( - [#include ], - [char *tmp = realpath((const char*)NULL, (char*)NULL);], + [ + #include + #include + ], + [ + #if _XOPEN_VERSION < 500 + #error + #elif _XOPEN_VERSION >= 700 || defined(PATH_MAX) + char *tmp = realpath((const char*)NULL, (char*)NULL); + #else + #error + #endif + ], [glibcxx_cv_realpath=yes], [glibcxx_cv_realpath=no]) ]) if test $glibcxx_cv_realpath = yes; then - AC_DEFINE(_GLIBCXX_USE_REALPATH, 1, [Define if realpath is available in .]) + AC_DEFINE(_GLIBCXX_USE_REALPATH, 1, [Define if usable realpath is available in .]) fi AC_MSG_RESULT($glibcxx_cv_realpath) dnl diff --git a/libstdc++-v3/src/filesystem/dir.cc b/libstdc++-v3/src/filesystem/dir.cc index 016a78d..cfa44b1 100644 --- a/libstdc++-v3/src/filesystem/dir.cc +++ b/libstdc++-v3/src/filesystem/dir.cc @@ -22,6 +22,8 @@ // see the files COPYING3 and COPYING.RUNTIME respectively. If not, see // . +#define _XOPEN_SOURCE 700 + #include #include #include diff --git a/libstdc++-v3/src/filesystem/ops.cc b/libstdc++-v3/src/filesystem/ops.cc index cefb927..45daf34 100644 --- a/libstdc++-v3/src/filesystem/ops.cc +++ b/libstdc++-v3/src/filesystem/ops.cc @@ -22,6 +22,8 @@ // see the files COPYING3 and COPYING.RUNTIME respectively. If not, see // . +#define _XOPEN_SOURCE 700 + #include #include #include @@ -96,23 +98,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 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 diff --git a/libstdc++-v3/testsuite/experimental/filesystem/operations/canonical.cc b/libstdc++-v3/testsuite/experimental/filesystem/operations/canonical.cc new file mode 100644 index 0000000..0bcb85b --- /dev/null +++ b/libstdc++-v3/testsuite/experimental/filesystem/operations/canonical.cc @@ -0,0 +1,74 @@ +// Copyright (C) 2015 Free Software Foundation, Inc. +// +// This file is part of the GNU ISO C++ Library. This library is free +// software; you can redistribute it and/or modify it under the +// terms of the GNU General Public License as published by the +// Free Software Foundation; either version 3, or (at your option) +// any later version. + +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License along +// with this library; see the file COPYING3. If not see +// . + +// { dg-options "-std=gnu++11 -lstdc++fs" } +// { dg-require-filesystem-ts "" } + +#include +#include +#include + +namespace fs = std::experimental::filesystem; + +void +test01() +{ + std::error_code ec; + fs::path p = ""; + canonical( p, ec ); + VERIFY( !ec ); + + canonical( __gnu_test::nonexistent_path(), ec ); + VERIFY( !ec ); + + p = "/"; + p = canonical( p, ec ); + VERIFY( p == "/" ); + VERIFY( ec ); + + p = "/."; + p = canonical( p, ec ); + VERIFY( p == "/" ); + VERIFY( ec ); + + p = "/.."; + p = canonical( p, ec ); + VERIFY( p == "/" ); + VERIFY( ec ); + + p = "/../.././."; + p = canonical( p, ec ); + VERIFY( p == "/" ); + VERIFY( ec ); + + p = "/dev/stdin"; + if (exists(p)) + { + auto p2 = canonical(p); + if (is_symlink(p)) + VERIFY( p != p2 ); + else + VERIFY( p == p2 ); + VERIFY( canonical(p2) == p2 ); + } +} + +int +main() +{ + test01(); +}