quark-mon.c (271 lines of code) (raw):
// SPDX-License-Identifier: Apache-2.0
/* Copyright (c) 2024 Elastic NV */
#include <err.h>
#include <errno.h>
#include <grp.h>
#include <pwd.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <unistd.h>
#include <sys/wait.h>
#include "quark.h"
#define MAN_QUARK_MON
#include "manpages.h"
static int gotsigint;
static void
dump_stats(struct quark_queue *qq)
{
struct quark_queue_stats s;
quark_queue_get_stats(qq, &s);
putchar('\n');
printf(
"%14s"
"%14s"
"%14s"
"%14s"
"%14s"
"%14s",
"insertions",
"removals",
"aggs",
"non-aggs",
"lost",
"gc-cols"
);
putchar('\n');
printf(
"%14llu"
"%14llu"
"%14llu"
"%14llu"
"%14llu"
"%14llu",
s.insertions, s.removals, s.aggregations,
s.non_aggregations, s.lost, s.garbage_collections);
putchar('\n');
}
static const char *
fetch_backend(struct quark_queue *qq)
{
struct quark_queue_stats s;
quark_queue_get_stats(qq, &s);
return (s.backend == QQ_EBPF ? "ebpf" :
s.backend == QQ_KPROBE ? "kprobe" :
"invalid");
}
static void
sigint_handler(int sig)
{
gotsigint = 1;
}
static void
priv_drop(void)
{
#ifdef NO_PRIVDROP
err(1, "built with NO_PRIVDROP");
#else
struct passwd *pw;
/* getpwnam_r is too painful for a demo */
if ((pw = getpwnam("nobody")) == NULL)
err(1, "getpwnam");
/* chroot */
if (chroot("/var/empty") == -1)
err(1, "chroot");
if (chdir("/") == -1)
err(1, "chdir");
/* setproctitle would be here */
/* become the weakling */
if (setgroups(1, &pw->pw_gid) ||
setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) ||
setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid))
err(1, "error dropping privileges");
#endif
}
static void
display_version(void)
{
printf("%s-%s\n", program_invocation_short_name, QUARK_VERSION);
printf("License: Apache-2.0\n");
printf("Copyright (c) 2024 Elastic NV\n");
exit(0);
}
static void
usage(void)
{
fprintf(stderr, "usage: %s -h\n", program_invocation_short_name);
fprintf(stderr, "usage: %s [-BbDefkNSstv] "
"[-C filename ] [-l maxlength] [-m maxnodes] [-P ppid]\n",
program_invocation_short_name);
fprintf(stderr, "usage: %s -V\n", program_invocation_short_name);
exit(1);
}
int
main(int argc, char *argv[])
{
int ch, maxnodes;
int do_priv_drop, do_snap, lflag;
u32 filter_ppid;
struct quark_queue *qq;
struct quark_queue_attr qa;
const struct quark_event *qev;
struct sigaction sigact;
FILE *graph_by_time, *graph_by_pidtime, *graph_cache;
quark_queue_default_attr(&qa);
qa.flags &= ~QQ_ALL_BACKENDS;
maxnodes = -1;
do_priv_drop = 0;
filter_ppid = 0;
do_snap = 1;
graph_by_time = graph_by_pidtime = graph_cache = NULL;
lflag = 0;
while ((ch = getopt(argc, argv, "BbC:Deghkl:m:NP:tSsvV")) != -1) {
const char *errstr;
switch (ch) {
case 'B':
qa.flags |= QQ_BYPASS;
break;
case 'b':
qa.flags |= QQ_EBPF;
break;
case 'C':
graph_cache = fopen(optarg, "w");
if (graph_cache == NULL)
err(1, "fopen %s", optarg);
break;
case 'D':
do_priv_drop = 1;
break;
case 'e':
qa.flags |= QQ_ENTRY_LEADER;
break;
case 'g':
qa.flags |= QQ_MIN_AGG;
break;
case 'h':
if (isatty(STDOUT_FILENO))
display_man();
else
usage();
break; /* NOTREACHED */
case 'k':
qa.flags |= QQ_KPROBE;
break;
case 'l':
if (optarg == NULL)
usage();
qa.max_length = strtonum(optarg, 1, INTMAX_MAX,
&errstr);
if (errstr != NULL)
errx(1, "invalid max length: %s", errstr);
lflag = 1;
break;
case 'm':
if (optarg == NULL)
usage();
maxnodes = strtonum(optarg, 1, 2000000, &errstr);
if (errstr != NULL)
errx(1, "invalid maxnodes: %s", errstr);
/* open graphviz files before priv_drop */
graph_by_time = fopen("quark_by_time.dot", "w");
if (graph_by_time == NULL)
err(1, "fopen");
graph_by_pidtime = fopen("quark_by_pidtime.dot", "w");
if (graph_by_pidtime == NULL)
err(1, "fopen");
break;
case 'N':
qa.flags |= QQ_DNS;
break;
case 'P':
if (optarg == NULL)
usage();
filter_ppid = strtonum(optarg, 1, UINT32_MAX, &errstr);
if (errstr != NULL)
errx(1, "invalid ppid: %s", errstr);
break;
case 's':
do_snap = 0;
break;
case 'S':
qa.flags |= QQ_SOCK_CONN;
break;
case 't':
qa.flags |= QQ_THREAD_EVENTS;
break;
case 'v':
quark_verbose++;
break;
case 'V':
display_version();
break;
default:
usage();
}
}
if (qa.flags & QQ_BYPASS) {
if (qa.flags &
(QQ_KPROBE|QQ_ENTRY_LEADER|QQ_MIN_AGG|QQ_THREAD_EVENTS) ||
graph_cache != NULL ||
maxnodes != -1 ||
lflag ||
filter_ppid ||
!do_snap)
errx(1, "bypass(B) cannot be used with options: CegklmPs");
qa.flags |= QQ_EBPF;
}
if ((qa.flags & QQ_ALL_BACKENDS) == 0)
qa.flags |= QQ_ALL_BACKENDS;
bzero(&sigact, sizeof(sigact));
sigact.sa_flags = SA_RESTART | SA_RESETHAND;
sigact.sa_handler = &sigint_handler;
if (sigaction(SIGINT, &sigact, NULL) == -1)
err(1, "sigaction");
if ((qq = calloc(1, sizeof(*qq))) == NULL)
err(1, "calloc");
if (quark_queue_open(qq, &qa) != 0)
err(1, "quark_queue_open");
if (quark_verbose)
printf("using %s for backend\n", fetch_backend(qq));
/* From now on we will be nobody */
if (do_priv_drop)
priv_drop();
/*
* Debug mode, let the tree grow to >= maxnodes and bail, without
* popping nodes
*/
while (!gotsigint && maxnodes != -1 && qq->length < maxnodes) {
quark_queue_populate(qq);
quark_queue_block(qq);
}
/*
* Should we print all processes learned through scraping
*/
if (do_snap) {
struct quark_process_iter qi;
struct quark_event fake_ev;
bzero(&fake_ev, sizeof(fake_ev));
quark_process_iter_init(&qi, qq);
while ((fake_ev.process = quark_process_iter_next(&qi)) != NULL)
quark_event_dump(&fake_ev, stdout);
}
/*
* Normal mode, collect, pop and dump elements until we get a sigint
*/
while (!gotsigint && maxnodes == -1) {
qev = quark_queue_get_event(qq);
/* No events, just block */
if (qev == NULL) {
if (quark_queue_block(qq) == -1 && errno != EINTR)
err(1, "quark_queue_block");
continue;
}
/*
* Filter out processes by parent pid if set.
*/
if (filter_ppid &&
qev->process != NULL &&
(qev->process->flags & QUARK_F_PROC) &&
filter_ppid != qev->process->proc_ppid)
continue;
quark_event_dump(qev, stdout);
}
if (graph_by_pidtime != NULL && graph_by_time != NULL) {
if (quark_dump_raw_event_graph(qq, graph_by_time,
graph_by_pidtime) == -1)
warn("quark_dump_raw_event_graph");
fclose(graph_by_time);
fclose(graph_by_pidtime);
graph_by_time = graph_by_pidtime = NULL;
}
if (graph_cache != NULL) {
if (quark_dump_process_cache_graph(qq, graph_cache) == -1)
warn("quark_dump_event_cache_graph");
fclose(graph_cache);
graph_cache = NULL;
}
dump_stats(qq);
quark_queue_close(qq);
free(qq);
return (0);
}