|  | // Copyright 2022 The Centipede Authors. | 
|  | // | 
|  | // Licensed under the Apache License, Version 2.0 (the "License"); | 
|  | // you may not use this file except in compliance with the License. | 
|  | // You may obtain a copy of the License at | 
|  | // | 
|  | //      https://www.apache.org/licenses/LICENSE-2.0 | 
|  | // | 
|  | // Unless required by applicable law or agreed to in writing, software | 
|  | // distributed under the License is distributed on an "AS IS" BASIS, | 
|  | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 
|  | // See the License for the specific language governing permissions and | 
|  | // limitations under the License. | 
|  |  | 
|  | // Implementation of remote_file.h for the local file system using pure Standard | 
|  | // Library APIs. | 
|  |  | 
|  | #if !defined(_MSC_VER) && !defined(__ANDROID__) && !defined(__Fuchsia__) | 
|  | #include <glob.h> | 
|  | #define FUZZTEST_HAS_OSS_GLOB | 
|  | #endif  // !defined(_MSC_VER) && !defined(__ANDROID__) && !defined(__Fuchsia__) | 
|  |  | 
|  | #if defined(_MSC_VER) | 
|  | #include <windows.h> | 
|  | #endif  // defined(_MSC_VER) | 
|  |  | 
|  | #include <cerrno> | 
|  | #include <cstdint> | 
|  | #include <cstdio> | 
|  | #include <cstring> | 
|  | #include <filesystem>  // NOLINT | 
|  | #include <memory> | 
|  | #include <string> | 
|  | #include <string_view> | 
|  | #include <system_error>  // NOLINT | 
|  | #include <utility> | 
|  | #include <vector> | 
|  |  | 
|  | #include "absl/base/nullability.h" | 
|  | #include "absl/status/status.h" | 
|  | #include "absl/status/statusor.h" | 
|  | #include "absl/strings/str_cat.h" | 
|  | #include "./common/defs.h" | 
|  | #include "./common/logging.h" | 
|  | #include "./common/remote_file.h" | 
|  | #include "./common/status_macros.h" | 
|  | #ifndef CENTIPEDE_DISABLE_RIEGELI | 
|  | #include "riegeli/bytes/fd_reader.h" | 
|  | #include "riegeli/bytes/fd_writer.h" | 
|  | #include "riegeli/bytes/reader.h" | 
|  | #include "riegeli/bytes/writer.h" | 
|  | #endif  // CENTIPEDE_DISABLE_RIEGELI | 
|  |  | 
|  | namespace fuzztest::internal { | 
|  | namespace { | 
|  |  | 
|  | class LocalRemoteFile : public RemoteFile { | 
|  | public: | 
|  | static absl::StatusOr<LocalRemoteFile *> Create(std::string path, | 
|  | std::string_view mode) { | 
|  | FILE *file = std::fopen(path.c_str(), mode.data()); | 
|  | if (file == nullptr) { | 
|  | return absl::UnknownError(absl::StrCat( | 
|  | "fopen() failed, path: ", path, ", errno: ", std::strerror(errno))); | 
|  | } | 
|  | return new LocalRemoteFile{std::move(path), file}; | 
|  | } | 
|  |  | 
|  | ~LocalRemoteFile() { | 
|  | FUZZTEST_CHECK(file_ == nullptr) | 
|  | << "Dtor called before Close(): " << VV(path_); | 
|  | } | 
|  |  | 
|  | // Movable but not copyable. | 
|  | LocalRemoteFile(const LocalRemoteFile &) = delete; | 
|  | LocalRemoteFile &operator=(const LocalRemoteFile &) = delete; | 
|  | LocalRemoteFile(LocalRemoteFile &&) = default; | 
|  | LocalRemoteFile &operator=(LocalRemoteFile &&) = default; | 
|  |  | 
|  | absl::Status SetWriteBufSize(size_t size) { | 
|  | if (write_buf_ != nullptr) { | 
|  | return absl::FailedPreconditionError("SetWriteBufCapacity called twice"); | 
|  | } | 
|  | write_buf_ = std::make_unique<char[]>(size); | 
|  | if (std::setvbuf(file_, write_buf_.get(), _IOFBF, size) != 0) { | 
|  | return absl::UnknownError( | 
|  | absl::StrCat("std::setvbuf failed, path: ", path_, | 
|  | ", errno: ", std::strerror(errno))); | 
|  | } | 
|  | return absl::OkStatus(); | 
|  | } | 
|  |  | 
|  | absl::Status Write(const ByteArray &ba) { | 
|  | static constexpr auto elt_size = sizeof(ba[0]); | 
|  | const auto elts_to_write = ba.size(); | 
|  | const auto elts_written = | 
|  | std::fwrite(ba.data(), elt_size, elts_to_write, file_); | 
|  | if (elts_written != elts_to_write) { | 
|  | return absl::UnknownError(absl::StrCat( | 
|  | "fwrite() wrote less elements that expected, wrote: ", elts_written, | 
|  | ", expected: ", elts_to_write, ", path: ", path_)); | 
|  | } | 
|  | return absl::OkStatus(); | 
|  | } | 
|  |  | 
|  | absl::Status Flush() { | 
|  | if (std::fflush(file_) != 0) { | 
|  | return absl::UnknownError("fflush() failed"); | 
|  | } | 
|  | return absl::OkStatus(); | 
|  | } | 
|  |  | 
|  | absl::Status Read(ByteArray &ba) { | 
|  | // Compute the file size as a difference between the end and start offsets. | 
|  | if (std::fseek(file_, 0, SEEK_END), 0 != 0) { | 
|  | return absl::UnknownError(absl::StrCat("fseek() failed on path: ", path_, | 
|  | ": ", std::strerror(errno))); | 
|  | } | 
|  | const auto file_size = std::ftell(file_); | 
|  | if (std::fseek(file_, 0, SEEK_SET), 0) { | 
|  | return absl::UnknownError(absl::StrCat("fseek() failed on path: ", path_, | 
|  | ": ", std::strerror(errno))); | 
|  | } | 
|  | static constexpr auto elt_size = sizeof(ba[0]); | 
|  | FUZZTEST_CHECK_EQ(file_size % elt_size, 0) | 
|  | << VV(file_size) << VV(elt_size) << VV(path_); | 
|  | if (file_size % elt_size != 0) { | 
|  | return absl::FailedPreconditionError( | 
|  | absl::StrCat("Attempting to read a file with inconsistent element (", | 
|  | elt_size, ") and file size (", file_size, "): ", path_)); | 
|  | } | 
|  | const auto elts_to_read = file_size / elt_size; | 
|  | ba.resize(elts_to_read); | 
|  | const auto elts_read = std::fread(ba.data(), elt_size, elts_to_read, file_); | 
|  | if (elts_read != elts_to_read) { | 
|  | return absl::UnknownError(absl::StrCat( | 
|  | "fread() read less elements that expected, wrote: ", elts_read, | 
|  | ", expected: ", elts_to_read, ", path: ", path_)); | 
|  | } | 
|  | return absl::OkStatus(); | 
|  | } | 
|  |  | 
|  | absl::Status Close() { | 
|  | if (std::fclose(file_) != 0) { | 
|  | return absl::UnknownError(absl::StrCat("fclose() failed on path: ", path_, | 
|  | ": ", std::strerror(errno))); | 
|  | } | 
|  | file_ = nullptr; | 
|  | write_buf_ = nullptr; | 
|  | return absl::OkStatus(); | 
|  | } | 
|  |  | 
|  | private: | 
|  | LocalRemoteFile(std::string path, FILE *file) | 
|  | : path_{std::move(path)}, file_{file} {} | 
|  |  | 
|  | std::string path_; | 
|  | FILE *file_; | 
|  | std::unique_ptr<char[]> write_buf_; | 
|  | }; | 
|  |  | 
|  | }  // namespace | 
|  |  | 
|  | #if defined(FUZZTEST_STUB_STD_FILESYSTEM) | 
|  |  | 
|  | absl::Status RemoteMkdir(std::string_view path) { | 
|  | FUZZTEST_LOG(FATAL) << "Filesystem API not supported in iOS/MacOS"; | 
|  | } | 
|  |  | 
|  | bool RemotePathExists(std::string_view path) { | 
|  | FUZZTEST_LOG(FATAL) << "Filesystem API not supported in iOS/MacOS"; | 
|  | } | 
|  |  | 
|  | bool RemotePathIsDirectory(std::string_view path) { | 
|  | FUZZTEST_LOG(FATAL) << "Filesystem API not supported in iOS/MacOS"; | 
|  | } | 
|  |  | 
|  | absl::StatusOr<std::vector<std::string>> RemoteListFiles(std::string_view path, | 
|  | bool recursively) { | 
|  | FUZZTEST_LOG(FATAL) << "Filesystem API not supported in iOS/MacOS"; | 
|  | } | 
|  |  | 
|  | absl::Status RemoteFileRename(std::string_view from, std::string_view to) { | 
|  | FUZZTEST_LOG(FATAL) << "Filesystem API not supported in iOS/MacOS"; | 
|  | } | 
|  |  | 
|  | absl::Status RemoteFileCopy(std::string_view from, std::string_view to) { | 
|  | FUZZTEST_LOG(FATAL) << "Filesystem API not supported in iOS/MacOS"; | 
|  | } | 
|  |  | 
|  | absl::Status RemotePathTouchExistingFile(std::string_view path) { | 
|  | FUZZTEST_LOG(FATAL) << "Filesystem API not supported in iOS/MacOS"; | 
|  | } | 
|  |  | 
|  | absl::Status RemotePathDelete(std::string_view path, bool recursively) { | 
|  | FUZZTEST_LOG(FATAL) << "Filesystem API not supported in iOS/MacOS"; | 
|  | } | 
|  |  | 
|  | #else | 
|  |  | 
|  | absl::Status RemoteMkdir(std::string_view path) { | 
|  | if (path.empty()) { | 
|  | return absl::InvalidArgumentError("Unable to RemoteMkdir() an empty path"); | 
|  | } | 
|  | std::error_code error; | 
|  | std::filesystem::create_directories(path, error); | 
|  | if (error) { | 
|  | return absl::UnknownError( | 
|  | absl::StrCat("create_directories() failed, path: ", std::string(path), | 
|  | ", error: ", error.message())); | 
|  | } | 
|  | return absl::OkStatus(); | 
|  | } | 
|  |  | 
|  | bool RemotePathExists(std::string_view path) { | 
|  | return std::filesystem::exists(path); | 
|  | } | 
|  |  | 
|  | bool RemotePathIsDirectory(std::string_view path) { | 
|  | return std::filesystem::is_directory(path); | 
|  | } | 
|  |  | 
|  | absl::StatusOr<std::vector<std::string>> RemoteListFiles(std::string_view path, | 
|  | bool recursively) { | 
|  | if (!std::filesystem::exists(path)) return std::vector<std::string>(); | 
|  | auto list_files = [](auto dir_iter) { | 
|  | std::vector<std::string> ret; | 
|  | for (const auto &entry : dir_iter) { | 
|  | if (entry.is_directory()) continue; | 
|  | // On Windows, there's no implicit conversion from `std::filesystem::path` | 
|  | // to `std::string`. | 
|  | ret.push_back(entry.path().string()); | 
|  | } | 
|  | return ret; | 
|  | }; | 
|  | return recursively | 
|  | ? list_files(std::filesystem::recursive_directory_iterator(path)) | 
|  | : list_files(std::filesystem::directory_iterator(path)); | 
|  | } | 
|  |  | 
|  | absl::Status RemoteFileRename(std::string_view from, std::string_view to) { | 
|  | std::error_code error; | 
|  | std::filesystem::rename(from, to, error); | 
|  | if (error) { | 
|  | return absl::UnknownError( | 
|  | absl::StrCat("filesystem::rename() failed, from: ", std::string(from), | 
|  | ", to: ", std::string(to), ", error: ", error.message())); | 
|  | } | 
|  | return absl::OkStatus(); | 
|  | } | 
|  |  | 
|  | absl::Status RemoteFileCopy(std::string_view from, std::string_view to) { | 
|  | std::error_code error; | 
|  | std::filesystem::copy( | 
|  | from, to, std::filesystem::copy_options::overwrite_existing, error); | 
|  | if (error) { | 
|  | return absl::UnknownError( | 
|  | absl::StrCat("filesystem::copy() failed, from: ", std::string(from), | 
|  | ", to: ", std::string(to), ", error: ", error.message())); | 
|  | } | 
|  | return absl::OkStatus(); | 
|  | } | 
|  |  | 
|  | absl::Status RemotePathTouchExistingFile(std::string_view path) { | 
|  | if (!RemotePathExists(path)) { | 
|  | return absl::InvalidArgumentError( | 
|  | absl::StrCat("path: ", std::string(path), " does not exist.")); | 
|  | } | 
|  |  | 
|  | #if defined(_MSC_VER) | 
|  | HANDLE file = CreateFileA(path.data(), GENERIC_READ, FILE_SHARE_READ, NULL, | 
|  | OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); | 
|  | if (file == INVALID_HANDLE_VALUE) { | 
|  | return absl::InternalError(absl::StrCat("Failed to open ", path, ".")); | 
|  | } | 
|  | SYSTEMTIME st; | 
|  | FILETIME mtime; | 
|  | GetSystemTime(&st); | 
|  | SystemTimeToFileTime(&st, &mtime); | 
|  | if (SetFileTime(file, nullptr, nullptr, &mtime)) { | 
|  | return absl::InternalError(absl::StrCat("Failed to set mtime for ", path)); | 
|  | } | 
|  | CloseHandle(file); | 
|  | #else | 
|  | if (0 != utimes(path.data(), nullptr)) { | 
|  | return absl::InternalError(absl::StrCat("Failed to set mtime for ", path, | 
|  | " (errno ", errno, ").")); | 
|  | } | 
|  | #endif | 
|  | return absl::OkStatus(); | 
|  | } | 
|  |  | 
|  | absl::Status RemotePathDelete(std::string_view path, bool recursively) { | 
|  | std::error_code error; | 
|  | if (recursively) { | 
|  | std::filesystem::remove_all(path, error); | 
|  | } else { | 
|  | std::filesystem::remove(path, error); | 
|  | } | 
|  | if (error) { | 
|  | return absl::UnknownError( | 
|  | absl::StrCat("filesystem::remove() or remove_all() failed, path: ", | 
|  | std::string(path), ", error: ", error.message())); | 
|  | } | 
|  | return absl::OkStatus(); | 
|  | } | 
|  |  | 
|  | #endif  // defined(FUZZTEST_STUB_STD_FILESYSTEM) | 
|  |  | 
|  | // TODO(ussuri): For now, simulate the old behavior, where a failure to open | 
|  | //  a file returned nullptr. Adjust the clients to expect non-null and use a | 
|  | //  normal ctor with a FUZZTEST_CHECK instead of `Create()` here instead. | 
|  | absl::StatusOr<RemoteFile *> RemoteFileOpen(std::string_view path, | 
|  | const char *mode) { | 
|  | return LocalRemoteFile::Create(std::string(path), mode); | 
|  | } | 
|  |  | 
|  | absl::Status RemoteFileClose(RemoteFile *absl_nonnull f) { | 
|  | auto *file = static_cast<LocalRemoteFile *>(f); | 
|  | RETURN_IF_NOT_OK(file->Close()); | 
|  | delete file; | 
|  | return absl::OkStatus(); | 
|  | } | 
|  |  | 
|  | absl::Status RemoteFileSetWriteBufferSize(RemoteFile *absl_nonnull f, | 
|  | size_t size) { | 
|  | return static_cast<LocalRemoteFile *>(f)->SetWriteBufSize(size); | 
|  | } | 
|  |  | 
|  | absl::Status RemoteFileAppend(RemoteFile *absl_nonnull f, const ByteArray &ba) { | 
|  | return static_cast<LocalRemoteFile *>(f)->Write(ba); | 
|  | } | 
|  |  | 
|  | absl::Status RemoteFileFlush(RemoteFile *absl_nonnull f) { | 
|  | return static_cast<LocalRemoteFile *>(f)->Flush(); | 
|  | } | 
|  |  | 
|  | absl::Status RemoteFileRead(RemoteFile *absl_nonnull f, ByteArray &ba) { | 
|  | return static_cast<LocalRemoteFile *>(f)->Read(ba); | 
|  | } | 
|  |  | 
|  | absl::StatusOr<int64_t> RemoteFileGetSize(std::string_view path) { | 
|  | FILE *f = std::fopen(path.data(), "r"); | 
|  | if (f == nullptr) { | 
|  | return absl::UnknownError( | 
|  | absl::StrCat("fopen() failed, path: ", std::string(path), | 
|  | ", errno: ", std::strerror(errno))); | 
|  | } | 
|  | if (std::fseek(f, 0, SEEK_END) != 0) { | 
|  | return absl::UnknownError( | 
|  | absl::StrCat("fseek() failed, path: ", std::string(path), | 
|  | ", errno: ", std::strerror(errno))); | 
|  | } | 
|  | const auto sz = std::ftell(f); | 
|  | if (sz == -1L) { | 
|  | return absl::UnknownError( | 
|  | absl::StrCat("ftell() failed, path: ", std::string(path), | 
|  | ", errno: ", std::strerror(errno))); | 
|  | } | 
|  | std::fclose(f); | 
|  | return sz; | 
|  | } | 
|  |  | 
|  | namespace { | 
|  |  | 
|  | #if defined(FUZZTEST_HAS_OSS_GLOB) | 
|  | int HandleGlobError(const char *epath, int eerrno) { | 
|  | if (eerrno == ENOENT) return 0; | 
|  | FUZZTEST_LOG(FATAL) << "Error while globbing path: " << VV(epath) | 
|  | << VV(eerrno); | 
|  | return -1; | 
|  | } | 
|  | #endif  // defined(FUZZTEST_HAS_OSS_GLOB) | 
|  |  | 
|  | }  // namespace | 
|  |  | 
|  | absl::Status RemoteGlobMatch(std::string_view glob, | 
|  | std::vector<std::string> &matches) { | 
|  | #if defined(FUZZTEST_HAS_OSS_GLOB) | 
|  | // See `man glob.3`. | 
|  | ::glob_t glob_ret = {}; | 
|  | if (int ret = ::glob(std::string{glob}.c_str(), GLOB_TILDE, HandleGlobError, | 
|  | &glob_ret); | 
|  | ret != 0) { | 
|  | if (ret == GLOB_NOMATCH) { | 
|  | return absl::NotFoundError(absl::StrCat( | 
|  | "glob() returned NOMATCH for pattern: ", std::string(glob))); | 
|  | } | 
|  | return absl::UnknownError(absl::StrCat( | 
|  | "glob() failed, pattern: ", std::string(glob), ", returned: ", ret)); | 
|  | } | 
|  | for (int i = 0; i < glob_ret.gl_pathc; ++i) { | 
|  | matches.emplace_back(glob_ret.gl_pathv[i]); | 
|  | } | 
|  | ::globfree(&glob_ret); | 
|  | return absl::OkStatus(); | 
|  | #else | 
|  | return absl::UnimplementedError( | 
|  | absl::StrCat(__func__, "() is not supported on this platform")); | 
|  | #endif  // defined(FUZZTEST_HAS_OSS_GLOB) | 
|  | } | 
|  |  | 
|  | #ifndef CENTIPEDE_DISABLE_RIEGELI | 
|  | absl::StatusOr<std::unique_ptr<riegeli::Reader>> CreateRiegeliFileReader( | 
|  | std::string_view file_path) { | 
|  | auto ret = std::make_unique<riegeli::FdReader<>>(file_path); | 
|  | RETURN_IF_NOT_OK(ret->status()); | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | absl::StatusOr<std::unique_ptr<riegeli::Writer>> CreateRiegeliFileWriter( | 
|  | std::string_view file_path, bool append) { | 
|  | auto ret = std::make_unique<riegeli::FdWriter<>>( | 
|  | file_path, riegeli::FdWriterBase::Options().set_append(append)); | 
|  | RETURN_IF_NOT_OK(ret->status()); | 
|  | return ret; | 
|  | } | 
|  | #endif  // CENTIPEDE_DISABLE_RIEGELI | 
|  |  | 
|  | }  // namespace fuzztest::internal |