diff --git a/centipede/BUILD b/centipede/BUILD index 2aa9c979..153477c1 100644 --- a/centipede/BUILD +++ b/centipede/BUILD @@ -616,6 +616,7 @@ cc_library( ":disable_riegeli": ["CENTIPEDE_DISABLE_RIEGELI"], "//conditions:default": [], }), + visibility = EXTENDED_API_VISIBILITY, deps = [ ":defs", ":logging", diff --git a/centipede/remote_file.cc b/centipede/remote_file.cc index 48015a77..e2a7c689 100644 --- a/centipede/remote_file.cc +++ b/centipede/remote_file.cc @@ -139,6 +139,10 @@ ABSL_ATTRIBUTE_WEAK bool RemotePathExists(std::string_view path) { return std::filesystem::exists(path); } +ABSL_ATTRIBUTE_WEAK bool RemotePathIsDirectory(std::string_view path) { + return std::filesystem::is_directory(path); +} + ABSL_ATTRIBUTE_WEAK int64_t RemoteFileGetSize(std::string_view path) { FILE *f = std::fopen(path.data(), "r"); CHECK(f != nullptr) << VV(path); @@ -171,6 +175,16 @@ ABSL_ATTRIBUTE_WEAK void RemoteGlobMatch(std::string_view glob, ::globfree(&glob_ret); } +ABSL_ATTRIBUTE_WEAK std::vector RemoteListDirectory( + std::string_view path) { + if (!std::filesystem::is_directory(path)) return {}; + std::vector ret; + for (const auto &entry : std::filesystem::directory_iterator(path)) { + ret.push_back(entry.path()); + } + return ret; +} + ABSL_ATTRIBUTE_WEAK std::vector RemoteListFilesRecursively( std::string_view path) { if (!std::filesystem::exists(path)) return {}; diff --git a/centipede/remote_file.h b/centipede/remote_file.h index 19ca891c..3d0ae640 100644 --- a/centipede/remote_file.h +++ b/centipede/remote_file.h @@ -76,12 +76,19 @@ void RemoteFileGetContents(const std::filesystem::path &path, // Returns true if `path` exists. bool RemotePathExists(std::string_view path); +// Returns true if `path` is a directory. +bool RemotePathIsDirectory(std::string_view path); + // Returns the size of the file at `path` in bytes. The file must exist. int64_t RemoteFileGetSize(std::string_view path); // Finds all files matching `glob` and appends them to `matches`. void RemoteGlobMatch(std::string_view glob, std::vector &matches); +// Returns a list of top-level paths under `path`. If `path` is not a directory, +// or it's an empty directory, returns an empty list. +std::vector RemoteListDirectory(std::string_view path); + // Recursively lists all files within `path`. Does not return any directories. // Returns an empty vector if `path` is an empty directory, or `path` does not // exist. Returns `{path}` if `path` is a non-directory. diff --git a/fuzztest/BUILD b/fuzztest/BUILD index e21c9234..2871676e 100644 --- a/fuzztest/BUILD +++ b/fuzztest/BUILD @@ -407,6 +407,7 @@ cc_library( "@com_google_absl//absl/hash", "@com_google_absl//absl/strings:str_format", "@com_google_absl//absl/strings:string_view", + "@com_google_fuzztest//centipede:remote_file", ], ) diff --git a/fuzztest/internal/io.cc b/fuzztest/internal/io.cc index 36589086..eedd8df6 100644 --- a/fuzztest/internal/io.cc +++ b/fuzztest/internal/io.cc @@ -14,11 +14,6 @@ #include "./fuzztest/internal/io.h" -#include -#include -#include -#include -#include #include #include #include @@ -30,6 +25,7 @@ #include "absl/hash/hash.h" #include "absl/strings/str_format.h" #include "absl/strings/string_view.h" +#include "./centipede/remote_file.h" #include "./fuzztest/internal/logging.h" #if defined(__APPLE__) @@ -39,55 +35,81 @@ __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_13_0) // std::filesystem requires macOS 10.15+ or iOS 13+. // Just stub out these functions. -#define STUB_FILESYSTEM +#define FUZZTEST_STUB_FILESYSTEM #endif #endif namespace fuzztest::internal { -#if defined(STUB_FILESYSTEM) +#if defined(FUZZTEST_STUB_FILESYSTEM) -bool WriteFile(absl::string_view filename, absl::string_view contents) { - FUZZTEST_INTERNAL_CHECK(false, "Can't replay in iOS/MacOS"); +bool WriteFile(absl::string_view path, absl::string_view contents) { + FUZZTEST_INTERNAL_CHECK(false, "Filesystem API not supported in iOS/MacOS"); } -std::string WriteDataToDir(absl::string_view data, absl::string_view dir) { - FUZZTEST_INTERNAL_CHECK(false, "Can't replay in iOS/MacOS"); +std::optional ReadFile(absl::string_view path) { + FUZZTEST_INTERNAL_CHECK(false, "Filesystem API not supported in iOS/MacOS"); } -std::vector ReadFileOrDirectory( - absl::string_view file_or_dir) { - FUZZTEST_INTERNAL_CHECK(false, "Can't replay in iOS/MacOS"); +bool IsDirectory(absl::string_view path) { + FUZZTEST_INTERNAL_CHECK(false, "Filesystem API not supported in iOS/MacOS"); } -std::optional ReadFile(absl::string_view file) { - FUZZTEST_INTERNAL_CHECK(false, "Can't replay in iOS/MacOS"); +bool CreateDirectory(absl::string_view path) { + FUZZTEST_INTERNAL_CHECK(false, "Filesystem API not supported in iOS/MacOS"); } -std::vector ListDirectory(absl::string_view dir) { - FUZZTEST_INTERNAL_CHECK(false, "Can't replay in iOS/MacOS"); +std::vector ListDirectory(absl::string_view path) { + FUZZTEST_INTERNAL_CHECK(false, "Filesystem API not supported in iOS/MacOS"); } -#else // defined(__APPLE__) +std::vector ListDirectoryRecursively(absl::string_view path) { + FUZZTEST_INTERNAL_CHECK(false, "Filesystem API not supported in iOS/MacOS"); +} -bool WriteFile(absl::string_view filename, absl::string_view contents) { - std::filesystem::path file_path{ - std::string_view{filename.data(), filename.size()}}; +#else // FUZZTEST_STUB_FILESYSTEM +bool WriteFile(absl::string_view path, absl::string_view contents) { // Just in case the directory does not currently exist. - // If it does, this is a noop. - std::filesystem::create_directories(file_path.parent_path()); - - std::ofstream file(file_path); - file << contents; - file.close(); - if (!file.good()) { - absl::FPrintF(GetStderr(), "%s:%d: Error writing %s: (%d) %s\n", __FILE__, - __LINE__, filename, errno, strerror(errno)); + if (!CreateDirectory(Dirname(path))) { + absl::FPrintF(GetStderr(), "[!] %s:%d: Couldn't create directory: %s\n", + __FILE__, __LINE__, path); + return false; + } + centipede::RemoteFileSetContents(path, std::string(contents)); + return true; +} + +std::optional ReadFile(absl::string_view path) { + std::string contents; + if (!centipede::RemotePathExists(path)) { + absl::FPrintF(GetStderr(), "[!] %s:%d: File doesn't exist: %s\n", __FILE__, + __LINE__, path); + return std::nullopt; } - return !file.fail(); + centipede::RemoteFileGetContents(path, contents); + return contents; +} + +bool IsDirectory(absl::string_view path) { + return centipede::RemotePathIsDirectory(path); +} + +bool CreateDirectory(absl::string_view path) { + centipede::RemoteMkdir(path); + return true; +} + +std::vector ListDirectory(absl::string_view path) { + return centipede::RemoteListDirectory(path); +} + +std::vector ListDirectoryRecursively(absl::string_view path) { + return centipede::RemoteListFilesRecursively(path); } +#endif // FUZZTEST_STUB_FILESYSTEM + std::string WriteDataToDir(absl::string_view data, absl::string_view outdir) { std::string filename(outdir); if (filename.back() != '/') filename += '/'; @@ -97,35 +119,21 @@ std::string WriteDataToDir(absl::string_view data, absl::string_view outdir) { return filename; } -std::optional ReadFile(absl::string_view file) { - std::filesystem::path file_path{std::string_view{file.data(), file.size()}}; - if (!std::filesystem::is_regular_file(file_path)) return std::nullopt; - std::ifstream stream(file_path); - if (!stream.good()) { - absl::FPrintF(stderr, "%s:%d: Error reading %s: (%d) %s\n", __FILE__, - __LINE__, file, errno, strerror(errno)); - return std::nullopt; - } - std::stringstream buffer; - buffer << stream.rdbuf(); - return buffer.str(); -} - std::vector ReadFileOrDirectory( absl::string_view file_or_dir) { std::vector out; + const auto try_append_file = [&](std::string path) { - std::optional data = ReadFile(path); - if (data.has_value()) { - out.push_back(FilePathAndData{std::move(path), *std::move(data)}); + std::optional contents = ReadFile(path); + if (contents.has_value()) { + out.push_back(FilePathAndData{std::move(path), *std::move(contents)}); } }; - std::filesystem::path file_or_dir_path{ - std::string_view{file_or_dir.data(), file_or_dir.size()}}; - if (std::filesystem::is_directory(file_or_dir_path)) { - for (const auto& entry : - std::filesystem::recursive_directory_iterator(file_or_dir_path)) { - try_append_file(entry.path().string()); + if (IsDirectory(file_or_dir)) { + for (const auto& path : ListDirectoryRecursively(file_or_dir)) { + if (!IsDirectory(path)) { + try_append_file(path); + } } } else { try_append_file(std::string(file_or_dir)); @@ -133,17 +141,13 @@ std::vector ReadFileOrDirectory( return out; } -std::vector ListDirectory(absl::string_view dir) { - std::vector out; - std::filesystem::path dir_path{std::string_view{dir.data(), dir.size()}}; - if (!std::filesystem::is_directory(dir_path)) return out; - for (const auto& entry : std::filesystem::directory_iterator(dir_path)) { - out.push_back(entry.path().string()); - } - return out; -} +absl::string_view Dirname(absl::string_view filename) { + auto last_slash_pos = filename.find_last_of("/\\"); -#endif // defined(STUB_FILESYSTEM) + return last_slash_pos == absl::string_view::npos + ? filename + : filename.substr(0, last_slash_pos); +} absl::string_view Basename(absl::string_view filename) { auto last_slash_pos = filename.find_last_of("/\\"); diff --git a/fuzztest/internal/io.h b/fuzztest/internal/io.h index b2f4d9e8..9ff73940 100644 --- a/fuzztest/internal/io.h +++ b/fuzztest/internal/io.h @@ -23,38 +23,53 @@ namespace fuzztest::internal { -bool WriteFile(absl::string_view filename, absl::string_view contents); +// Writes `contents` to the file at `path`. Returns true on success, false +// otherwise. +bool WriteFile(absl::string_view path, absl::string_view contents); + +// Returns the contents of the file at `path` or std::nullopt on failure. +std::optional ReadFile(absl::string_view path); + +// Returns true if `path` is a directory, false otherwise. +bool IsDirectory(absl::string_view path); + +// Creates directory at `path`, *recursively* creating parent directories if +// necessary. Returns true on success, false otherwise. +bool CreateDirectory(absl::string_view path); + +// Returns a list of top-level paths under `path`. If `path` is not a directory, +// or it's an empty directory, returns an empty list. +std::vector ListDirectory(absl::string_view path); + +// Returns all paths under `path` *recursively*. If `path` is not a directory, +// returns an empty list. +std::vector ListDirectoryRecursively(absl::string_view path); // Write `data` to its hash-based filename in `dir`. Returns the `dir`-appended // path to the file. std::string WriteDataToDir(absl::string_view data, absl::string_view dir); -// Reads `file` and returns its content. If `file` is not a regular file or -// reading it fails, returns `std::nullopt`. -std::optional ReadFile(absl::string_view file); - struct FilePathAndData { std::string path; std::string data; }; // If `file_or_dir` is a directory, returns a list of its files' paths and -// contents. If `file_or_dir` is a file, returns a singleton list with its path -// and content. In all other cases, returns an empty list. +// contents *recursively*. If `file_or_dir` is a file, returns a singleton list +// with its path and content. In all other cases, returns an empty list. std::vector ReadFileOrDirectory(absl::string_view file_or_dir); -// Returns a list of top-level paths in `dir`. If `dir` is not a directory, -// returns an empty list. -std::vector ListDirectory(absl::string_view dir); - -// Returns the basename of `filename`. -absl::string_view Basename(absl::string_view filename); - // Reads files as strings from the directory `dir` and returns a vector usable // by .WithSeeds(). std::vector> ReadFilesFromDirectory( absl::string_view dir); +// Returns the dirname of `filename`. +absl::string_view Dirname(absl::string_view filename); + +// Returns the basename of `filename`. +absl::string_view Basename(absl::string_view filename); + } // namespace fuzztest::internal #endif // FUZZTEST_FUZZTEST_INTERNAL_IO_H_