summaryrefslogtreecommitdiff
path: root/src/proc.c
diff options
context:
space:
mode:
authordaniel <daniel@planethacker.net>2025-05-06 16:57:32 -0700
committerdaniel <daniel@planethacker.net>2025-05-06 16:57:32 -0700
commit2278df1493e064c197913e49b5d1935942d83448 (patch)
tree42f06ab2f76e2ddf228bafbb03f79621975a4534 /src/proc.c
initial import
Diffstat (limited to 'src/proc.c')
-rw-r--r--src/proc.c268
1 files changed, 268 insertions, 0 deletions
diff --git a/src/proc.c b/src/proc.c
new file mode 100644
index 0000000..8381ac7
--- /dev/null
+++ b/src/proc.c
@@ -0,0 +1,268 @@
+/* proc.c - various functions to deal with stuff in the /proc directory */
+
+#include <stdio.h>
+#include <fcntl.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <sys/types.h>
+
+#include <linux/limits.h>
+
+#include "error.h"
+#include "proc.h"
+#include "base64.h"
+#include "string_common.h"
+
+#undef SHOW_READLINK_ERRORS
+#undef SHOW_GET_PROC_STATUS_ERRORS
+
+// helper to build proc paths
+static void build_proc_path(char *buf, size_t size, pid_t pid, const char *suffix) {
+ snprintf(buf, size, "/proc/%d/%s", pid, suffix);
+}
+
+// Get the CWD of a PID from /proc/PID/cwd
+char *proc_cwd(pid_t pid) {
+ char cwd_path[PATH_MAX];
+ static char cwd[PATH_MAX];
+
+ memset(cwd, '\0', sizeof(cwd));
+ build_proc_path(cwd_path, sizeof(cwd_path), pid, "cwd");
+
+ if (readlink(cwd_path, cwd, sizeof(cwd)) == -1) {
+#ifdef SHOW_READLINK_ERRORS
+ error("readlink %s: %s", cwd_path, strerror(errno));
+#endif /* SHOW_READLINK_ERRORS */
+ return NULL;
+ }
+
+ return cwd;
+}
+
+// Get the environment of a PID from /proc/PID/environ
+char *proc_environ(pid_t pid) {
+ int fd;
+ int bytes;
+ char environ_path[PATH_MAX];
+ static char environ[ARG_MAX];
+
+ build_proc_path(environ_path, sizeof(environ_path), pid, "environ");
+
+ fd = open(environ_path, O_RDONLY);
+ if (fd == -1) {
+ return NULL;
+ }
+
+ bytes = read(fd, environ, sizeof(environ));
+ close(fd);
+
+ return (char *)base64_encode((const unsigned char *)environ, bytes, NULL);
+}
+
+// Get the path of a PID's executable file from /proc/PID/exe
+char *proc_get_exe_path(pid_t pid) {
+ char exe_path[PATH_MAX];
+ static char real_path[PATH_MAX];
+
+ memset(real_path, '\0', sizeof(real_path));
+ build_proc_path(exe_path, sizeof(exe_path), pid, "exe");
+
+ if (readlink(exe_path, real_path, sizeof(real_path)) == -1) {
+#ifdef SHOW_READLINK_ERRORS
+ error("readlink %s: %s", exe_path, strerror(errno));
+#endif /* SHOW_READLINK_ERRORS */
+ return NULL;
+ }
+
+ return real_path;
+}
+
+// Get the command line of a PID from /proc/PID/cmdline
+char *proc_get_cmdline(pid_t pid) {
+ int fd;
+ char cmdline_path[PATH_MAX] = {0};
+ static char buf[ARG_MAX];
+ int bytes;
+
+ build_proc_path(cmdline_path, sizeof(cmdline_path), pid, "cmdline");
+
+ /* read *argv[] from cmdline_path */
+ fd = open(cmdline_path, O_RDONLY);
+ if (fd == -1) {
+ return NULL;
+ }
+
+ bytes = read(fd, buf, sizeof(buf));
+ close(fd);
+
+ /* *argv[] is null delimited, replace nulls with spaces */
+ for (int i = 0; i < bytes - 1; i++) {
+ if (buf[i] == '\0') {
+ buf[i] = ' ';
+ }
+ }
+
+ return buf;
+}
+
+// Extract useful information from /proc/PID/status
+struct proc_status proc_get_status(pid_t pid) {
+ FILE *fp;
+ struct proc_status result = {0};
+ char proc_status[PATH_MAX];
+ char buf[1024];
+
+ build_proc_path(proc_status, sizeof(proc_status), pid, "status");
+
+ fp = fopen(proc_status, "r");
+ if (fp == NULL) {
+#ifdef SHOW_GET_PROC_STATUS_ERRORS
+ error("error opening %s: %s", proc_status, strerror(errno));
+#endif
+ result.pid = -1;
+ return result;
+ }
+
+ result.pid = pid;
+
+ while(fgets(buf, sizeof(buf), fp) != NULL) {
+ /*
+ * use a switch on the first letter so we aren't parsing every
+ * line like a villager. This is ugly af but fast.
+ */
+ switch (buf[0]) {
+ case 'C':
+ if (startswith(buf, "CapInh:")) {
+ sscanf(buf, "CapInh:\t%lx", &result.cap_inh);
+ } else if (startswith(buf, "CapPrm:")) {
+ sscanf(buf, "CapPrm:\t%lx", &result.cap_prm);
+ } else if (startswith(buf, "CapEff:")) {
+ sscanf(buf, "CapEff:\t%lx", &result.cap_eff);
+ } else if (startswith(buf, "CapBnd:")) {
+ sscanf(buf, "CapBnd:\t%lx", &result.cap_bnd);
+ } else if (startswith(buf, "CapAmb:")) {
+ sscanf(buf, "CapAmb:\t%lx", &result.cap_amb);
+ }
+ break;
+
+ case 'F':
+ if (startswith(buf, "FDSize:")) {
+ sscanf(buf, "FDSize:\t%u", &result.fdsize);
+ }
+ break;
+
+ case 'G':
+ if (startswith(buf, "Gid:")) {
+ sscanf(buf, "Gid:\t%d\t%d\t%d\t%d\n",
+ &result.gid, &result.egid,
+ &result.ssgid, &result.fsgid);
+ }
+ break;
+
+ case 'N':
+ if (startswith(buf, "Name:")) {
+ sscanf(buf, "Name:\t%16s\n", result.name);
+ } else if (startswith(buf, "NoNewPrivs:")) {
+ sscanf(buf, "NoNewPrivs:\t%d", &result.no_new_privs);
+ }
+ break;
+
+ case 'P':
+ if (startswith(buf, "PPid:")) {
+ sscanf(buf, "PPid:\t%d", &result.ppid);
+ }
+ break;
+
+ case 'S':
+ if (startswith(buf, "State:")) {
+ sscanf(buf, "State:\t%c", &result.state);
+ } else if (startswith(buf, "Seccomp:")) {
+ sscanf(buf, "Seccomp:\t%d", &result.seccomp);
+ }
+ break;
+
+ case 'T':
+ if (startswith(buf, "Tgid:")) {
+ sscanf(buf, "Tgid:\t%d", &result.tgid);
+ } else if (startswith(buf, "Threads:")) {
+ sscanf(buf, "Threads:\t%u", &result.threads);
+ } else if (startswith(buf, "TracerPid:")) {
+ sscanf(buf, "TracerPid:\t%d", &result.tracer_pid);
+ }
+ break;
+
+ case 'U':
+ if (startswith(buf, "Uid:")) {
+ sscanf(buf, "Uid:\t%d\t%d\t%d\t%d\n",
+ &result.uid, &result.euid,
+ &result.ssuid, &result.fsuid);
+ }
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ fclose(fp);
+ return result;
+}
+
+// Get useful information from /proc/PID/stat
+struct proc_stat proc_parse_stat(pid_t pid) {
+ struct proc_stat ps = {0};
+ char path[PATH_MAX];
+ FILE *fp;
+ char buf[4096];
+
+ build_proc_path(path, sizeof(path), pid, "stat");
+
+ fp = fopen(path, "r");
+ if (!fp) {
+ return ps;
+ }
+
+ /*
+ * pid (comm) state ppid pgrp session tty_nr tpgid flags minflt
+ * cminflt majflt cmajflt utime stime cutime cstime priority nice
+ * num_threads itrealvalue starttime vsize rss ...
+ *
+ * comm can have spaces. skip to ')' then read values into
+ * corresponding members of proc_stat structure.
+ */
+ if (fgets(buf, sizeof(buf), fp)) {
+ char *ptr = strchr(buf, ')');
+ if (ptr) {
+ ptr += 2; // skip ") "
+
+ // read state separately to shut up gcc
+ sscanf(ptr, "%c", &ps.state);
+ ptr++;
+ while (*ptr == ' ') ptr++;
+
+ // TODO clean this up.
+ sscanf(ptr,
+ "%*d %*d %*d %d " // skip ppid, pgrp, session. capture tty_nr
+ "%*d %*u %*u %*u %*u %*u %*u %*u %*u "
+ "%lu %lu " // utime stime
+ "%*ld %*ld "
+ "%ld %ld " // priority nice
+ "%*d %*ld "
+ "%lu " // starttime
+ "%lu " // vsize
+ "%ld", // rss
+ &ps.tty_nr,
+ &ps.utime,
+ &ps.stime,
+ &ps.priority,
+ &ps.nice,
+ &ps.starttime,
+ &ps.vsize,
+ &ps.rss);
+ }
+ }
+
+ fclose(fp);
+ return ps;
+}