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