/* proc.c - various functions to deal with stuff in the /proc directory */ #include #include #include #include #include #include #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; }