| // 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. | |
| #include <sys/stat.h> | |
| #include <sys/types.h> | |
| #if !defined(_MSC_VER) && !defined(__ANDROID__) && !defined(__Fuchsia__) && \ | |
| !defined(__LLVM_LIBC__) | |
| #include <glob.h> | |
| #define FUZZTEST_HAS_OSS_GLOB | |
| #endif // !defined(_MSC_VER) && !defined(__ANDROID__) && | |
| // !defined(__Fuchsia__) && !defined(__LLVM_LIBC__) | |
| #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 "absl/time/time.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(ByteSpan contents) { | |
| static constexpr auto elt_size = sizeof(contents[0]); | |
| const auto elts_to_write = contents.size(); | |
| const auto elts_written = | |
| std::fwrite(contents.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, ByteSpan contents) { | |
| return static_cast<LocalRemoteFile*>(f)->Write(contents); | |
| } | |
| 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; | |
| } | |
| absl::StatusOr<absl::Time> RemoteFileGetMTime(std::string_view path) { | |
| struct ::stat st; | |
| if (::stat(std::string(path).c_str(), &st) != 0) { | |
| return absl::UnknownError( | |
| absl::StrCat("stat() failed, path: ", std::string(path), | |
| ", errno: ", std::strerror(errno))); | |
| } | |
| return absl::FromTimeT(st.st_mtime); | |
| } | |
| 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 |