blob: 200304a132ff84b604de043b1534883b6a10b120 [file] [log] [blame]
// Copyright 2018 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <inttypes.h>
#include <limits.h>
#include <stdio.h>
#include <string.h>
#include <fbl/auto_call.h>
#include <fbl/macros.h>
#include <fbl/string.h>
#include <fbl/string_buffer.h>
#include <fbl/string_piece.h>
#include <fbl/string_printf.h>
#include <fuzz-utils/fuzzer.h>
#include <fuzz-utils/path.h>
#include <fuzz-utils/string-list.h>
#include <lib/fdio/spawn.h>
#include <lib/zx/process.h>
#include <lib/zx/time.h>
#include <task-utils/walker.h>
#include <zircon/errors.h>
#include <zircon/status.h>
#include <zircon/syscalls/object.h>
#include <zircon/types.h>
namespace fuzzing {
namespace {
// List of supported subcommands
enum Command : uint32_t {
kNone,
kHelp,
kList,
kSeeds,
kStart,
kCheck,
kStop,
kRepro,
kMerge,
};
// Usage information for specific tool subcommands. Keep in sync with //scripts/devshell/fuzz!
const struct {
Command cmd;
const char* name;
const char* args;
const char* desc;
} kCommands[] = {
{kHelp, "help", "", "Prints this message and exits."},
{kList, "list", "[name]", "Lists fuzzers matching 'name' if provided, or all\nfuzzers."},
{kSeeds, "seeds", "<name>", "Lists the seed corpus location(s) for the fuzzer."},
{kStart, "start", "<name> [...]",
"Starts the named fuzzer. Additional arguments are\npassed to the fuzzer."},
{kCheck, "check", "<name>",
"Reports information about the named fuzzer, such as\nexecution status, corpus size, and "
"number of\nartifacts."},
{kStop, "stop", "<name>", "Stops all instances of the named fuzzer."},
{kRepro, "repro", "<name> [...]",
"Runs the named fuzzer on specific inputs. If no\nadditional inputs are provided, uses "
"previously\nfound artifacts."},
{kMerge, "merge", "<name> [...]",
"Merges the corpus for the named fuzzer. If no\nadditional inputs are provided, minimizes "
"the\ncurrent corpus."},
};
// |kArtifactPrefixes| should matches the prefixes in libFuzzer passed to |Fuzzer::DumpCurrentUnit|
// or |Fuzzer::WriteUnitToFileWithPrefix|.
constexpr const char* kArtifactPrefixes[] = {
"crash", "leak", "mismatch", "oom", "slow-unit", "timeout",
};
constexpr size_t kArtifactPrefixesLen = sizeof(kArtifactPrefixes) / sizeof(kArtifactPrefixes[0]);
} // namespace
// Public methods
Fuzzer::~Fuzzer() {}
zx_status_t Fuzzer::Main(int argc, char** argv) {
Fuzzer fuzzer;
StringList args(argv + 1, argc - 1);
return fuzzer.Run(&args);
}
// Protected methods
Fuzzer::Fuzzer() : cmd_(kNone), out_(stdout), err_(stderr) {}
void Fuzzer::Reset() {
cmd_ = kNone;
name_.clear();
target_.clear();
root_.clear();
resource_path_.Reset();
data_path_.Reset();
inputs_.clear();
options_.clear();
process_.reset();
out_ = stdout;
err_ = stderr;
}
zx_status_t Fuzzer::Run(StringList* args) {
ZX_DEBUG_ASSERT(args);
zx_status_t rc;
if ((rc = SetCommand(args->first())) != ZX_OK || (rc = SetFuzzer(args->next())) != ZX_OK ||
(rc = LoadOptions()) != ZX_OK) {
return rc;
}
const char* arg;
while ((arg = args->next())) {
if (*arg != '-') {
inputs_.push_back(arg);
} else if ((rc = SetOption(arg + 1)) != ZX_OK) {
return rc;
}
}
switch (cmd_) {
case kHelp:
return Help();
case kList:
return List();
case kSeeds:
return Seeds();
case kStart:
return Start();
case kCheck:
return Check();
case kStop:
return Stop();
case kRepro:
return Repro();
case kMerge:
return Merge();
default:
// Shouldn't get here.
ZX_DEBUG_ASSERT(false);
return ZX_ERR_INTERNAL;
}
}
zx_status_t Fuzzer::SetOption(const fbl::String& option) {
const char* ptr = option.c_str();
while (*ptr && *ptr != '#' && (*ptr == '-' || isspace(*ptr))) {
++ptr;
}
const char* mark = ptr;
while (*ptr && *ptr != '#' && *ptr != '=' && !isspace(*ptr)) {
++ptr;
}
fbl::String key(mark, ptr - mark);
while (*ptr && *ptr != '#' && (*ptr == '=' || isspace(*ptr))) {
++ptr;
}
mark = ptr;
while (*ptr && *ptr != '#' && !isspace(*ptr)) {
++ptr;
}
fbl::String val(mark, ptr - mark);
return SetOption(key, val);
}
zx_status_t Fuzzer::SetOption(const fbl::String& key, const fbl::String& value) {
// Ignore blank options
if (key.empty() && value.empty()) {
return ZX_OK;
}
// Must have both key and value
if (key.empty() || value.empty()) {
fprintf(err_, "Empty key or value: '%s'='%s'\n", key.c_str(), value.c_str());
return ZX_ERR_INVALID_ARGS;
}
// Save the option
options_.set(key, value);
return ZX_OK;
}
zx_status_t Fuzzer::RebasePath(const fbl::String& path, Path* out) {
zx_status_t rc;
out->Reset();
if (!root_.empty() && (rc = out->Push(root_)) != ZX_OK) {
fprintf(err_, "failed to move to '%s': %s\n", root_.c_str(), zx_status_get_string(rc));
return rc;
}
if ((rc = out->Push(path)) != ZX_OK) {
return rc;
}
return ZX_OK;
}
zx_status_t Fuzzer::GetPackagePath(const fbl::String& package, Path* out) {
zx_status_t rc;
if ((rc = RebasePath("pkgfs/packages", out)) != ZX_OK) {
return rc;
}
auto pop_prefix = fbl::MakeAutoCall([&out]() { out->Pop(); });
if ((rc = out->Push(package)) != ZX_OK) {
fprintf(err_, "failed to move to '%s': %s\n", package.c_str(), zx_status_get_string(rc));
return rc;
}
auto pop_package = fbl::MakeAutoCall([&out]() { out->Pop(); });
auto versions = out->List();
long int max = -1;
const char* max_version = nullptr;
for (const char* version = versions->first(); version; version = versions->next()) {
if (version[0] == '\0') {
continue;
}
char* endptr = nullptr;
long int val = strtol(version, &endptr, 10);
if (endptr[0] != '\0') {
continue;
}
if (val > max) {
max = val;
max_version = version;
}
}
if (!max_version) {
fprintf(err_, "No versions available for package: %s\n", package.c_str());
return ZX_ERR_NOT_FOUND;
}
if ((rc = out->Push(max_version)) != ZX_OK) {
fprintf(err_, "failed to move to '%s': %s\n", max_version, zx_status_get_string(rc));
return rc;
}
pop_package.cancel();
pop_prefix.cancel();
return ZX_OK;
}
void Fuzzer::FindZirconFuzzers(const fbl::String& zircon_path, const fbl::String& target,
StringMap* out) {
Path path;
if (RebasePath(zircon_path, &path) != ZX_OK) {
return;
}
auto targets = path.List();
for (const char* t = targets->first(); t; t = targets->next()) {
}
targets->keep_if(target);
for (const char* t = targets->first(); t; t = targets->next()) {
out->set(fbl::StringPrintf("zircon_fuzzers/%s", t), path.Join(t));
}
}
void Fuzzer::FindFuchsiaFuzzers(const fbl::String& package, const fbl::String& target,
StringMap* out) {
Path path;
if (RebasePath("pkgfs/packages", &path) != ZX_OK) {
return;
}
auto packages = path.List();
packages->keep_if(package);
for (const char* p = packages->first(); p; p = packages->next()) {
if (GetPackagePath(p, &path) != ZX_OK || path.Push("data") != ZX_OK) {
continue;
}
auto targets = path.List();
targets->keep_if(target);
path.Pop();
for (const char* t = targets->first(); t; t = targets->next()) {
if (path.IsFile(fbl::StringPrintf("data/%s/corpora", t)) &&
path.IsFile(fbl::StringPrintf("data/%s/dictionary", t)) &&
path.IsFile(fbl::StringPrintf("data/%s/options", t)) &&
path.IsFile(fbl::StringPrintf("meta/%s.cmx", t))) {
out->set(fbl::StringPrintf("%s/%s", p, t),
fbl::StringPrintf("fuchsia-pkg://fuchsia.com/%s#meta/%s.cmx", p, t));
}
}
}
}
void Fuzzer::FindFuzzers(const fbl::String& package, const fbl::String& target, StringMap* out) {
if (strstr("zircon_fuzzers", package.c_str()) != nullptr) {
FindZirconFuzzers("boot/test/fuzz", target, out);
FindZirconFuzzers("system/test/fuzz", target, out);
}
FindFuchsiaFuzzers(package, target, out);
}
static zx_status_t ParseName(const fbl::String& name, fbl::String* out_package,
fbl::String* out_target) {
const char* ptr = name.c_str();
const char* sep = strchr(ptr, '/');
if (!sep) {
return ZX_ERR_NOT_FOUND;
}
out_package->Set(ptr, sep - ptr);
out_target->Set(sep + 1);
return ZX_OK;
}
void Fuzzer::FindFuzzers(const fbl::String& name, StringMap* out) {
ZX_DEBUG_ASSERT(out);
// Scan the system for available fuzzers
out->clear();
fbl::String package, target;
if (ParseName(name, &package, &target) == ZX_OK) {
FindFuzzers(package, target, out);
} else if (name.length() != 0) {
FindFuzzers(name, "", out);
FindFuzzers("", name, out);
} else {
FindFuzzers("", "", out);
}
}
void Fuzzer::GetArgs(StringList* out) {
out->clear();
if (strstr(target_.c_str(), "fuchsia-pkg://fuchsia.com/") == target_.c_str()) {
out->push_back("/bin/run");
}
out->push_back(target_);
const char* key;
const char* val;
options_.begin();
while (options_.next(&key, &val)) {
out->push_back(fbl::StringPrintf("-%s=%s", key, val));
}
for (const char* input = inputs_.first(); input; input = inputs_.next()) {
out->push_back(input);
}
}
zx_status_t Fuzzer::Execute() {
zx_status_t rc;
// If the "-jobs=N" option is set, output will be sent to fuzz-<job>.log and can run to
// completion in the background.
const char* jobs = options_.get("jobs");
bool background = jobs && atoi(jobs) != 0;
// Copy all command line arguments.
StringList args;
GetArgs(&args);
size_t argc = args.length();
const char* argv[argc + 1];
argv[0] = args.first();
fprintf(out_, "+ %s", argv[0]);
for (size_t i = 1; i < argc; ++i) {
argv[i] = args.next();
fprintf(out_, " %s", argv[i]);
}
argv[argc] = nullptr;
fprintf(out_, "\n");
// This works even in a component, since FDIO_SPAWN_CLONE_ALL clones the namespace and argv[0]
// in the correct namespace name, /pkg/bin/<binary>.
if ((rc = fdio_spawn(ZX_HANDLE_INVALID, FDIO_SPAWN_CLONE_ALL, argv[0], argv,
process_.reset_and_get_address())) != ZX_OK) {
Path path;
if (strcmp(argv[0], "/bin/run") != 0) {
fprintf(err_, "Failed to spawn '%s': %s\n", argv[0], zx_status_get_string(rc));
} else if (GetPackagePath("run", &path) != ZX_OK) {
fprintf(err_, "Required package 'run' is missing.\n");
} else if (argc > 1) {
fprintf(err_, "Failed to spawn '%s': %s\n", argv[1], zx_status_get_string(rc));
} else {
fprintf(err_, "Malformed command line: '%s'\n", argv[0]);
}
return rc;
}
if (background) {
return ZX_OK;
}
if ((rc = process_.wait_one(ZX_TASK_TERMINATED, zx::time::infinite(), nullptr)) != ZX_OK) {
fprintf(err_, "Failed while waiting for process to end: %s\n", zx_status_get_string(rc));
return rc;
}
zx_info_process_t proc_info;
if ((rc = process_.get_info(ZX_INFO_PROCESS, &proc_info, sizeof(proc_info), nullptr,
nullptr)) != ZX_OK) {
fprintf(err_, "Failed to get exit code for process: %s\n", zx_status_get_string(rc));
return rc;
}
if (proc_info.return_code != ZX_OK) {
fprintf(out_, "Fuzzer returned non-zero exit code: %" PRId64 "\n", proc_info.return_code);
}
return ZX_OK;
}
// |fuzzing::Walker| is a |TaskEnumerator| used to find a given fuzzer |executable| and print status
// or end it.
class Walker final : public TaskEnumerator {
public:
explicit Walker(const Fuzzer* fuzzer, bool kill) : fuzzer_(fuzzer), kill_(kill), killed_(0) {}
~Walker() {}
size_t killed() const { return killed_; }
zx_status_t OnProcess(int depth, zx_handle_t task, zx_koid_t koid, zx_koid_t pkoid) override {
if (!fuzzer_->CheckProcess(task, kill_)) {
return ZX_OK;
}
if (kill_) {
++killed_;
return ZX_OK;
}
return ZX_ERR_STOP;
}
protected:
bool has_on_process() const override { return true; }
private:
const Fuzzer* fuzzer_;
bool kill_;
size_t killed_;
};
bool Fuzzer::CheckProcess(zx_handle_t task, bool kill) const {
char name[ZX_MAX_NAME_LEN];
if (zx_object_get_property(task, ZX_PROP_NAME, name, sizeof(name)) != ZX_OK) {
return false;
}
const char* target = target_.c_str();
const char* meta = strstr(target, "#meta/");
if (meta) {
target = meta + strlen("#meta/");
}
if (strncmp(name, target, sizeof(name) - 1) != 0) {
return false;
}
if (kill) {
zx_task_kill(task);
return true;
}
zx_info_process_t info;
if (zx_object_get_info(task, ZX_INFO_PROCESS, &info, sizeof(info), nullptr, nullptr) != ZX_OK) {
return false;
}
if (!info.started) {
fprintf(out_, "%s: NOT STARTED\n", name_.c_str());
} else if (!info.exited) {
fprintf(out_, "%s: RUNNING\n", name_.c_str());
} else {
fprintf(out_, "%s: EXITED (return code = %" PRId64 ")\n", name_.c_str(), info.return_code);
}
return true;
}
// Private methods
zx_status_t Fuzzer::SetCommand(const char* command) {
cmd_ = kNone;
options_.clear();
inputs_.clear();
if (!command) {
fprintf(err_, "Missing command. Try 'help'.\n");
return ZX_ERR_INVALID_ARGS;
}
for (size_t i = 0; i < sizeof(kCommands) / sizeof(kCommands[0]); ++i) {
if (strcmp(command, kCommands[i].name) == 0) {
cmd_ = kCommands[i].cmd;
break;
}
}
if (cmd_ == kNone) {
fprintf(err_, "Unknown command '%s'. Try 'help'.\n", command);
return ZX_ERR_INVALID_ARGS;
}
return ZX_OK;
}
zx_status_t Fuzzer::SetFuzzer(const char* name) {
zx_status_t rc;
// Early exit for commands that don't need a single, selected fuzzer
switch (cmd_) {
case kHelp:
case kList:
if (name) {
name_.Set(name);
}
return ZX_OK;
default:
break;
}
if (!name) {
fprintf(err_, "Missing fuzzer name.\n");
return ZX_ERR_INVALID_ARGS;
}
name_.Set(name);
// Determine the fuzzer
StringMap fuzzers;
FindFuzzers(name_, &fuzzers);
switch (fuzzers.size()) {
case 0:
fprintf(err_, "No matching fuzzers for '%s'.\n", name);
return ZX_ERR_NOT_FOUND;
case 1:
break;
default:
fprintf(err_, "Multiple matching fuzzers for '%s':\n", name);
List();
return ZX_ERR_INVALID_ARGS;
}
fuzzers.begin();
fuzzers.next(&name_, &target_);
fbl::String package, target;
if ((rc = ParseName(name_, &package, &target)) != ZX_OK) {
return rc;
}
// Determine the directory that holds the fuzzing resources. It may not be present if fuzzing
// Zircon standalone.
if ((rc = GetPackagePath(package, &resource_path_)) != ZX_OK ||
(rc = resource_path_.Push("data")) != ZX_OK ||
(rc = resource_path_.Push(target)) != ZX_OK) {
// No-op: The directory may not be present when fuzzing standalone Zircon.
resource_path_.Reset();
}
// Ensure the directories that will hold the fuzzing outputs are present.
if ((rc = RebasePath("data", &data_path_)) != ZX_OK ||
(rc = data_path_.Ensure("fuzzing")) != ZX_OK ||
(rc = data_path_.Push("fuzzing")) != ZX_OK || (rc = data_path_.Ensure(package)) != ZX_OK ||
(rc = data_path_.Push(package)) != ZX_OK || (rc = data_path_.Ensure(target)) != ZX_OK ||
(rc = data_path_.Push(target)) != ZX_OK || (rc = data_path_.Ensure("corpus")) != ZX_OK) {
fprintf(err_, "Failed to establish data path for '%s/%s': %s\n", package.c_str(),
target.c_str(), zx_status_get_string(rc));
return ZX_ERR_IO;
}
return ZX_OK;
}
zx_status_t Fuzzer::LoadOptions() {
zx_status_t rc;
switch (cmd_) {
case kHelp:
case kList:
case kSeeds:
// No options needed
return ZX_OK;
case kStart:
if ((rc = SetOption("jobs", "1")) != ZX_OK) {
return rc;
}
break;
case kMerge:
if ((rc = SetOption("merge", "1")) != ZX_OK ||
(rc = SetOption("merge_control_file", data_path_.Join(".mergefile"))) != ZX_OK) {
return rc;
}
break;
default:
break;
}
// Artifacts go in the data directory
if ((rc = SetOption("artifact_prefix", data_path_.c_str())) != ZX_OK) {
return rc;
}
// Early exit if no resources
if (resource_path_.length() <= 1) {
return ZX_OK;
}
// Record the (optional) dictionary
size_t dict_size;
if ((rc = resource_path_.GetSize("dictionary", &dict_size)) == ZX_OK && dict_size != 0 &&
(rc = SetOption("dict", resource_path_.Join("dictionary"))) != ZX_OK) {
fprintf(err_, "failed to set dictionary option: %s\n", zx_status_get_string(rc));
return rc;
}
// Read the (optional) options file
fbl::String options = resource_path_.Join("options");
FILE* f = fopen(options.c_str(), "r");
if (f) {
auto close_f = fbl::MakeAutoCall([&f]() { fclose(f); });
char buffer[PATH_MAX];
while (fgets(buffer, sizeof(buffer), f)) {
if ((rc = SetOption(buffer)) != ZX_OK) {
fprintf(err_, "Failed to set option: %s", zx_status_get_string(rc));
return rc;
}
}
}
return ZX_OK;
}
// Specific subcommands
zx_status_t Fuzzer::Help() {
fprintf(out_, "Run a fuzz test\n");
fprintf(out_, "\n");
fprintf(out_, "Usage: fuzz <command> [command-arguments]\n");
fprintf(out_, "\n");
fprintf(out_, "Commands:\n");
fprintf(out_, "\n");
for (size_t i = 0; i < sizeof(kCommands) / sizeof(kCommands[0]); ++i) {
fprintf(out_, " %-7s %-15s ", kCommands[i].name, kCommands[i].args);
const char* desc = kCommands[i].desc;
const char* fmt = "%s\n";
for (const char* nl = strchr(desc, '\n'); nl; nl = strchr(desc, '\n')) {
fbl::String line(desc, nl - desc);
fprintf(out_, fmt, line.c_str());
desc = nl + 1;
fmt = " %s\n";
}
fprintf(out_, fmt, desc);
}
return ZX_OK;
}
zx_status_t Fuzzer::List() {
StringMap fuzzers;
FindFuzzers(name_, &fuzzers);
if (fuzzers.is_empty()) {
fprintf(out_, "No matching fuzzers.\n");
return ZX_OK;
}
fprintf(out_, "Found %zu matching fuzzers:\n", fuzzers.size());
const char* name;
fuzzers.begin();
while (fuzzers.next(&name, nullptr)) {
fprintf(out_, " %s\n", name);
}
return ZX_OK;
}
zx_status_t Fuzzer::Seeds() {
if (resource_path_.length() <= 1) {
fprintf(out_, "No seed corpora found for %s.\n", name_.c_str());
return ZX_OK;
}
fbl::String corpora = resource_path_.Join("corpora");
FILE* f = fopen(corpora.c_str(), "r");
if (!f) {
fprintf(out_, "No seed corpora found for %s.\n", name_.c_str());
return ZX_OK;
}
auto close_f = fbl::MakeAutoCall([&f]() { fclose(f); });
char buffer[PATH_MAX];
while (fgets(buffer, sizeof(buffer), f)) {
fprintf(out_, "%s\n", buffer);
}
return ZX_OK;
}
zx_status_t Fuzzer::Start() {
zx_status_t rc;
// If no inputs, use the default corpus
if (inputs_.is_empty()) {
if ((rc = data_path_.Ensure("corpus")) != ZX_OK) {
fprintf(err_, "Failed to make empty corpus: %s\n", zx_status_get_string(rc));
return rc;
}
inputs_.push_front(data_path_.Join("corpus"));
}
return Execute();
}
zx_status_t Fuzzer::Check() {
// Report fuzzer execution status
Walker walker(this, false /* !kill */);
if (walker.WalkRootJobTree() != ZX_ERR_STOP) {
fprintf(out_, "%s: STOPPED\n", name_.c_str());
}
// Fuzzer details
fprintf(out_, " Target info: %s\n", target_.c_str());
fprintf(out_, " Output path: %s\n", data_path_.c_str());
// Report corpus details, if present
if (data_path_.Push("corpus") != ZX_OK) {
fprintf(out_, " Corpus size: 0 inputs / 0 bytes\n");
} else {
auto corpus = data_path_.List();
size_t corpus_len = 0;
size_t corpus_size = 0;
for (const char* input = corpus->first(); input; input = corpus->next()) {
size_t input_size;
if (data_path_.GetSize(input, &input_size) == ZX_OK) {
++corpus_len;
corpus_size += input_size;
}
}
fprintf(out_, " Corpus size: %zu inputs / %zu bytes\n", corpus_len, corpus_size);
data_path_.Pop();
}
// Report number of artifacts.
auto artifacts = data_path_.List();
StringList prefixes(kArtifactPrefixes,
sizeof(kArtifactPrefixes) / sizeof(kArtifactPrefixes[0]));
artifacts->keep_if_any(&prefixes);
size_t num_artifacts = artifacts->length();
if (num_artifacts == 0) {
fprintf(out_, " Artifacts: None\n");
} else {
const char* artifact = artifacts->first();
fprintf(out_, " Artifacts: %s\n", artifact);
while ((artifact = artifacts->next())) {
fprintf(out_, " %s\n", artifact);
}
}
return ZX_OK;
}
zx_status_t Fuzzer::Stop() {
Walker walker(this, true /* kill */);
walker.WalkRootJobTree();
fprintf(out_, "Stopped %zu tasks.\n", walker.killed());
return ZX_OK;
}
zx_status_t Fuzzer::Repro() {
zx_status_t rc;
// If no patterns, match all artifacts
if (inputs_.is_empty()) {
inputs_.push_back("");
}
// Filter data for just artifacts that match one or more supplied patterns
auto artifacts = data_path_.List();
StringList prefixes(kArtifactPrefixes, kArtifactPrefixesLen);
artifacts->keep_if_any(&prefixes);
artifacts->keep_if_any(&inputs_);
// Get full paths of artifacts
inputs_.clear();
for (const char* artifact = artifacts->first(); artifact; artifact = artifacts->next()) {
inputs_.push_back(data_path_.Join(artifact));
}
// Nothing to repro
if (inputs_.is_empty()) {
fprintf(err_, "No matching artifacts found.\n");
return ZX_ERR_NOT_FOUND;
}
if ((rc = Execute()) != ZX_OK) {
fprintf(err_, "Failed to execute: %s\n", zx_status_get_string(rc));
return rc;
}
return ZX_OK;
}
zx_status_t Fuzzer::Merge() {
zx_status_t rc;
// If no inputs and no merge in process, minimize the previous corpus (and there must be an
// existing corpus!)
size_t mergefile_len = 0;
data_path_.GetSize(".mergefile", &mergefile_len);
if (inputs_.is_empty() && mergefile_len == 0) {
if ((rc = data_path_.Rename("corpus", "corpus.prev")) != ZX_OK) {
fprintf(err_, "Failed to move 'corpus' for minimization: %s\n",
zx_status_get_string(rc));
return rc;
}
}
if (inputs_.is_empty()) {
inputs_.push_back(data_path_.Join("corpus.prev"));
}
// Make sure the corpus directory exists, and make sure the output corpus is the first argument
if ((rc = data_path_.Ensure("corpus")) != ZX_OK) {
fprintf(err_, "Failed to ensure 'corpus': %s\n", zx_status_get_string(rc));
return rc;
}
inputs_.erase_if(data_path_.Join("corpus"));
inputs_.push_front(data_path_.Join("corpus"));
if ((rc = Execute()) != ZX_OK) {
fprintf(err_, "Failed to execute: %s\n", zx_status_get_string(rc));
return rc;
}
// Merge complete; cleanup temporary files.
if ((rc = data_path_.Remove("corpus.prev")) != ZX_OK ||
(rc = data_path_.Remove(".mergefile")) != ZX_OK) {
fprintf(err_, "Failed to remove merge control files: %s\n", zx_status_get_string(rc));
return rc;
}
return ZX_OK;
}
} // namespace fuzzing