| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743 |
- // Part of the Carbon Language project, under the Apache License v2.0 with LLVM
- // Exceptions. See /LICENSE for license information.
- // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
- #include "common/filesystem.h"
- #include <fcntl.h>
- #include <time.h>
- #include <unistd.h>
- #include <chrono>
- #include "common/build_data.h"
- #include "llvm/Support/MathExtras.h"
- namespace Carbon::Filesystem {
- // Render an error number from `errno` to the provided stream using the richest
- // rendering available on the platform.
- static auto PrintErrorNumber(llvm::raw_ostream& out, int errnum) -> void {
- #if defined(_GNU_SOURCE) && \
- (__GLIBC__ > 2 || (__GLIBC__ == 2 && __GLIBC_MINOR__ >= 32))
- // For sufficiently recent glibc versions, use GNU-specific routines to
- // compute the error name and description.
- llvm::StringRef name = strerrordesc_np(errnum);
- llvm::StringRef desc = strerrorname_np(errnum);
- out << llvm::formatv("{0}: {1}", name, desc);
- #elif defined(__APPLE__) || defined(_GNU_SOURCE) || defined(_POSIX_SOURCE)
- // Broadly portable fallback for Unix-like systems.
- char buffer[4096];
- #ifdef _GNU_SOURCE
- const char* str = strerror_r(errnum, buffer, sizeof(buffer));
- // The GNU version doesn't report a meta-error.
- int meta_error = 0;
- #else
- int meta_error = strerror_r(errnum, buffer, sizeof(buffer));
- const char* str = buffer;
- #endif
- if (meta_error == 0) {
- out << llvm::formatv("errno {0}: {1}", errnum, llvm::StringRef(str));
- } else {
- out << llvm::formatv(
- "error number {0}; encountered meta-error number {1} while rendering "
- "an error message",
- errnum, meta_error);
- }
- #else
- #error TODO: Implement this for other platforms.
- #endif
- }
- auto FdError::Print(llvm::raw_ostream& out) const -> void {
- // The `format_` member is a `StringLiteral` that is null terminated, so
- // `.data()` is safe here.
- // NOLINTNEXTLINE(bugprone-suspicious-stringview-data-usage)
- out << llvm::formatv(format_.data(), fd_) << " failed: ";
- PrintErrorNumber(out, unix_errnum());
- }
- auto PathError::Print(llvm::raw_ostream& out) const -> void {
- // The `format_` member is a `StringLiteral` that is null terminated, so
- // `.data()` is safe here.
- // NOLINTNEXTLINE(bugprone-suspicious-stringview-data-usage)
- out << llvm::formatv(format_.data(), path_,
- dir_fd_ == AT_FDCWD ? std::string("AT_FDCWD")
- : std::to_string(dir_fd_))
- << " failed: ";
- PrintErrorNumber(out, unix_errnum());
- }
- auto Internal::FileRefBase::ReadFileToString()
- -> ErrorOr<std::string, FdError> {
- std::string result;
- // Read a buffer at a time until we reach the end. We use the pipe buffer
- // length as our max buffer size as it is likely to be small but reasonable
- // for the OS, and in the case of pipes the same chunking in which the data
- // will arrive.
- //
- // TODO: Replace this with a smaller buffer and using `resize_and_overwrite`
- // to read into the string in-place for larger strings. Unclear if that will
- // be any faster, but it will be much more friendly to callers with
- // constrained stack sizes and use less memory overall.
- std::byte buffer[PIPE_BUF];
- CARBON_RETURN_IF_ERROR(SeekFromBeginning(0));
- for (;;) {
- auto read_result = ReadToBuffer(buffer);
- if (!read_result.ok()) {
- return std::move(read_result).error();
- }
- if (read_result->empty()) {
- // EOF
- break;
- }
- result.append(reinterpret_cast<const char*>(read_result->data()),
- read_result->size());
- }
- return result;
- }
- auto Internal::FileRefBase::WriteFileFromString(llvm::StringRef str)
- -> ErrorOr<Success, FdError> {
- CARBON_RETURN_IF_ERROR(SeekFromBeginning(0));
- auto bytes = llvm::ArrayRef<std::byte>(
- reinterpret_cast<const std::byte*>(str.data()), str.size());
- while (!bytes.empty()) {
- auto write_result = WriteFromBuffer(bytes);
- if (!write_result.ok()) {
- return std::move(write_result).error();
- }
- bytes = *write_result;
- }
- CARBON_RETURN_IF_ERROR(Truncate(str.size()));
- return Success();
- }
- // A macOS specific sleep routine that builds on more standard utilities. This
- // is technically a portable implementation so we always compile it but only use
- // it on macOS where the more efficient direct use of `clock_nanosleep` isn't
- // available.
- [[maybe_unused]]
- static auto SleepMacos(Duration sleep) -> void {
- TimePoint stop = Clock::now() + sleep;
- timespec sleep_ts = Internal::DurationToTimespec(sleep);
- for (;;) {
- timespec rem_sleep_ts = {};
- int result = nanosleep(&sleep_ts, &rem_sleep_ts);
- if (result == 0) {
- return;
- }
- // Continue sleeping if we get interrupted by a resumable signal. For
- // everything else report it.
- if (errno != EINTR) {
- int errnum = errno;
- RawStringOstream error_os;
- PrintErrorNumber(error_os, errnum);
- CARBON_FATAL("Unexpected error while sleeping: {0}", error_os.TakeStr());
- }
- // Update to the remaining sleep time for the next attempt at sleeping.
- sleep_ts = rem_sleep_ts;
- // Also check if the clock has passed our stop time as a fallback to avoid
- // too much clock skew.
- if (Clock::now() > stop) {
- return;
- }
- }
- }
- static auto Sleep(Duration sleep) -> void {
- // For every platform but macOS we can sleep directly on an absolute time.
- #if __APPLE__
- // On Apple platforms, dispatch to a specialized routine.
- SleepMacos(sleep);
- #else
- // We use `clock_gettime` instead of the filesystem `Clock` or some other
- // `std::chrono` clock because we want to use the exact same clock that we'll
- // use for sleeping below, and we'll need the time in a `timespec` for that
- // call anyways. We do use a monotonic clock to try and avoid sleeps being
- // interrupted by clock changes.
- timespec ts = {};
- int result = clock_gettime(CLOCK_MONOTONIC, &ts);
- CARBON_CHECK(result == 0, "Error getting the time: {0}", strerror(errno));
- // Now convert the timespec to a duration that we can safely do arithmetic on.
- // Since the sleep interval is in nanoseconds it is tempting to directly do
- // arithmetic here, but this has a subtle pitfall near the boundary between
- // the nanosecond component and the second component.
- //
- // Note that our `Duration` uses `__int128` to avoid worrying about running
- // out of precision to represent the final deadline.
- Duration stop_time = std::chrono::seconds(ts.tv_sec);
- stop_time += std::chrono::nanoseconds(ts.tv_nsec);
- stop_time += sleep;
- // Now convert back to timespec.
- ts = Internal::DurationToTimespec(stop_time);
- do {
- result = clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &ts, nullptr);
- // Continue sleeping if we get interrupted by a resumable signal. Because
- // we're using a monotonic clock and an absolute deadline time we will
- // eventually progress past that deadline.
- } while (result != 0 && (errno == EINTR));
- if (result != 0) {
- int errnum = errno;
- RawStringOstream error_os;
- PrintErrorNumber(error_os, errnum);
- CARBON_FATAL("Unexpected error while sleeping: {0}", error_os.TakeStr());
- }
- #endif
- }
- auto Internal::FileRefBase::TryLock(FileLock::Kind kind, Duration deadline,
- Duration poll_interval)
- -> ErrorOr<FileLock, FdError> {
- CARBON_CHECK(poll_interval <= deadline);
- if (deadline != Duration(0) && poll_interval == Duration(0)) {
- // If the caller didn't provide a poll interval but did provide a deadline,
- // pick a poll interval to roughly be 1/1000th of the deadline but at least
- // 1 microsecond. We don't support polling faster than 1 microsecond given
- // how expensive file locking is.
- poll_interval =
- std::max(Duration(std::chrono::microseconds(1)), deadline / 1000);
- }
- if (deadline != Duration(0)) {
- CARBON_CHECK(
- deadline >= std::chrono::microseconds(10),
- "A deadline for a file lock shorter than 10 microseconds is not "
- "supported, callers can implement their own polling logic.");
- CARBON_CHECK(poll_interval >= std::chrono::microseconds(1),
- "Polling for a file lock faster than every microsecond is not "
- "supported, callers can implement their own polling logic.");
- }
- auto stop = Clock::now() + deadline;
- for (;;) {
- int result = flock(fd_, static_cast<int>(kind) | LOCK_NB);
- if (result == 0) {
- return FileLock(fd_);
- }
- // Return an error if this is something other than blocking for the lock to
- // be available, or we didn't get a deadline for continuing to try and
- // acquire the lock, or we've reached our deadline.
- if (errno != EWOULDBLOCK || deadline == Duration(0) ||
- Clock::now() >= stop) {
- return FdError(errno, "File::TryLock on '{0}'", fd_);
- }
- // The caller requested attempting to wait up to a deadline to acquire the
- // lock with a specific poll interval. Try to sleep for that poll interval
- // before trying the lock again.
- Sleep(poll_interval);
- }
- }
- auto DirRef::AppendEntriesIf(
- llvm::SmallVectorImpl<std::filesystem::path>& entries,
- llvm::function_ref<auto(llvm::StringRef name)->bool> predicate)
- -> ErrorOr<Success, FdError> {
- CARBON_ASSIGN_OR_RETURN(Reader reader, Read());
- for (const Entry& entry : reader) {
- llvm::StringRef name = entry.name();
- if (name == "." || name == "..") {
- continue;
- }
- if (predicate && !predicate(name)) {
- continue;
- }
- entries.push_back(name.str());
- }
- return Success();
- }
- auto DirRef::AppendEntriesIf(
- llvm::SmallVectorImpl<std::filesystem::path>& dir_entries,
- llvm::SmallVectorImpl<std::filesystem::path>& non_dir_entries,
- llvm::function_ref<auto(llvm::StringRef name)->bool> predicate)
- -> ErrorOr<Success, FdError> {
- CARBON_ASSIGN_OR_RETURN(Reader reader, Read());
- for (const Entry& entry : reader) {
- llvm::StringRef name = entry.name();
- if (name == "." || name == "..") {
- continue;
- }
- if (predicate && !predicate(name)) {
- continue;
- }
- std::filesystem::path name_path = name.str();
- if (entry.is_known_dir()) {
- dir_entries.push_back(std::move(name_path));
- continue;
- }
- if (!entry.is_unknown_type()) {
- non_dir_entries.push_back(std::move(name_path));
- continue;
- }
- auto stat_result = Lstat(name_path);
- if (!stat_result.ok()) {
- return FdError(stat_result.error().unix_errnum(),
- "Dir::AppendEntriesIf on '{0}' failed while stat-ing "
- "entries to determine which are directories",
- dfd_);
- }
- if (stat_result->is_dir()) {
- dir_entries.push_back(std::move(name_path));
- } else {
- non_dir_entries.push_back(std::move(name_path));
- }
- }
- return Success();
- }
- auto DirRef::OpenDir(const std::filesystem::path& path,
- CreationOptions creation_options, ModeType creation_mode,
- OpenFlags open_flags) -> ErrorOr<Dir, PathError> {
- // If we potentially need to create a directory, we have to do that
- // separately as no systems support `O_CREAT | O_DIRECTORY`, even though
- // that would be (much) nicer.
- if (creation_options == CreateNew) {
- // If we are required to be the one that created the directory, disable
- // following the last symlink when we open that directory. The last symlink
- // is the only one that matters for security here because it is only valid
- // to create the last component. It is that directory component that we want
- // to ensure has not been replaced with a symlink by an adversarial
- // concurrent process.
- open_flags |= OpenFlags::NoFollow;
- }
- if (creation_options != OpenExisting) {
- CARBON_CHECK(creation_options != CreateAlways,
- "Invalid `creation_options` value of `CreateAlways`: there is "
- "no support for truncating directories, and so they cannot be "
- "created in an analogous way to files if they already exist.");
- if (mkdirat(dfd_, path.c_str(), creation_mode) != 0) {
- // Unless the error is just that the path already exists, and that is
- // allowed for the requested creation flags, report any error here as part
- // of opening just like we would if the error originated from `openat`
- // with `O_CREAT`.
- if (creation_options == CreateNew || errno != EEXIST) {
- return PathError(errno,
- "Calling `mkdirat` on '{0}' relative to '{1}' during "
- "DirRef::OpenDir",
- path, dfd_);
- }
- }
- }
- // Open this path as a directory. Note that this has to succeed, and when we
- // created the directory we require the last component to not be a symlink in
- // case it was _replaced_ with a symlink while running.
- int result_fd =
- openat(dfd_, path.c_str(), static_cast<int>(open_flags) | O_DIRECTORY);
- if (result_fd == -1) {
- // No need for `EINTR` handling here as if this is a FIFO it would be an
- // error with `O_DIRECTORY`.
- return PathError(
- errno,
- "Calling `openat` on '{0}' relative to '{1}' during DirRef::OpenDir",
- path, dfd_);
- }
- Dir result(result_fd);
- // If we were required to create the directory, we also need to verify that
- // the opened file descriptor continues to have the same permissions and the
- // correct owner as we couldn't do the creation atomically with the open. This
- // defends against an adversarial removal of the created directory and
- // creation of a new directory with the same name but either with wider
- // permissions such as all-write, or with a different owner.
- //
- // We don't defend against replacement with a directory of the same name, same
- // permissions, same owner, but different group. There is no good way to do
- // this defense given the complexity of group assignment, and there appears to
- // be no need. Achieving such a replacement without superuser power would
- // require a parent directory with `setgid` bit, and a group that gives the
- // attacker access -- but such a parent directory would make *any* creation
- // vulnerable without any need for a replacement, so we can't defend against
- // that here. The caller has ample tools to defend against this including
- // taking care with the parent directory and restricting the group permission
- // bits which we *do* verify.
- if (creation_options == CreateNew) {
- auto stat_result = result.Stat();
- if (!stat_result.ok()) {
- // Manually propagate this error so we can attach it back to the opened
- // path and relative directory.
- return PathError(stat_result.error().unix_errnum(),
- "DirRef::Stat after opening '{0}' relative to '{1}'",
- path, dfd_);
- }
- // Check that the owning UID is the current effective UID.
- if (stat_result->unix_uid() != geteuid()) {
- // Model this as `EPERM`, which is a bit awkward, but should be fine.
- return PathError(EPERM,
- "Unexpected UID change after creating '{0}' relative to "
- "'{1}' during DirRef::OpenDir",
- path, dfd_);
- }
- // Check that the permissions are a subset of the requested ones. They may
- // have been masked down by `umask`, but if there are *new* permissions,
- // that would be a security issue.
- if ((stat_result->permissions() & creation_mode) !=
- stat_result->permissions()) {
- // Model this with `EPERM` and a custom message.
- return PathError(EPERM,
- "Unexpected permissions after creating '{0}' relative "
- "to '{1}' during DirRef::OpenDir",
- path, dfd_);
- }
- }
- return result;
- }
- auto DirRef::ReadFileToString(const std::filesystem::path& path)
- -> ErrorOr<std::string, PathError> {
- CARBON_ASSIGN_OR_RETURN(ReadFile f, OpenReadOnly(path));
- auto result = f.ReadFileToString();
- if (result.ok()) {
- return *std::move(result);
- }
- return PathError(result.error().unix_errnum(),
- "Dir::ReadFileToString on '{0}' relative to '{1}'", path,
- dfd_);
- }
- auto DirRef::WriteFileFromString(const std::filesystem::path& path,
- llvm::StringRef content,
- CreationOptions creation_options)
- -> ErrorOr<Success, PathError> {
- CARBON_ASSIGN_OR_RETURN(WriteFile f, OpenWriteOnly(path, creation_options));
- auto write_result = f.WriteFileFromString(content);
- // Immediately close the file as even if there was a write error we don't want
- // to leave the file open.
- auto close_result = std::move(f).Close();
- // Now report the first error encountered or return success.
- if (!write_result.ok()) {
- return PathError(
- write_result.error().unix_errnum(),
- "Write error in Dir::WriteFileFromString on '{0}' relative to '{1}'",
- path, dfd_);
- }
- if (!close_result.ok()) {
- return PathError(
- close_result.error().unix_errnum(),
- "Close error in Dir::WriteFileFromString on '{0}' relative to '{1}'",
- path, dfd_);
- }
- return Success();
- }
- auto DirRef::CreateDirectories(const std::filesystem::path& path,
- ModeType creation_mode)
- -> ErrorOr<Dir, PathError> {
- // Avoid having to handle an empty path by immediately rejecting it as
- // invalid.
- if (path.empty()) {
- return PathError(EINVAL,
- "DirRef::CreateDirectories on '{0}' relative to '{1}'",
- path, dfd_);
- }
- // Try directly opening the directory and use that if successful. This is an
- // important hot path case of users essentially doing an "open-always" form of
- // creating multiple steps of directories.
- auto open_result = OpenDir(path, OpenExisting);
- if (open_result.ok()) {
- return std::move(*open_result);
- } else if (!open_result.error().no_entity()) {
- return std::move(open_result).error();
- }
- // Walk from the full path towards this directory (or the root) to find the
- // first existing directory. This is faster than walking down as no file
- // descriptors have to be allocated for any intervening directories, etc. We
- // keep the path components that are missing as we pop them off for easy
- // traversal back down.
- std::optional<Dir> work_dir;
- // Paths typically consist of relatively few components
- // and so we can use a bit of stack and avoid allocating and moving the paths
- // in common cases. We use `8` as an arbitrary but likely good for all of the
- // hottest cases.
- llvm::SmallVector<std::filesystem::path, 8> missing_components;
- missing_components.push_back(path.filename());
- for (std::filesystem::path parent_path = path.parent_path();
- !parent_path.empty(); parent_path = parent_path.parent_path()) {
- auto open_result = OpenDir(parent_path, OpenExisting);
- if (open_result.ok()) {
- work_dir = std::move(*open_result);
- break;
- }
- missing_components.push_back(parent_path.filename());
- }
- CARBON_CHECK(!missing_components.empty());
- // If we haven't yet opened an intermediate directory, start by creating one
- // relative to this directory. We can't do this as part of the loop below as
- // `this` and the newly opened directory have different types.
- if (!work_dir) {
- std::filesystem::path component = missing_components.pop_back_val();
- CARBON_ASSIGN_OR_RETURN(
- Dir component_dir,
- OpenDir(component, CreationOptions::OpenAlways, creation_mode));
- // Move this component into our temporary directory slot.
- work_dir = std::move(component_dir);
- }
- // Now walk through the remaining components opening and creating each
- // relative to the previous.
- while (!missing_components.empty()) {
- std::filesystem::path component = missing_components.pop_back_val();
- CARBON_ASSIGN_OR_RETURN(
- Dir component_dir,
- work_dir->OpenDir(component, CreationOptions::OpenAlways,
- creation_mode));
- // Close the current temporary directory and move the new component
- // directory object into its place.
- work_dir = std::move(component_dir);
- }
- CARBON_CHECK(work_dir,
- "Should always have created at least one directory for a "
- "non-empty path!");
- return std::move(work_dir).value();
- }
- auto DirRef::Rmtree(const std::filesystem::path& path)
- -> ErrorOr<Success, PathError> {
- struct DirAndIterator {
- DirRef::Reader dir;
- ssize_t dir_entry_start;
- };
- llvm::SmallVector<DirAndIterator> dir_stack;
- llvm::SmallVector<std::filesystem::path> dir_entries;
- llvm::SmallVector<std::filesystem::path> unknown_entries;
- dir_entries.push_back(path);
- for (;;) {
- // When we bottom out, we're removing the initial tree path and doing so
- // relative to `this` directory.
- DirRef current = dir_stack.empty() ? *this : dir_stack.back().dir;
- ssize_t dir_entry_start =
- dir_stack.empty() ? 0 : dir_stack.back().dir_entry_start;
- // If we've finished all the child directories of the current entry in the
- // stack, pop it off and continue.
- if (dir_entry_start == static_cast<ssize_t>(dir_entries.size())) {
- dir_stack.pop_back();
- continue;
- }
- CARBON_CHECK(dir_entry_start < static_cast<ssize_t>(dir_entries.size()));
- // Take the last entry under the current directory and try removing it.
- const std::filesystem::path& entry_path = dir_entries.back();
- auto rmdir_result = current.Rmdir(entry_path);
- if (rmdir_result.ok() || rmdir_result.error().no_entity()) {
- // Removed here or elsewhere already, so pop the entry.
- dir_entries.pop_back();
- if (dir_entries.empty()) {
- // The last entry is the input path with an empty stack, so we've
- // finished at this point.
- CARBON_CHECK(dir_stack.empty());
- return Success();
- }
- continue;
- }
- // If we get any error other than not-empty, just return that.
- if (!rmdir_result.error().not_empty()) {
- return std::move(rmdir_result).error();
- }
- // Recurse into the subdirectory since it isn't empty, opening it, getting a
- // reader, and pushing it onto our stack.
- CARBON_ASSIGN_OR_RETURN(Dir subdir, current.OpenDir(entry_path));
- auto read_result = std::move(subdir).TakeAndRead();
- if (!read_result.ok()) {
- return PathError(
- read_result.error().unix_errnum(),
- "Dir::Read on '{0}' relative to '{1}' during RmdirRecursively",
- entry_path, current.dfd_);
- }
- dir_stack.push_back(
- {*std::move(read_result), static_cast<ssize_t>(dir_entries.size())});
- // Now read the directory entries. It would be nice to be able to directly
- // remove the files and empty directories as we find them when reading, and
- // the POSIX spec appears to require that to work, but testing shows at
- // least some Linux environments don't work reliably in this case and will
- // fail to visit some entries entirely. As a consequence, we walk the entire
- // directory and collect the entries into data structures before beginning
- // to remove them.
- DirRef::Reader& subdir_reader = dir_stack.back().dir;
- for (const auto& entry : subdir_reader) {
- llvm::StringRef name = entry.name();
- if (name == "." || name == "..") {
- continue;
- }
- if (entry.is_known_dir()) {
- dir_entries.push_back(name.str());
- } else {
- // We end up here for entries known to be regular files, other kinds of
- // non-directory entries, or when the entry kind isn't known.
- //
- // Unless we *know* the entry is a directory, we put it into the unknown
- // entries. For these, we unlink them first in case they are
- // non-directory entries and use the failure of that to move any
- // directories that end up here to the directory entries list.
- unknown_entries.push_back(name.str());
- }
- }
- // We can immediately try to unlink all the unknown entries, which will
- // include any regular files, and use an error on directories that were
- // unknown above to switch them to the `dir_entries` list.
- while (!unknown_entries.empty()) {
- std::filesystem::path name = unknown_entries.pop_back_val();
- auto unlink_result = subdir_reader.Unlink(name);
- if (unlink_result.ok() || unlink_result.error().no_entity()) {
- continue;
- } else if (!unlink_result.error().is_dir()) {
- return std::move(unlink_result).error();
- }
- dir_entries.push_back(std::move(name));
- }
- // We'll handle the directory entries we've queued here in the next
- // iteration, removing them or recursing as needed.
- }
- }
- auto DirRef::ReadlinkSlow(const std::filesystem::path& path)
- -> ErrorOr<std::string, PathError> {
- constexpr ssize_t MinBufferSize =
- #ifdef PATH_MAX
- PATH_MAX
- #else
- 1024
- #endif
- ;
- // Read directly into a string to avoid allocating two large buffers.
- std::string large_buffer;
- // Stat the symlink to get an initial guess at the size.
- CARBON_ASSIGN_OR_RETURN(FileStatus status, Lstat(path));
- // We try to use the size from the `lstat` unless it is empty, in which case
- // we try to use our minimum buffer size which is `PATH_MAX` or a constant
- // value. We have a fallback to dynamically discover an adequate buffer size
- // below that will handle any inaccuracy.
- ssize_t buffer_size = status.size();
- if (buffer_size == 0) {
- buffer_size = MinBufferSize;
- }
- large_buffer.resize(status.size());
- ssize_t result =
- readlinkat(dfd_, path.c_str(), large_buffer.data(), large_buffer.size());
- if (result == -1) {
- return PathError(errno, "Readlink on '{0}' relative to '{1}'", path, dfd_);
- }
- // Now the really bad fallback case: if there are racing writes to the
- // symlink, the guessed size may not have been large enough. As a last-ditch
- // effort, begin doubling (from the next power of two >= our min buffer size)
- // the length until it fits. We cap this at 10 MiB to prevent egregious file
- // system contents (or some bug somewhere) from exhausting memory.
- constexpr ssize_t MaxBufferSize = 10 << 20;
- while (result == static_cast<ssize_t>(large_buffer.size())) {
- int64_t next_buffer_size = std::max<ssize_t>(
- MinBufferSize, llvm::NextPowerOf2(large_buffer.size()));
- if (next_buffer_size > MaxBufferSize) {
- return PathError(ENOMEM, "Readlink on '{0}' relative to '{1}'", path,
- dfd_);
- }
- large_buffer.resize(next_buffer_size);
- result = readlinkat(dfd_, path.c_str(), large_buffer.data(),
- large_buffer.size());
- if (result == -1) {
- return PathError(errno, "Readlink on '{0}' relative to '{1}'", path,
- dfd_);
- }
- }
- // Fix-up the size of the string and return it.
- large_buffer.resize(result);
- return large_buffer;
- }
- auto MakeTmpDir() -> ErrorOr<RemovingDir, Error> {
- std::filesystem::path tmpdir_path = "/tmp";
- // We use both `TEST_TMPDIR` and `TMPDIR`. The `TEST_TMPDIR` is set by Bazel
- // and preferred to keep tests using the expected output tree rather than
- // the system temporary directory.
- for (const char* tmpdir_env_name : {"TEST_TMPDIR", "TMPDIR"}) {
- const char* tmpdir_env_cstr = getenv(tmpdir_env_name);
- if (tmpdir_env_cstr == nullptr) {
- continue;
- }
- std::filesystem::path tmpdir_env = tmpdir_env_cstr;
- tmpdir_path = std::move(tmpdir_env);
- break;
- }
- std::filesystem::path target = BuildData::BuildTarget.str();
- tmpdir_path /= target.filename();
- return MakeTmpDirWithPrefix(std::move(tmpdir_path));
- }
- auto MakeTmpDirWithPrefix(std::filesystem::path prefix)
- -> ErrorOr<RemovingDir, Error> {
- std::filesystem::path tmpdir_path = std::move(prefix);
- tmpdir_path += ".XXXXXX";
- std::string tmpdir_path_buffer = tmpdir_path.native();
- char* result = mkdtemp(tmpdir_path_buffer.data());
- if (result == nullptr) {
- RawStringOstream os;
- os << llvm::formatv("Calling mkdtemp on '{0}' failed: ",
- tmpdir_path.native());
- PrintErrorNumber(os, errno);
- return Error(os.TakeStr());
- }
- CARBON_CHECK(result == tmpdir_path_buffer.data(),
- "`mkdtemp` used a modified path");
- tmpdir_path = std::move(tmpdir_path_buffer);
- // Because `mkdtemp` doesn't return an open directory atomically, open the
- // created directory and perform safety checks similar to `OpenDir` when
- // creating a new directory.
- CARBON_ASSIGN_OR_RETURN(
- Dir tmp, Cwd().OpenDir(tmpdir_path, OpenExisting, /*creation_mode=*/0,
- OpenFlags::NoFollow));
- // Make sure we try to remove the directory from here on out.
- RemovingDir result_dir(std::move(tmp), tmpdir_path);
- // It's a bit awkward to report `fstat` errors as `Error`s, but we
- // don't have much choice. The stat failing here would be very weird.
- CARBON_ASSIGN_OR_RETURN(FileStatus stat, result_dir.Stat());
- // The permissions must be exactly 0700 for a temporary directory, and the UID
- // should be ours.
- if (stat.permissions() != 0700 && stat.unix_uid() != geteuid()) {
- return Error(
- llvm::formatv("Found incorrect permissions or UID on tmpdir '{0}'",
- tmpdir_path.native())
- .str());
- }
- return result_dir;
- }
- } // namespace Carbon::Filesystem
|