github.com/SagerNet/gvisor@v0.0.0-20210707092255-7731c139d75c/test/util/fs_util.cc (about) 1 // Copyright 2018 The gVisor Authors. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 #include "test/util/fs_util.h" 16 17 #include <dirent.h> 18 #ifdef __linux__ 19 #include <linux/magic.h> 20 #endif // __linux__ 21 #include <sys/stat.h> 22 #include <sys/statfs.h> 23 #include <sys/types.h> 24 #include <unistd.h> 25 26 #include "gmock/gmock.h" 27 #include "absl/strings/match.h" 28 #include "absl/strings/str_cat.h" 29 #include "absl/strings/str_split.h" 30 #include "absl/strings/string_view.h" 31 #include "absl/time/clock.h" 32 #include "absl/time/time.h" 33 #include "test/util/cleanup.h" 34 #include "test/util/file_descriptor.h" 35 #include "test/util/posix_error.h" 36 37 namespace gvisor { 38 namespace testing { 39 40 namespace { 41 PosixError WriteContentsToFD(int fd, absl::string_view contents) { 42 int written = 0; 43 while (static_cast<absl::string_view::size_type>(written) < contents.size()) { 44 int wrote = write(fd, contents.data() + written, contents.size() - written); 45 if (wrote < 0) { 46 if (errno == EINTR) { 47 continue; 48 } 49 return PosixError( 50 errno, absl::StrCat("WriteContentsToFD fd: ", fd, " write failure.")); 51 } 52 written += wrote; 53 } 54 return NoError(); 55 } 56 } // namespace 57 58 namespace internal { 59 60 // Given a collection of file paths, append them all together, 61 // ensuring that the proper path separators are inserted between them. 62 std::string JoinPathImpl(std::initializer_list<absl::string_view> paths) { 63 std::string result; 64 65 if (paths.size() != 0) { 66 // This size calculation is worst-case: it assumes one extra "/" for every 67 // path other than the first. 68 size_t total_size = paths.size() - 1; 69 for (const absl::string_view path : paths) total_size += path.size(); 70 result.resize(total_size); 71 72 auto begin = result.begin(); 73 auto out = begin; 74 bool trailing_slash = false; 75 for (absl::string_view path : paths) { 76 if (path.empty()) continue; 77 if (path.front() == '/') { 78 if (trailing_slash) { 79 path.remove_prefix(1); 80 } 81 } else { 82 if (!trailing_slash && out != begin) *out++ = '/'; 83 } 84 const size_t this_size = path.size(); 85 memcpy(&*out, path.data(), this_size); 86 out += this_size; 87 trailing_slash = out[-1] == '/'; 88 } 89 result.erase(out - begin); 90 } 91 return result; 92 } 93 } // namespace internal 94 95 // Returns a status or the current working directory. 96 PosixErrorOr<std::string> GetCWD() { 97 char buffer[PATH_MAX + 1] = {}; 98 if (getcwd(buffer, PATH_MAX) == nullptr) { 99 return PosixError(errno, "GetCWD() failed"); 100 } 101 102 return std::string(buffer); 103 } 104 105 PosixErrorOr<struct stat> Stat(absl::string_view path) { 106 struct stat stat_buf; 107 int res = stat(std::string(path).c_str(), &stat_buf); 108 if (res < 0) { 109 return PosixError(errno, absl::StrCat("stat ", path)); 110 } 111 return stat_buf; 112 } 113 114 PosixErrorOr<struct stat> Lstat(absl::string_view path) { 115 struct stat stat_buf; 116 int res = lstat(std::string(path).c_str(), &stat_buf); 117 if (res < 0) { 118 return PosixError(errno, absl::StrCat("lstat ", path)); 119 } 120 return stat_buf; 121 } 122 123 PosixErrorOr<struct stat> Fstat(int fd) { 124 struct stat stat_buf; 125 int res = fstat(fd, &stat_buf); 126 if (res < 0) { 127 return PosixError(errno, absl::StrCat("fstat ", fd)); 128 } 129 return stat_buf; 130 } 131 132 PosixErrorOr<bool> Exists(absl::string_view path) { 133 struct stat stat_buf; 134 int res = lstat(std::string(path).c_str(), &stat_buf); 135 if (res < 0) { 136 if (errno == ENOENT) { 137 return false; 138 } 139 return PosixError(errno, absl::StrCat("lstat ", path)); 140 } 141 return true; 142 } 143 144 PosixErrorOr<bool> IsDirectory(absl::string_view path) { 145 ASSIGN_OR_RETURN_ERRNO(struct stat stat_buf, Lstat(path)); 146 if (S_ISDIR(stat_buf.st_mode)) { 147 return true; 148 } 149 150 return false; 151 } 152 153 PosixError Delete(absl::string_view path) { 154 int res = unlink(std::string(path).c_str()); 155 if (res < 0) { 156 return PosixError(errno, absl::StrCat("unlink ", path)); 157 } 158 159 return NoError(); 160 } 161 162 PosixError Truncate(absl::string_view path, int length) { 163 int res = truncate(std::string(path).c_str(), length); 164 if (res < 0) { 165 return PosixError(errno, 166 absl::StrCat("truncate ", path, " to length ", length)); 167 } 168 169 return NoError(); 170 } 171 172 PosixError Chmod(absl::string_view path, int mode) { 173 int res = chmod(std::string(path).c_str(), mode); 174 if (res < 0) { 175 return PosixError(errno, absl::StrCat("chmod ", path)); 176 } 177 178 return NoError(); 179 } 180 181 PosixError MknodAt(const FileDescriptor& dfd, absl::string_view path, int mode, 182 dev_t dev) { 183 int res = mknodat(dfd.get(), std::string(path).c_str(), mode, dev); 184 if (res < 0) { 185 return PosixError(errno, absl::StrCat("mknod ", path)); 186 } 187 188 return NoError(); 189 } 190 191 PosixError UnlinkAt(const FileDescriptor& dfd, absl::string_view path, 192 int flags) { 193 int res = unlinkat(dfd.get(), std::string(path).c_str(), flags); 194 if (res < 0) { 195 return PosixError(errno, absl::StrCat("unlink ", path)); 196 } 197 198 return NoError(); 199 } 200 201 PosixError Mkdir(absl::string_view path, int mode) { 202 int res = mkdir(std::string(path).c_str(), mode); 203 if (res < 0) { 204 return PosixError(errno, absl::StrCat("mkdir ", path, " mode ", mode)); 205 } 206 207 return NoError(); 208 } 209 210 PosixError Rmdir(absl::string_view path) { 211 int res = rmdir(std::string(path).c_str()); 212 if (res < 0) { 213 return PosixError(errno, absl::StrCat("rmdir ", path)); 214 } 215 216 return NoError(); 217 } 218 219 PosixError SetContents(absl::string_view path, absl::string_view contents) { 220 ASSIGN_OR_RETURN_ERRNO(bool exists, Exists(path)); 221 if (!exists) { 222 return PosixError( 223 ENOENT, absl::StrCat("SetContents file ", path, " doesn't exist.")); 224 } 225 226 ASSIGN_OR_RETURN_ERRNO(auto fd, Open(std::string(path), O_WRONLY | O_TRUNC)); 227 return WriteContentsToFD(fd.get(), contents); 228 } 229 230 // Create a file with the given contents (if it does not already exist with the 231 // given mode) and then set the contents. 232 PosixError CreateWithContents(absl::string_view path, 233 absl::string_view contents, int mode) { 234 ASSIGN_OR_RETURN_ERRNO( 235 auto fd, Open(std::string(path), O_WRONLY | O_CREAT | O_TRUNC, mode)); 236 return WriteContentsToFD(fd.get(), contents); 237 } 238 239 PosixError GetContents(absl::string_view path, std::string* output) { 240 ASSIGN_OR_RETURN_ERRNO(auto fd, Open(std::string(path), O_RDONLY)); 241 output->clear(); 242 243 // Keep reading until we hit an EOF or an error. 244 return GetContentsFD(fd.get(), output); 245 } 246 247 PosixErrorOr<std::string> GetContents(absl::string_view path) { 248 std::string ret; 249 RETURN_IF_ERRNO(GetContents(path, &ret)); 250 return ret; 251 } 252 253 PosixErrorOr<std::string> GetContentsFD(int fd) { 254 std::string ret; 255 RETURN_IF_ERRNO(GetContentsFD(fd, &ret)); 256 return ret; 257 } 258 259 PosixError GetContentsFD(int fd, std::string* output) { 260 // Keep reading until we hit an EOF or an error. 261 while (true) { 262 char buf[16 * 1024] = {}; // Read in 16KB chunks. 263 int bytes_read = read(fd, buf, sizeof(buf)); 264 if (bytes_read < 0) { 265 if (errno == EINTR) { 266 continue; 267 } 268 return PosixError(errno, "GetContentsFD read failure."); 269 } 270 271 if (bytes_read == 0) { 272 break; // EOF. 273 } 274 275 output->append(buf, bytes_read); 276 } 277 return NoError(); 278 } 279 280 PosixErrorOr<std::string> ReadLink(absl::string_view path) { 281 char buf[PATH_MAX + 1] = {}; 282 int ret = readlink(std::string(path).c_str(), buf, PATH_MAX); 283 if (ret < 0) { 284 return PosixError(errno, absl::StrCat("readlink ", path)); 285 } 286 287 return std::string(buf, ret); 288 } 289 290 PosixError WalkTree( 291 absl::string_view path, bool recursive, 292 const std::function<void(absl::string_view, const struct stat&)>& cb) { 293 DIR* dir = opendir(std::string(path).c_str()); 294 if (dir == nullptr) { 295 return PosixError(errno, absl::StrCat("opendir ", path)); 296 } 297 auto dir_closer = Cleanup([&dir]() { closedir(dir); }); 298 while (true) { 299 // Readdir(3): If the end of the directory stream is reached, NULL is 300 // returned and errno is not changed. If an error occurs, NULL is returned 301 // and errno is set appropriately. To distinguish end of stream and from an 302 // error, set errno to zero before calling readdir() and then check the 303 // value of errno if NULL is returned. 304 errno = 0; 305 struct dirent* dp = readdir(dir); 306 if (dp == nullptr) { 307 if (errno != 0) { 308 return PosixError(errno, absl::StrCat("readdir ", path)); 309 } 310 break; // We're done. 311 } 312 313 if (strcmp(dp->d_name, ".") == 0 || strcmp(dp->d_name, "..") == 0) { 314 // Skip dots. 315 continue; 316 } 317 318 auto full_path = JoinPath(path, dp->d_name); 319 ASSIGN_OR_RETURN_ERRNO(struct stat s, Stat(full_path)); 320 if (S_ISDIR(s.st_mode) && recursive) { 321 RETURN_IF_ERRNO(WalkTree(full_path, recursive, cb)); 322 } else { 323 cb(full_path, s); 324 } 325 } 326 // We're done walking so let's invoke our cleanup callback now. 327 dir_closer.Release()(); 328 329 // And we have to dispatch the callback on the base directory. 330 ASSIGN_OR_RETURN_ERRNO(struct stat s, Stat(path)); 331 cb(path, s); 332 333 return NoError(); 334 } 335 336 PosixErrorOr<std::vector<std::string>> ListDir(absl::string_view abspath, 337 bool skipdots) { 338 std::vector<std::string> files; 339 340 DIR* dir = opendir(std::string(abspath).c_str()); 341 if (dir == nullptr) { 342 return PosixError(errno, absl::StrCat("opendir ", abspath)); 343 } 344 auto dir_closer = Cleanup([&dir]() { closedir(dir); }); 345 while (true) { 346 // Readdir(3): If the end of the directory stream is reached, NULL is 347 // returned and errno is not changed. If an error occurs, NULL is returned 348 // and errno is set appropriately. To distinguish end of stream and from an 349 // error, set errno to zero before calling readdir() and then check the 350 // value of errno if NULL is returned. 351 errno = 0; 352 struct dirent* dp = readdir(dir); 353 if (dp == nullptr) { 354 if (errno != 0) { 355 return PosixError(errno, absl::StrCat("readdir ", abspath)); 356 } 357 break; // We're done. 358 } 359 360 if (strcmp(dp->d_name, ".") == 0 || strcmp(dp->d_name, "..") == 0) { 361 if (skipdots) { 362 continue; 363 } 364 } 365 files.push_back(std::string(dp->d_name)); 366 } 367 368 return files; 369 } 370 371 PosixError DirContains(absl::string_view path, 372 const std::vector<std::string>& expect, 373 const std::vector<std::string>& exclude) { 374 ASSIGN_OR_RETURN_ERRNO(auto listing, ListDir(path, false)); 375 376 for (auto& expected_entry : expect) { 377 auto cursor = std::find(listing.begin(), listing.end(), expected_entry); 378 if (cursor == listing.end()) { 379 return PosixError(ENOENT, absl::StrFormat("Failed to find '%s' in '%s'", 380 expected_entry, path)); 381 } 382 } 383 for (auto& excluded_entry : exclude) { 384 auto cursor = std::find(listing.begin(), listing.end(), excluded_entry); 385 if (cursor != listing.end()) { 386 return PosixError(ENOENT, absl::StrCat("File '", excluded_entry, 387 "' found in path '", path, "'")); 388 } 389 } 390 return NoError(); 391 } 392 393 PosixError EventuallyDirContains(absl::string_view path, 394 const std::vector<std::string>& expect, 395 const std::vector<std::string>& exclude) { 396 constexpr int kRetryCount = 100; 397 const absl::Duration kRetryDelay = absl::Milliseconds(100); 398 399 for (int i = 0; i < kRetryCount; ++i) { 400 auto res = DirContains(path, expect, exclude); 401 if (res.ok()) { 402 return res; 403 } 404 if (i < kRetryCount - 1) { 405 // Sleep if this isn't the final iteration. 406 absl::SleepFor(kRetryDelay); 407 } 408 } 409 return PosixError(ETIMEDOUT, 410 "Timed out while waiting for directory to contain files "); 411 } 412 413 PosixError RecursivelyDelete(absl::string_view path, int* undeleted_dirs, 414 int* undeleted_files) { 415 ASSIGN_OR_RETURN_ERRNO(bool exists, Exists(path)); 416 if (!exists) { 417 return PosixError(ENOENT, absl::StrCat(path, " does not exist")); 418 } 419 420 ASSIGN_OR_RETURN_ERRNO(bool dir, IsDirectory(path)); 421 if (!dir) { 422 // Nothing recursive needs to happen we can just call Delete. 423 auto status = Delete(path); 424 if (!status.ok() && undeleted_files) { 425 (*undeleted_files)++; 426 } 427 return status; 428 } 429 430 return WalkTree(path, /*recursive=*/true, 431 [&](absl::string_view absolute_path, const struct stat& s) { 432 if (S_ISDIR(s.st_mode)) { 433 auto rm_status = Rmdir(absolute_path); 434 if (!rm_status.ok() && undeleted_dirs) { 435 (*undeleted_dirs)++; 436 } 437 } else { 438 auto delete_status = Delete(absolute_path); 439 if (!delete_status.ok() && undeleted_files) { 440 (*undeleted_files)++; 441 } 442 } 443 }); 444 } 445 446 PosixError RecursivelyCreateDir(absl::string_view path) { 447 if (path.empty() || path == "/") { 448 return PosixError(EINVAL, "Cannot create root!"); 449 } 450 451 // Does it already exist, if so we're done. 452 ASSIGN_OR_RETURN_ERRNO(bool exists, Exists(path)); 453 if (exists) { 454 return NoError(); 455 } 456 457 // Do we need to create directories under us? 458 auto dirname = Dirname(path); 459 ASSIGN_OR_RETURN_ERRNO(exists, Exists(dirname)); 460 if (!exists) { 461 RETURN_IF_ERRNO(RecursivelyCreateDir(dirname)); 462 } 463 464 return Mkdir(path); 465 } 466 467 // Makes a path absolute with respect to an optional base. If no base is 468 // provided it will use the current working directory. 469 PosixErrorOr<std::string> MakeAbsolute(absl::string_view filename, 470 absl::string_view base) { 471 if (filename.empty()) { 472 return PosixError(EINVAL, "filename cannot be empty."); 473 } 474 475 if (filename[0] == '/') { 476 // This path is already absolute. 477 return std::string(filename); 478 } 479 480 std::string actual_base; 481 if (!base.empty()) { 482 actual_base = std::string(base); 483 } else { 484 auto cwd_or = GetCWD(); 485 RETURN_IF_ERRNO(cwd_or.error()); 486 actual_base = cwd_or.ValueOrDie(); 487 } 488 489 // Reverse iterate removing trailing slashes, effectively right trim '/'. 490 for (int i = actual_base.size() - 1; i >= 0 && actual_base[i] == '/'; --i) { 491 actual_base.erase(i, 1); 492 } 493 494 if (filename == ".") { 495 return actual_base.empty() ? "/" : actual_base; 496 } 497 498 return absl::StrCat(actual_base, "/", filename); 499 } 500 501 std::string CleanPath(const absl::string_view unclean_path) { 502 std::string path = std::string(unclean_path); 503 const char* src = path.c_str(); 504 std::string::iterator dst = path.begin(); 505 506 // Check for absolute path and determine initial backtrack limit. 507 const bool is_absolute_path = *src == '/'; 508 if (is_absolute_path) { 509 *dst++ = *src++; 510 while (*src == '/') ++src; 511 } 512 std::string::const_iterator backtrack_limit = dst; 513 514 // Process all parts 515 while (*src) { 516 bool parsed = false; 517 518 if (src[0] == '.') { 519 // 1dot ".<whateverisnext>", check for END or SEP. 520 if (src[1] == '/' || !src[1]) { 521 if (*++src) { 522 ++src; 523 } 524 parsed = true; 525 } else if (src[1] == '.' && (src[2] == '/' || !src[2])) { 526 // 2dot END or SEP (".." | "../<whateverisnext>"). 527 src += 2; 528 if (dst != backtrack_limit) { 529 // We can backtrack the previous part 530 for (--dst; dst != backtrack_limit && dst[-1] != '/'; --dst) { 531 // Empty. 532 } 533 } else if (!is_absolute_path) { 534 // Failed to backtrack and we can't skip it either. Rewind and copy. 535 src -= 2; 536 *dst++ = *src++; 537 *dst++ = *src++; 538 if (*src) { 539 *dst++ = *src; 540 } 541 // We can never backtrack over a copied "../" part so set new limit. 542 backtrack_limit = dst; 543 } 544 if (*src) { 545 ++src; 546 } 547 parsed = true; 548 } 549 } 550 551 // If not parsed, copy entire part until the next SEP or EOS. 552 if (!parsed) { 553 while (*src && *src != '/') { 554 *dst++ = *src++; 555 } 556 if (*src) { 557 *dst++ = *src++; 558 } 559 } 560 561 // Skip consecutive SEP occurrences 562 while (*src == '/') { 563 ++src; 564 } 565 } 566 567 // Calculate and check the length of the cleaned path. 568 int path_length = dst - path.begin(); 569 if (path_length != 0) { 570 // Remove trailing '/' except if it is root path ("/" ==> path_length := 1) 571 if (path_length > 1 && path[path_length - 1] == '/') { 572 --path_length; 573 } 574 path.resize(path_length); 575 } else { 576 // The cleaned path is empty; assign "." as per the spec. 577 path.assign(1, '.'); 578 } 579 return path; 580 } 581 582 PosixErrorOr<std::string> GetRelativePath(absl::string_view source, 583 absl::string_view dest) { 584 if (!absl::StartsWith(source, "/") || !absl::StartsWith(dest, "/")) { 585 // At least one of the inputs is not an absolute path. 586 return PosixError( 587 EINVAL, 588 "GetRelativePath: At least one of the inputs is not an absolute path."); 589 } 590 const std::string clean_source = CleanPath(source); 591 const std::string clean_dest = CleanPath(dest); 592 auto source_parts = absl::StrSplit(clean_source, '/', absl::SkipEmpty()); 593 auto dest_parts = absl::StrSplit(clean_dest, '/', absl::SkipEmpty()); 594 auto source_iter = source_parts.begin(); 595 auto dest_iter = dest_parts.begin(); 596 597 // Advance past common prefix. 598 while (source_iter != source_parts.end() && dest_iter != dest_parts.end() && 599 *source_iter == *dest_iter) { 600 ++source_iter; 601 ++dest_iter; 602 } 603 604 // Build result backtracking. 605 std::string result = ""; 606 while (source_iter != source_parts.end()) { 607 absl::StrAppend(&result, "../"); 608 ++source_iter; 609 } 610 611 // Add remaining path to dest. 612 while (dest_iter != dest_parts.end()) { 613 absl::StrAppend(&result, *dest_iter, "/"); 614 ++dest_iter; 615 } 616 617 if (result.empty()) { 618 return std::string("."); 619 } 620 621 // Remove trailing slash. 622 result.erase(result.size() - 1); 623 return result; 624 } 625 626 absl::string_view Dirname(absl::string_view path) { 627 return SplitPath(path).first; 628 } 629 630 absl::string_view Basename(absl::string_view path) { 631 return SplitPath(path).second; 632 } 633 634 std::pair<absl::string_view, absl::string_view> SplitPath( 635 absl::string_view path) { 636 std::string::size_type pos = path.find_last_of('/'); 637 638 // Handle the case with no '/' in 'path'. 639 if (pos == absl::string_view::npos) { 640 return std::make_pair(path.substr(0, 0), path); 641 } 642 643 // Handle the case with a single leading '/' in 'path'. 644 if (pos == 0) { 645 return std::make_pair(path.substr(0, 1), absl::ClippedSubstr(path, 1)); 646 } 647 648 return std::make_pair(path.substr(0, pos), 649 absl::ClippedSubstr(path, pos + 1)); 650 } 651 652 std::string JoinPath(absl::string_view path1, absl::string_view path2) { 653 if (path1.empty()) { 654 return std::string(path2); 655 } 656 if (path2.empty()) { 657 return std::string(path1); 658 } 659 660 if (path1.back() == '/') { 661 if (path2.front() == '/') { 662 return absl::StrCat(path1, absl::ClippedSubstr(path2, 1)); 663 } 664 } else { 665 if (path2.front() != '/') { 666 return absl::StrCat(path1, "/", path2); 667 } 668 } 669 return absl::StrCat(path1, path2); 670 } 671 672 PosixErrorOr<std::string> ProcessExePath(int pid) { 673 if (pid <= 0) { 674 return PosixError(EINVAL, "Invalid pid specified"); 675 } 676 677 return ReadLink(absl::StrCat("/proc/", pid, "/exe")); 678 } 679 680 #ifdef __linux__ 681 PosixErrorOr<bool> IsTmpfs(const std::string& path) { 682 struct statfs stat; 683 if (statfs(path.c_str(), &stat)) { 684 if (errno == ENOENT) { 685 // Nothing at path, don't raise this as an error. Instead, just report no 686 // tmpfs at path. 687 return false; 688 } 689 return PosixError(errno, 690 absl::StrFormat("statfs(\"%s\", %#p)", path, &stat)); 691 } 692 return stat.f_type == TMPFS_MAGIC; 693 } 694 #endif // __linux__ 695 696 PosixErrorOr<bool> IsOverlayfs(const std::string& path) { 697 struct statfs stat; 698 if (statfs(path.c_str(), &stat)) { 699 if (errno == ENOENT) { 700 // Nothing at path, don't raise this as an error. Instead, just report no 701 // overlayfs at path. 702 return false; 703 } 704 return PosixError(errno, 705 absl::StrFormat("statfs(\"%s\", %#p)", path, &stat)); 706 } 707 return stat.f_type == OVERLAYFS_SUPER_MAGIC; 708 } 709 710 PosixError CheckSameFile(const FileDescriptor& fd1, const FileDescriptor& fd2) { 711 struct stat stat_result1, stat_result2; 712 int res = fstat(fd1.get(), &stat_result1); 713 if (res < 0) { 714 return PosixError(errno, absl::StrCat("fstat ", fd1.get())); 715 } 716 717 res = fstat(fd2.get(), &stat_result2); 718 if (res < 0) { 719 return PosixError(errno, absl::StrCat("fstat ", fd2.get())); 720 } 721 EXPECT_EQ(stat_result1.st_dev, stat_result2.st_dev); 722 EXPECT_EQ(stat_result1.st_ino, stat_result2.st_ino); 723 724 return NoError(); 725 } 726 } // namespace testing 727 } // namespace gvisor