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