xref: /netbsd-src/external/gpl3/gcc/dist/libstdc++-v3/src/filesystem/ops-common.h (revision 3117ece4fc4a4ca4489ba793710b60b0d26bab6c)
1 // Filesystem operation utilities -*- C++ -*-
2 
3 // Copyright (C) 2014-2022 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>  // mkdir, chmod
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 #define _GLIBCXX_USE_CHMOD 1
133 
134   inline int mkdir(const wchar_t* path, mode_t)
135   { return ::_wmkdir(path); }
136 #define _GLIBCXX_USE_MKDIR 1
137 
138   inline wchar_t* getcwd(wchar_t* buf, size_t size)
139   { return ::_wgetcwd(buf, size > (size_t)INT_MAX ? INT_MAX : (int)size); }
140 #define _GLIBCXX_USE_GETCWD 1
141 
142   inline int chdir(const wchar_t* path)
143   { return ::_wchdir(path); }
144 #define _GLIBCXX_USE_CHDIR 1
145 
146 #if !_GLIBCXX_USE_UTIMENSAT && _GLIBCXX_HAVE_UTIME_H
147   using utimbuf = _utimbuf;
148 
149   inline int utime(const wchar_t* path, utimbuf* times)
150   { return ::_wutime(path, times); }
151 #endif
152 
153   inline int rename(const wchar_t* oldname, const wchar_t* newname)
154   {
155     if (MoveFileExW(oldname, newname,
156 		    MOVEFILE_REPLACE_EXISTING | MOVEFILE_COPY_ALLOWED))
157       return 0;
158     if (GetLastError() == ERROR_ACCESS_DENIED)
159       errno = EACCES;
160     else
161       errno = EIO;
162     return -1;
163   }
164 
165   using off_t = _off64_t;
166   inline int truncate(const wchar_t* path, _off64_t length)
167   {
168     const int fd = ::_wopen(path, _O_BINARY|_O_RDWR);
169     if (fd == -1)
170       return fd;
171     const int ret = ::ftruncate64(fd, length);
172     int err;
173     ::_get_errno(&err);
174     ::_close(fd);
175     ::_set_errno(err);
176     return ret;
177   }
178   using char_type = wchar_t;
179 #elif defined _GLIBCXX_HAVE_UNISTD_H && ! defined __AVR__
180   using ::open;
181   using ::close;
182 # ifdef _GLIBCXX_HAVE_SYS_STAT_H
183   typedef struct ::stat stat_type;
184   using ::stat;
185 #  ifdef _GLIBCXX_USE_LSTAT
186   using ::lstat;
187 #  else
188   inline int lstat(const char* path, stat_type* buffer)
189   { return stat(path, buffer); }
190 #  endif
191 # endif
192   using ::mode_t;
193 # if _GLIBCXX_USE_CHMOD
194   using ::chmod;
195 # endif
196 # if _GLIBCXX_USE_MKDIR
197   using ::mkdir;
198 # endif
199 # if _GLIBCXX_USE_GETCWD
200   using ::getcwd;
201 # endif
202 # if _GLIBCXX_USE_CHDIR
203   using ::chdir;
204 # endif
205 # if !_GLIBCXX_USE_UTIMENSAT && _GLIBCXX_USE_UTIME
206   using ::utimbuf;
207   using ::utime;
208 # endif
209   using ::rename;
210   using ::off_t;
211 # ifdef _GLIBCXX_HAVE_TRUNCATE
212   using ::truncate;
213 # else
214   inline int truncate(const char* path, off_t length)
215   {
216     if (length == 0)
217       {
218 	const int fd = ::open(path, O_WRONLY|O_TRUNC);
219 	if (fd == -1)
220 	  return fd;
221 	::close(fd);
222 	return 0;
223       }
224     errno = ENOTSUP;
225     return -1;
226   }
227 # endif
228   using char_type = char;
229 #else // ! _GLIBCXX_FILESYSTEM_IS_WINDOWS && ! _GLIBCXX_HAVE_UNISTD_H
230   inline int open(const char*, int, ...) { errno = ENOSYS; return -1; }
231   inline int close(int) { errno = ENOSYS; return -1; }
232   using mode_t = int;
233   inline int chmod(const char*, mode_t) { errno = ENOSYS; return -1; }
234   inline int mkdir(const char*, mode_t) { errno = ENOSYS; return -1; }
235   inline char* getcwd(char*, size_t) { errno = ENOSYS; return nullptr; }
236   inline int chdir(const char*) { errno = ENOSYS; return -1; }
237   inline int rename(const char*, const char*) { errno = ENOSYS; return -1; }
238   using off_t = long;
239   inline int truncate(const char*, off_t) { errno = ENOSYS; return -1; }
240   using char_type = char;
241 #endif // _GLIBCXX_FILESYSTEM_IS_WINDOWS
242 } // namespace __gnu_posix
243 
244   template<typename Bitmask>
245     inline bool is_set(Bitmask obj, Bitmask bits)
246     {
247       return (obj & bits) != Bitmask::none;
248     }
249 
250   inline bool
251   is_not_found_errno(int err) noexcept
252   {
253     return err == ENOENT || err == ENOTDIR;
254   }
255 
256 #ifdef _GLIBCXX_HAVE_SYS_STAT_H
257   using __gnu_posix::stat_type;
258 
259   inline std::chrono::system_clock::time_point
260   file_time(const stat_type& st, std::error_code& ec) noexcept
261   {
262     using namespace std::chrono;
263 #ifdef _GLIBCXX_USE_ST_MTIM
264     time_t s = st.st_mtim.tv_sec;
265     nanoseconds ns{st.st_mtim.tv_nsec};
266 #else
267     time_t s = st.st_mtime;
268     nanoseconds ns{};
269 #endif
270 
271     // FIXME
272     // There are possible timespec values which will overflow
273     // chrono::system_clock::time_point but would not overflow
274     // __file_clock::time_point, due to its different epoch.
275     //
276     // By checking for overflow of the intermediate system_clock::duration
277     // type, we report an error for values which are actually representable
278     // in the file_time_type result type.
279     //
280     // Howard Hinnant's solution for this problem is to use
281     // duration<__int128>{s} + ns, which doesn't overflow.
282     // An alternative would be to do the epoch correction on s before
283     // the addition, and then go straight to file_time_type instead of
284     // going via chrono::system_clock::time_point.
285     //
286     // (This only applies to the C++17 Filesystem library, because for the
287     // Filesystem TS we don't have a distinct __file_clock, we just use the
288     // system clock for file timestamps).
289     if (seconds{s} >= floor<seconds>(system_clock::duration::max()))
290       {
291 	ec = std::make_error_code(std::errc::value_too_large); // EOVERFLOW
292 	return system_clock::time_point::min();
293       }
294     ec.clear();
295     return system_clock::time_point{seconds{s} + ns};
296   }
297 
298   struct copy_options_existing_file
299   {
300     bool skip, update, overwrite;
301   };
302 
303 #endif // _GLIBCXX_HAVE_SYS_STAT_H
304 
305 } // namespace filesystem
306 
307 // BEGIN/END macros must be defined before including this file.
308 _GLIBCXX_BEGIN_NAMESPACE_FILESYSTEM
309 
310 #ifdef _GLIBCXX_HAVE_SYS_STAT_H
311   using std::filesystem::__gnu_posix::stat_type;
312   using std::filesystem::__gnu_posix::char_type;
313 
314   bool
315   do_copy_file(const char_type* from, const char_type* to,
316 	       std::filesystem::copy_options_existing_file options,
317 	       stat_type* from_st, stat_type* to_st,
318 	       std::error_code& ec) noexcept;
319 
320   void
321   do_space(const char_type* pathname,
322 	   uintmax_t& capacity, uintmax_t& free, uintmax_t& available,
323 	   std::error_code&);
324 
325 
326   inline file_type
327   make_file_type(const stat_type& st) noexcept
328   {
329 #ifdef _GLIBCXX_HAVE_S_ISREG
330     if (S_ISREG(st.st_mode))
331       return file_type::regular;
332     else if (S_ISDIR(st.st_mode))
333       return file_type::directory;
334     else if (S_ISCHR(st.st_mode))
335       return file_type::character;
336     else if (S_ISBLK(st.st_mode))
337       return file_type::block;
338     else if (S_ISFIFO(st.st_mode))
339       return file_type::fifo;
340 #ifdef S_ISLNK // not present in mingw
341     else if (S_ISLNK(st.st_mode))
342       return file_type::symlink;
343 #endif
344 #ifdef S_ISSOCK // not present until POSIX:2001
345     else if (S_ISSOCK(st.st_mode))
346       return file_type::socket;
347 #endif
348 #endif
349     return file_type::unknown;
350   }
351 
352   inline file_status
353   make_file_status(const stat_type& st) noexcept
354   {
355     return file_status{
356 	make_file_type(st),
357 	static_cast<perms>(st.st_mode) & perms::mask
358     };
359   }
360 
361   inline std::filesystem::copy_options_existing_file
362   copy_file_options(copy_options opt)
363   {
364     using std::filesystem::is_set;
365     return {
366 	is_set(opt, copy_options::skip_existing),
367 	is_set(opt, copy_options::update_existing),
368 	is_set(opt, copy_options::overwrite_existing)
369     };
370   }
371 
372 #ifdef NEED_DO_COPY_FILE
373   bool
374   do_copy_file(const char_type* from, const char_type* to,
375 	       std::filesystem::copy_options_existing_file options,
376 	       stat_type* from_st, stat_type* to_st,
377 	       std::error_code& ec) noexcept
378   {
379     namespace fs = std::filesystem;
380     namespace posix = fs::__gnu_posix;
381 
382     stat_type st1, st2;
383     file_status t, f;
384 
385     if (to_st == nullptr)
386       {
387 	if (posix::stat(to, &st1))
388 	  {
389 	    const int err = errno;
390 	    if (!fs::is_not_found_errno(err))
391 	      {
392 		ec.assign(err, std::generic_category());
393 		return false;
394 	      }
395 	  }
396 	else
397 	  to_st = &st1;
398       }
399     else if (to_st == from_st)
400       to_st = nullptr;
401 
402     if (to_st == nullptr)
403       t = file_status{file_type::not_found};
404     else
405       t = make_file_status(*to_st);
406 
407     if (from_st == nullptr)
408       {
409 	if (posix::stat(from, &st2))
410 	  {
411 	    ec.assign(errno, std::generic_category());
412 	    return false;
413 	  }
414 	else
415 	  from_st = &st2;
416       }
417     f = make_file_status(*from_st);
418     // _GLIBCXX_RESOLVE_LIB_DEFECTS
419     // 2712. copy_file() has a number of unspecified error conditions
420     if (!is_regular_file(f))
421       {
422 	ec = std::make_error_code(std::errc::invalid_argument);
423 	return false;
424       }
425 
426     if (exists(t))
427       {
428 	if (!is_regular_file(t))
429 	  {
430 	    ec = std::make_error_code(std::errc::invalid_argument);
431 	    return false;
432 	  }
433 
434 	if (to_st->st_dev == from_st->st_dev
435 	    && to_st->st_ino == from_st->st_ino)
436 	  {
437 	    ec = std::make_error_code(std::errc::file_exists);
438 	    return false;
439 	  }
440 
441 	if (options.skip)
442 	  {
443 	    ec.clear();
444 	    return false;
445 	  }
446 	else if (options.update)
447 	  {
448 	    const auto from_mtime = fs::file_time(*from_st, ec);
449 	    if (ec)
450 	      return false;
451 	    if ((from_mtime <= fs::file_time(*to_st, ec)) || ec)
452 	      return false;
453 	  }
454 	else if (!options.overwrite)
455 	  {
456 	    ec = std::make_error_code(std::errc::file_exists);
457 	    return false;
458 	  }
459 	else if (!is_regular_file(t))
460 	  {
461 	    ec = std::make_error_code(std::errc::invalid_argument);
462 	    return false;
463 	  }
464       }
465 
466     struct CloseFD {
467       ~CloseFD() { if (fd != -1) posix::close(fd); }
468       bool close() { return posix::close(std::__exchange(fd, -1)) == 0; }
469       int fd;
470     };
471 
472     int common_flags = 0;
473 #ifdef O_CLOEXEC
474     common_flags |= O_CLOEXEC;
475 #endif
476 #ifdef _GLIBCXX_FILESYSTEM_IS_WINDOWS
477     common_flags |= O_BINARY;
478 #endif
479 
480     const int iflag = O_RDONLY | common_flags;
481     CloseFD in = { posix::open(from, iflag) };
482     if (in.fd == -1)
483       {
484 	ec.assign(errno, std::generic_category());
485 	return false;
486       }
487     int oflag = O_WRONLY | O_CREAT | common_flags;
488     if (options.overwrite || options.update)
489       oflag |= O_TRUNC;
490     else
491       oflag |= O_EXCL;
492     CloseFD out = { posix::open(to, oflag, S_IWUSR) };
493     if (out.fd == -1)
494       {
495 	if (errno == EEXIST && options.skip)
496 	  ec.clear();
497 	else
498 	  ec.assign(errno, std::generic_category());
499 	return false;
500       }
501 
502 #if defined _GLIBCXX_USE_FCHMOD && ! defined _GLIBCXX_FILESYSTEM_IS_WINDOWS
503     if (::fchmod(out.fd, from_st->st_mode))
504 #elif defined _GLIBCXX_USE_FCHMODAT && ! defined _GLIBCXX_FILESYSTEM_IS_WINDOWS
505     if (::fchmodat(AT_FDCWD, to, from_st->st_mode, 0))
506 #elif defined _GLIBCXX_USE_CHMOD
507     if (posix::chmod(to, from_st->st_mode))
508 #else
509     if (false)
510 #endif
511       {
512 	ec.assign(errno, std::generic_category());
513 	return false;
514       }
515 
516 #if defined _GLIBCXX_USE_SENDFILE && ! defined _GLIBCXX_FILESYSTEM_IS_WINDOWS
517     size_t count = from_st->st_size;
518     ssize_t n = 0;
519     if (count != 0)
520       {
521 	off_t offset = 0;
522 	n = ::sendfile(out.fd, in.fd, &offset, count);
523 	if (n < 0 && errno != ENOSYS && errno != EINVAL)
524 	  {
525 	    ec.assign(errno, std::generic_category());
526 	    return false;
527 	  }
528 	if ((size_t)n == count)
529 	  {
530 	    if (!out.close() || !in.close())
531 	      {
532 		ec.assign(errno, std::generic_category());
533 		return false;
534 	      }
535 	    ec.clear();
536 	    return true;
537 	  }
538 	else if (n > 0)
539 	  count -= n;
540       }
541 #endif // _GLIBCXX_USE_SENDFILE
542 
543     using std::ios;
544     __gnu_cxx::stdio_filebuf<char> sbin(in.fd, ios::in|ios::binary);
545     __gnu_cxx::stdio_filebuf<char> sbout(out.fd, ios::out|ios::binary);
546 
547     if (sbin.is_open())
548       in.fd = -1;
549     if (sbout.is_open())
550       out.fd = -1;
551 
552 #ifdef _GLIBCXX_USE_SENDFILE
553     if (n != 0)
554       {
555 	if (n < 0)
556 	  n = 0;
557 
558 	const auto p1 = sbin.pubseekoff(n, ios::beg, ios::in);
559 	const auto p2 = sbout.pubseekoff(n, ios::beg, ios::out);
560 
561 	const std::streampos errpos(std::streamoff(-1));
562 	if (p1 == errpos || p2 == errpos)
563 	  {
564 	    ec = std::make_error_code(std::errc::io_error);
565 	    return false;
566 	  }
567       }
568 #endif
569 
570     // ostream::operator<<(streambuf*) fails if it extracts no characters,
571     // so don't try to use it for empty files. But from_st->st_size == 0 for
572     // some special files (e.g. procfs, see PR libstdc++/108178) so just try
573     // to read a character to decide whether there is anything to copy or not.
574     if (sbin.sgetc() != char_traits<char>::eof())
575       if (!(std::ostream(&sbout) << &sbin))
576 	{
577 	  ec = std::make_error_code(std::errc::io_error);
578 	  return false;
579 	}
580 
581     if (!sbout.close() || !sbin.close())
582       {
583 	ec.assign(errno, std::generic_category());
584 	return false;
585       }
586     ec.clear();
587     return true;
588   }
589 #endif // NEED_DO_COPY_FILE
590 
591 #ifdef NEED_DO_SPACE
592 #pragma GCC diagnostic push
593 #pragma GCC diagnostic ignored "-Wunused-parameter"
594   void
595   do_space(const char_type* pathname,
596 	   uintmax_t& capacity, uintmax_t& free, uintmax_t& available,
597 	   std::error_code& ec)
598   {
599 #ifdef _GLIBCXX_HAVE_SYS_STATVFS_H
600     struct ::statvfs f;
601     if (::statvfs(pathname, &f))
602 	ec.assign(errno, std::generic_category());
603     else
604       {
605 	if (f.f_frsize != (unsigned long)-1)
606 	  {
607 	    const uintmax_t fragment_size = f.f_frsize;
608 	    const fsblkcnt_t unknown = -1;
609 	    if (f.f_blocks != unknown)
610 	      capacity = f.f_blocks * fragment_size;
611 	    if (f.f_bfree != unknown)
612 	      free = f.f_bfree * fragment_size;
613 	    if (f.f_bavail != unknown)
614 	      available = f.f_bavail * fragment_size;
615 	  }
616 	ec.clear();
617       }
618 #elif _GLIBCXX_FILESYSTEM_IS_WINDOWS
619     ULARGE_INTEGER bytes_avail = {}, bytes_total = {}, bytes_free = {};
620     if (GetDiskFreeSpaceExW(pathname, &bytes_avail, &bytes_total, &bytes_free))
621       {
622 	if (bytes_total.QuadPart != 0)
623 	  capacity = bytes_total.QuadPart;
624 	if (bytes_free.QuadPart != 0)
625 	  free = bytes_free.QuadPart;
626 	if (bytes_avail.QuadPart != 0)
627 	  available = bytes_avail.QuadPart;
628 	ec.clear();
629       }
630     else
631       ec = std::__last_system_error();
632 #else
633     ec = std::make_error_code(std::errc::function_not_supported);
634 #endif
635   }
636 #pragma GCC diagnostic pop
637 #endif // NEED_DO_SPACE
638 
639 #endif // _GLIBCXX_HAVE_SYS_STAT_H
640 
641   // Find OS-specific name of temporary directory from the environment,
642   // Caller must check that the path is an accessible directory.
643 #ifdef _GLIBCXX_FILESYSTEM_IS_WINDOWS
644   inline wstring
645   get_temp_directory_from_env(error_code& ec)
646   {
647     unsigned len = 1024;
648     std::wstring buf;
649     do
650       {
651 	buf.resize(len);
652 	len = GetTempPathW(buf.size(), buf.data());
653       }
654     while (len > buf.size());
655 
656     if (len == 0)
657       ec = __last_system_error();
658     else
659       ec.clear();
660 
661     buf.resize(len);
662     return buf;
663   }
664 #else
665   inline const char*
666   get_temp_directory_from_env(error_code& ec) noexcept
667   {
668     ec.clear();
669     for (auto env : { "TMPDIR", "TMP", "TEMP", "TEMPDIR" })
670       {
671 #if _GLIBCXX_HAVE_SECURE_GETENV
672 	auto tmpdir = ::secure_getenv(env);
673 #else
674 	auto tmpdir = ::getenv(env);
675 #endif
676 	if (tmpdir)
677 	  return tmpdir;
678       }
679     return "/tmp";
680   }
681 #endif
682 
683 _GLIBCXX_END_NAMESPACE_FILESYSTEM
684 
685 _GLIBCXX_END_NAMESPACE_VERSION
686 } // namespace std
687 
688 #endif // _GLIBCXX_OPS_COMMON_H
689