You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
507 lines
20 KiB
507 lines
20 KiB
// 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.
|
|
|
|
#include "./common/blob_file.h"
|
|
|
|
#include <algorithm>
|
|
#include <cstddef>
|
|
#include <cstdint>
|
|
#include <cstring>
|
|
#include <iterator>
|
|
#include <memory>
|
|
#include <string>
|
|
#include <string_view>
|
|
#include <utility>
|
|
#include <vector>
|
|
|
|
#include "absl/base/nullability.h"
|
|
#include "absl/base/optimization.h"
|
|
#include "absl/log/check.h"
|
|
#include "absl/log/log.h"
|
|
#include "absl/status/status.h"
|
|
#include "absl/strings/str_format.h"
|
|
#include "absl/strings/string_view.h"
|
|
#include "absl/time/clock.h"
|
|
#include "absl/time/time.h"
|
|
#include "./common/defs.h"
|
|
#include "./common/hash.h"
|
|
#include "./common/logging.h"
|
|
#include "./common/remote_file.h"
|
|
#include "./common/status_macros.h"
|
|
#ifndef CENTIPEDE_DISABLE_RIEGELI
|
|
#include "riegeli/base/object.h"
|
|
#include "riegeli/base/types.h"
|
|
#include "riegeli/bytes/reader.h"
|
|
#include "riegeli/bytes/writer.h"
|
|
#include "riegeli/records/record_reader.h"
|
|
#include "riegeli/records/record_writer.h"
|
|
#endif // CENTIPEDE_DISABLE_RIEGELI
|
|
|
|
namespace fuzztest::internal {
|
|
namespace {
|
|
|
|
constexpr size_t kMagicLen = 11;
|
|
constexpr uint8_t kPackBegMagic[] = "-Centipede-";
|
|
constexpr uint8_t kPackEndMagic[] = "-edepitneC-";
|
|
static_assert(sizeof(kPackBegMagic) == kMagicLen + 1);
|
|
static_assert(sizeof(kPackEndMagic) == kMagicLen + 1);
|
|
|
|
// TODO(ussuri): Return more informative statuses, at least with the file path
|
|
// included. That will require adjustments in the test: use
|
|
// `testing::status::StatusIs` instead of direct `absl::Status` comparisons).
|
|
|
|
// Simple implementations of `BlobFileReader` / `BlobFileWriter` based on
|
|
// `PackBytesForAppendFile()` / `UnpackBytesFromAppendFile()`.
|
|
// We expect to eventually replace this code with something more robust,
|
|
// and efficient, e.g. possibly https://github.com/google/riegeli.
|
|
// But the current implementation is fully functional.
|
|
class SimpleBlobFileReader : public BlobFileReader {
|
|
public:
|
|
~SimpleBlobFileReader() override {
|
|
if (file_ && !closed_) {
|
|
// Virtual resolution is off in dtors, so use a specific Close().
|
|
CHECK_OK(SimpleBlobFileReader::Close());
|
|
}
|
|
}
|
|
|
|
absl::Status Open(std::string_view path) override {
|
|
if (closed_) return absl::FailedPreconditionError("already closed");
|
|
if (file_) return absl::FailedPreconditionError("already open");
|
|
ASSIGN_OR_RETURN_IF_NOT_OK(file_, RemoteFileOpen(path, "r"));
|
|
if (file_ == nullptr) return absl::UnknownError("can't open file");
|
|
// Read the entire file at once.
|
|
// It may be useful to read the file in chunks, but if we are going
|
|
// to migrate to something else, it's not important here.
|
|
ByteArray raw_bytes;
|
|
RETURN_IF_NOT_OK(RemoteFileRead(file_, raw_bytes));
|
|
RETURN_IF_NOT_OK(
|
|
RemoteFileClose(file_)); // close the file here, we won't need it.
|
|
UnpackBytesFromAppendFile(raw_bytes, &unpacked_blobs_);
|
|
return absl::OkStatus();
|
|
}
|
|
|
|
absl::Status Read(ByteSpan &blob) override {
|
|
if (closed_) return absl::FailedPreconditionError("already closed");
|
|
if (!file_) return absl::FailedPreconditionError("was not open");
|
|
if (next_to_read_blob_index_ == unpacked_blobs_.size())
|
|
return absl::OutOfRangeError("no more blobs");
|
|
if (next_to_read_blob_index_ != 0) // Clear the previous blob to save RAM.
|
|
unpacked_blobs_[next_to_read_blob_index_ - 1].clear();
|
|
blob = ByteSpan(unpacked_blobs_[next_to_read_blob_index_]);
|
|
++next_to_read_blob_index_;
|
|
return absl::OkStatus();
|
|
}
|
|
|
|
// Closes the file (it must be open).
|
|
absl::Status Close() override {
|
|
if (closed_) return absl::FailedPreconditionError("already closed");
|
|
if (!file_) return absl::FailedPreconditionError("was not open");
|
|
closed_ = true;
|
|
// Nothing to do here, we've already closed the underlying file (in Open()).
|
|
return absl::OkStatus();
|
|
}
|
|
|
|
private:
|
|
RemoteFile *file_ = nullptr;
|
|
bool closed_ = false;
|
|
std::vector<ByteArray> unpacked_blobs_;
|
|
size_t next_to_read_blob_index_ = 0;
|
|
};
|
|
|
|
// See SimpleBlobFileReader.
|
|
class SimpleBlobFileWriter : public BlobFileWriter {
|
|
public:
|
|
~SimpleBlobFileWriter() override {
|
|
if (file_ && !closed_) {
|
|
// Virtual resolution is off in dtors, so use a specific Close().
|
|
CHECK_OK(SimpleBlobFileWriter::Close());
|
|
}
|
|
}
|
|
|
|
absl::Status Open(std::string_view path, std::string_view mode) override {
|
|
CHECK(mode == "w" || mode == "a") << VV(mode);
|
|
if (closed_) return absl::FailedPreconditionError("already closed");
|
|
if (file_) return absl::FailedPreconditionError("already open");
|
|
ASSIGN_OR_RETURN_IF_NOT_OK(file_, RemoteFileOpen(path, mode.data()));
|
|
if (file_ == nullptr) return absl::UnknownError("can't open file");
|
|
RETURN_IF_NOT_OK(RemoteFileSetWriteBufferSize(file_, kMaxBufferedBytes));
|
|
return absl::OkStatus();
|
|
}
|
|
|
|
absl::Status Write(ByteSpan blob) override {
|
|
if (closed_) return absl::FailedPreconditionError("already closed");
|
|
if (!file_) return absl::FailedPreconditionError("was not open");
|
|
ByteArray packed = PackBytesForAppendFile(blob);
|
|
RETURN_IF_NOT_OK(RemoteFileAppend(file_, packed));
|
|
RETURN_IF_NOT_OK(RemoteFileFlush(file_));
|
|
return absl::OkStatus();
|
|
}
|
|
|
|
absl::Status Close() override {
|
|
if (closed_) return absl::FailedPreconditionError("already closed");
|
|
if (!file_) return absl::FailedPreconditionError("was not open");
|
|
closed_ = true;
|
|
RETURN_IF_NOT_OK(RemoteFileClose(file_));
|
|
return absl::OkStatus();
|
|
}
|
|
|
|
private:
|
|
static constexpr uint64_t kMB = 1024UL * 1024UL;
|
|
static constexpr uint64_t kMaxBufferedBytes = 100 * kMB;
|
|
|
|
RemoteFile *file_ = nullptr;
|
|
bool closed_ = false;
|
|
};
|
|
|
|
// Implementation of `BlobFileReader` that can read files written in legacy or
|
|
// Riegeli (https://github.com/google/riegeli) format.
|
|
class DefaultBlobFileReader : public BlobFileReader {
|
|
public:
|
|
~DefaultBlobFileReader() override {
|
|
// Virtual resolution is off in dtors, so use a specific Close().
|
|
CHECK_OK(DefaultBlobFileReader::Close());
|
|
}
|
|
|
|
absl::Status Open(std::string_view path) override {
|
|
if (absl::Status s = Close(); !s.ok()) return s;
|
|
|
|
#ifndef CENTIPEDE_DISABLE_RIEGELI
|
|
ASSIGN_OR_RETURN_IF_NOT_OK(std::unique_ptr<riegeli::Reader> new_reader,
|
|
CreateRiegeliFileReader(path));
|
|
riegeli_reader_.Reset(std::move(new_reader));
|
|
if (ABSL_PREDICT_TRUE(riegeli_reader_.CheckFileFormat())) {
|
|
// File could be opened and is in the Riegeli format.
|
|
return absl::OkStatus();
|
|
}
|
|
if (ABSL_PREDICT_FALSE(!riegeli_reader_.src()->ok())) {
|
|
// File could not be opened.
|
|
return riegeli_reader_.src()->status();
|
|
}
|
|
// File could be opened but is not in the Riegeli format.
|
|
riegeli_reader_.Reset(riegeli::kClosed);
|
|
#endif // CENTIPEDE_DISABLE_RIEGELI
|
|
|
|
// Because we fall back to a legacy reader, we can't distinguish between
|
|
// an empty blob file and a file that is not a blob file. Once this behavior
|
|
// changes (e.g., we get rid of the legacy reader), consider b/349115475 and
|
|
// track down places that would benefit from this distinction.
|
|
legacy_reader_ = std::make_unique<SimpleBlobFileReader>();
|
|
if (absl::Status s = legacy_reader_->Open(path); !s.ok()) {
|
|
legacy_reader_ = nullptr;
|
|
return s;
|
|
}
|
|
return absl::OkStatus();
|
|
}
|
|
|
|
absl::Status Read(ByteSpan &blob) override {
|
|
#ifdef CENTIPEDE_DISABLE_RIEGELI
|
|
if (legacy_reader_)
|
|
return legacy_reader_->Read(blob);
|
|
else
|
|
return absl::FailedPreconditionError("no reader open");
|
|
#else
|
|
if (ABSL_PREDICT_FALSE(legacy_reader_)) return legacy_reader_->Read(blob);
|
|
|
|
absl::string_view record;
|
|
if (!riegeli_reader_.ReadRecord(record)) {
|
|
if (riegeli_reader_.ok())
|
|
return absl::OutOfRangeError("no more blobs");
|
|
else
|
|
return riegeli_reader_.status();
|
|
}
|
|
blob = AsByteSpan(record);
|
|
return absl::OkStatus();
|
|
#endif // CENTIPEDE_DISABLE_RIEGELI
|
|
}
|
|
|
|
absl::Status Close() override {
|
|
#ifdef CENTIPEDE_DISABLE_RIEGELI
|
|
legacy_reader_ = nullptr;
|
|
return absl::OkStatus();
|
|
#else
|
|
if (ABSL_PREDICT_FALSE(legacy_reader_)) {
|
|
legacy_reader_ = nullptr;
|
|
return absl::OkStatus();
|
|
}
|
|
|
|
// `riegeli_reader_` not being ok will result in `Close()` failing, but its
|
|
// non-ok status stems from a previously failed operation in an `Open()` or
|
|
// `Read()` call whose errors were already propagated there - these are
|
|
// therefore filtered out here.
|
|
// `Close()` failing on an ok reader is due to the file being in an invalid
|
|
// state that primarily arises from an incomplete concurrent write (which
|
|
// can happen even with every write being flushed - see comment in
|
|
// `RiegeliWriter::Write()`) - these are therefore logged but not propagated
|
|
// as failures.
|
|
// TODO(b/313706444): Reconsider error handling after experiments.
|
|
// TODO(b/310701588): Try adding a test for this.
|
|
if (riegeli_reader_.ok() && !riegeli_reader_.Close()) {
|
|
LOG(WARNING) << "Ignoring errors while closing Riegeli file: "
|
|
<< riegeli_reader_.status();
|
|
}
|
|
// Any non-ok status of `riegeli_reader_` persists for subsequent
|
|
// operations; therefore, re-initialize it to a closed ok state.
|
|
riegeli_reader_.Reset(riegeli::kClosed);
|
|
return absl::OkStatus();
|
|
#endif // CENTIPEDE_DISABLE_RIEGELI
|
|
}
|
|
|
|
private:
|
|
std::unique_ptr<SimpleBlobFileReader> legacy_reader_ = nullptr;
|
|
#ifndef CENTIPEDE_DISABLE_RIEGELI
|
|
riegeli::RecordReader<std::unique_ptr<riegeli::Reader>> riegeli_reader_{
|
|
riegeli::kClosed};
|
|
#endif // CENTIPEDE_DISABLE_RIEGELI
|
|
};
|
|
|
|
#ifndef CENTIPEDE_DISABLE_RIEGELI
|
|
// Implementation of `BlobFileWriter` using Riegeli
|
|
// (https://github.com/google/riegeli).
|
|
class RiegeliWriter : public BlobFileWriter {
|
|
public:
|
|
~RiegeliWriter() override {
|
|
// Virtual resolution is off in dtors, so use a specific Close().
|
|
CHECK_OK(RiegeliWriter::Close());
|
|
}
|
|
|
|
absl::Status Open(std::string_view path, std::string_view mode) override {
|
|
CHECK(mode == "w" || mode == "a") << VV(mode);
|
|
if (absl::Status s = Close(); !s.ok()) return s;
|
|
const auto kWriterOpts =
|
|
riegeli::RecordWriterBase::Options{}.set_chunk_size(kMaxBufferedBytes);
|
|
ASSIGN_OR_RETURN_IF_NOT_OK(std::unique_ptr<riegeli::Writer> new_writer,
|
|
CreateRiegeliFileWriter(path, mode == "a"));
|
|
writer_.Reset(std::move(new_writer), kWriterOpts);
|
|
RETURN_IF_NOT_OK(writer_.status());
|
|
path_ = path;
|
|
opened_at_ = absl::Now();
|
|
flushed_at_ = absl::Now();
|
|
written_blobs_ = 0;
|
|
written_bytes_ = 0;
|
|
buffered_blobs_ = 0;
|
|
buffered_bytes_ = 0;
|
|
return absl::OkStatus();
|
|
}
|
|
|
|
absl::Status Write(ByteSpan blob) override {
|
|
std::string_view blob_view = AsStringView(blob);
|
|
const auto now = absl::Now();
|
|
if (!PreWriteFlush(blob.size())) return writer_.status();
|
|
if (!writer_.WriteRecord(
|
|
absl::string_view{blob_view.data(), blob_view.size()})) {
|
|
return writer_.status();
|
|
}
|
|
if (!PostWriteFlush(blob.size())) return writer_.status();
|
|
write_duration_ += absl::Now() - now;
|
|
if (written_blobs_ + buffered_blobs_ % 10000 == 0)
|
|
VLOG(10) << "Current stats: " << StatsString();
|
|
return absl::OkStatus();
|
|
}
|
|
|
|
absl::Status Close() override {
|
|
// Writer already being in a bad state will result in close failure but
|
|
// those errors have already been reported.
|
|
if (!writer_.ok()) {
|
|
writer_.Reset(riegeli::kClosed);
|
|
return absl::OkStatus();
|
|
}
|
|
if (!writer_.Close()) return writer_.status();
|
|
flushed_at_ = absl::Now();
|
|
written_blobs_ += buffered_blobs_;
|
|
written_bytes_ += buffered_bytes_;
|
|
buffered_blobs_ = 0;
|
|
buffered_bytes_ = 0;
|
|
VLOG(1) << "Final stats: " << StatsString();
|
|
return absl::OkStatus();
|
|
}
|
|
|
|
private:
|
|
static constexpr uint64_t kMB = 1024UL * 1024UL;
|
|
// Buffering/flushing control settings. The defaults were chosen based on
|
|
// experimental runs and intuition with the idea to balance good buffering
|
|
// performance and a steady stream of blobs being committed to the file, so
|
|
// external readers see updates frequently enough.
|
|
// TODO(ussuri): Once Riegeli is the sole blob writer, maybe expose these
|
|
// as Centipede flags and plumb them through `DefaultBlobFileWriterFactory()`.
|
|
static constexpr uint64_t kMaxBufferedBlobs = 10000;
|
|
// Riegeli's default is 1 MB.
|
|
static constexpr uint64_t kMaxBufferedBytes = 100 * kMB;
|
|
static constexpr absl::Duration kMaxFlushInterval = absl::Minutes(1);
|
|
// For each record, Riegeli also writes its offset in the stream to the file.
|
|
static constexpr size_t kRiegeliPerRecordMetadataSize = sizeof(uint64_t);
|
|
|
|
// Riegeli's automatic flushing occurs when it accumulates over
|
|
// `Options::chunk_size()` of data, not on record boundaries. Our outputs
|
|
// are continuously consumed by external readers, so we can't tolerate
|
|
// partially written records at the end of a file. Therefore, we explicitly
|
|
// flush when we're just about to cross the chunk size boundary, or if the
|
|
// client writes infrequently, or if the size of records is small relative
|
|
// to the chunk size. The latter two cases are to make the data visible to
|
|
// readers earlier; however, note that the compression performance may
|
|
// suffer.
|
|
bool PreWriteFlush(size_t blob_size) {
|
|
const auto record_size = blob_size + kRiegeliPerRecordMetadataSize;
|
|
const std::string_view flush_reason =
|
|
(buffered_blobs_ > kMaxBufferedBlobs) ? "blobs"
|
|
: (buffered_bytes_ + record_size > kMaxBufferedBytes) ? "bytes"
|
|
: (absl::Now() - flushed_at_ > kMaxFlushInterval) ? "time"
|
|
: "";
|
|
if (!flush_reason.empty()) {
|
|
VLOG(20) << "Flushing b/c " << flush_reason << ": " << StatsString();
|
|
if (!writer_.Flush(riegeli::FlushType::kFromMachine)) return false;
|
|
flushed_at_ = absl::Now();
|
|
written_blobs_ += buffered_blobs_;
|
|
written_bytes_ += buffered_bytes_;
|
|
buffered_blobs_ = 0;
|
|
buffered_bytes_ = 0;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// In the rare case where the current blob itself exceeds the chunk size,
|
|
// `Write()` will auto-flush a portion of it to the file, but the remainder
|
|
// will remain in the buffer, so we need to force-flush it to maintain file
|
|
// completeness.
|
|
bool PostWriteFlush(size_t blob_size) {
|
|
const auto record_size = blob_size + kRiegeliPerRecordMetadataSize;
|
|
if (record_size >= kMaxBufferedBytes) {
|
|
VLOG(20) << "Post-write flushing b/c blob size: " << StatsString();
|
|
if (!writer_.Flush(riegeli::FlushType::kFromMachine)) return false;
|
|
flushed_at_ = absl::Now();
|
|
written_blobs_ += 1;
|
|
written_bytes_ += record_size;
|
|
buffered_blobs_ = 0;
|
|
buffered_bytes_ = 0;
|
|
} else {
|
|
buffered_blobs_ += 1;
|
|
buffered_bytes_ += record_size;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// Returns a debug string with the effective writing rate for the current file
|
|
// path. The effective rate is measured as a ratio of the total bytes passed
|
|
// to `Write()` and the elapsed time from the file opening till now.
|
|
std::string StatsString() const {
|
|
const auto opened_secs = absl::ToDoubleSeconds(absl::Now() - opened_at_);
|
|
const auto write_secs = absl::ToDoubleSeconds(write_duration_);
|
|
const auto total_bytes = written_bytes_ + buffered_bytes_;
|
|
const auto throughput =
|
|
write_secs > 0.0 ? (1.0 * total_bytes / write_secs) : 0;
|
|
const auto file_size = writer_.EstimatedSize();
|
|
const auto compression =
|
|
file_size > 0 ? (1.0 * written_bytes_ / file_size) : 0;
|
|
std::string stats = absl::StrFormat(
|
|
"written/buffered blobs: %llu/%llu, written/buffered bytes: %llu/%llu, "
|
|
"opened: %f sec, writing: %f sec, throughput: %.0f B/sec, "
|
|
"file size: %llu, compression: %.1f, path: %s",
|
|
written_blobs_, buffered_blobs_, written_bytes_, buffered_bytes_,
|
|
opened_secs, write_secs, throughput, file_size, compression, path_);
|
|
return stats;
|
|
}
|
|
|
|
// The underlying Riegeli writer.
|
|
riegeli::RecordWriter<std::unique_ptr<riegeli::Writer>> writer_{
|
|
riegeli::kClosed};
|
|
|
|
// Buffering/flushing control.
|
|
absl::Time flushed_at_ = absl::InfiniteFuture();
|
|
uint64_t buffered_blobs_ = 0;
|
|
uint64_t buffered_bytes_ = 0;
|
|
|
|
// Telemetry.
|
|
std::string path_;
|
|
absl::Time opened_at_ = absl::InfiniteFuture();
|
|
absl::Duration write_duration_ = absl::ZeroDuration();
|
|
uint64_t written_blobs_ = 0;
|
|
uint64_t written_bytes_ = 0;
|
|
};
|
|
#endif // CENTIPEDE_DISABLE_RIEGELI
|
|
|
|
} // namespace
|
|
|
|
// Pack 'data' such that it can be appended to a file and later extracted:
|
|
// * kPackBegMagic
|
|
// * hash(data)
|
|
// * data.size() (8 bytes)
|
|
// * data itself
|
|
// * kPackEndMagic
|
|
// Storing the magics and the hash is a precaution against partial writes.
|
|
// UnpackBytesFromAppendFile looks for the kPackBegMagic and so
|
|
// it will ignore any partially-written data.
|
|
//
|
|
// This is simple and efficient, but I wonder if there is a ready-to-use
|
|
// standard open-source alternative. Or should we just use tar?
|
|
ByteArray PackBytesForAppendFile(ByteSpan blob) {
|
|
ByteArray res;
|
|
auto hash = Hash(blob);
|
|
CHECK_EQ(hash.size(), kHashLen);
|
|
size_t size = blob.size();
|
|
uint8_t size_bytes[sizeof(size)];
|
|
std::memcpy(size_bytes, &size, sizeof(size));
|
|
res.insert(res.end(), std::begin(kPackBegMagic),
|
|
std::begin(kPackBegMagic) + kMagicLen);
|
|
res.insert(res.end(), hash.begin(), hash.end());
|
|
res.insert(res.end(), std::begin(size_bytes), std::end(size_bytes));
|
|
res.insert(res.end(), blob.begin(), blob.end());
|
|
res.insert(res.end(), std::begin(kPackEndMagic),
|
|
std::begin(kPackEndMagic) + kMagicLen);
|
|
return res;
|
|
}
|
|
|
|
// Reverse to a sequence of PackBytesForAppendFile() appended to each other.
|
|
void UnpackBytesFromAppendFile(const ByteArray &packed_data,
|
|
std::vector<ByteArray> *absl_nullable unpacked,
|
|
std::vector<std::string> *absl_nullable hashes) {
|
|
auto pos = packed_data.cbegin();
|
|
while (true) {
|
|
pos = std::search(pos, packed_data.end(), &kPackBegMagic[0],
|
|
&kPackBegMagic[kMagicLen]);
|
|
if (pos == packed_data.end()) return;
|
|
pos += kMagicLen;
|
|
if (packed_data.end() - pos < kHashLen) return;
|
|
std::string hash(pos, pos + kHashLen);
|
|
pos += kHashLen;
|
|
size_t size = 0;
|
|
if (packed_data.end() - pos < sizeof(size)) return;
|
|
std::memcpy(&size, &*pos, sizeof(size));
|
|
pos += sizeof(size);
|
|
if (packed_data.end() - pos < size) return;
|
|
ByteArray ba(pos, pos + size);
|
|
pos += size;
|
|
if (packed_data.end() - pos < kMagicLen) return;
|
|
if (std::memcmp(&*pos, kPackEndMagic, kMagicLen) != 0) continue;
|
|
pos += kMagicLen;
|
|
if (hash != Hash(ba)) continue;
|
|
if (unpacked) unpacked->push_back(std::move(ba));
|
|
if (hashes) hashes->push_back(std::move(hash));
|
|
}
|
|
}
|
|
|
|
std::unique_ptr<BlobFileReader> DefaultBlobFileReaderFactory() {
|
|
return std::make_unique<DefaultBlobFileReader>();
|
|
}
|
|
|
|
std::unique_ptr<BlobFileWriter> DefaultBlobFileWriterFactory(bool riegeli) {
|
|
if (riegeli)
|
|
#ifdef CENTIPEDE_DISABLE_RIEGELI
|
|
LOG(FATAL) << "Riegeli unavailable: built with --use_riegeli set to false.";
|
|
#else
|
|
return std::make_unique<RiegeliWriter>();
|
|
#endif // CENTIPEDE_DISABLE_RIEGELI
|
|
else
|
|
return std::make_unique<SimpleBlobFileWriter>();
|
|
}
|
|
|
|
} // namespace fuzztest::internal
|
|
|