diff options
Diffstat (limited to 'src/proc.c')
| -rw-r--r-- | src/proc.c | 268 |
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; +} |
