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 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