blob: c0c16b5f8e79100fb24fcade798479387881730b [file] [log] [blame]
// Copyright 2020 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 "diagnostics/cros_healthd/fetchers/process_fetcher.h"
#include <unistd.h>
#include <cstdint>
#include <limits>
#include <string>
#include <utility>
#include <vector>
#include <base/bind.h>
#include <base/numerics/safe_conversions.h>
#include <base/optional.h>
#include <base/strings/string_number_conversions.h>
#include <base/strings/string_split.h>
#include <base/strings/string_util.h>
#include <base/strings/stringprintf.h>
#include <re2/re2.h>
#include "diagnostics/cros_healthd/utils/error_utils.h"
#include "diagnostics/cros_healthd/utils/file_utils.h"
#include "diagnostics/cros_healthd/utils/procfs_utils.h"
#include "mojo/cros_healthd_probe.mojom.h"
namespace diagnostics {
namespace {
namespace mojo_ipc = chromeos::cros_healthd::mojom;
// Regex used to parse a process's statm file.
constexpr char kProcessStatmFileRegex[] =
R"((\d+)\s+(\d+)\s+\d+\s+\d+\s+\d+\s+\d+\s+\d+)";
// Regex used to parse procfs's uptime file.
constexpr char kUptimeFileRegex[] = R"(([.\d]+)\s+[.\d]+)";
// Regex used to parse the process's Uid field in the status file.
constexpr char kUidStatusRegex[] = R"(\s*(\d+)\s+\d+\s+\d+\s+\d+)";
// Converts the raw process state read from procfs to a mojo_ipc::ProcessState.
// If the conversion is successful, returns base::nullopt and sets
// |mojo_state_out| to the converted value. If the conversion fails,
// |mojo_state_out| is invalid and an appropriate error is returned.
base::Optional<mojo_ipc::ProbeErrorPtr> GetProcessState(
const std::string& raw_state, mojo_ipc::ProcessState* mojo_state_out) {
DCHECK(mojo_state_out);
// See https://man7.org/linux/man-pages/man5/proc.5.html for allowable raw
// state values.
if (raw_state == "R") {
*mojo_state_out = mojo_ipc::ProcessState::kRunning;
} else if (raw_state == "S") {
*mojo_state_out = mojo_ipc::ProcessState::kSleeping;
} else if (raw_state == "D") {
*mojo_state_out = mojo_ipc::ProcessState::kWaiting;
} else if (raw_state == "Z") {
*mojo_state_out = mojo_ipc::ProcessState::kZombie;
} else if (raw_state == "T") {
*mojo_state_out = mojo_ipc::ProcessState::kStopped;
} else if (raw_state == "t") {
*mojo_state_out = mojo_ipc::ProcessState::kTracingStop;
} else if (raw_state == "X") {
*mojo_state_out = mojo_ipc::ProcessState::kDead;
} else {
return CreateAndLogProbeError(mojo_ipc::ErrorType::kParseError,
"Undefined process state: " + raw_state);
}
return base::nullopt;
}
// Converts |str| to a signed, 8-bit integer. If the conversion is successful,
// returns base::nullopt and sets |int_out| to the converted value. If the
// conversion fails, |int_out| is invalid and an appropriate error is returned.
base::Optional<mojo_ipc::ProbeErrorPtr> GetInt8FromString(
const std::string& str, int8_t* int_out) {
DCHECK(int_out);
int full_size_int;
if (!base::StringToInt(str, &full_size_int)) {
return CreateAndLogProbeError(mojo_ipc::ErrorType::kParseError,
"Failed to convert " + str + " to int.");
}
if (full_size_int > std::numeric_limits<int8_t>::max()) {
return CreateAndLogProbeError(
mojo_ipc::ErrorType::kParseError,
"Integer too large for int8_t: " + std::to_string(full_size_int));
}
*int_out = static_cast<int8_t>(full_size_int);
return base::nullopt;
}
} // namespace
ProcessFetcher::ProcessFetcher(pid_t process_id, const base::FilePath& root_dir)
: root_dir_(root_dir),
proc_pid_dir_(GetProcProcessDirectoryPath(root_dir, process_id)) {}
ProcessFetcher::~ProcessFetcher() = default;
void ProcessFetcher::FetchProcessInfo(
base::OnceCallback<void(mojo_ipc::ProcessResultPtr)> callback) {
mojo_ipc::ProcessInfo process_info;
// Number of ticks after system boot that the process started.
uint64_t start_time_ticks;
auto error = ParseProcPidStat(&process_info.state, &process_info.priority,
&process_info.nice, &start_time_ticks);
if (error.has_value()) {
std::move(callback).Run(
mojo_ipc::ProcessResult::NewError(std::move(error.value())));
return;
}
error = CalculateProcessUptime(start_time_ticks, &process_info.uptime_ticks);
if (error.has_value()) {
std::move(callback).Run(
mojo_ipc::ProcessResult::NewError(std::move(error.value())));
return;
}
error = ParseProcPidStatm(&process_info.total_memory_kib,
&process_info.resident_memory_kib,
&process_info.free_memory_kib);
if (error.has_value()) {
std::move(callback).Run(
mojo_ipc::ProcessResult::NewError(std::move(error.value())));
return;
}
uid_t user_id;
error = GetProcessUid(&user_id);
if (error.has_value()) {
std::move(callback).Run(
mojo_ipc::ProcessResult::NewError(std::move(error.value())));
return;
}
process_info.user_id = static_cast<uint32_t>(user_id);
if (!ReadAndTrimString(proc_pid_dir_, kProcessCmdlineFile,
&process_info.command)) {
std::move(callback).Run(
mojo_ipc::ProcessResult::NewError(CreateAndLogProbeError(
mojo_ipc::ErrorType::kFileReadError,
"Failed to read " +
proc_pid_dir_.Append(kProcessCmdlineFile).value())));
return;
}
std::move(callback).Run(
mojo_ipc::ProcessResult::NewProcessInfo(process_info.Clone()));
return;
}
base::Optional<mojo_ipc::ProbeErrorPtr> ProcessFetcher::ParseProcPidStat(
mojo_ipc::ProcessState* state,
int8_t* priority,
int8_t* nice,
uint64_t* start_time_ticks) {
// Note that start_time_ticks is the only pointer actually dereferenced in
// this function. The helper functions which set |state|, |priority| and
// |nice| are responsible for checking the validity of those three pointers.
DCHECK(start_time_ticks);
std::string stat_contents;
const base::FilePath kProcPidStatFile =
proc_pid_dir_.Append(kProcessStatFile);
if (!ReadAndTrimString(proc_pid_dir_, kProcessStatFile, &stat_contents)) {
return CreateAndLogProbeError(mojo_ipc::ErrorType::kFileReadError,
"Failed to read " + kProcPidStatFile.value());
}
std::vector<base::StringPiece> stat_tokens =
base::SplitStringPiece(stat_contents, base::kWhitespaceASCII,
base::KEEP_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
if (stat_tokens.size() <= ProcPidStatIndices::kMaxValue) {
return CreateAndLogProbeError(
mojo_ipc::ErrorType::kParseError,
"Failed to tokenize " + kProcPidStatFile.value());
}
auto error = GetProcessState(
stat_tokens[ProcPidStatIndices::kState].as_string(), state);
if (error.has_value())
return error;
error = GetInt8FromString(
stat_tokens[ProcPidStatIndices::kPriority].as_string(), priority);
if (error.has_value())
return error;
error = GetInt8FromString(stat_tokens[ProcPidStatIndices::kNice].as_string(),
nice);
if (error.has_value())
return error;
base::StringPiece start_time_str =
stat_tokens[ProcPidStatIndices::kStartTime];
if (!base::StringToUint64(start_time_str, start_time_ticks)) {
return CreateAndLogProbeError(
mojo_ipc::ErrorType::kParseError,
"Failed to convert starttime to uint64: " + start_time_str.as_string());
}
return base::nullopt;
}
base::Optional<mojo_ipc::ProbeErrorPtr> ProcessFetcher::ParseProcPidStatm(
uint32_t* total_memory_kib,
uint32_t* resident_memory_kib,
uint32_t* free_memory_kib) {
DCHECK(total_memory_kib);
DCHECK(resident_memory_kib);
DCHECK(free_memory_kib);
std::string statm_contents;
if (!ReadAndTrimString(proc_pid_dir_, kProcessStatmFile, &statm_contents)) {
return CreateAndLogProbeError(
mojo_ipc::ErrorType::kFileReadError,
"Failed to read " + proc_pid_dir_.Append(kProcessStatmFile).value());
}
std::string total_memory_pages_str;
std::string resident_memory_pages_str;
if (!RE2::FullMatch(statm_contents, kProcessStatmFileRegex,
&total_memory_pages_str, &resident_memory_pages_str)) {
return CreateAndLogProbeError(
mojo_ipc::ErrorType::kParseError,
"Failed to parse process's statm file: " + statm_contents);
}
uint32_t total_memory_pages;
if (!base::StringToUint(total_memory_pages_str, &total_memory_pages)) {
return CreateAndLogProbeError(
mojo_ipc::ErrorType::kParseError,
"Failed to convert total memory to uint32_t: " +
total_memory_pages_str);
}
uint32_t resident_memory_pages;
if (!base::StringToUint(resident_memory_pages_str, &resident_memory_pages)) {
return CreateAndLogProbeError(
mojo_ipc::ErrorType::kParseError,
"Failed to convert resident memory to uint32_t: " +
resident_memory_pages_str);
}
if (resident_memory_pages > total_memory_pages) {
return CreateAndLogProbeError(
mojo_ipc::ErrorType::kParseError,
base::StringPrintf("Process's resident memory (%u pages) higher than "
"total memory (%u pages).",
resident_memory_pages, total_memory_pages));
}
const auto kPageSizeInBytes = sysconf(_SC_PAGESIZE);
if (kPageSizeInBytes == -1) {
return CreateAndLogProbeError(mojo_ipc::ErrorType::kSystemUtilityError,
"Failed to run sysconf(_SC_PAGESIZE).");
}
const auto kPageSizeInKiB = kPageSizeInBytes / 1024;
*total_memory_kib =
static_cast<uint32_t>(total_memory_pages * kPageSizeInKiB);
*resident_memory_kib =
static_cast<uint32_t>(resident_memory_pages * kPageSizeInKiB);
*free_memory_kib = static_cast<uint32_t>(
(total_memory_pages - resident_memory_pages) * kPageSizeInKiB);
return base::nullopt;
}
base::Optional<mojo_ipc::ProbeErrorPtr> ProcessFetcher::CalculateProcessUptime(
uint64_t start_time_ticks, uint64_t* process_uptime_ticks) {
DCHECK(process_uptime_ticks);
std::string uptime_contents;
base::FilePath uptime_path = GetProcUptimePath(root_dir_);
if (!ReadAndTrimString(uptime_path, &uptime_contents)) {
return CreateAndLogProbeError(mojo_ipc::ErrorType::kFileReadError,
"Failed to read " + uptime_path.value());
}
std::string system_uptime_str;
if (!RE2::FullMatch(uptime_contents, kUptimeFileRegex, &system_uptime_str)) {
return CreateAndLogProbeError(
mojo_ipc::ErrorType::kParseError,
"Failed to parse uptime file: " + uptime_contents);
}
double system_uptime_seconds;
if (!base::StringToDouble(system_uptime_str, &system_uptime_seconds)) {
return CreateAndLogProbeError(
mojo_ipc::ErrorType::kParseError,
"Failed to convert system uptime to double: " + system_uptime_str);
}
const auto kClockTicksPerSecond = sysconf(_SC_CLK_TCK);
if (kClockTicksPerSecond == -1) {
return CreateAndLogProbeError(mojo_ipc::ErrorType::kSystemUtilityError,
"Failed to run sysconf(_SC_CLK_TCK).");
}
*process_uptime_ticks =
static_cast<uint64_t>(system_uptime_seconds *
static_cast<double>(kClockTicksPerSecond)) -
start_time_ticks;
return base::nullopt;
}
base::Optional<mojo_ipc::ProbeErrorPtr> ProcessFetcher::GetProcessUid(
uid_t* user_id) {
DCHECK(user_id);
std::string status_contents;
if (!ReadAndTrimString(proc_pid_dir_, kProcessStatusFile, &status_contents)) {
return CreateAndLogProbeError(
mojo_ipc::ErrorType::kFileReadError,
"Failed to read " + proc_pid_dir_.Append(kProcessStatusFile).value());
}
base::StringPairs status_key_value_pairs;
if (!base::SplitStringIntoKeyValuePairs(status_contents, ':', '\n',
&status_key_value_pairs)) {
return CreateAndLogProbeError(
mojo_ipc::ErrorType::kParseError,
"Failed to tokenize status file: " + status_contents);
}
bool uid_key_found = false;
std::string uid_str;
for (const auto& kv_pair : status_key_value_pairs) {
if (kv_pair.first != "Uid")
continue;
std::string uid_value = kv_pair.second;
if (!RE2::FullMatch(uid_value, kUidStatusRegex, &uid_str)) {
return CreateAndLogProbeError(mojo_ipc::ErrorType::kParseError,
"Failed to parse Uid value: " + uid_value);
}
unsigned int user_id_uint;
if (!base::StringToUint(uid_str, &user_id_uint)) {
return CreateAndLogProbeError(
mojo_ipc::ErrorType::kParseError,
"Failed to convert Uid to uint: " + uid_str);
}
*user_id = static_cast<uid_t>(user_id_uint);
uid_key_found = true;
break;
}
if (!uid_key_found) {
return CreateAndLogProbeError(mojo_ipc::ErrorType::kParseError,
"Failed to find Uid key.");
}
return base::nullopt;
}
} // namespace diagnostics