1  // Filesystem operation utilities -*- C++ -*-
       2  
       3  // Copyright (C) 2014-2023 Free Software Foundation, Inc.
       4  //
       5  // This file is part of the GNU ISO C++ Library.  This library is free
       6  // software; you can redistribute it and/or modify it under the
       7  // terms of the GNU General Public License as published by the
       8  // Free Software Foundation; either version 3, or (at your option)
       9  // any later version.
      10  
      11  // This library is distributed in the hope that it will be useful,
      12  // but WITHOUT ANY WARRANTY; without even the implied warranty of
      13  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      14  // GNU General Public License for more details.
      15  
      16  // Under Section 7 of GPL version 3, you are granted additional
      17  // permissions described in the GCC Runtime Library Exception, version
      18  // 3.1, as published by the Free Software Foundation.
      19  
      20  // You should have received a copy of the GNU General Public License and
      21  // a copy of the GCC Runtime Library Exception along with this program;
      22  // see the files COPYING3 and COPYING.RUNTIME respectively.  If not, see
      23  // <http://www.gnu.org/licenses/>.
      24  
      25  #ifndef _GLIBCXX_OPS_COMMON_H
      26  #define _GLIBCXX_OPS_COMMON_H 1
      27  
      28  #include <chrono>
      29  #include <bits/move.h> // std::__exchange
      30  
      31  #ifdef _GLIBCXX_HAVE_UNISTD_H
      32  # include <unistd.h>
      33  # ifdef _GLIBCXX_HAVE_FCNTL_H
      34  #  include <fcntl.h>  // AT_FDCWD, O_TRUNC etc.
      35  # endif
      36  # if defined(_GLIBCXX_HAVE_SYS_STAT_H) && defined(_GLIBCXX_HAVE_SYS_TYPES_H)
      37  #  include <sys/types.h>
      38  #  include <sys/stat.h>
      39  # endif
      40  #endif
      41  #if !_GLIBCXX_USE_UTIMENSAT && _GLIBCXX_HAVE_UTIME_H
      42  # include <utime.h> // utime
      43  #endif
      44  
      45  #ifdef _GLIBCXX_FILESYSTEM_IS_WINDOWS
      46  # include <wchar.h>
      47  #endif
      48  
      49  #ifdef NEED_DO_COPY_FILE
      50  # include <filesystem>
      51  # include <ext/stdio_filebuf.h>
      52  # ifdef _GLIBCXX_USE_SENDFILE
      53  #  include <sys/sendfile.h> // sendfile
      54  # endif
      55  #endif
      56  
      57  namespace std _GLIBCXX_VISIBILITY(default)
      58  {
      59  _GLIBCXX_BEGIN_NAMESPACE_VERSION
      60  
      61    // Get the last OS error (for POSIX this is just errno).
      62    inline error_code
      63    __last_system_error() noexcept
      64    {
      65  #ifdef _GLIBCXX_FILESYSTEM_IS_WINDOWS
      66      // N.B. use error_code::default_error_condition() to convert to generic.
      67      return {(int)::GetLastError(), std::system_category()};
      68  #else
      69      return {errno, std::generic_category()};
      70  #endif
      71    }
      72  
      73    // Get an error code indicating unsupported functionality.
      74    //
      75    // This should be used when a function is unable to behave as specified
      76    // due to an incomplete or partial implementation, e.g.
      77    // filesystem::equivalent(a, b) if is_other(a) && is_other(b) is true.
      78    //
      79    // Use errc::function_not_supported for functions that are entirely
      80    // unimplemented, e.g. create_symlink on Windows.
      81    //
      82    // Use errc::invalid_argument for requests to perform operations outside
      83    // the spec, e.g. trying to copy a directory using filesystem::copy_file.
      84    inline error_code
      85    __unsupported() noexcept
      86    {
      87  #if defined __AVR__
      88      // avr-libc defines ENOTSUP and EOPNOTSUPP but with nonsense values.
      89      // ENOSYS is defined though, so use an error_code corresponding to that.
      90      // This contradicts the comment above, but we don't have much choice.
      91      return std::make_error_code(std::errc::function_not_supported);
      92  #elif defined ENOTSUP
      93      return std::make_error_code(std::errc::not_supported);
      94  #elif defined EOPNOTSUPP
      95      // This is supposed to be for socket operations
      96      return std::make_error_code(std::errc::operation_not_supported);
      97  #else
      98      return std::make_error_code(std::errc::invalid_argument);
      99  #endif
     100    }
     101  
     102  namespace filesystem
     103  {
     104  namespace __gnu_posix
     105  {
     106  #ifdef _GLIBCXX_FILESYSTEM_IS_WINDOWS
     107  // Adapt the Windows _wxxx functions to look like POSIX xxx, but for wchar_t*.
     108    inline int open(const wchar_t* path, int flags)
     109    { return ::_wopen(path, flags); }
     110  
     111    inline int open(const wchar_t* path, int flags, int mode)
     112    { return ::_wopen(path, flags, mode); }
     113  
     114    inline int close(int fd)
     115    { return ::_close(fd); }
     116  
     117    typedef struct ::__stat64 stat_type;
     118  
     119    inline int stat(const wchar_t* path, stat_type* buffer)
     120    { return ::_wstat64(path, buffer); }
     121  
     122    inline int lstat(const wchar_t* path, stat_type* buffer)
     123    {
     124      // FIXME: symlinks not currently supported
     125      return stat(path, buffer);
     126    }
     127  
     128    using ::mode_t;
     129  
     130    inline int chmod(const wchar_t* path, mode_t mode)
     131    { return ::_wchmod(path, mode); }
     132  
     133    inline int mkdir(const wchar_t* path, mode_t)
     134    { return ::_wmkdir(path); }
     135  
     136    inline wchar_t* getcwd(wchar_t* buf, size_t size)
     137    { return ::_wgetcwd(buf, size > (size_t)INT_MAX ? INT_MAX : (int)size); }
     138  
     139    inline int chdir(const wchar_t* path)
     140    { return ::_wchdir(path); }
     141  
     142  #if !_GLIBCXX_USE_UTIMENSAT && _GLIBCXX_HAVE_UTIME_H
     143    using utimbuf = _utimbuf;
     144  
     145    inline int utime(const wchar_t* path, utimbuf* times)
     146    { return ::_wutime(path, times); }
     147  #endif
     148  
     149    inline int rename(const wchar_t* oldname, const wchar_t* newname)
     150    {
     151      if (MoveFileExW(oldname, newname,
     152  		    MOVEFILE_REPLACE_EXISTING | MOVEFILE_COPY_ALLOWED))
     153        return 0;
     154      if (GetLastError() == ERROR_ACCESS_DENIED)
     155        errno = EACCES;
     156      else
     157        errno = EIO;
     158      return -1;
     159    }
     160  
     161    using off_t = _off64_t;
     162    inline int truncate(const wchar_t* path, _off64_t length)
     163    {
     164      const int fd = ::_wopen(path, _O_BINARY|_O_RDWR);
     165      if (fd == -1)
     166        return fd;
     167      const int ret = ::ftruncate64(fd, length);
     168      int err;
     169      ::_get_errno(&err);
     170      ::_close(fd);
     171      ::_set_errno(err);
     172      return ret;
     173    }
     174    using char_type = wchar_t;
     175  #elif defined _GLIBCXX_HAVE_UNISTD_H && ! defined __AVR__
     176    using ::open;
     177    using ::close;
     178  # ifdef _GLIBCXX_HAVE_SYS_STAT_H
     179    typedef struct ::stat stat_type;
     180    using ::stat;
     181  #  ifdef _GLIBCXX_USE_LSTAT
     182    using ::lstat;
     183  #  else
     184    inline int lstat(const char* path, stat_type* buffer)
     185    { return stat(path, buffer); }
     186  #  endif
     187  # endif
     188    using ::mode_t;
     189    using ::chmod;
     190    using ::mkdir;
     191    using ::getcwd;
     192    using ::chdir;
     193  # if !_GLIBCXX_USE_UTIMENSAT && _GLIBCXX_USE_UTIME
     194    using ::utimbuf;
     195    using ::utime;
     196  # endif
     197    using ::rename;
     198    using ::off_t;
     199  # ifdef _GLIBCXX_HAVE_TRUNCATE
     200    using ::truncate;
     201  # else
     202    inline int truncate(const char* path, off_t length)
     203    {
     204      if (length == 0)
     205        {
     206  	const int fd = ::open(path, O_WRONLY|O_TRUNC);
     207  	if (fd == -1)
     208  	  return fd;
     209  	::close(fd);
     210  	return 0;
     211        }
     212      errno = ENOTSUP;
     213      return -1;
     214    }
     215  # endif
     216    using char_type = char;
     217  #else // ! _GLIBCXX_FILESYSTEM_IS_WINDOWS && ! _GLIBCXX_HAVE_UNISTD_H
     218    inline int open(const char*, int, ...) { errno = ENOSYS; return -1; }
     219    inline int close(int) { errno = ENOSYS; return -1; }
     220    using mode_t = int;
     221    inline int chmod(const char*, mode_t) { errno = ENOSYS; return -1; }
     222    inline int mkdir(const char*, mode_t) { errno = ENOSYS; return -1; }
     223    inline char* getcwd(char*, size_t) { errno = ENOSYS; return nullptr; }
     224    inline int chdir(const char*) { errno = ENOSYS; return -1; }
     225    inline int rename(const char*, const char*) { errno = ENOSYS; return -1; }
     226    using off_t = long;
     227    inline int truncate(const char*, off_t) { errno = ENOSYS; return -1; }
     228    using char_type = char;
     229  #endif // _GLIBCXX_FILESYSTEM_IS_WINDOWS
     230  } // namespace __gnu_posix
     231  
     232    template<typename Bitmask>
     233      inline bool is_set(Bitmask obj, Bitmask bits)
     234      {
     235        return (obj & bits) != Bitmask::none;
     236      }
     237  
     238    inline bool
     239    is_not_found_errno(int err) noexcept
     240    {
     241      return err == ENOENT || err == ENOTDIR;
     242    }
     243  
     244  #ifdef _GLIBCXX_HAVE_SYS_STAT_H
     245    using __gnu_posix::stat_type;
     246  
     247    inline std::chrono::system_clock::time_point
     248    file_time(const stat_type& st, std::error_code& ec) noexcept
     249    {
     250      using namespace std::chrono;
     251  #ifdef _GLIBCXX_USE_ST_MTIM
     252      time_t s = st.st_mtim.tv_sec;
     253      nanoseconds ns{st.st_mtim.tv_nsec};
     254  #else
     255      time_t s = st.st_mtime;
     256      nanoseconds ns{};
     257  #endif
     258  
     259      // FIXME
     260      // There are possible timespec values which will overflow
     261      // chrono::system_clock::time_point but would not overflow
     262      // __file_clock::time_point, due to its different epoch.
     263      //
     264      // By checking for overflow of the intermediate system_clock::duration
     265      // type, we report an error for values which are actually representable
     266      // in the file_time_type result type.
     267      //
     268      // Howard Hinnant's solution for this problem is to use
     269      // duration<__int128>{s} + ns, which doesn't overflow.
     270      // An alternative would be to do the epoch correction on s before
     271      // the addition, and then go straight to file_time_type instead of
     272      // going via chrono::system_clock::time_point.
     273      //
     274      // (This only applies to the C++17 Filesystem library, because for the
     275      // Filesystem TS we don't have a distinct __file_clock, we just use the
     276      // system clock for file timestamps).
     277      if (seconds{s} >= floor<seconds>(system_clock::duration::max()))
     278        {
     279  	ec = std::make_error_code(std::errc::value_too_large); // EOVERFLOW
     280  	return system_clock::time_point::min();
     281        }
     282      ec.clear();
     283      return system_clock::time_point{seconds{s} + ns};
     284    }
     285  
     286    struct copy_options_existing_file
     287    {
     288      bool skip, update, overwrite;
     289    };
     290  
     291  #endif // _GLIBCXX_HAVE_SYS_STAT_H
     292  
     293  } // namespace filesystem
     294  
     295  // BEGIN/END macros must be defined before including this file.
     296  _GLIBCXX_BEGIN_NAMESPACE_FILESYSTEM
     297  
     298  #ifdef _GLIBCXX_HAVE_SYS_STAT_H
     299    using std::filesystem::__gnu_posix::stat_type;
     300    using std::filesystem::__gnu_posix::char_type;
     301  
     302    bool
     303    do_copy_file(const char_type* from, const char_type* to,
     304  	       std::filesystem::copy_options_existing_file options,
     305  	       stat_type* from_st, stat_type* to_st,
     306  	       std::error_code& ec) noexcept;
     307  
     308    void
     309    do_space(const char_type* pathname,
     310  	   uintmax_t& capacity, uintmax_t& free, uintmax_t& available,
     311  	   std::error_code&);
     312  
     313  
     314    inline file_type
     315    make_file_type(const stat_type& st) noexcept
     316    {
     317  #ifdef _GLIBCXX_HAVE_S_ISREG
     318      if (S_ISREG(st.st_mode))
     319        return file_type::regular;
     320      else if (S_ISDIR(st.st_mode))
     321        return file_type::directory;
     322      else if (S_ISCHR(st.st_mode))
     323        return file_type::character;
     324      else if (S_ISBLK(st.st_mode))
     325        return file_type::block;
     326      else if (S_ISFIFO(st.st_mode))
     327        return file_type::fifo;
     328  #ifdef S_ISLNK // not present in mingw
     329      else if (S_ISLNK(st.st_mode))
     330        return file_type::symlink;
     331  #endif
     332  #ifdef S_ISSOCK // not present until POSIX:2001
     333      else if (S_ISSOCK(st.st_mode))
     334        return file_type::socket;
     335  #endif
     336  #endif
     337      return file_type::unknown;
     338    }
     339  
     340    inline file_status
     341    make_file_status(const stat_type& st) noexcept
     342    {
     343      return file_status{
     344  	make_file_type(st),
     345  	static_cast<perms>(st.st_mode) & perms::mask
     346      };
     347    }
     348  
     349    inline std::filesystem::copy_options_existing_file
     350    copy_file_options(copy_options opt)
     351    {
     352      using std::filesystem::is_set;
     353      return {
     354  	is_set(opt, copy_options::skip_existing),
     355  	is_set(opt, copy_options::update_existing),
     356  	is_set(opt, copy_options::overwrite_existing)
     357      };
     358    }
     359  
     360  #ifdef NEED_DO_COPY_FILE
     361    bool
     362    do_copy_file(const char_type* from, const char_type* to,
     363  	       std::filesystem::copy_options_existing_file options,
     364  	       stat_type* from_st, stat_type* to_st,
     365  	       std::error_code& ec) noexcept
     366    {
     367      namespace fs = std::filesystem;
     368      namespace posix = fs::__gnu_posix;
     369  
     370      stat_type st1, st2;
     371      file_status t, f;
     372  
     373      if (to_st == nullptr)
     374        {
     375  	if (posix::stat(to, &st1))
     376  	  {
     377  	    const int err = errno;
     378  	    if (!fs::is_not_found_errno(err))
     379  	      {
     380  		ec.assign(err, std::generic_category());
     381  		return false;
     382  	      }
     383  	  }
     384  	else
     385  	  to_st = &st1;
     386        }
     387      else if (to_st == from_st)
     388        to_st = nullptr;
     389  
     390      if (to_st == nullptr)
     391        t = file_status{file_type::not_found};
     392      else
     393        t = make_file_status(*to_st);
     394  
     395      if (from_st == nullptr)
     396        {
     397  	if (posix::stat(from, &st2))
     398  	  {
     399  	    ec.assign(errno, std::generic_category());
     400  	    return false;
     401  	  }
     402  	else
     403  	  from_st = &st2;
     404        }
     405      f = make_file_status(*from_st);
     406      // _GLIBCXX_RESOLVE_LIB_DEFECTS
     407      // 2712. copy_file() has a number of unspecified error conditions
     408      if (!is_regular_file(f))
     409        {
     410  	ec = std::make_error_code(std::errc::invalid_argument);
     411  	return false;
     412        }
     413  
     414      if (exists(t))
     415        {
     416  	if (!is_regular_file(t))
     417  	  {
     418  	    ec = std::make_error_code(std::errc::invalid_argument);
     419  	    return false;
     420  	  }
     421  
     422  	if (to_st->st_dev == from_st->st_dev
     423  	    && to_st->st_ino == from_st->st_ino)
     424  	  {
     425  	    ec = std::make_error_code(std::errc::file_exists);
     426  	    return false;
     427  	  }
     428  
     429  	if (options.skip)
     430  	  {
     431  	    ec.clear();
     432  	    return false;
     433  	  }
     434  	else if (options.update)
     435  	  {
     436  	    const auto from_mtime = fs::file_time(*from_st, ec);
     437  	    if (ec)
     438  	      return false;
     439  	    if ((from_mtime <= fs::file_time(*to_st, ec)) || ec)
     440  	      return false;
     441  	  }
     442  	else if (!options.overwrite)
     443  	  {
     444  	    ec = std::make_error_code(std::errc::file_exists);
     445  	    return false;
     446  	  }
     447  	else if (!is_regular_file(t))
     448  	  {
     449  	    ec = std::make_error_code(std::errc::invalid_argument);
     450  	    return false;
     451  	  }
     452        }
     453  
     454      struct CloseFD {
     455        ~CloseFD() { if (fd != -1) posix::close(fd); }
     456        bool close() { return posix::close(std::__exchange(fd, -1)) == 0; }
     457        int fd;
     458      };
     459  
     460      int common_flags = 0;
     461  #ifdef O_CLOEXEC
     462      common_flags |= O_CLOEXEC;
     463  #endif
     464  #ifdef _GLIBCXX_FILESYSTEM_IS_WINDOWS
     465      common_flags |= O_BINARY;
     466  #endif
     467  
     468      const int iflag = O_RDONLY | common_flags;
     469      CloseFD in = { posix::open(from, iflag) };
     470      if (in.fd == -1)
     471        {
     472  	ec.assign(errno, std::generic_category());
     473  	return false;
     474        }
     475      int oflag = O_WRONLY | O_CREAT | common_flags;
     476      if (options.overwrite || options.update)
     477        oflag |= O_TRUNC;
     478      else
     479        oflag |= O_EXCL;
     480      CloseFD out = { posix::open(to, oflag, S_IWUSR) };
     481      if (out.fd == -1)
     482        {
     483  	if (errno == EEXIST && options.skip)
     484  	  ec.clear();
     485  	else
     486  	  ec.assign(errno, std::generic_category());
     487  	return false;
     488        }
     489  
     490  #if defined _GLIBCXX_USE_FCHMOD && ! defined _GLIBCXX_FILESYSTEM_IS_WINDOWS
     491      if (::fchmod(out.fd, from_st->st_mode))
     492  #elif defined _GLIBCXX_USE_FCHMODAT && ! defined _GLIBCXX_FILESYSTEM_IS_WINDOWS
     493      if (::fchmodat(AT_FDCWD, to, from_st->st_mode, 0))
     494  #else
     495      if (posix::chmod(to, from_st->st_mode))
     496  #endif
     497        {
     498  	ec.assign(errno, std::generic_category());
     499  	return false;
     500        }
     501  
     502      size_t count = from_st->st_size;
     503  #if defined _GLIBCXX_USE_SENDFILE && ! defined _GLIBCXX_FILESYSTEM_IS_WINDOWS
     504      ssize_t n = 0;
     505      if (count != 0)
     506        {
     507  	off_t offset = 0;
     508  	n = ::sendfile(out.fd, in.fd, &offset, count);
     509  	if (n < 0 && errno != ENOSYS && errno != EINVAL)
     510  	  {
     511  	    ec.assign(errno, std::generic_category());
     512  	    return false;
     513  	  }
     514  	if ((size_t)n == count)
     515  	  {
     516  	    if (!out.close() || !in.close())
     517  	      {
     518  		ec.assign(errno, std::generic_category());
     519  		return false;
     520  	      }
     521  	    ec.clear();
     522  	    return true;
     523  	  }
     524  	else if (n > 0)
     525  	  count -= n;
     526        }
     527  #endif // _GLIBCXX_USE_SENDFILE
     528  
     529      using std::ios;
     530      __gnu_cxx::stdio_filebuf<char> sbin(in.fd, ios::in|ios::binary);
     531      __gnu_cxx::stdio_filebuf<char> sbout(out.fd, ios::out|ios::binary);
     532  
     533      if (sbin.is_open())
     534        in.fd = -1;
     535      if (sbout.is_open())
     536        out.fd = -1;
     537  
     538  #ifdef _GLIBCXX_USE_SENDFILE
     539      if (n != 0)
     540        {
     541  	if (n < 0)
     542  	  n = 0;
     543  
     544  	const auto p1 = sbin.pubseekoff(n, ios::beg, ios::in);
     545  	const auto p2 = sbout.pubseekoff(n, ios::beg, ios::out);
     546  
     547  	const std::streampos errpos(std::streamoff(-1));
     548  	if (p1 == errpos || p2 == errpos)
     549  	  {
     550  	    ec = std::make_error_code(std::errc::io_error);
     551  	    return false;
     552  	  }
     553        }
     554  #endif
     555  
     556      // ostream::operator<<(streambuf*) fails if it extracts no characters,
     557      // so don't try to use it for empty files. But from_st->st_size == 0 for
     558      // some special files (e.g. procfs, see PR libstdc++/108178) so just try
     559      // to read a character to decide whether there is anything to copy or not.
     560      if (sbin.sgetc() != char_traits<char>::eof())
     561        if (!(std::ostream(&sbout) << &sbin))
     562  	{
     563  	  ec = std::make_error_code(std::errc::io_error);
     564  	  return false;
     565  	}
     566  
     567      if (!sbout.close() || !sbin.close())
     568        {
     569  	ec.assign(errno, std::generic_category());
     570  	return false;
     571        }
     572      ec.clear();
     573      return true;
     574    }
     575  #endif // NEED_DO_COPY_FILE
     576  
     577  #ifdef NEED_DO_SPACE
     578  #pragma GCC diagnostic push
     579  #pragma GCC diagnostic ignored "-Wunused-parameter"
     580    void
     581    do_space(const char_type* pathname,
     582  	   uintmax_t& capacity, uintmax_t& free, uintmax_t& available,
     583  	   std::error_code& ec)
     584    {
     585  #ifdef _GLIBCXX_HAVE_SYS_STATVFS_H
     586      struct ::statvfs f;
     587      if (::statvfs(pathname, &f))
     588  	ec.assign(errno, std::generic_category());
     589      else
     590        {
     591  	if (f.f_frsize != (unsigned long)-1)
     592  	  {
     593  	    const uintmax_t fragment_size = f.f_frsize;
     594  	    const fsblkcnt_t unknown = -1;
     595  	    if (f.f_blocks != unknown)
     596  	      capacity = f.f_blocks * fragment_size;
     597  	    if (f.f_bfree != unknown)
     598  	      free = f.f_bfree * fragment_size;
     599  	    if (f.f_bavail != unknown)
     600  	      available = f.f_bavail * fragment_size;
     601  	  }
     602  	ec.clear();
     603        }
     604  #elif _GLIBCXX_FILESYSTEM_IS_WINDOWS
     605      ULARGE_INTEGER bytes_avail = {}, bytes_total = {}, bytes_free = {};
     606      if (GetDiskFreeSpaceExW(pathname, &bytes_avail, &bytes_total, &bytes_free))
     607        {
     608  	if (bytes_total.QuadPart != 0)
     609  	  capacity = bytes_total.QuadPart;
     610  	if (bytes_free.QuadPart != 0)
     611  	  free = bytes_free.QuadPart;
     612  	if (bytes_avail.QuadPart != 0)
     613  	  available = bytes_avail.QuadPart;
     614  	ec.clear();
     615        }
     616      else
     617        ec = std::__last_system_error();
     618  #else
     619      ec = std::make_error_code(std::errc::function_not_supported);
     620  #endif
     621    }
     622  #pragma GCC diagnostic pop
     623  #endif // NEED_DO_SPACE
     624  
     625  #endif // _GLIBCXX_HAVE_SYS_STAT_H
     626  
     627    // Find OS-specific name of temporary directory from the environment,
     628    // Caller must check that the path is an accessible directory.
     629  #ifdef _GLIBCXX_FILESYSTEM_IS_WINDOWS
     630    inline wstring
     631    get_temp_directory_from_env(error_code& ec)
     632    {
     633      unsigned len = 1024;
     634      std::wstring buf;
     635      do
     636        {
     637  	buf.resize(len);
     638  	len = GetTempPathW(buf.size(), buf.data());
     639        }
     640      while (len > buf.size());
     641  
     642      if (len == 0)
     643        ec = __last_system_error();
     644      else
     645        ec.clear();
     646  
     647      buf.resize(len);
     648      return buf;
     649    }
     650  #else
     651    inline const char*
     652    get_temp_directory_from_env(error_code& ec) noexcept
     653    {
     654      ec.clear();
     655      for (auto env : { "TMPDIR", "TMP", "TEMP", "TEMPDIR" })
     656        {
     657  #if _GLIBCXX_HAVE_SECURE_GETENV
     658  	auto tmpdir = ::secure_getenv(env);
     659  #else
     660  	auto tmpdir = ::getenv(env);
     661  #endif
     662  	if (tmpdir)
     663  	  return tmpdir;
     664        }
     665      return "/tmp";
     666    }
     667  #endif
     668  
     669  _GLIBCXX_END_NAMESPACE_FILESYSTEM
     670  
     671  _GLIBCXX_END_NAMESPACE_VERSION
     672  } // namespace std
     673  
     674  #endif // _GLIBCXX_OPS_COMMON_H