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