blob: ed5b06d95a9424dc408578fc44179f96ba3473d0 [file] [log] [blame]
// Copyright 2022 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 <fcntl.h>
#include <getopt.h>
#include <lib/zxdump/dump.h>
#include <lib/zxdump/fd-writer.h>
#include <zircon/assert.h>
#include <zircon/status.h>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <iostream>
#include <optional>
#include <string>
#include <string_view>
#include <fbl/unique_fd.h>
#include <task-utils/get.h>
namespace {
using namespace std::literals;
// Command-line flags controlling the dump are parsed into this object, which
// is passed around to the methods affected by policy choices.
struct Flags {
std::string OutputFile(zx_koid_t pid) const {
std::string filename{output_prefix_ ? *output_prefix_ : "core."sv};
filename += std::to_string(pid);
return filename;
}
std::optional<std::string_view> output_prefix_;
size_t limit_ = zxdump::DefaultLimit();
};
// This handles writing a single output file, and removing that output file if
// the dump is aborted before `Ok(true)` is called.
class Writer : public zxdump::FdWriter {
public:
Writer(fbl::unique_fd fd, std::string filename)
: zxdump::FdWriter{std::move(fd)}, filename_{std::move(filename)} {}
// Write errors from errno use the file name.
void Error(std::string_view op) {
ZX_DEBUG_ASSERT(!op.empty());
std::string_view fn = filename_;
if (fn.empty()) {
fn = "<stdout>"sv;
}
std::cerr << fn << ": "sv << op << ": "sv << strerror(errno) << std::endl;
}
// Called with true if the output file should be preserved at destruction.
bool Ok(bool ok) {
if (ok) {
filename_.clear();
}
return ok;
}
~Writer() {
if (!filename_.empty()) {
remove(filename_.c_str());
}
}
private:
std::string filename_;
};
// This handles collecting and producing the dump for one process.
// The Writer and Flags objects get passed in to control the details
// and of the dump and where it goes.
class ProcessDumper {
public:
ProcessDumper(zx::unowned_process process, zx_koid_t pid)
: dumper_{std::move(process)}, pid_(pid) {}
// Read errors from syscalls use the PID.
void Error(const zxdump::Error& error) const {
std::cerr << pid_ << ": "sv << error << std::endl;
}
// Phase 1: Collect underpants!
bool Collect(const Flags& flags) {
auto result = dumper_.CollectProcess(flags.limit_);
if (result.is_error()) {
Error(result.error_value());
return false;
}
return true;
}
// Phase 2: ???
bool Dump(Writer& writer, const Flags& flags) {
// File offset calculations start fresh in each ET_CORE file.
writer.ResetOffset();
// Now write the accumulated header data first: not including the memory.
// These iovecs will point into storage in the ProcessDump object itself.
size_t total;
if (auto result = dumper_.DumpHeaders(writer.AccumulateFragmentsCallback(), flags.limit_);
result.is_error()) {
writer.Error(result.error_value());
return false;
} else {
total = result.value();
}
if (total > flags.limit_) {
errno = EFBIG;
writer.Error("not written");
return false;
}
if (auto result = writer.WriteFragments(); result.is_error()) {
writer.Error(result.error_value());
return false;
}
return true;
}
private:
zxdump::ProcessDump<zx::unowned_process> dumper_;
zx_koid_t pid_;
};
fbl::unique_fd CreateOutputFile(const std::string& outfile) {
fbl::unique_fd fd{open(outfile.c_str(), O_WRONLY | O_CREAT | O_TRUNC | O_EXCL, 0666)};
if (!fd) {
perror(outfile.c_str());
}
return fd;
}
// Phase 3: Profit!
bool WriteProcessCoreFile(zx::process process, zx_koid_t pid, const Flags& flags) {
std::string outfile = flags.OutputFile(pid);
fbl::unique_fd fd = CreateOutputFile(outfile);
if (!fd) {
return false;
}
Writer writer{std::move(fd), std::move(outfile)};
ProcessDumper dumper{zx::unowned_process{process}, pid};
return writer.Ok(dumper.Collect(flags) && dumper.Dump(writer, flags));
}
constexpr const char kOptString[] = "hlo:";
constexpr const option kLongOpts[] = {
{"help", no_argument, nullptr, 'h'}, //
{"limit", required_argument, nullptr, 'l'}, //
{"output-prefix", required_argument, nullptr, 'o'}, //
{nullptr, no_argument, nullptr, 0}, //
};
} // namespace
int main(int argc, char** argv) {
Flags flags;
auto usage = [&](int status = EXIT_FAILURE) {
std::cerr << "Usage: " << argv[0] << R"""( [SWITCHES...] PID...
--help, -h print this message
--output-prefix=PREFIX, -o PREFIX write <PREFIX><PID>, not core.<PID>
--limit=BYTES, -l BYTES truncate output to BYTES per process
)""";
return status;
};
while (true) {
switch (getopt_long(argc, argv, kOptString, kLongOpts, nullptr)) {
case -1:
// This ends the loop. All other cases continue (or return).
break;
case 'o':
flags.output_prefix_ = optarg;
continue;
case 'l': {
char* p;
flags.limit_ = strtoul(optarg, &p, 0);
if (*p != '\0') {
return usage();
}
continue;
}
default:
return usage(EXIT_SUCCESS);
}
break;
}
if (optind == argc) {
return usage();
}
int exit_status = EXIT_SUCCESS;
for (int i = optind; i < argc; ++i) {
char* p;
zx_koid_t pid = strtoul(argv[i], &p, 0);
if (*p != '\0') {
std::cerr << "Not a PID (KOID): " << argv[i] << std::endl;
return usage();
}
zx_obj_type_t type;
zx_handle_t handle;
zx_status_t status = get_task_by_koid(pid, &type, &handle);
if (status != ZX_OK) {
std::cerr << pid << ": " << zx_status_get_string(status) << std::endl;
status = EXIT_FAILURE;
continue;
}
switch (type) {
default:
std::cerr << pid << ": KOID is not a process\n";
zx_handle_close(handle);
exit_status = EXIT_FAILURE;
break;
case ZX_OBJ_TYPE_PROCESS:
if (!WriteProcessCoreFile(zx::process{handle}, pid, flags)) {
exit_status = EXIT_FAILURE;
}
break;
}
}
return exit_status;
}