249 lines
7.5 KiB
C++
249 lines
7.5 KiB
C++
/*
|
|
* Copyright (C) 2023 The Android Open Source Project
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
#include "common/libs/utils/proc_file_utils.h"
|
|
|
|
#include <sys/stat.h>
|
|
|
|
#include <regex>
|
|
#include <sstream>
|
|
|
|
#include <android-base/file.h>
|
|
#include <android-base/parseint.h>
|
|
#include <android-base/strings.h>
|
|
|
|
#include "common/libs/fs/shared_buf.h"
|
|
#include "common/libs/fs/shared_fd.h"
|
|
#include "common/libs/utils/files.h"
|
|
|
|
namespace cuttlefish {
|
|
|
|
// TODO(kwstephenkim): This logic is used broadly, so consider
|
|
// to create a new library.
|
|
template <typename... Args>
|
|
static std::string ConcatToString(Args&&... args) {
|
|
std::stringstream concatenator;
|
|
(concatenator << ... << std::forward<Args>(args));
|
|
return concatenator.str();
|
|
}
|
|
|
|
static std::string PidDirPath(const pid_t pid) {
|
|
return ConcatToString(kProcDir, "/", pid);
|
|
}
|
|
|
|
/* ReadFile does not work for /proc/<pid>/<some files>
|
|
* ReadFile requires the file size to be known in advance,
|
|
* which is not the case here.
|
|
*/
|
|
static Result<std::string> ReadAll(const std::string& file_path) {
|
|
SharedFD fd = SharedFD::Open(file_path, O_RDONLY);
|
|
CF_EXPECT(fd->IsOpen());
|
|
// should be good size to read all Envs or Args,
|
|
// whichever bigger
|
|
const int buf_size = 1024;
|
|
std::string output;
|
|
ssize_t nread = 0;
|
|
do {
|
|
std::vector<char> buf(buf_size);
|
|
nread = ReadExact(fd, buf.data(), buf_size);
|
|
CF_EXPECT(nread >= 0, "ReadExact returns " << nread);
|
|
output.append(buf.begin(), buf.end());
|
|
} while (nread > 0);
|
|
return output;
|
|
}
|
|
|
|
/**
|
|
* Tokenizes the given string, using '\0' as a delimiter
|
|
*
|
|
* android::base::Tokenize works mostly except the delimiter can't be '\0'.
|
|
* The /proc/<pid>/environ file has the list of environment variables, delimited
|
|
* by '\0'. Needs a dedicated tokenizer.
|
|
*
|
|
*/
|
|
static std::vector<std::string> TokenizeByNullChar(const std::string& input) {
|
|
if (input.empty()) {
|
|
return {};
|
|
}
|
|
std::vector<std::string> tokens;
|
|
std::string token;
|
|
for (int i = 0; i < input.size(); i++) {
|
|
if (input.at(i) != '\0') {
|
|
token.append(1, input.at(i));
|
|
} else {
|
|
if (token.empty()) {
|
|
break;
|
|
}
|
|
tokens.push_back(token);
|
|
token.clear();
|
|
}
|
|
}
|
|
if (!token.empty()) {
|
|
tokens.push_back(token);
|
|
}
|
|
return tokens;
|
|
}
|
|
|
|
Result<std::vector<pid_t>> CollectPids(const uid_t uid) {
|
|
CF_EXPECT(DirectoryExists(kProcDir));
|
|
auto subdirs = CF_EXPECT(DirectoryContents(kProcDir));
|
|
std::regex pid_dir_pattern("[0-9]+");
|
|
std::vector<pid_t> pids;
|
|
for (const auto& subdir : subdirs) {
|
|
if (!std::regex_match(subdir, pid_dir_pattern)) {
|
|
continue;
|
|
}
|
|
int pid;
|
|
// Shouldn't failed here. If failed, either regex or
|
|
// android::base::ParseInt needs serious fixes
|
|
CF_EXPECT(android::base::ParseInt(subdir, &pid));
|
|
struct stat dir_stat_buf;
|
|
if (::stat(PidDirPath(pid).data(), &dir_stat_buf) != 0) {
|
|
continue;
|
|
}
|
|
if (dir_stat_buf.st_uid != uid) {
|
|
continue;
|
|
}
|
|
// as we collect cuttlefish-related stuff, we want exe to be
|
|
// shared by the same owner
|
|
struct stat exe_stat_buf;
|
|
std::string exe_path = PidDirPath(pid) + "/exe";
|
|
if (::stat(exe_path.data(), &exe_stat_buf) != 0) {
|
|
continue;
|
|
}
|
|
if (exe_stat_buf.st_uid != uid) {
|
|
continue;
|
|
}
|
|
pids.push_back(pid);
|
|
}
|
|
return pids;
|
|
}
|
|
|
|
Result<std::vector<std::string>> GetCmdArgs(const pid_t pid) {
|
|
std::string cmdline_file_path = PidDirPath(pid) + "/cmdline";
|
|
auto owner = CF_EXPECT(OwnerUid(cmdline_file_path));
|
|
CF_EXPECT(getuid() == owner);
|
|
std::string contents = CF_EXPECT(ReadAll(cmdline_file_path));
|
|
return TokenizeByNullChar(contents);
|
|
}
|
|
|
|
Result<std::string> GetExecutablePath(const pid_t pid) {
|
|
std::string exec_target_path;
|
|
std::string proc_exe_path = ConcatToString("/proc/", pid, "/exe");
|
|
CF_EXPECT(
|
|
android::base::Readlink(proc_exe_path, std::addressof(exec_target_path)),
|
|
proc_exe_path << " Should be a symbolic link but it is not.");
|
|
std::string suffix(" (deleted)");
|
|
if (android::base::EndsWith(exec_target_path, suffix)) {
|
|
return exec_target_path.substr(0, exec_target_path.size() - suffix.size());
|
|
}
|
|
return exec_target_path;
|
|
}
|
|
|
|
Result<std::vector<pid_t>> CollectPidsByExecName(const std::string& exec_name,
|
|
const uid_t uid) {
|
|
CF_EXPECT(cpp_basename(exec_name) == exec_name);
|
|
auto input_pids = CF_EXPECT(CollectPids(uid));
|
|
std::vector<pid_t> output_pids;
|
|
for (const auto pid : input_pids) {
|
|
auto pid_exec_path = GetExecutablePath(pid);
|
|
if (!pid_exec_path.ok()) {
|
|
LOG(ERROR) << pid_exec_path.error().Trace();
|
|
continue;
|
|
}
|
|
if (cpp_basename(*pid_exec_path) == exec_name) {
|
|
output_pids.push_back(pid);
|
|
}
|
|
}
|
|
return output_pids;
|
|
}
|
|
|
|
Result<std::vector<pid_t>> CollectPidsByExecPath(const std::string& exec_path,
|
|
const uid_t uid) {
|
|
auto input_pids = CF_EXPECT(CollectPids(uid));
|
|
std::vector<pid_t> output_pids;
|
|
for (const auto pid : input_pids) {
|
|
auto pid_exec_path = GetExecutablePath(pid);
|
|
if (!pid_exec_path.ok()) {
|
|
continue;
|
|
}
|
|
if (*pid_exec_path == exec_path) {
|
|
output_pids.push_back(pid);
|
|
}
|
|
}
|
|
return output_pids;
|
|
}
|
|
|
|
Result<std::vector<pid_t>> CollectPidsByArgv0(const std::string& expected_argv0,
|
|
const uid_t uid) {
|
|
auto input_pids = CF_EXPECT(CollectPids(uid));
|
|
std::vector<pid_t> output_pids;
|
|
for (const auto pid : input_pids) {
|
|
auto argv_result = GetCmdArgs(pid);
|
|
if (!argv_result.ok()) {
|
|
continue;
|
|
}
|
|
if (argv_result->empty()) {
|
|
continue;
|
|
}
|
|
if (argv_result->front() == expected_argv0) {
|
|
output_pids.push_back(pid);
|
|
}
|
|
}
|
|
return output_pids;
|
|
}
|
|
|
|
Result<uid_t> OwnerUid(const pid_t pid) {
|
|
auto proc_pid_path = PidDirPath(pid);
|
|
auto uid = CF_EXPECT(OwnerUid(proc_pid_path));
|
|
return uid;
|
|
}
|
|
|
|
Result<uid_t> OwnerUid(const std::string& path) {
|
|
struct stat buf;
|
|
CF_EXPECT_EQ(::stat(path.data(), &buf), 0);
|
|
return buf.st_uid;
|
|
}
|
|
|
|
Result<std::unordered_map<std::string, std::string>> GetEnvs(const pid_t pid) {
|
|
std::string environ_file_path = PidDirPath(pid) + "/environ";
|
|
auto owner = CF_EXPECT(OwnerUid(environ_file_path));
|
|
CF_EXPECT(getuid() == owner, "Owned by another user of uid" << owner);
|
|
std::string environ = CF_EXPECT(ReadAll(environ_file_path));
|
|
std::vector<std::string> lines = TokenizeByNullChar(environ);
|
|
// now, each line looks like: HOME=/home/user
|
|
std::unordered_map<std::string, std::string> envs;
|
|
for (const auto& line : lines) {
|
|
auto pos = line.find_first_of('=');
|
|
if (pos == std::string::npos) {
|
|
LOG(ERROR) << "Found an invalid env: " << line << " and ignored.";
|
|
continue;
|
|
}
|
|
std::string key = line.substr(0, pos);
|
|
std::string value = line.substr(pos + 1);
|
|
envs[key] = value;
|
|
}
|
|
return envs;
|
|
}
|
|
|
|
Result<ProcInfo> ExtractProcInfo(const pid_t pid) {
|
|
return ProcInfo{.pid_ = pid,
|
|
.actual_exec_path_ = CF_EXPECT(GetExecutablePath(pid)),
|
|
.envs_ = CF_EXPECT(GetEnvs(pid)),
|
|
.args_ = CF_EXPECT(GetCmdArgs(pid))};
|
|
}
|
|
|
|
} // namespace cuttlefish
|