blob: 24e447867713beae38d40262839607cc4229fab5 [file] [log] [blame]
// Copyright 2018 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/crash_sender_util.h"
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <map>
#include <string>
#include <vector>
#include <base/files/file_util.h>
#include <base/strings/string_number_conversions.h>
#include <base/strings/string_split.h>
#include <base/strings/stringprintf.h>
#include <brillo/flag_helper.h>
#include "crash-reporter/crash_sender_paths.h"
#include "crash-reporter/paths.h"
#include "crash-reporter/util.h"
namespace util {
namespace {
// getenv() wrapper that returns an empty string, if the environment variable is
// not defined.
std::string GetEnv(const std::string& name) {
const char* value = getenv(name.c_str());
return value ? value : "";
}
// Shows the usage of crash_sender and exits the process as a success.
void ShowUsageAndExit() {
printf(
"Usage: crash_sender [options]\n"
"Options:\n"
" -e <var>=<val> Set env |var| to |val| (only some vars)\n");
exit(EXIT_SUCCESS);
}
} // namespace
void ParseCommandLine(int argc, const char* const* argv) {
std::map<std::string, std::string> env_vars;
for (const EnvPair& pair : kEnvironmentVariables) {
// Honor the existing value if it's already set.
const char* value = getenv(pair.name);
env_vars[pair.name] = value ? value : pair.value;
}
// Process -e options, and collect other options.
std::vector<const char*> new_argv;
new_argv.push_back(argv[0]);
for (int i = 1; i < argc; ++i) {
if (std::string(argv[i]) == "-e") {
if (i + 1 < argc) {
++i;
std::string name_value = argv[i];
std::vector<std::string> pair = base::SplitString(
name_value, "=", base::KEEP_WHITESPACE, base::SPLIT_WANT_ALL);
if (pair.size() == 2) {
if (env_vars.count(pair[0]) == 0) {
LOG(ERROR) << "Unknown variable name: " << pair[0];
exit(EXIT_FAILURE);
}
env_vars[pair[0]] = pair[1];
} else {
LOG(ERROR) << "Malformed value for -e: " << name_value;
exit(EXIT_FAILURE);
}
} else {
LOG(ERROR) << "Value for -e is missing";
exit(EXIT_FAILURE);
}
} else {
new_argv.push_back(argv[i]);
}
}
// argv[argc] should be a null pointer per the C standard.
new_argv.push_back(nullptr);
// Process the remaining flags.
DEFINE_bool(h, false, "Show this help and exit");
brillo::FlagHelper::Init(new_argv.size() - 1, new_argv.data(),
"Chromium OS Crash Sender");
// TODO(satorux): Remove this once -e option is gone.
if (FLAGS_h)
ShowUsageAndExit();
// Set the predefined environment variables.
for (const auto& it : env_vars)
setenv(it.first.c_str(), it.second.c_str(), 1 /* overwrite */);
}
bool IsMock() {
return base::PathExists(
paths::GetAt(paths::kSystemRunStateDirectory, paths::kMockCrashSending));
}
bool ShouldPauseSending() {
return (base::PathExists(paths::Get(paths::kPauseCrashSending)) &&
GetEnv("OVERRIDE_PAUSE_SENDING") == "0");
}
bool CheckDependencies(base::FilePath* missing_path) {
const char* const kDependencies[] = {
paths::kFind, paths::kMetricsClient,
paths::kRestrictedCertificatesDirectory,
};
for (const char* dependency : kDependencies) {
const base::FilePath path = paths::Get(dependency);
int permissions = 0;
// Check if |path| is an executable or a directory.
if (!(base::GetPosixFilePermissions(path, &permissions) &&
(permissions & base::FILE_PERMISSION_EXECUTE_BY_USER))) {
*missing_path = path;
return false;
}
}
return true;
}
Sender::Sender(const Sender::Options& options)
: shell_script_(options.shell_script), proxy_(options.proxy) {}
bool Sender::Init() {
if (!scoped_temp_dir_.CreateUniqueTempDir()) {
PLOG(ERROR) << "Failed to create a temporary directory.";
return false;
}
return true;
}
bool Sender::SendCrashes(const base::FilePath& crash_dir) {
if (!base::DirectoryExists(crash_dir)) {
// Directory not existing is not an error.
return true;
}
const int child_pid = fork();
if (child_pid == 0) {
char* shell_script_path = const_cast<char*>(shell_script_.value().c_str());
char* temp_dir_path =
const_cast<char*>(scoped_temp_dir_.GetPath().value().c_str());
char* crash_dir_path = const_cast<char*>(crash_dir.value().c_str());
char* shell_argv[] = {shell_script_path, temp_dir_path, crash_dir_path,
nullptr};
execve(shell_script_path, shell_argv, environ);
exit(EXIT_FAILURE); // execve() failed.
} else {
int status = 0;
if (waitpid(child_pid, &status, 0) < 0) {
PLOG(ERROR) << "Failed to wait for the child process: " << child_pid;
return false;
}
if (!WIFEXITED(status)) {
LOG(ERROR) << "Terminated abnormally: " << status;
return false;
}
int exit_code = WEXITSTATUS(status);
if (exit_code != 0) {
LOG(ERROR) << "Terminated with non-zero exit code: " << exit_code;
return false;
}
}
return true;
}
bool Sender::SendUserCrashes() {
scoped_refptr<dbus::Bus> bus;
bool fully_successful = true;
// Set up the session manager proxy if it's not given from the options.
if (!proxy_) {
dbus::Bus::Options options;
options.bus_type = dbus::Bus::SYSTEM;
scoped_refptr<dbus::Bus> bus = new dbus::Bus(options);
CHECK(bus->Connect());
proxy_.reset(new org::chromium::SessionManagerInterfaceProxy(bus));
}
std::vector<base::FilePath> directories;
if (util::GetUserCrashDirectories(proxy_.get(), &directories)) {
for (auto directory : directories) {
if (!SendCrashes(directory)) {
LOG(ERROR) << "Skipped " << directory.value();
fully_successful = false;
}
}
}
if (bus)
bus->ShutdownAndBlock();
return fully_successful;
}
} // namespace util