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