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