blob: bc414ab7a1ac8ac9acdad3d5b964fa1a909ccd0b [file] [log] [blame]
// Copyright (c) 2010 The Chromium OS 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 "crash-reporter/user_collector.h"
#include <fcntl.h> // For creat.
#include <grp.h> // For struct group.
#include <pwd.h> // For struct passwd.
#include <sys/types.h> // For getpwuid_r, getgrnam_r, WEXITSTATUS.
#include <sys/wait.h> // For waitpid.
#include <unistd.h> // For execv and fork.
#include <string>
#include <vector>
#include "base/eintr_wrapper.h"
#include "base/file_util.h"
#include "base/logging.h"
#include "base/string_util.h"
#include "crash-reporter/system_logging.h"
// This procfs file is used to cause kernel core file writing to
// instead pipe the core file into a user space process. See
// core(5) man page.
static const char kCorePatternFile[] = "/proc/sys/kernel/core_pattern";
static const char kCoreToMinidumpConverterPath[] = "/usr/bin/core2md";
static const char kLeaveCoreFile[] = "/root/.leave_core";
const char *UserCollector::kUserId = "Uid:\t";
const char *UserCollector::kGroupId = "Gid:\t";
UserCollector::UserCollector()
: generate_diagnostics_(false),
core_pattern_file_(kCorePatternFile),
initialized_(false) {
}
void UserCollector::Initialize(
UserCollector::CountCrashFunction count_crash_function,
const std::string &our_path,
UserCollector::IsFeedbackAllowedFunction is_feedback_allowed_function,
SystemLogging *logger,
bool generate_diagnostics) {
CrashCollector::Initialize(count_crash_function,
is_feedback_allowed_function,
logger);
our_path_ = our_path;
initialized_ = true;
generate_diagnostics_ = generate_diagnostics;
}
UserCollector::~UserCollector() {
}
std::string UserCollector::GetPattern(bool enabled) const {
if (enabled) {
return StringPrintf("|%s --signal=%%s --pid=%%p", our_path_.c_str());
} else {
return "core";
}
}
bool UserCollector::SetUpInternal(bool enabled) {
CHECK(initialized_);
logger_->LogInfo("%s user crash handling",
enabled ? "Enabling" : "Disabling");
std::string pattern = GetPattern(enabled);
if (file_util::WriteFile(FilePath(core_pattern_file_),
pattern.c_str(),
pattern.length()) !=
static_cast<int>(pattern.length())) {
logger_->LogError("Unable to write %s", core_pattern_file_.c_str());
return false;
}
return true;
}
FilePath UserCollector::GetProcessPath(pid_t pid) {
return FilePath(StringPrintf("/proc/%d", pid));
}
bool UserCollector::GetSymlinkTarget(const FilePath &symlink,
FilePath *target) {
int max_size = 32;
scoped_array<char> buffer;
while (true) {
buffer.reset(new char[max_size + 1]);
ssize_t size = readlink(symlink.value().c_str(), buffer.get(), max_size);
if (size < 0) {
return false;
}
buffer[size] = 0;
if (size == max_size) {
// Avoid overflow when doubling.
if (max_size * 2 > max_size) {
max_size *= 2;
continue;
} else {
return false;
}
}
break;
}
*target = FilePath(buffer.get());
return true;
}
bool UserCollector::GetExecutableBaseNameFromPid(uid_t pid,
std::string *base_name) {
FilePath target;
if (!GetSymlinkTarget(GetProcessPath(pid).Append("exe"), &target))
return false;
*base_name = target.BaseName().value();
return true;
}
bool UserCollector::GetIdFromStatus(const char *prefix,
IdKind kind,
const std::string &status_contents,
int *id) {
// From fs/proc/array.c:task_state(), this file contains:
// \nUid:\t<uid>\t<euid>\t<suid>\t<fsuid>\n
std::vector<std::string> status_lines;
SplitString(status_contents, '\n', &status_lines);
std::vector<std::string>::iterator line_iterator;
for (line_iterator = status_lines.begin();
line_iterator != status_lines.end();
++line_iterator) {
if (line_iterator->find(prefix) == 0)
break;
}
if (line_iterator == status_lines.end()) {
return false;
}
std::string id_substring = line_iterator->substr(strlen(prefix),
std::string::npos);
std::vector<std::string> ids;
SplitString(id_substring, '\t', &ids);
if (ids.size() != kIdMax || kind < 0 || kind >= kIdMax) {
return false;
}
const char *number = ids[kind].c_str();
char *end_number = NULL;
*id = strtol(number, &end_number, 10);
if (*end_number != '\0')
return false;
return true;
}
bool UserCollector::CopyOffProcFiles(pid_t pid,
const FilePath &container_dir) {
if (!file_util::CreateDirectory(container_dir)) {
logger_->LogInfo("Could not create %s", container_dir.value().c_str());
return false;
}
FilePath process_path = GetProcessPath(pid);
if (!file_util::PathExists(process_path)) {
logger_->LogWarning("Path %s does not exist",
process_path.value().c_str());
return false;
}
static const char *proc_files[] = {
"auxv",
"cmdline",
"environ",
"maps",
"status"
};
for (unsigned i = 0; i < arraysize(proc_files); ++i) {
if (!file_util::CopyFile(process_path.Append(proc_files[i]),
container_dir.Append(proc_files[i]))) {
logger_->LogWarning("Could not copy %s file", proc_files[i]);
return false;
}
}
return true;
}
bool UserCollector::GetCreatedCrashDirectory(pid_t pid,
FilePath *crash_file_path) {
FilePath process_path = GetProcessPath(pid);
std::string status;
if (!file_util::ReadFileToString(process_path.Append("status"),
&status)) {
logger_->LogError("Could not read status file");
return false;
}
int process_euid;
if (!GetIdFromStatus(kUserId, kIdEffective, status, &process_euid)) {
logger_->LogError("Could not find euid in status file");
return false;
}
return GetCreatedCrashDirectoryByEuid(process_euid,
crash_file_path);
}
bool UserCollector::CopyStdinToCoreFile(const FilePath &core_path) {
// Copy off all stdin to a core file.
FilePath stdin_path("/dev/fd/0");
if (file_util::CopyFile(stdin_path, core_path)) {
return true;
}
logger_->LogError("Could not write core file");
// If the file system was full, make sure we remove any remnants.
file_util::Delete(core_path, false);
return false;
}
int UserCollector::ForkExecAndPipe(std::vector<const char *> &arguments,
const char *output_file) {
// Copy off a writeable version of arguments.
scoped_array<char*> argv(new char *[arguments.size() + 1]);
int total_args_size = 0;
for (size_t i = 0; i < arguments.size(); ++i) {
if (arguments[i] == NULL) {
logger_->LogError("Bad parameter");
return -1;
}
total_args_size += strlen(arguments[i]) + 1;
}
scoped_array<char> buffer(new char[total_args_size]);
char *buffer_pointer = &buffer[0];
for (size_t i = 0; i < arguments.size(); ++i) {
argv[i] = buffer_pointer;
strcpy(buffer_pointer, arguments[i]);
buffer_pointer += strlen(arguments[i]);
*buffer_pointer = '\0';
++buffer_pointer;
}
argv[arguments.size()] = NULL;
int pid = fork();
if (pid < 0) {
logger_->LogError("Fork failed: %d", errno);
return -1;
}
if (pid == 0) {
int output_handle = creat(output_file, 0700);
if (output_handle < 0) {
logger_->LogError("Could not create %s: %d", output_file, errno);
// Avoid exit() to avoid atexit handlers from parent.
_exit(127);
}
dup2(output_handle, 1);
dup2(output_handle, 2);
execv(argv[0], &argv[0]);
logger_->LogError("Exec failed: %d", errno);
_exit(127);
}
int status = 0;
if (HANDLE_EINTR(waitpid(pid, &status, 0)) < 0) {
logger_->LogError("Problem waiting for pid: %d", errno);
return -1;
}
if (!WIFEXITED(status)) {
logger_->LogError("Process did not exit normally: %x", status);
return -1;
}
return WEXITSTATUS(status);
}
bool UserCollector::ConvertCoreToMinidump(const FilePath &core_path,
const FilePath &procfs_directory,
const FilePath &minidump_path,
const FilePath &temp_directory) {
FilePath output_path = temp_directory.Append("output");
std::vector<const char *> core2md_arguments;
core2md_arguments.push_back(kCoreToMinidumpConverterPath);
core2md_arguments.push_back(core_path.value().c_str());
core2md_arguments.push_back(procfs_directory.value().c_str());
core2md_arguments.push_back(minidump_path.value().c_str());
int errorlevel = ForkExecAndPipe(core2md_arguments,
output_path.value().c_str());
std::string output;
file_util::ReadFileToString(output_path, &output);
if (errorlevel != 0) {
logger_->LogInfo("Problem during %s [result=%d]: %s",
kCoreToMinidumpConverterPath,
errorlevel,
output.c_str());
return false;
}
if (!file_util::PathExists(minidump_path)) {
logger_->LogError("Minidump file %s was not created",
minidump_path.value().c_str());
return false;
}
return true;
}
bool UserCollector::GenerateDiagnostics(pid_t pid,
const std::string &exec_name) {
FilePath container_dir("/tmp");
container_dir = container_dir.Append(
StringPrintf("crash_reporter.%d", pid));
if (!CopyOffProcFiles(pid, container_dir)) {
file_util::Delete(container_dir, true);
return false;
}
FilePath crash_path;
if (!GetCreatedCrashDirectory(pid, &crash_path)) {
file_util::Delete(container_dir, true);
return false;
}
std::string dump_basename = FormatDumpBasename(exec_name, time(NULL), pid);
FilePath core_path = crash_path.Append(
StringPrintf("%s.core", dump_basename.c_str()));
if (!CopyStdinToCoreFile(core_path)) {
file_util::Delete(container_dir, true);
return false;
}
FilePath minidump_path = crash_path.Append(
StringPrintf("%s.dmp", dump_basename.c_str()));
bool conversion_result = true;
if (!ConvertCoreToMinidump(core_path,
container_dir, // procfs directory
minidump_path,
container_dir)) { // temporary directory
// Note we leave the container directory for inspection.
conversion_result = false;
}
WriteCrashMetaData(
crash_path.Append(
StringPrintf("%s.meta", dump_basename.c_str())),
exec_name,
minidump_path.value());
if (conversion_result) {
logger_->LogInfo("Stored minidump to %s", minidump_path.value().c_str());
}
if (!file_util::PathExists(FilePath(kLeaveCoreFile))) {
file_util::Delete(core_path, false);
} else {
logger_->LogInfo("Leaving core file at %s", core_path.value().c_str());
}
return conversion_result;
}
bool UserCollector::HandleCrash(int signal, int pid, const char *force_exec) {
CHECK(initialized_);
std::string exec;
if (force_exec) {
exec.assign(force_exec);
} else if (!GetExecutableBaseNameFromPid(pid, &exec)) {
// If for some reason we don't have the base name, avoid completely
// failing by indicating an unknown name.
exec = "unknown";
}
bool feedback = is_feedback_allowed_function_();
logger_->LogWarning("Received crash notification for %s[%d] sig %d (%s)",
exec.c_str(), pid, signal,
feedback ? "handling" : "ignoring");
if (feedback) {
count_crash_function_();
if (generate_diagnostics_) {
return GenerateDiagnostics(pid, exec);
}
}
return true;
}