blob: 64234cfbcef8930c0dd0859a813686945b31eb1e [file]
// Copyright 2022 Google LLC
//
// 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
//
// http://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.
#include "./fuzztest/internal/io.h"
#include <cerrno>
#include <cstdio>
#include <cstring>
#include <filesystem>
#include <fstream>
#include <optional>
#include <sstream>
#include <string>
#include <string_view>
#include <tuple>
#include <utility>
#include <vector>
#include "absl/hash/hash.h"
#include "absl/strings/str_format.h"
#include "absl/strings/string_view.h"
#include "./fuzztest/internal/logging.h"
#if defined(__APPLE__)
#if (defined(__MAC_OS_X_VERSION_MIN_REQUIRED) && \
__MAC_OS_X_VERSION_MIN_REQUIRED < __MAC_10_15) || \
(defined(__IPHONE_OS_VERSION_MIN_REQUIRED) && \
__IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_13_0)
// std::filesystem requires macOS 10.15+ or iOS 13+.
// Just stub out these functions.
#define FUZZTEST_STUB_FILESYSTEM
#endif
#endif
namespace fuzztest::internal {
#if defined(FUZZTEST_STUB_FILESYSTEM)
// TODO(lszekeres): Return absl::Status instead of bool for these.
bool WriteFile(absl::string_view path, absl::string_view contents) {
FUZZTEST_INTERNAL_CHECK(false, "Filesystem API not supported in iOS/MacOS");
}
std::optional<std::string> ReadFile(absl::string_view path) {
FUZZTEST_INTERNAL_CHECK(false, "Filesystem API not supported in iOS/MacOS");
}
bool IsDirectory(absl::string_view path) {
FUZZTEST_INTERNAL_CHECK(false, "Filesystem API not supported in iOS/MacOS");
}
bool CreateDirectory(absl::string_view path) {
FUZZTEST_INTERNAL_CHECK(false, "Filesystem API not supported in iOS/MacOS");
}
std::vector<std::string> ListDirectory(absl::string_view path) {
FUZZTEST_INTERNAL_CHECK(false, "Filesystem API not supported in iOS/MacOS");
}
std::vector<std::string> ListDirectoryRecursively(absl::string_view path) {
FUZZTEST_INTERNAL_CHECK(false, "Filesystem API not supported in iOS/MacOS");
}
#else
bool WriteFile(absl::string_view path, absl::string_view contents) {
const std::filesystem::path fs_path{
std::string_view{path.data(), path.size()}};
// Just in case the directory does not currently exist.
// If it does, this is a noop.
CreateDirectory(fs_path.parent_path().string());
std::ofstream file(fs_path);
file << contents;
file.close();
if (!file.good()) {
absl::FPrintF(GetStderr(), "[!] %s:%d: Error writing %s: (%d) %s\n",
__FILE__, __LINE__, path, errno, strerror(errno));
}
return !file.fail();
}
std::optional<std::string> ReadFile(absl::string_view path) {
const std::filesystem::path fs_path{
std::string_view{path.data(), path.size()}};
std::ifstream stream(fs_path);
if (!stream.good()) {
// Using stderr instead of GetStderr() to avoid initialization-order-fiasco
// when reading files at static init time with
// `.WithSeeds(fuzztest::ReadFilesFromDirectory(...))`.
absl::FPrintF(stderr, "[!] %s:%d: Error reading %s: (%d) %s\n",
__FILE__, __LINE__, path, errno, strerror(errno));
return std::nullopt;
}
std::stringstream buffer;
buffer << stream.rdbuf();
return buffer.str();
}
bool IsDirectory(absl::string_view path) {
const std::filesystem::path fs_path{
std::string_view{path.data(), path.size()}};
return std::filesystem::is_directory(fs_path);
}
bool CreateDirectory(absl::string_view path) {
const std::filesystem::path fs_path{
std::string_view{path.data(), path.size()}};
return std::filesystem::create_directories(fs_path);
}
std::vector<std::string> ListDirectory(absl::string_view path) {
std::vector<std::string> output_paths;
const std::filesystem::path fs_path{
std::string_view{path.data(), path.size()}};
if (!std::filesystem::is_directory(fs_path)) return output_paths;
for (const auto& entry : std::filesystem::directory_iterator(fs_path)) {
output_paths.push_back(entry.path().string());
}
return output_paths;
}
std::vector<std::string> ListDirectoryRecursively(absl::string_view path) {
std::vector<std::string> output_paths;
const std::filesystem::path fs_path{
std::string_view{path.data(), path.size()}};
for (const auto& entry :
std::filesystem::recursive_directory_iterator(fs_path)) {
output_paths.push_back(entry.path().string());
}
return output_paths;
}
#endif // FUZZTEST_STUB_FILESYSTEM
std::string WriteDataToDir(absl::string_view data, absl::string_view outdir) {
std::string filename(outdir);
if (filename.back() != '/') filename += '/';
absl::StrAppendFormat(&filename, "%016x",
absl::Hash<absl::string_view>{}(data));
if (!WriteFile(filename, data)) return "";
return filename;
}
std::vector<FilePathAndData> ReadFileOrDirectory(
absl::string_view file_or_dir) {
std::vector<FilePathAndData> out;
const auto try_append_file = [&](std::string path) {
std::optional<std::string> contents = ReadFile(path);
if (contents.has_value()) {
out.push_back(FilePathAndData{std::move(path), *std::move(contents)});
}
};
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));
}
return out;
}
absl::string_view Basename(absl::string_view filename) {
auto last_slash_pos = filename.find_last_of("/\\");
return last_slash_pos == absl::string_view::npos
? filename
: filename.substr(last_slash_pos + 1);
}
std::vector<std::tuple<std::string>> ReadFilesFromDirectory(
absl::string_view dir) {
std::vector<FilePathAndData> files =
ReadFileOrDirectory({dir.data(), dir.size()});
std::vector<std::tuple<std::string>> out;
out.reserve(files.size());
for (const FilePathAndData& file : files) {
out.push_back(std::make_tuple(file.data));
}
return out;
}
} // namespace fuzztest::internal