6#include <openssl/md5.h>
7#include <openssl/sha.h>
12namespace fs = std::filesystem;
17std::string
slurp(
const fs::path& p,
bool binary=
false) {
18 std::ifstream in(p, binary ? std::ios::binary : std::ios::in);
20 std::ostringstream ss; ss << in.rdbuf();
return ss.str();
23 std::vector<std::string> out; std::string cur;
25 if (c ==
'\0') {
if (!cur.empty()) out.push_back(cur); cur.clear(); }
26 else cur.push_back(c);
28 if (!cur.empty()) out.push_back(cur);
32 std::ifstream in(p, std::ios::binary);
if (!in)
return {};
33 MD5_CTX ctx; MD5_Init(&ctx);
35 while (in.read(buf,
sizeof(buf)) || in.gcount() > 0) MD5_Update(&ctx, buf,
static_cast<size_t>(in.gcount()));
36 unsigned char out[MD5_DIGEST_LENGTH]; MD5_Final(out, &ctx);
37 static const char* hex =
"0123456789abcdef";
38 std::string s; s.reserve(MD5_DIGEST_LENGTH * 2);
39 for (
unsigned char b : out) { s.push_back(hex[b >> 4]); s.push_back(hex[b & 0xF]); }
43 std::ifstream in(p, std::ios::binary);
if (!in)
return {};
44 SHA_CTX ctx; SHA1_Init(&ctx);
46 while (in.read(buf,
sizeof(buf)) || in.gcount() > 0) SHA1_Update(&ctx, buf,
static_cast<size_t>(in.gcount()));
47 unsigned char out[SHA_DIGEST_LENGTH]; SHA1_Final(out, &ctx);
48 static const char* hex =
"0123456789abcdef";
49 std::string s; s.reserve(SHA_DIGEST_LENGTH * 2);
50 for (
unsigned char b : out) { s.push_back(hex[b >> 4]); s.push_back(hex[b & 0xF]); }
54 std::ifstream in(p, std::ios::binary);
if (!in)
return {};
55 SHA256_CTX ctx; SHA256_Init(&ctx);
57 while (in.read(buf,
sizeof(buf)) || in.gcount() > 0) SHA256_Update(&ctx, buf,
static_cast<size_t>(in.gcount()));
58 unsigned char out[SHA256_DIGEST_LENGTH]; SHA256_Final(out, &ctx);
59 static const char* hex =
"0123456789abcdef";
60 std::string s; s.reserve(SHA256_DIGEST_LENGTH * 2);
61 for (
unsigned char b : out) { s.push_back(hex[b >> 4]); s.push_back(hex[b & 0xF]); }
67 fs::path base = fs::path(
"/proc") / std::to_string(pid);
68 if (!fs::exists(base))
return std::nullopt;
71 p.
path = fs::read_symlink(base /
"exe", ec).string();
72 ec.clear(); p.
cwd = fs::read_symlink(base /
"cwd", ec).string();
73 p.
args = split_nulls(slurp(base /
"cmdline",
true));
74 auto env = split_nulls(slurp(base /
"environ",
true));
75 for (
const auto& e : env) {
auto pos=e.find(
'=');
if (pos!=std::string::npos) p.
env[e.substr(0,pos)] = e.substr(pos+1); }
80 std::ifstream comm_file(base /
"comm");
82 std::string comm_line;
83 if (std::getline(comm_file, comm_line)) {
84 while (!comm_line.empty() &&
85 (comm_line.back() ==
'\n' || comm_line.back() ==
'\r' ||
86 std::isspace(
static_cast<unsigned char>(comm_line.back()))))
88 p.
comm = std::move(comm_line);
93 std::ifstream status(base /
"status");
95 while (std::getline(status, line)) {
97 if (p.
comm.empty() && line.rfind(
"Name:", 0) == 0) {
98 p.
comm = line.substr(5);
99 while (!p.
comm.empty() && std::isspace(
static_cast<unsigned char>(p.
comm.front()))) p.
comm.erase(p.
comm.begin());
100 while (!p.
comm.empty() && std::isspace(
static_cast<unsigned char>(p.
comm.back()))) p.
comm.pop_back();
102 else if (line.rfind(
"PPid:", 0) == 0) p.
ppid = std::stoull(line.substr(6));
103 else if (line.rfind(
"Uid:", 0) == 0) { std::istringstream iss(line.substr(5)); iss >> p.
uid; }
105 for (
auto current = p.
pid; current > 0;) {
106 fs::path status_path = fs::path(
"/proc") / std::to_string(current) /
"status";
107 std::ifstream st(status_path);
109 std::string name; std::uint64_t ppid=0;
110 while (std::getline(st, line)) {
111 if (line.rfind(
"Name:",0)==0) { name = line.substr(6);
while (!name.empty() && std::isspace(
static_cast<unsigned char>(name.front()))) name.erase(name.begin()); }
112 else if (line.rfind(
"PPid:",0)==0) ppid = std::stoull(line.substr(6));
114 p.
process_tree.emplace_back(name,
static_cast<std::uint32_t
>(current));
115 if (ppid == current)
break;
122std::optional<bsc::model::ProcessInfo>
ProcMon::cached(std::uint32_t pid,
bool allow_recent_exit)
const {
123 const auto now = std::chrono::steady_clock::now();
125 if (
const auto it =
cache_.find(pid); it !=
cache_.end())
return it->second.info;
126 if (allow_recent_exit) {
128 if (now - it->second.updated_at <= kRecentExitTtl)
return it->second.info;
136 std::chrono::milliseconds max_age,
137 std::size_t limit)
const {
138 const auto now = std::chrono::steady_clock::now();
139 std::vector<std::pair<std::chrono::steady_clock::time_point, bsc::model::ProcessInfo>> tmp;
142 auto consider = [&](
const CacheEntry& entry) {
143 if (entry.info.uid != uid)
return;
144 if (entry.info.comm.empty() && entry.info.path.empty())
return;
145 if (max_age.count() > 0 && now - entry.updated_at > max_age)
return;
146 tmp.emplace_back(entry.updated_at, entry.info);
148 for (
const auto& [pid, entry] :
cache_) consider(entry);
149 for (
const auto& [pid, entry] :
exited_) consider(entry);
151 std::sort(tmp.begin(), tmp.end(), [](
const auto& a,
const auto& b) {
152 return a.first > b.first;
155 std::vector<bsc::model::ProcessInfo> out;
156 out.reserve(std::min(limit, tmp.size()));
157 for (
const auto& [ts, info] : tmp) {
159 if (out.size() >= limit)
break;
166 std::size_t limit)
const {
167 const auto now = std::chrono::steady_clock::now();
168 std::vector<std::pair<std::chrono::steady_clock::time_point, bsc::model::ProcessInfo>> tmp;
171 auto consider = [&](
const CacheEntry& entry) {
172 if (entry.info.comm.empty() && entry.info.path.empty())
return;
173 if (max_age.count() > 0 && now - entry.updated_at > max_age)
return;
174 tmp.emplace_back(entry.updated_at, entry.info);
176 for (
const auto& [pid, entry] :
cache_) consider(entry);
177 for (
const auto& [pid, entry] :
exited_) consider(entry);
179 std::sort(tmp.begin(), tmp.end(), [](
const auto& a,
const auto& b) {
180 return a.first > b.first;
183 std::vector<bsc::model::ProcessInfo> out;
184 out.reserve(std::min(limit, tmp.size()));
185 for (
const auto& [ts, info] : tmp) {
187 if (out.size() >= limit)
break;
192std::optional<bsc::model::ProcessInfo>
ProcMon::inspect(std::uint32_t pid,
bool checksums)
const {
193 const auto now = std::chrono::steady_clock::now();
196 if (
const auto it =
cache_.find(pid); it !=
cache_.end()) {
197 const auto age = now - it->second.updated_at;
198 const auto fresh = age <= kFreshCacheTtl;
199 const auto checksum_ok = !checksums || it->second.has_checksums;
200 if (fresh && checksum_ok)
return it->second.info;
203 const auto age = now - it->second.updated_at;
204 const auto checksum_ok = !checksums || it->second.has_checksums;
205 if (age <= kRecentExitTtl && checksum_ok)
return it->second.info;
206 if (age > kRecentExitTtl)
exited_.erase(it);
212 if (
auto stale =
cached(pid,
true))
return stale;
223 cache_[pid] = std::move(entry);
229 auto& entry =
cache_[
static_cast<std::uint32_t
>(info.
pid)];
231 entry.has_checksums = !info.
checksums.empty();
232 entry.updated_at = std::chrono::steady_clock::now();
233 exited_.erase(
static_cast<std::uint32_t
>(info.
pid));
244 if (
const auto it =
cache_.find(parent_pid); it !=
cache_.end()) {
245 auto child = it->second;
246 child.info.pid = child_pid;
247 child.info.ppid = parent_pid;
248 child.info.checksums.clear();
249 child.has_checksums =
false;
250 child.updated_at = std::chrono::steady_clock::now();
251 child.info.process_tree.clear();
252 cache_[child_pid] = std::move(child);
265 if (
const auto it =
cache_.find(pid); it !=
cache_.end()) {
266 auto entry = it->second;
267 entry.updated_at = std::chrono::steady_clock::now();
268 exited_[pid] = std::move(entry);
std::string sha256_file(const std::string &path)
std::vector< bsc::model::ProcessInfo > recent_candidates_any(std::chrono::milliseconds max_age=std::chrono::milliseconds(1500), std::size_t limit=8) const
std::unordered_map< std::uint32_t, CacheEntry > exited_
void note_exit(std::uint32_t pid) const
static std::optional< bsc::model::ProcessInfo > inspect_procfs(std::uint32_t pid, bool checksums)
std::vector< bsc::model::ProcessInfo > recent_candidates(std::uint32_t uid, std::chrono::milliseconds max_age=std::chrono::milliseconds(5000), std::size_t limit=8) const
void cache_process(const bsc::model::ProcessInfo &info) const
std::optional< bsc::model::ProcessInfo > cached(std::uint32_t pid, bool allow_recent_exit=true) const
void note_fork(std::uint32_t child_pid, std::uint32_t parent_pid) const
std::unordered_map< std::uint32_t, CacheEntry > cache_
void invalidate(std::uint32_t pid) const
void note_exec(std::uint32_t pid, bool checksums=false) const
std::optional< bsc::model::ProcessInfo > inspect(std::uint32_t pid, bool checksums=false) const
constexpr auto kFreshCacheTtl
std::string md5_file(const fs::path &p)
std::string slurp(const fs::path &p, bool binary=false)
std::string sha1_file(const fs::path &p)
constexpr auto kRecentExitTtl
std::vector< std::string > split_nulls(const std::string &s)
std::string sha256_file(const fs::path &p)
std::map< std::string, std::string > env
std::map< std::string, std::string > checksums
std::vector< std::pair< std::string, std::uint32_t > > process_tree
std::vector< std::string > args
bsc::model::ProcessInfo info
std::chrono::steady_clock::time_point updated_at