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