diff mbox

[v3] Fix some Filesystem TS operations

Message ID 20150515183734.GU30202@redhat.com
State New
Headers show

Commit Message

Jonathan Wakely May 15, 2015, 6:37 p.m. UTC
Testing revealed a few bugs in how I handled paths that don't exist.
The new __gnu_test::nonexistent_path() function is a bit hacky but
should be good enough for the testsuite.

I've also made filesystem::temp_directory_path() check the TMP,
TEMPDIR and TEMP environment variables, as suggested in
[fs.op.temp_dir_path] at
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4099.html#fs.op.temp_dir_path
rather than my original implementation which only checked TMPDIR
(because I think that's the only one mentioned by POSIX). I'm not
unsure whether it's better to follow the non-normative note or only
check for the POSIX name. Anyone else have any thoughts?

Tested x86_64-linux, committed to trunk.

Comments

Jonathan Wakely May 15, 2015, 7:04 p.m. UTC | #1
On 15/05/15 19:37 +0100, Jonathan Wakely wrote:
>I've also made filesystem::temp_directory_path() check the TMP,
>TEMPDIR and TEMP environment variables, as suggested in
>[fs.op.temp_dir_path] at
>http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4099.html#fs.op.temp_dir_path
>rather than my original implementation which only checked TMPDIR
>(because I think that's the only one mentioned by POSIX). I'm not
>unsure whether it's better to follow the non-normative note or only
>check for the POSIX name. Anyone else have any thoughts?

Oops, unintended double negative. I meant I'm unsure.
diff mbox

Patch

commit 7bb46da1e629300e84641a59303d32ecbee7047c
Author: Jonathan Wakely <jwakely@redhat.com>
Date:   Fri May 15 13:06:09 2015 +0100

    	* src/filesystem/ops.cc (stat_type): Define alias for struct stat and
    	use throughout the file.
    	(make_file_type): New function.
    	(file_size(const path&, error_code&)): Report an error for anything
    	that isn't a regular file.
    	(status(const path&), symlink_status(const path&)): Do not throw for
    	file_type::not_found.
    	(temp_directory_path()): Check additional environment variables.
    	* testsuite/experimental/filesystem/operations/exists.cc: New.
    	* testsuite/experimental/filesystem/operations/file_size.cc: New.
    	* testsuite/experimental/filesystem/operations/status.cc: New.
    	* testsuite/experimental/filesystem/operations/temp_directory_path.cc:
    	New.

diff --git a/libstdc++-v3/src/filesystem/ops.cc b/libstdc++-v3/src/filesystem/ops.cc
index f24cc19..661685a 100644
--- a/libstdc++-v3/src/filesystem/ops.cc
+++ b/libstdc++-v3/src/filesystem/ops.cc
@@ -150,33 +150,39 @@  namespace
 #ifdef _GLIBCXX_HAVE_SYS_STAT_H
 namespace
 {
-  fs::file_status
-  make_file_status(const struct ::stat& st)
+  typedef struct ::stat stat_type;
+
+  inline fs::file_type
+  make_file_type(const stat_type& st)
   {
-    using fs::file_status;
     using fs::file_type;
-    using fs::perms;
-    file_type ft;
-    perms perm = static_cast<perms>(st.st_mode) & perms::mask;
 #ifdef _GLIBCXX_HAVE_S_ISREG
     if (S_ISREG(st.st_mode))
-      ft = file_type::regular;
+      return file_type::regular;
     else if (S_ISDIR(st.st_mode))
-      ft = file_type::directory;
+      return file_type::directory;
     else if (S_ISCHR(st.st_mode))
-      ft = file_type::character;
+      return file_type::character;
     else if (S_ISBLK(st.st_mode))
-      ft = file_type::block;
+      return file_type::block;
     else if (S_ISFIFO(st.st_mode))
-      ft = file_type::fifo;
+      return file_type::fifo;
     else if (S_ISLNK(st.st_mode))
-      ft = file_type::symlink;
+      return file_type::symlink;
     else if (S_ISSOCK(st.st_mode))
-      ft = file_type::socket;
-    else
+      return file_type::socket;
 #endif
-      ft = file_type::unknown;
-    return file_status{ft, perm};
+    return file_type::unknown;
+
+  }
+
+  inline fs::file_status
+  make_file_status(const stat_type& st)
+  {
+    return fs::file_status{
+	make_file_type(st),
+	static_cast<fs::perms>(st.st_mode) & fs::perms::mask
+    };
   }
 
   inline bool
@@ -186,7 +192,7 @@  namespace
   }
 
   inline fs::file_time_type
-  file_time(const struct ::stat& st)
+  file_time(const stat_type& st)
   {
     using namespace std::chrono;
     return fs::file_time_type{
@@ -201,10 +207,10 @@  namespace
   bool
   do_copy_file(const fs::path& from, const fs::path& to,
 	       fs::copy_options option,
-	       struct ::stat* from_st, struct ::stat* to_st,
+	       stat_type* from_st, stat_type* to_st,
 	       std::error_code& ec) noexcept
   {
-    struct ::stat st1, st2;
+    stat_type st1, st2;
     fs::file_status t, f;
 
     if (to_st == nullptr)
@@ -342,7 +348,7 @@  fs::copy(const path& from, const path& to, copy_options options,
   bool use_lstat = create_symlinks || skip_symlinks;
 
   file_status f, t;
-  struct ::stat from_st, to_st;
+  stat_type from_st, to_st;
   if (use_lstat
       ? ::lstat(from.c_str(), &from_st)
       : ::stat(from.c_str(), &from_st))
@@ -564,7 +570,7 @@  fs::create_directory(const path& p, const path& attributes,
 		     error_code& ec) noexcept
 {
 #ifdef _GLIBCXX_HAVE_SYS_STAT_H
-  struct ::stat st;
+  stat_type st;
   if (::stat(attributes.c_str(), &st))
     {
       ec.assign(errno, std::generic_category());
@@ -747,7 +753,7 @@  bool
 fs::equivalent(const path& p1, const path& p2, error_code& ec) noexcept
 {
 #ifdef _GLIBCXX_HAVE_SYS_STAT_H
-  struct ::stat st1, st2;
+  stat_type st1, st2;
   if (::stat(p1.c_str(), &st1) == 0 && ::stat(p2.c_str(), &st2) == 0)
     {
       file_status s1 = make_file_status(st1);
@@ -789,7 +795,7 @@  namespace
     do_stat(const fs::path& p, std::error_code& ec, Accessor f, T deflt)
     {
 #ifdef _GLIBCXX_HAVE_SYS_STAT_H
-      struct ::stat st;
+      stat_type st;
       if (::stat(p.c_str(), &st))
 	{
 	  ec.assign(errno, std::generic_category());
@@ -807,8 +813,24 @@  namespace
 std::uintmax_t
 fs::file_size(const path& p, error_code& ec) noexcept
 {
-  return do_stat(p, ec, std::mem_fn(&stat::st_size),
-		 static_cast<uintmax_t>(-1));
+  struct S
+  {
+    S(const stat_type& st) : type(make_file_type(st)), size(st.st_size) { }
+    S() : type(file_type::not_found) { }
+    file_type type;
+    size_t size;
+  };
+  auto s = do_stat(p, ec, [](const auto& st) { return S{st}; }, S{});
+  if (s.type == file_type::regular)
+    return s.size;
+  if (!ec)
+    {
+      if (s.type == file_type::directory)
+	ec = std::make_error_code(std::errc::is_a_directory);
+      else
+	ec = std::make_error_code(std::errc::not_supported);
+    }
+  return -1;
 }
 
 std::uintmax_t
@@ -938,7 +960,7 @@  fs::read_symlink(const path& p)
 fs::path fs::read_symlink(const path& p, error_code& ec)
 {
 #ifdef _GLIBCXX_HAVE_SYS_STAT_H
-  struct ::stat st;
+  stat_type st;
   if (::lstat(p.c_str(), &st))
     {
       ec.assign(errno, std::generic_category());
@@ -1094,13 +1116,13 @@  fs::file_status
 fs::status(const fs::path& p, std::error_code& ec) noexcept
 {
   file_status status;
-  struct ::stat st;
+  stat_type st;
   if (::stat(p.c_str(), &st))
     {
       int err = errno;
       ec.assign(err, std::generic_category());
       if (is_not_found_errno(err))
-	status = file_status{file_type::not_found};
+	status.type(file_type::not_found);
     }
   else
     {
@@ -1114,13 +1136,13 @@  fs::file_status
 fs::symlink_status(const fs::path& p, std::error_code& ec) noexcept
 {
   file_status status;
-  struct ::stat st;
+  stat_type st;
   if (::lstat(p.c_str(), &st))
     {
       int err = errno;
       ec.assign(err, std::generic_category());
       if (is_not_found_errno(err))
-	status = file_status{file_type::not_found};
+	status.type(file_type::not_found);
     }
   else
     {
@@ -1135,20 +1157,20 @@  fs::file_status
 fs::status(const fs::path& p)
 {
   std::error_code ec;
-  auto s = status(p, ec);
-  if (ec.value())
+  auto result = status(p, ec);
+  if (result.type() == file_type::none)
     _GLIBCXX_THROW_OR_ABORT(filesystem_error("status", p, ec));
-  return s;
+  return result;
 }
 
 fs::file_status
 fs::symlink_status(const fs::path& p)
 {
   std::error_code ec;
-  auto s = symlink_status(p, ec);
-  if (ec.value())
-    _GLIBCXX_THROW_OR_ABORT(filesystem_error("symlink_status", ec));
-  return s;
+  auto result = symlink_status(p, ec);
+  if (result.type() == file_type::none)
+    _GLIBCXX_THROW_OR_ABORT(filesystem_error("symlink_status", p, ec));
+  return result;
 }
 
 fs::path
@@ -1194,11 +1216,18 @@  fs::path fs::temp_directory_path(error_code& ec)
   ec = std::make_error_code(std::errc::not_supported);
   return {}; // TODO
 #else
-  const char* tmpdir = ::getenv("TMPDIR");
-  if (!tmpdir)
-    tmpdir = "/tmp";
-  ec.clear();
-  return tmpdir;
+  const char* tmpdir = nullptr;
+  const char* env[] = { "TMPDIR", "TMP", "TEMP", "TEMPDIR", nullptr };
+  for (auto e = env; tmpdir == nullptr && *e != nullptr; ++e)
+    tmpdir = ::getenv(*e);
+  path p = tmpdir ? tmpdir : "/tmp";
+  if (exists(p) && is_directory(p))
+    {
+      ec.clear();
+      return p;
+    }
+  ec = std::make_error_code(std::errc::not_a_directory);
+  return {};
 #endif
 }
 
diff --git a/libstdc++-v3/testsuite/experimental/filesystem/operations/exists.cc b/libstdc++-v3/testsuite/experimental/filesystem/operations/exists.cc
new file mode 100644
index 0000000..0f1e5aa
--- /dev/null
+++ b/libstdc++-v3/testsuite/experimental/filesystem/operations/exists.cc
@@ -0,0 +1,58 @@ 
+// 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
+// <http://www.gnu.org/licenses/>.
+
+// { dg-options "-std=gnu++11 -lstdc++fs" }
+// { dg-require-filesystem-ts "" }
+
+#include <experimental/filesystem>
+#include <testsuite_hooks.h>
+
+using std::experimental::filesystem::path;
+
+void
+test01()
+{
+  VERIFY( exists(path{"/"}) );
+  VERIFY( exists(path{"/."}) );
+  VERIFY( exists(path{"."}) );
+}
+
+void
+test02()
+{
+  path rel{"xXxXx"};
+  while (exists(rel))
+    rel /= "x";
+  VERIFY( !exists(rel) );
+}
+
+void
+test03()
+{
+  path abs{"/xXxXx"};
+  while (exists(abs))
+    abs /= "x";
+  VERIFY( !exists(abs) );
+}
+
+int
+main()
+{
+  test01();
+  test02();
+  test03();
+}
diff --git a/libstdc++-v3/testsuite/experimental/filesystem/operations/file_size.cc b/libstdc++-v3/testsuite/experimental/filesystem/operations/file_size.cc
new file mode 100644
index 0000000..04fa7bb
--- /dev/null
+++ b/libstdc++-v3/testsuite/experimental/filesystem/operations/file_size.cc
@@ -0,0 +1,70 @@ 
+// 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
+// <http://www.gnu.org/licenses/>.
+
+// { dg-options "-std=gnu++11 -lstdc++fs" }
+// { dg-require-filesystem-ts "" }
+
+#include <experimental/filesystem>
+#include <testsuite_hooks.h>
+#include <testsuite_fs.h>
+
+namespace fs = std::experimental::filesystem;
+
+void
+test01()
+{
+  std::error_code ec;
+  size_t size = fs::file_size(".", ec);
+  VERIFY( ec == std::errc::is_a_directory );
+  VERIFY( size == -1 );
+
+  try {
+    size = fs::file_size(".");
+    ec.clear();
+  } catch (const fs::filesystem_error& e) {
+    ec = e.code();
+  }
+  VERIFY( ec == std::errc::is_a_directory );
+  VERIFY( size == -1 );
+}
+
+void
+test02()
+{
+  fs::path p = __gnu_test::nonexistent_path();
+
+  std::error_code ec;
+  size_t size = fs::file_size(p, ec);
+  VERIFY( ec );
+  VERIFY( size == -1 );
+
+  try {
+    size = fs::file_size(p);
+    ec.clear();
+  } catch (const fs::filesystem_error& e) {
+    ec = e.code();
+  }
+  VERIFY( ec );
+  VERIFY( size == -1 );
+}
+
+int
+main()
+{
+  test01();
+  test02();
+}
diff --git a/libstdc++-v3/testsuite/experimental/filesystem/operations/status.cc b/libstdc++-v3/testsuite/experimental/filesystem/operations/status.cc
new file mode 100644
index 0000000..2c54494
--- /dev/null
+++ b/libstdc++-v3/testsuite/experimental/filesystem/operations/status.cc
@@ -0,0 +1,58 @@ 
+// 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
+// <http://www.gnu.org/licenses/>.
+
+// { dg-options "-std=gnu++11 -lstdc++fs" }
+// { dg-require-filesystem-ts "" }
+
+#include <experimental/filesystem>
+#include <testsuite_hooks.h>
+#include <testsuite_fs.h>
+
+namespace fs = std::experimental::filesystem;
+
+void
+test01()
+{
+  std::error_code ec;
+  fs::file_status st1 = fs::status(".", ec);
+  VERIFY( !ec );
+  VERIFY( st1.type() == fs::file_type::directory );
+
+  fs::file_status st2 = fs::status(".");
+  VERIFY( st2.type() == fs::file_type::directory );
+}
+
+void
+test02()
+{
+  fs::path p = __gnu_test::nonexistent_path();
+
+  std::error_code ec;
+  fs::file_status st1 = fs::status(p, ec);
+  VERIFY( ec );
+  VERIFY( st1.type() == fs::file_type::not_found );
+
+  fs::file_status st2 = fs::status(p);
+  VERIFY( st2.type() == fs::file_type::not_found );
+}
+
+int
+main()
+{
+  test01();
+  test02();
+}
diff --git a/libstdc++-v3/testsuite/experimental/filesystem/operations/temp_directory_path.cc b/libstdc++-v3/testsuite/experimental/filesystem/operations/temp_directory_path.cc
new file mode 100644
index 0000000..2aacd1c
--- /dev/null
+++ b/libstdc++-v3/testsuite/experimental/filesystem/operations/temp_directory_path.cc
@@ -0,0 +1,80 @@ 
+// 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
+// <http://www.gnu.org/licenses/>.
+
+// { dg-options "-std=gnu++11 -lstdc++fs" }
+// { dg-require-filesystem-ts "" }
+
+#include <experimental/filesystem>
+#include <stdlib.h>
+#include <testsuite_hooks.h>
+#include <testsuite_fs.h>
+
+void
+clean_env()
+{
+  ::unsetenv("TMPDIR");
+  ::unsetenv("TMP");
+  ::unsetenv("TEMPDIR");
+  ::unsetenv("TEMP");
+}
+
+namespace fs = std::experimental::filesystem;
+
+void
+test01()
+{
+  clean_env();
+
+  if (!fs::exists("/tmp"))
+    return; // just give up
+
+  std::error_code ec;
+  fs::path p1 = fs::temp_directory_path(ec);
+  VERIFY( exists(p1) );
+
+  fs::path p2 = fs::temp_directory_path();
+  VERIFY( p1 == p2 );
+}
+
+void
+test02()
+{
+  clean_env();
+
+  if (::setenv("TMPDIR", __gnu_test::nonexistent_path().string().c_str(), 1))
+    return; // just give up
+
+  std::error_code ec;
+  fs::path p = fs::temp_directory_path(ec);
+  VERIFY( ec );
+
+  std::error_code ec2;
+  try {
+    p = fs::temp_directory_path();
+  } catch (const fs::filesystem_error& e) {
+    ec2 = e.code();
+  }
+  VERIFY( ec2 == ec );
+}
+
+
+int
+main()
+{
+  test01();
+  test02();
+}
diff --git a/libstdc++-v3/testsuite/util/testsuite_fs.h b/libstdc++-v3/testsuite/util/testsuite_fs.h
index 0fb3a45..f404a7a 100644
--- a/libstdc++-v3/testsuite/util/testsuite_fs.h
+++ b/libstdc++-v3/testsuite/util/testsuite_fs.h
@@ -25,6 +25,11 @@ 
 #include <experimental/filesystem>
 #include <iostream>
 #include <string>
+#include <cstdio>
+#if defined(_GNU_SOURCE) || _XOPEN_SOURCE >= 500 || _POSIX_C_SOURCE >= 200112L
+# include <stdlib.h>
+# include <unistd.h>
+#endif
 
 namespace __gnu_test
 {
@@ -63,5 +68,31 @@  namespace __gnu_test
     "a", "a/b", "a/b/", "a/b/c", "a/b/c.d", "a/b/..", "a/b/c.", "a/b/.c"
   };
 
+  // This is NOT supposed to be a secure way to get a unique name!
+  // We just need a path that doesn't exist for testing purposes.
+  std::experimental::filesystem::path
+  nonexistent_path()
+  {
+    std::experimental::filesystem::path p;
+#if defined(_GNU_SOURCE) || _XOPEN_SOURCE >= 500 || _POSIX_C_SOURCE >= 200112L
+    char tmp[] = "test.XXXXXX";
+    int fd = ::mkstemp(tmp);
+    if (fd == -1)
+      throw std::experimental::filesystem::filesystem_error("mkstemp failed",
+	  std::error_code(errno, std::generic_category()));
+    ::unlink(tmp);
+    ::close(fd);
+    p = tmp;
+#else
+    char* tmp = tempnam(".", "test.");
+    if (!tmp)
+      throw std::experimental::filesystem::filesystem_error("tempnam failed",
+	  std::error_code(errno, std::generic_category()));
+    p = tmp;
+    ::free(tmp);
+#endif
+    return p;
+  }
+
 } // namespace __gnu_test
 #endif