blob: b91f093c7dad9170addeb6f77370925c31bf1e56 [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 "runtime_probe/probe_function.h"
#include <sys/select.h>
#include <sys/time.h>
#include <unistd.h>
#include <algorithm>
#include <vector>
#include <base/files/file_util.h>
#include <base/json/json_reader.h>
#include <base/json/json_writer.h>
#include <base/values.h>
#include <chromeos/dbus/service_constants.h>
#include <dbus/bus.h>
#include <dbus/message.h>
#include <dbus/object_proxy.h>
namespace runtime_probe {
namespace {
enum class PipeState {
PENDING,
ERROR,
DONE,
};
// The system-defined size of buffer used to read from a pipe.
const size_t kBufferSize = PIPE_BUF;
// Seconds to wait for runtime_probe_helper to send probe results.
const time_t kWaitSeconds = 5;
PipeState ReadPipe(int src_fd, std::string* dst_str) {
char buffer[kBufferSize];
const ssize_t bytes_read = HANDLE_EINTR(read(src_fd, buffer, kBufferSize));
if (bytes_read < 0 && errno != EAGAIN && errno != EWOULDBLOCK) {
PLOG(ERROR) << "read() from fd " << src_fd << " failed";
return PipeState::ERROR;
}
if (bytes_read == 0) {
return PipeState::DONE;
}
if (bytes_read > 0) {
dst_str->append(buffer, bytes_read);
}
return PipeState::PENDING;
}
bool ReadNonblockingPipeToString(int fd, std::string* out) {
fd_set read_fds;
struct timeval timeout;
FD_ZERO(&read_fds);
FD_SET(fd, &read_fds);
timeout.tv_sec = kWaitSeconds;
timeout.tv_usec = 0;
while (true) {
int retval = select(fd + 1, &read_fds, nullptr, nullptr, &timeout);
if (retval < 0) {
PLOG(ERROR) << "select() failed from runtime_probe_helper";
return false;
}
// Should only happen on timeout. Log a warning here, so we get at least a
// log if the process is stale.
if (retval == 0) {
LOG(WARNING) << "select() timed out. Process might be stale.";
return false;
}
PipeState state = ReadPipe(fd, out);
if (state == PipeState::DONE) {
return true;
}
if (state == PipeState::ERROR) {
return false;
}
}
}
} // namespace
using DataType = typename ProbeFunction::DataType;
std::unique_ptr<ProbeFunction> ProbeFunction::FromValue(const base::Value& dv) {
if (!dv.is_dict()) {
LOG(ERROR) << "ProbeFunction::FromValue takes a dictionary as parameter";
return nullptr;
}
if (dv.DictSize() == 0) {
LOG(ERROR) << "No function name found in the ProbeFunction dictionary";
return nullptr;
}
if (dv.DictSize() > 1) {
LOG(ERROR) << "More than 1 function names specified in the ProbeFunction"
" dictionary";
return nullptr;
}
const auto& it = dv.DictItems().begin();
// function_name is the only key exists in the dictionary */
const auto& function_name = it->first;
const auto& kwargs = it->second;
if (registered_functions_.find(function_name) ==
registered_functions_.end()) {
// TODO(stimim): Should report an error.
LOG(ERROR) << "Function \"" << function_name << "\" not found";
return nullptr;
}
if (!kwargs.is_dict()) {
// TODO(stimim): implement syntax sugar.
LOG(ERROR) << "Function argument should be a dictionary";
return nullptr;
}
std::unique_ptr<ProbeFunction> ret_value =
registered_functions_[function_name](kwargs);
ret_value->raw_value_ = dv.Clone();
return ret_value;
}
constexpr auto kDebugdRunProbeHelperMethodName = "EvaluateProbeFunction";
constexpr auto kDebugdRunProbeHelperDefaultTimeoutMs = 10 * 1000; // in ms
bool ProbeFunction::InvokeHelper(std::string* result) const {
std::string tmp_json_string;
CHECK(raw_value_.has_value());
base::JSONWriter::Write(*raw_value_, &tmp_json_string);
dbus::Bus::Options ops;
ops.bus_type = dbus::Bus::SYSTEM;
scoped_refptr<dbus::Bus> bus(new dbus::Bus(std::move(ops)));
if (!bus->Connect()) {
LOG(ERROR) << "Failed to connect to system D-Bus service.";
return false;
}
dbus::ObjectProxy* object_proxy = bus->GetObjectProxy(
debugd::kDebugdServiceName, dbus::ObjectPath(debugd::kDebugdServicePath));
dbus::MethodCall method_call(debugd::kDebugdInterface,
kDebugdRunProbeHelperMethodName);
dbus::MessageWriter writer(&method_call);
writer.AppendString(GetFunctionName());
writer.AppendString(tmp_json_string);
std::unique_ptr<dbus::Response> response = object_proxy->CallMethodAndBlock(
&method_call, kDebugdRunProbeHelperDefaultTimeoutMs);
if (!response) {
LOG(ERROR) << "Failed to issue D-Bus call to method "
<< kDebugdRunProbeHelperMethodName
<< " of debugd D-Bus interface.";
return false;
}
dbus::MessageReader reader(response.get());
base::ScopedFD read_fd{};
if (!reader.PopFileDescriptor(&read_fd)) {
LOG(ERROR) << "Failed to read fd that represents the read end of the pipe"
" from debugd.";
return false;
}
if (!ReadNonblockingPipeToString(read_fd.get(), result)) {
LOG(ERROR) << "Cannot read result from helper";
return false;
}
return true;
}
base::Optional<base::Value> ProbeFunction::InvokeHelperToJSON() const {
std::string raw_output;
if (!InvokeHelper(&raw_output)) {
return base::nullopt;
}
return base::JSONReader::Read(raw_output);
}
int ProbeFunction::EvalInHelper(std::string* output) const {
return 0;
}
} // namespace runtime_probe