diff options
Diffstat (limited to 'library/readproc.c')
-rw-r--r-- | library/readproc.c | 1627 |
1 files changed, 1627 insertions, 0 deletions
diff --git a/library/readproc.c b/library/readproc.c new file mode 100644 index 0000000..2dfe4c9 --- /dev/null +++ b/library/readproc.c @@ -0,0 +1,1627 @@ +/* + * readproc - interface to process table + * + * Copyright © 2002-2023 Craig Small <csmall@dropbear.xyz> + * Copyright © 2011-2023 Jim Warner <james.warner@comcast.net> + * Copyright © 1998-2010 Albert Cahalan + * Copyright © 2010-2011 Jan Görig <jgorig@redhat.com> + * Copyright © 1998 Michael K. Johnson + * Copyright © 1996 Charles L. Blake. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <stddef.h> +#include <stdio.h> +#include <stdlib.h> +#include <errno.h> +#include <stdarg.h> +#include <string.h> +#include <unistd.h> +#include <signal.h> +#include <fcntl.h> +#include <dirent.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <limits.h> +#include <stdint.h> +#ifdef WITH_SYSTEMD +#include <systemd/sd-login.h> +#endif +#ifdef WITH_ELOGIND +#include <elogind/sd-login.h> +#endif + +#include "devname.h" +#include "escape.h" +#include "misc.h" +#include "pwcache.h" +#include "readproc.h" + +// sometimes it's easier to do this manually, w/o gcc helping +#ifdef PROF +extern void __cyg_profile_func_enter(void*,void*); +#define ENTER(x) __cyg_profile_func_enter((void*)x,(void*)x) +#define LEAVE(x) __cyg_profile_func_exit((void*)x,(void*)x) +#else +#define ENTER(x) +#define LEAVE(x) +#endif + +#ifdef FALSE_THREADS +#define IS_THREAD(q) ( q->tid != q->tgid ) +#endif + +// utility buffers of MAX_BUFSZ bytes each, available to +// any function following an openproc() call +static __thread char *src_buffer, + *dst_buffer; +#define MAX_BUFSZ 1024*64*2 + +// dynamic 'utility' buffer support for file2str() calls +struct utlbuf_s { + char *buf; // dynamically grown buffer + int siz; // current len of the above +} utlbuf_s; + +static int task_dir_missing; + + +// free any additional dynamically acquired storage associated with a proc_t +static inline void free_acquired (proc_t *p) { + /* + * here we free those items that might exist even when not explicitly | + * requested by our caller. it is expected that pid.c will then free | + * any remaining dynamic memory which might be dangling off a proc_t. | */ + if (p->cgname) free(p->cgname); + if (p->cgroup) free(p->cgroup); + if (p->cmd) free(p->cmd); + if (p->sd_mach) free(p->sd_mach); + if (p->sd_ouid) free(p->sd_ouid); + if (p->sd_seat) free(p->sd_seat); + if (p->sd_sess) free(p->sd_sess); + if (p->sd_slice) free(p->sd_slice); + if (p->sd_unit) free(p->sd_unit); + if (p->sd_uunit) free(p->sd_uunit); + if (p->supgid) free(p->supgid); + + memset(p, '\0', sizeof(proc_t)); +} + + +/////////////////////////////////////////////////////////////////////////// + +typedef struct status_table_struct { + unsigned char name[8]; // /proc/*/status field name + unsigned char len; // name length +#ifdef LABEL_OFFSET + long offset; // jump address offset +#else + void *addr; +#endif +} status_table_struct; + +#ifdef LABEL_OFFSET +#define F(x) {#x, sizeof(#x)-1, (long)(&&case_##x-&&base)}, +#else +#define F(x) {#x, sizeof(#x)-1, &&case_##x}, +#endif +#define NUL {"", 0, 0}, + +#define GPERF_TABLE_SIZE 128 + +// Derived from: +// gperf -7 --language=ANSI-C --key-positions=1,3,4 -C -n -c <if-not-piped> +// ( --key-positions verified by omission & reported "Computed positions" ) +// +// Suggested method: +// Grep this file for "case_", then strip those down to the name. +// Eliminate duplicates (due to #ifs), the ' case_' prefix and +// any c comments. Leave the colon and newline so that "Pid:\n", +// "Threads:\n", etc. would be lines, but no quote, no escape, etc. +// +// After a pipe through gperf, insert the resulting 'asso_values' +// into our 'asso' array. Then convert the gperf 'wordlist' array +// into our 'table' array by wrapping the string literals within +// the F macro and replacing empty strings with the NUL define. +// +// In the status_table_struct watch out for name size (grrr, expanding) +// and the number of entries. Currently, the table is padded to 128 +// entries and we therefore mask with 127. + +static int status2proc (char *S, proc_t *restrict P, int is_proc) { + long Threads = 0; + long Tgid = 0; + long Pid = 0; + + // 128 entries because we trust the kernel to use ASCII names + static const unsigned char asso[] = + { + 101, 101, 101, 101, 101, 101, 101, 101, 101, 101, + 101, 101, 101, 101, 101, 101, 101, 101, 101, 101, + 101, 101, 101, 101, 101, 101, 101, 101, 101, 101, + 101, 101, 101, 101, 101, 101, 101, 101, 101, 101, + 101, 101, 101, 101, 101, 101, 101, 101, 101, 101, + 101, 101, 101, 101, 101, 101, 101, 101, 6, 101, + 101, 101, 101, 101, 101, 45, 55, 25, 31, 50, + 50, 10, 0, 35, 101, 101, 21, 101, 30, 101, + 20, 36, 0, 5, 0, 40, 0, 0, 101, 101, + 101, 101, 101, 101, 101, 101, 101, 30, 101, 15, + 0, 1, 101, 10, 101, 10, 101, 101, 101, 25, + 101, 40, 0, 101, 0, 50, 6, 40, 101, 1, + 35, 101, 101, 101, 101, 101, 101, 101 + }; + + static const status_table_struct table[GPERF_TABLE_SIZE] = { + F(VmHWM) + F(Threads) + NUL NUL NUL + F(VmRSS) + F(VmSwap) + NUL NUL NUL + F(Tgid) + F(VmStk) + NUL NUL NUL + F(VmSize) + F(Gid) + NUL NUL NUL + F(VmPTE) + F(VmPeak) + NUL NUL NUL + F(ShdPnd) + F(Pid) + NUL NUL NUL + F(PPid) + F(VmLib) + NUL NUL NUL + F(SigPnd) + F(VmLck) + NUL NUL NUL + F(SigCgt) + F(State) + NUL NUL NUL + F(CapPrm) + F(Uid) + NUL NUL NUL + F(SigIgn) + F(SigQ) + NUL NUL NUL + F(RssShmem) + F(Name) + NUL NUL NUL + F(CapInh) + F(VmData) + NUL NUL NUL + F(FDSize) + NUL NUL NUL NUL + F(SigBlk) + NUL NUL NUL NUL + F(CapEff) + NUL NUL NUL NUL + F(CapBnd) + NUL NUL NUL NUL + F(VmExe) + NUL NUL NUL NUL + F(Groups) + NUL NUL NUL NUL + F(RssAnon) + NUL NUL NUL NUL + F(RssFile) + }; + +#undef F +#undef NUL + +ENTER(0x220); + + goto base; + + for(;;){ + char *colon; + status_table_struct entry; + + // advance to next line + S = strchr(S, '\n'); + if(!S) break; // if no newline + S++; + + // examine a field name (hash and compare) + base: + if(!*S) break; + if((!S[0] || !S[1] || !S[2] || !S[3])) break; + entry = table[(GPERF_TABLE_SIZE -1) & (asso[S[3]&127] + asso[S[2]&127] + asso[S[0]&127])]; + colon = strchr(S, ':'); + if(!colon) break; + if(colon[1]!='\t') break; + if(colon-S != entry.len) continue; + if(memcmp(entry.name,S,colon-S)) continue; + + S = colon+2; // past the '\t' + +#ifdef LABEL_OFFSET + goto *(&&base + entry.offset); +#else + goto *entry.addr; +#endif + + case_Name: + { char buf[64], raw[64]; + unsigned u = 0; + while(u < sizeof(raw) - 1u){ + int c = *S++; + if(c=='\n') break; + if(c=='\0') break; // should never happen + if(c=='\\'){ + c = *S++; + if(c=='\n') break; // should never happen + if(!c) break; // should never happen + if(c=='n') c='\n'; // else we assume it is '\\' + } + raw[u++] = c; + } + raw[u] = '\0'; +#ifdef FALSE_THREADS + if (!IS_THREAD(P)) { +#endif + if (!P->cmd) { + escape_str(buf, raw, sizeof(buf)); + if (!(P->cmd = strdup(buf))) return 1; + } +#ifdef FALSE_THREADS + } +#endif + S--; // put back the '\n' or '\0' + continue; + } + case_ShdPnd: + memcpy(P->signal, S, 16); + P->signal[16] = '\0'; + continue; + case_SigBlk: + memcpy(P->blocked, S, 16); + P->blocked[16] = '\0'; + continue; + case_SigCgt: + memcpy(P->sigcatch, S, 16); + P->sigcatch[16] = '\0'; + continue; + case_SigIgn: + memcpy(P->sigignore, S, 16); + P->sigignore[16] = '\0'; + continue; + case_SigPnd: + memcpy(P->_sigpnd, S, 16); + P->_sigpnd[16] = '\0'; + continue; + case_State: + P->state = *S; + continue; + case_Tgid: + Tgid = strtol(S,&S,10); + continue; + case_Pid: + Pid = strtol(S,&S,10); + continue; + case_PPid: + P->ppid = strtol(S,&S,10); + continue; + case_Threads: + Threads = strtol(S,&S,10); + continue; + case_Uid: + P->ruid = strtol(S,&S,10); + P->euid = strtol(S,&S,10); + P->suid = strtol(S,&S,10); + P->fuid = strtol(S,&S,10); + continue; + case_Gid: + P->rgid = strtol(S,&S,10); + P->egid = strtol(S,&S,10); + P->sgid = strtol(S,&S,10); + P->fgid = strtol(S,&S,10); + continue; + case_VmData: + P->vm_data = (unsigned long)strtol(S,&S,10); + continue; + case_VmExe: + P->vm_exe = (unsigned long)strtol(S,&S,10); + continue; + case_VmLck: + P->vm_lock = (unsigned long)strtol(S,&S,10); + continue; + case_VmLib: + P->vm_lib = (unsigned long)strtol(S,&S,10); + continue; + case_VmRSS: + P->vm_rss = (unsigned long)strtol(S,&S,10); + continue; + case_RssAnon: // subset of VmRSS, linux-4.5 + P->vm_rss_anon = (unsigned long)strtol(S,&S,10); + continue; + case_RssFile: // subset of VmRSS, linux-4.5 + P->vm_rss_file = (unsigned long)strtol(S,&S,10); + continue; + case_RssShmem: // subset of VmRSS, linux-4.5 + P->vm_rss_shared = (unsigned long)strtol(S,&S,10); + continue; + case_VmSize: + P->vm_size = (unsigned long)strtol(S,&S,10); + continue; + case_VmStk: + P->vm_stack = (unsigned long)strtol(S,&S,10); + continue; + case_VmSwap: // Linux 2.6.34 + P->vm_swap = (unsigned long)strtol(S,&S,10); + continue; + case_Groups: + { char *ss = S, *nl = strchr(S, '\n'); + size_t j; + +#ifdef FALSE_THREADS + if (IS_THREAD(P)) continue; +#endif + while (' ' == *ss || '\t' == *ss) ss++; + if (ss >= nl) continue; + j = nl ? (size_t)(nl - ss) : strlen(ss); + if (j > 0 && j < INT_MAX) { + P->supgid = malloc(j+1); // +1 in case space disappears + if (!P->supgid) + return 1; + memcpy(P->supgid, ss, j); + if (' ' != P->supgid[--j]) ++j; + P->supgid[j] = '\0'; // whack the space or the newline + for ( ; j; j--) + if (' ' == P->supgid[j]) + P->supgid[j] = ','; + } + continue; + } + case_CapBnd: + case_CapEff: + case_CapInh: + case_CapPrm: + case_FDSize: + case_SigQ: + case_VmHWM: // 2005, peak VmRSS unless VmRSS is bigger + case_VmPTE: + case_VmPeak: // 2005, peak VmSize unless VmSize is bigger + continue; + } + +#if 0 + // recent kernels supply per-tgid pending signals + if(is_proc && *ShdPnd){ + memcpy(P->signal, ShdPnd, 16); + P->signal[16] = '\0'; + } +#endif + + // recent kernels supply per-tgid pending signals + if(!is_proc || !P->signal[0]){ + memcpy(P->signal, P->_sigpnd, 16); + P->signal[16] = '\0'; + } + + // Linux 2.4.13-pre1 to max 2.4.xx have a useless "Tgid" + // that is not initialized for built-in kernel tasks. + // Only 2.6.0 and above have "Threads" (nlwp) info. + + if(Threads){ + P->nlwp = Threads; + P->tgid = Tgid; // the POSIX PID value + P->tid = Pid; // the thread ID + }else{ + P->nlwp = 1; + P->tgid = Pid; + P->tid = Pid; + } + +#ifdef FALSE_THREADS + if (!IS_THREAD(P)) { +#endif + if (!P->supgid) { + P->supgid = strdup("-"); + if (!P->supgid) + return 1; + } +#ifdef FALSE_THREADS + } +#endif +LEAVE(0x220); + return 0; +} +#undef GPERF_TABLE_SIZE + + +static int supgrps_from_supgids (proc_t *p) { + char *g, *s; + int t; + +#ifdef FALSE_THREADS + if (IS_THREAD(p)) return 0; +#endif + if (!p->supgid || '-' == *p->supgid) + goto wrap_up; + + s = p->supgid; + t = 0; + do { + const int max = P_G_SZ+2; + char *end = NULL; + gid_t gid; + int len; + + while (',' == *s) ++s; + gid = strtol(s, &end, 10); + if (end <= s) break; + s = end; + g = pwcache_get_group(gid); + + if ((t >= INT_MAX - max) + || (!(p->supgrp = realloc(p->supgrp, t + max)))) + return 1; + + len = snprintf(p->supgrp+t, max, "%s%s", t ? "," : "", g); + if (len <= 0) (p->supgrp+t)[len = 0] = '\0'; + else if (len >= max) len = max-1; + t += len; + } while (*s); + +wrap_up: + if (!p->supgrp + && !(p->supgrp = strdup("-"))) + return 1; + return 0; +} + + +/////////////////////////////////////////////////////////////////////// + +static inline void oomscore2proc(const char *S, proc_t *restrict P) +{ + sscanf(S, "%d", &P->oom_score); +} + +static inline void oomadj2proc(const char *S, proc_t *restrict P) +{ + sscanf(S, "%d", &P->oom_adj); +} + + +/////////////////////////////////////////////////////////////////////// + +static int sd2proc (proc_t *restrict p) { +#if defined(WITH_SYSTEMD) || defined(WITH_ELOGIND) + char buf[64]; + uid_t uid; + + if (0 > sd_pid_get_machine_name(p->tid, &p->sd_mach)) { + if (!(p->sd_mach = strdup("-"))) + return 1; + } + if (0 > sd_pid_get_owner_uid(p->tid, &uid)) { + if (!(p->sd_ouid = strdup("-"))) + return 1; + } else { + snprintf(buf, sizeof(buf), "%d", (int)uid); + if (!(p->sd_ouid = strdup(buf))) + return 1; + } + if (0 > sd_pid_get_session(p->tid, &p->sd_sess)) { + if (!(p->sd_sess = strdup("-"))) + return 1; + if (!(p->sd_seat = strdup("-"))) + return 1; + } else { + if (0 > sd_session_get_seat(p->sd_sess, &p->sd_seat)) + if (!(p->sd_seat = strdup("-"))) + return 1; + } + if (0 > sd_pid_get_slice(p->tid, &p->sd_slice)) + if (!(p->sd_slice = strdup("-"))) + return 1; + if (0 > sd_pid_get_unit(p->tid, &p->sd_unit)) + if (!(p->sd_unit = strdup("-"))) + return 1; + if (0 > sd_pid_get_user_unit(p->tid, &p->sd_uunit)) + if (!(p->sd_uunit = strdup("-"))) + return 1; +#else + if (!(p->sd_mach = strdup("?"))) + return 1; + if (!(p->sd_ouid = strdup("?"))) + return 1; + if (!(p->sd_seat = strdup("?"))) + return 1; + if (!(p->sd_sess = strdup("?"))) + return 1; + if (!(p->sd_slice = strdup("?"))) + return 1; + if (!(p->sd_unit = strdup("?"))) + return 1; + if (!(p->sd_uunit = strdup("?"))) + return 1; +#endif + return 0; +} + + +/////////////////////////////////////////////////////////////////////// + +// Reads /proc/*/stat files, being careful not to trip over processes with +// names like ":-) 1 2 3 4 5 6". +static int stat2proc (const char *S, proc_t *restrict P) { + char buf[64], raw[64]; + size_t num; + char *tmp; + +ENTER(0x160); + + /* fill in default values for older kernels */ + P->processor = 0; + P->rtprio = -1; + P->sched = -1; + P->nlwp = 0; + + S = strchr(S, '('); + if (!S) return 0; + S++; + tmp = strrchr(S, ')'); + if (!tmp || !tmp[1]) return 0; +#ifdef FALSE_THREADS + if (!IS_THREAD(P)) { +#endif + if (!P->cmd) { + num = tmp - S; + memcpy(raw, S, num); + raw[num] = '\0'; + escape_str(buf, raw, sizeof(buf)); + if (!(P->cmd = strdup(buf))) return 1; + } +#ifdef FALSE_THREADS + } +#endif + S = tmp + 2; // skip ") " + + sscanf(S, + "%c " // state + "%d %d %d %d %d " // ppid, pgrp, sid, tty_nr, tty_pgrp + "%lu %lu %lu %lu %lu " // flags, min_flt, cmin_flt, maj_flt, cmaj_flt + "%llu %llu %llu %llu " // utime, stime, cutime, cstime + "%d %d " // priority, nice + "%d " // num_threads + "%lu " // 'alarm' == it_real_value (obsolete, always 0) + "%llu " // start_time + "%lu " // vsize + "%lu " // rss + "%lu %lu %lu %lu %lu %lu " // rsslim, start_code, end_code, start_stack, esp, eip + "%*s %*s %*s %*s " // pending, blocked, sigign, sigcatch <=== DISCARDED + "%lu %*u %*u " // 0 (former wchan), 0, 0 <=== Placeholders only + "%d %d " // exit_signal, task_cpu + "%d %d " // rt_priority, policy (sched) + "%llu %llu %llu", // blkio_ticks, gtime, cgtime + &P->state, + &P->ppid, &P->pgrp, &P->session, &P->tty, &P->tpgid, + &P->flags, &P->min_flt, &P->cmin_flt, &P->maj_flt, &P->cmaj_flt, + &P->utime, &P->stime, &P->cutime, &P->cstime, + &P->priority, &P->nice, + &P->nlwp, + &P->alarm, + &P->start_time, + &P->vsize, + &P->rss, + &P->rss_rlim, &P->start_code, &P->end_code, &P->start_stack, &P->kstk_esp, &P->kstk_eip, +/* P->signal, P->blocked, P->sigignore, P->sigcatch, */ /* can't use */ + &P->wchan, /* &P->nswap, &P->cnswap, */ /* nswap and cnswap dead for 2.4.xx and up */ +/* -- Linux 2.0.35 ends here -- */ + &P->exit_signal, &P->processor, /* 2.2.1 ends with "exit_signal" */ +/* -- Linux 2.2.8 to 2.5.17 end here -- */ + &P->rtprio, &P->sched, /* both added to 2.5.18 */ + &P->blkio_tics, &P->gtime, &P->cgtime + ); + + if(!P->nlwp) + P->nlwp = 1; + + return 0; +LEAVE(0x160); +} + + +///////////////////////////////////////////////////////////////////////// + +static void statm2proc(const char *s, proc_t *restrict P) { + sscanf(s, "%lu %lu %lu %lu %lu %lu %lu", + &P->size, &P->resident, &P->share, + &P->trs, &P->lrs, &P->drs, &P->dt); +} + +static void io2proc(const char *s, proc_t *restrict P) { + sscanf(s, "rchar: %lu wchar: %lu syscr: %lu syscw: %lu read_bytes: %lu write_bytes: %lu cancelled_write_bytes: %lu", + &P->rchar, &P->wchar, &P->syscr, + &P->syscw, &P->read_bytes, &P->write_bytes, &P->cancelled_write_bytes); +} + + // Assuming permissions have allowed the read of smaps_rollup, this + // guy will extract some %lu data. Considering the number of items, + // we are between small enough to use a sscanf and large enough for + // a search.h approach. Thus we roll (get it?) our own custom code. +static void smaps2proc (const char *s, proc_t *restrict P) { + #define enMAX (int)((sizeof(smaptab) / sizeof(smaptab[0]))) + // 1st proc_t data field + #define fZERO tid + // a smaptab entry generator + #define mkENT(F) { #F ":", -1, offsetof(proc_t, smap_ ## F) } + // make a target field + #define mkOBJ(X) ( (unsigned long *)((void *)&P->fZERO + smaptab[X].offs) ) + static struct { + const char *item; + int slen; + int offs; + } smaptab[] = { + /* Size smaps only, not rollup */ + /* KernelPageSize " */ + /* MMUPageSize " */ + mkENT(Rss), + mkENT(Pss), + mkENT(Pss_Anon), /* rollup only, not smaps */ + mkENT(Pss_File), /* " */ + mkENT(Pss_Shmem), /* " */ + mkENT(Shared_Clean), + mkENT(Shared_Dirty), + mkENT(Private_Clean), + mkENT(Private_Dirty), + mkENT(Referenced), + mkENT(Anonymous), + mkENT(LazyFree), + mkENT(AnonHugePages), + mkENT(ShmemPmdMapped), + mkENT(FilePmdMapped), + mkENT(Shared_Hugetlb), + mkENT(Private_Hugetlb), + mkENT(Swap), + mkENT(SwapPss), + mkENT(Locked) + /* THPeligible smaps only, not rollup */ + /* ProtectionKey " */ + /* VmFlags " */ + }; + char *head, *tail; + int i; + + if (smaptab[0].slen < 0) { + for (i = 0; i < enMAX; i++) + smaptab[i].slen = (int)strlen(smaptab[i].item); + } + for (i = 0; i < enMAX; i++) { + if (!(head = strstr(s, smaptab[i].item))) + continue; + head += smaptab[i].slen; + *mkOBJ(i) = strtoul(head, &tail, 10); + // saves some overhead BUT makes us dependent on current order + s = tail; + } + #undef enMAX + #undef fZERO + #undef mkENT + #undef mkOBJ +} + +static int file2str(const char *directory, const char *what, struct utlbuf_s *ub) { + #define buffGRW 1024 + char path[PROCPATHLEN]; + int fd, num, tot_read = 0, len; + + /* on first use we preallocate a buffer of minimum size to emulate + former 'local static' behavior -- even if this read fails, that + buffer will likely soon be used for another subdirectory anyway + ( besides, with the calloc call we will never need use memcpy ) */ + if (ub->buf) ub->buf[0] = '\0'; + else { + ub->buf = calloc(1, (ub->siz = buffGRW)); + if (!ub->buf) return -1; + } + len = snprintf(path, sizeof path, "%s/%s", directory, what); + if (len <= 0 || (size_t)len >= sizeof path) return -1; + if (-1 == (fd = open(path, O_RDONLY, 0))) return -1; + while (0 < (num = read(fd, ub->buf + tot_read, ub->siz - tot_read))) { + tot_read += num; + if (tot_read < ub->siz) break; + if (ub->siz >= INT_MAX - buffGRW) { + tot_read--; + break; + } + if (!(ub->buf = realloc(ub->buf, (ub->siz += buffGRW)))) { + close(fd); + return -1; + } + }; + ub->buf[tot_read] = '\0'; + close(fd); + if (tot_read < 1) return -1; + return tot_read; + #undef buffGRW +} + + +static char **file2strvec(const char *directory, const char *what) { + char buf[2048]; /* read buf bytes at a time */ + char *p, *rbuf = 0, *endbuf, **q, **ret, *strp; + int fd, tot = 0, n, c, end_of_file = 0; + int align; + + const int len = snprintf(buf, sizeof buf, "%s/%s", directory, what); + if(len <= 0 || (size_t)len >= sizeof buf) return NULL; + fd = open(buf, O_RDONLY, 0); + if(fd==-1) return NULL; + + /* read whole file into a memory buffer, allocating as we go */ + while ((n = read(fd, buf, sizeof buf - 1)) >= 0) { + if (n < (int)(sizeof buf - 1)) + end_of_file = 1; + if (n <= 0 && tot <= 0) { /* nothing read now, nothing read before */ + break; /* process died between our open and read */ + } + /* ARG_LEN is our guesstimated median length of a command-line argument + or environment variable (the minimum is 1, the maximum is 131072) */ + #define ARG_LEN 64 + if (tot >= INT_MAX / (ARG_LEN + (int)sizeof(char*)) * ARG_LEN - n) { + end_of_file = 1; /* integer overflow: null-terminate and break */ + n = 0; /* but tot > 0 */ + } + #undef ARG_LEN + if (end_of_file && + ((n > 0 && buf[n-1] != '\0') || /* last read char not null */ + (n <= 0 && rbuf && rbuf[tot-1] != '\0'))) /* last read char not null */ + + buf[n++] = '\0'; /* so append null-terminator */ + + if (n <= 0) break; /* unneeded (end_of_file = 1) but avoid realloc */ + rbuf = realloc(rbuf, tot + n); /* allocate more memory */ + if (!rbuf) { + close(fd); + return NULL; + } + memcpy(rbuf + tot, buf, n); /* copy buffer into it */ + tot += n; /* increment total byte ctr */ + if (end_of_file) + break; + } + close(fd); + if (n < 0 || tot <= 0) { /* error, or nothing read */ + if (rbuf) free(rbuf); + return NULL; /* read error */ + } + + rbuf[tot-1] = '\0'; /* belt and suspenders (the while loop did it, too) */ + endbuf = rbuf + tot; /* count space for pointers */ + align = (sizeof(char*)-1) - ((tot + sizeof(char*)-1) & (sizeof(char*)-1)); + c = sizeof(char*); /* one extra for NULL term */ + for (p = rbuf; p < endbuf; p++) { + if (!*p || *p == '\n') { + if (c >= INT_MAX - (tot + (int)sizeof(char*) + align)) break; + c += sizeof(char*); + } + if (*p == '\n') + *p = 0; + } + + rbuf = realloc(rbuf, tot + c + align); /* make room for ptrs AT END */ + if (!rbuf) return NULL; + endbuf = rbuf + tot; /* addr just past data buf */ + q = ret = (char**) (endbuf+align); /* ==> free(*ret) to dealloc */ + for (strp = p = rbuf; p < endbuf; p++) { + if (!*p) { /* NUL char implies that */ + if (c < 2 * (int)sizeof(char*)) break; + c -= sizeof(char*); + *q++ = strp; /* point ptrs to the strings */ + strp = p+1; /* next string -> next char */ + } + } + *q = 0; /* null ptr list terminator */ + return ret; +} + + + // this is the former under utilized 'read_cmdline', which has been + // generalized in support of these new libproc flags: + // PROC_EDITCGRPCVT, PROC_EDITCMDLCVT and PROC_EDITENVRCVT +static int read_unvectored(char *restrict const dst, unsigned sz, const char *whom, const char *what, char sep) { + char path[PROCPATHLEN]; + int fd, len; + unsigned n = 0; + + if(sz <= 0) return 0; + if(sz >= INT_MAX) sz = INT_MAX-1; + dst[0] = '\0'; + + len = snprintf(path, sizeof(path), "%s/%s", whom, what); + if(len <= 0 || (size_t)len >= sizeof(path)) return 0; + fd = open(path, O_RDONLY); + if(fd==-1) return 0; + + for(;;){ + ssize_t r = read(fd,dst+n,sz-n); + if(r==-1){ + if(errno==EINTR) continue; + break; + } + if(r<=0) break; // EOF + n += r; + if(n==sz) { // filled the buffer + --n; // make room for '\0' + break; + } + } + close(fd); + if(n){ + unsigned i = n; + while(i && dst[i-1]=='\0') --i; // skip trailing zeroes + while(i--) + if(dst[i]=='\n' || dst[i]=='\0') dst[i]=sep; + if(dst[n-1]==' ') dst[n-1]='\0'; + } + dst[n] = '\0'; + return n; +} + + +char **vectorize_this_str (const char *src) { + #define pSZ (sizeof(char*)) + char *cpy, **vec; + size_t adj, tot; + + tot = strlen(src) + 1; // prep for our vectors + if (tot < 1 || tot >= INT_MAX) tot = INT_MAX-1; // integer overflow? + adj = (pSZ-1) - ((tot + pSZ-1) & (pSZ-1)); // calc alignment bytes + cpy = calloc(1, tot + adj + (2 * pSZ)); // get new larger buffer + if (!cpy) return NULL; // oops, looks like ENOMEM + snprintf(cpy, tot, "%s", src); // duplicate their string + vec = (char**)(cpy + tot + adj); // prep pointer to pointers + *vec = cpy; // point 1st vector to string + *(vec+1) = NULL; // null ptr 'list' delimit + return vec; // ==> free(*vec) to dealloc + #undef pSZ +} + + + // This littl' guy just serves those true vectorized fields + // ( when a /proc source field didn't exist ) +static int vectorize_dash_rc (char ***vec) { + if (!(*vec = vectorize_this_str("-"))) + return 1; + return 0; +} + + + // This routine reads a 'cgroup' for the designated proc_t and + // guarantees the caller a valid proc_t.cgroup pointer. +static int fill_cgroup_cvt (const char *directory, proc_t *restrict p) { + #define vMAX ( MAX_BUFSZ - (int)(dst - dst_buffer) ) + char *src, *dst, *grp, *eob, *name; + int tot, x, len; + + *(dst = dst_buffer) = '\0'; // empty destination + tot = read_unvectored(src_buffer, MAX_BUFSZ, directory, "cgroup", '\0'); + for (src = src_buffer, eob = src_buffer + tot; src < eob; src += x) { + x = 1; // loop assist + if (!*src) continue; + x = strlen((grp = src)); + if ('/' == grp[x - 1]) continue; // skip empty root cgroups +#if 0 + grp += strspn(grp, "0123456789:"); // jump past group number +#endif + if (vMAX <= 1) break; + len = snprintf(dst, vMAX, "%s", (dst > dst_buffer) ? "," : ""); + if (len < 0 || len >= vMAX) break; + dst += len; + dst += escape_str(dst, grp, vMAX); + } + if (!(p->cgroup = strdup(dst_buffer[0] ? dst_buffer : "-"))) + return 1; + name = strstr(p->cgroup, ":name="); + if (name && *(name+6)) name += 6; else name = p->cgroup; + if (!(p->cgname = strdup(name))) + return 1; + return 0; + #undef vMAX +} + + + // This routine reads a 'cmdline' for the designated proc_t, "escapes" + // the result into a single string while guaranteeing the caller a + // valid proc_t.cmdline pointer. +static int fill_cmdline_cvt (const char *directory, proc_t *restrict p) { + #define uFLG ( ESC_BRACKETS | ESC_DEFUNCT ) + if (read_unvectored(src_buffer, MAX_BUFSZ, directory, "cmdline", ' ')) + escape_str(dst_buffer, src_buffer, MAX_BUFSZ); + else + escape_command(dst_buffer, p, MAX_BUFSZ, uFLG); + p->cmdline = strdup(dst_buffer[0] ? dst_buffer : "?"); + if (!p->cmdline) + return 1; + return 0; + #undef uFLG +} + + + // This routine reads an 'environ' for the designated proc_t and + // guarantees the caller a valid proc_t.environ pointer. +static int fill_environ_cvt (const char *directory, proc_t *restrict p) { + dst_buffer[0] = '\0'; + if (read_unvectored(src_buffer, MAX_BUFSZ, directory, "environ", ' ')) + escape_str(dst_buffer, src_buffer, MAX_BUFSZ); + p->environ = strdup(dst_buffer[0] ? dst_buffer : "-"); + if (!p->environ) + return 1; + return 0; +} + + + // Provide the means to value proc_t.lxcname (perhaps only with "-") while + // tracking all names already seen thus avoiding the overhead of repeating + // malloc() and free() calls. +static char *lxc_containers (const char *path) { + static __thread struct utlbuf_s ub = { NULL, 0 }; // util buffer for whole cgroup + static char lxc_none[] = "-"; + static char lxc_oops[] = "?"; // used when memory alloc fails + /* + try to locate the lxc delimiter eyecatcher somewhere in a task's cgroup + directory -- the following are from nested privileged plus unprivileged + containers, where the '/lxc/' delimiter precedes the container name ... + 10:cpuset:/lxc/lxc-P/lxc/lxc-P-nested + 10:cpuset:/user.slice/user-1000.slice/session-c2.scope/lxc/lxc-U/lxc/lxc-U-nested + + ... some minor complications are the potential addition of more cgroups + for a controller displacing the lxc name (normally last on a line), and + environments with unexpected /proc/##/cgroup ordering/contents as with: + 10:cpuset:/lxc/lxc-P/lxc/lxc-P-nested/MY-NEW-CGROUP + or + 2:name=systemd:/ + 1:cpuset,cpu,cpuacct,devices,freezer,net_cls,blkio,perf_event,net_prio:/lxc/lxc-P + */ + if (file2str(path, "cgroup", &ub) > 0) { + /* ouch, the next defaults could be changed at lxc ./configure time + ( and a changed 'lxc.cgroup.pattern' is only available to root ) */ + static const char *lxc_delm1 = "lxc.payload."; // with lxc-4.0.0 + static const char *lxc_delm2 = "lxc.payload/"; // thru lxc-3.2.1 + static const char *lxc_delm3 = "lxc/"; // thru lxc-3.0.3 + const char *delim; + char *p1; + + if ((p1 = strstr(ub.buf, (delim = lxc_delm1))) + || ((p1 = strstr(ub.buf, (delim = lxc_delm2))) + || ((p1 = strstr(ub.buf, (delim = lxc_delm3)))))) { + static __thread struct lxc_ele { + struct lxc_ele *next; + char *name; + } *anchor = NULL; + struct lxc_ele *ele = anchor; + int delim_len = strlen(delim); + char *p2; + + if ((p2 = strchr(p1, '\n'))) // isolate a controller's line + *p2 = '\0'; + do { // deal with nested containers + p2 = p1 + delim_len; + p1 = strstr(p2, delim); + } while (p1); + if ((p1 = strchr(p2, '/'))) // isolate name only substring + *p1 = '\0'; + while (ele) { // have we already seen a name + if (!strcmp(ele->name, p2)) + return ele->name; // return just a recycled name + ele = ele->next; + } + if (!(ele = (struct lxc_ele *)malloc(sizeof(struct lxc_ele)))) + return lxc_oops; + if (!(ele->name = strdup(p2))) { + free(ele); + return lxc_oops; + } + ele->next = anchor; // push the new container name + anchor = ele; + return ele->name; // return a new container name + } + } + return lxc_none; +} + + + // Provide the user id at login (or -1 if not available) +static int login_uid (const char *path) { + char buf[PROCPATHLEN]; + int fd, id, in; + + id = -1; + snprintf(buf, sizeof(buf), "%s/loginuid", path); + if ((fd = open(buf, O_RDONLY, 0)) != -1) { + in = read(fd, buf, sizeof(buf) - 1); + close(fd); + if (in > 0) { + buf[in] = '\0'; + id = atoi(buf); + } + } + return id; +} + + +static char *readlink_exe (const char *path){ + char buf[PROCPATHLEN]; + int in; + + snprintf(buf, sizeof(buf), "%s/exe", path); + in = (int)readlink(buf, src_buffer, MAX_BUFSZ-1); + if (in > 0) { + src_buffer[in] = '\0'; + escape_str(dst_buffer, src_buffer, MAX_BUFSZ); + return strdup(dst_buffer); + } + return strdup("-"); +} + + + // Provide the autogroup fields (or -1 if not available) +static void autogroup_fill (const char *path, proc_t *p) { + char buf[PROCPATHLEN]; + int fd, in; + + p->autogrp_id = -1; + snprintf(buf, sizeof(buf), "%s/autogroup", path); + if ((fd = open(buf, O_RDONLY, 0)) != -1) { + in = read(fd, buf, sizeof(buf) - 1); + close(fd); + if (in > 0) { + buf[in] = '\0'; + sscanf(buf, "/autogroup-%d nice %d" + , &p->autogrp_id, &p->autogrp_nice); + } + } +} + + +/////////////////////////////////////////////////////////////////////// + +/* These are some nice GNU C expression subscope "inline" functions. + * The can be used with arbitrary types and evaluate their arguments + * exactly once. + */ + +/* Test if item X of type T is present in the 0 terminated list L */ +# define XinL(T, X, L) ( { \ + T x = (X), *l = (L); \ + while (*l && *l != x) l++; \ + *l == x; \ + } ) + +/* Test if item X of type T is present in the list L of length N */ +# define XinLN(T, X, L, N) ( { \ + T x = (X), *l = (L); \ + int i = 0, n = (N); \ + while (i < n && l[i] != x) i++; \ + i < n && l[i] == x; \ + } ) + + +////////////////////////////////////////////////////////////////////////////////// +// This reads process info from /proc in the traditional way, for one process. +// The pid (tgid? tid?) is already in p, and a path to it in path, with some +// room to spare. +static proc_t *simple_readproc(PROCTAB *restrict const PT, proc_t *restrict const p) { + static __thread struct utlbuf_s ub = { NULL, 0 }; // buf for stat,statm,status + static __thread struct stat sb; // stat() buffer + char *restrict const path = PT->path; + unsigned flags = PT->flags; + int rc = 0; + + if (stat(path, &sb) == -1) /* no such dirent (anymore) */ + goto next_proc; + + if ((flags & PROC_UID) && !XinLN(uid_t, sb.st_uid, PT->uids, PT->nuid)) + goto next_proc; /* not one of the requested uids */ + + p->euid = sb.st_uid; /* need a way to get real uid */ + p->egid = sb.st_gid; /* need a way to get real gid */ + + if (flags & PROC_FILLSTAT) { // read /proc/#/stat + if (file2str(path, "stat", &ub) == -1) + goto next_proc; + rc += stat2proc(ub.buf, p); + } + + if (flags & PROC_FILLIO) { // read /proc/#/io + if (file2str(path, "io", &ub) != -1) + io2proc(ub.buf, p); + } + + if (flags & PROC_FILLSMAPS) { // read /proc/#/smaps_rollup + if (file2str(path, "smaps_rollup", &ub) != -1) + smaps2proc(ub.buf, p); + } + + if (flags & PROC_FILLMEM) { // read /proc/#/statm + if (file2str(path, "statm", &ub) != -1) + statm2proc(ub.buf, p); + } + + if (flags & PROC_FILLSTATUS) { // read /proc/#/status + if (file2str(path, "status", &ub) != -1){ + rc += status2proc(ub.buf, p, 1); + if (flags & (PROC_FILL_SUPGRP & ~PROC_FILLSTATUS)) + rc += supgrps_from_supgids(p); + if (flags & (PROC_FILL_OUSERS & ~PROC_FILLSTATUS)) { + p->ruser = pwcache_get_user(p->ruid); + p->suser = pwcache_get_user(p->suid); + p->fuser = pwcache_get_user(p->fuid); + } + if (flags & (PROC_FILL_OGROUPS & ~PROC_FILLSTATUS)) { + p->rgroup = pwcache_get_group(p->rgid); + p->sgroup = pwcache_get_group(p->sgid); + p->fgroup = pwcache_get_group(p->fgid); + } + } + } + + // if multithreaded, some values are crap + if(p->nlwp > 1) + p->wchan = ~0ul; + + /* some number->text resolving which is time consuming */ + /* ( names are cached, so memcpy to arrays was silly ) */ + if (flags & PROC_FILLUSR) + p->euser = pwcache_get_user(p->euid); + if (flags & PROC_FILLGRP) + p->egroup = pwcache_get_group(p->egid); + + if (flags & PROC_FILLENV) // read /proc/#/environ + if (!(p->environ_v = file2strvec(path, "environ"))) + rc += vectorize_dash_rc(&p->environ_v); + if (flags & PROC_EDITENVRCVT) + rc += fill_environ_cvt(path, p); + + if (flags & PROC_FILLARG) // read /proc/#/cmdline + if (!(p->cmdline_v = file2strvec(path, "cmdline"))) + rc += vectorize_dash_rc(&p->cmdline_v); + if (flags & PROC_EDITCMDLCVT) + rc += fill_cmdline_cvt(path, p); + + if ((flags & PROC_FILLCGROUP)) // read /proc/#/cgroup + if (!(p->cgroup_v = file2strvec(path, "cgroup"))) + rc += vectorize_dash_rc(&p->cgroup_v); + if (flags & PROC_EDITCGRPCVT) + rc += fill_cgroup_cvt(path, p); + + if (flags & PROC_FILLOOM) { + if (file2str(path, "oom_score", &ub) != -1) + oomscore2proc(ub.buf, p); + if (file2str(path, "oom_score_adj", &ub) != -1) + oomadj2proc(ub.buf, p); + } + + if (flags & PROC_FILLNS) // read /proc/#/ns/* + procps_ns_read_pid(p->tid, &(p->ns)); + + + if (flags & PROC_FILLSYSTEMD) // get sd-login.h stuff + rc += sd2proc(p); + + if (flags & PROC_FILL_LXC) // value the lxc name + p->lxcname = lxc_containers(path); + + if (flags & PROC_FILL_LUID) // value the login user id + p->luid = login_uid(path); + + if (flags & PROC_FILL_EXE) { + if (!(p->exe = readlink_exe(path))) + rc += 1; + } + + if (flags & PROC_FILLAUTOGRP) // value the 2 autogroup fields + autogroup_fill(path, p); + + // openproc() ensured that a ppid will be present when needed ... + if (rc == 0) { + if (PT->hide_kernel && (p->ppid == 2 || p->tid == 2)) { + free_acquired(p); + return NULL; + } + return p; + } + errno = ENOMEM; +next_proc: + return NULL; +} + + +////////////////////////////////////////////////////////////////////////////////// +// This reads /proc/*/task/* data, for one task. +// t is the POSIX thread (task group member, generally not the leader) +// path is a path to the task, with some room to spare. +static proc_t *simple_readtask(PROCTAB *restrict const PT, proc_t *restrict const t, char *restrict const path) { + static __thread struct utlbuf_s ub = { NULL, 0 }; // buf for stat,statm,status + static __thread struct stat sb; // stat() buffer + unsigned flags = PT->flags; + int rc = 0; + + if (stat(path, &sb) == -1) /* no such dirent (anymore) */ + goto next_task; + +// if ((flags & PROC_UID) && !XinLN(uid_t, sb.st_uid, PT->uids, PT->nuid)) +// goto next_task; /* not one of the requested uids */ + + t->euid = sb.st_uid; /* need a way to get real uid */ + t->egid = sb.st_gid; /* need a way to get real gid */ + + if (flags & PROC_FILLSTAT) { // read /proc/#/task/#/stat + if (file2str(path, "stat", &ub) == -1) + goto next_task; + rc += stat2proc(ub.buf, t); + } + + if (flags & PROC_FILLIO) { // read /proc/#/task/#/io + if (file2str(path, "io", &ub) != -1) + io2proc(ub.buf, t); + } + + if (flags & PROC_FILLSMAPS) { // read /proc/#/task/#/smaps_rollup + if (file2str(path, "smaps_rollup", &ub) != -1) + smaps2proc(ub.buf, t); + } + + if (flags & PROC_FILLMEM) { // read /proc/#/task/#/statm + if (file2str(path, "statm", &ub) != -1) + statm2proc(ub.buf, t); + } + + if (flags & PROC_FILLSTATUS) { // read /proc/#/task/#/status + if (file2str(path, "status", &ub) != -1) { + rc += status2proc(ub.buf, t, 0); + if (flags & (PROC_FILL_SUPGRP & ~PROC_FILLSTATUS)) + rc += supgrps_from_supgids(t); + if (flags & (PROC_FILL_OUSERS & ~PROC_FILLSTATUS)) { + t->ruser = pwcache_get_user(t->ruid); + t->suser = pwcache_get_user(t->suid); + t->fuser = pwcache_get_user(t->fuid); + } + if (flags & (PROC_FILL_OGROUPS & ~PROC_FILLSTATUS)) { + t->rgroup = pwcache_get_group(t->rgid); + t->sgroup = pwcache_get_group(t->sgid); + t->fgroup = pwcache_get_group(t->fgid); + } + } + } + + /* some number->text resolving which is time consuming */ + /* ( names are cached, so memcpy to arrays was silly ) */ + if (flags & PROC_FILLUSR) + t->euser = pwcache_get_user(t->euid); + if (flags & PROC_FILLGRP) + t->egroup = pwcache_get_group(t->egid); + +#ifdef FALSE_THREADS + if (!IS_THREAD(t)) { +#endif + if (flags & PROC_FILLARG) // read /proc/#/task/#/cmdline + if (!(t->cmdline_v = file2strvec(path, "cmdline"))) + rc += vectorize_dash_rc(&t->cmdline_v); + if (flags & PROC_EDITCMDLCVT) + rc += fill_cmdline_cvt(path, t); + + if (flags & PROC_FILLENV) // read /proc/#/task/#/environ + if (!(t->environ_v = file2strvec(path, "environ"))) + rc += vectorize_dash_rc(&t->environ_v); + if (flags & PROC_EDITENVRCVT) + rc += fill_environ_cvt(path, t); + + if ((flags & PROC_FILLCGROUP)) // read /proc/#/task/#/cgroup + if (!(t->cgroup_v = file2strvec(path, "cgroup"))) + rc += vectorize_dash_rc(&t->cgroup_v); + if (flags & PROC_EDITCGRPCVT) + rc += fill_cgroup_cvt(path, t); + + if (flags & PROC_FILLSYSTEMD) // get sd-login.h stuff + rc += sd2proc(t); + + if (flags & PROC_FILL_EXE) { + if (!(t->exe = readlink_exe(path))) + rc += 1; + } +#ifdef FALSE_THREADS + } +#endif + + if (flags & PROC_FILLOOM) { + if (file2str(path, "oom_score", &ub) != -1) + oomscore2proc(ub.buf, t); + if (file2str(path, "oom_score_adj", &ub) != -1) + oomadj2proc(ub.buf, t); + } + if (flags & PROC_FILLNS) // read /proc/#/task/#/ns/* + procps_ns_read_pid(t->tid, &(t->ns)); + + if (flags & PROC_FILL_LXC) + t->lxcname = lxc_containers(path); + + if (flags & PROC_FILL_LUID) + t->luid = login_uid(path); + + if (flags & PROC_FILLAUTOGRP) // value the 2 autogroup fields + autogroup_fill(path, t); + + // openproc() ensured that a ppid will be present when needed ... + if (rc == 0) { + if (PT->hide_kernel && (t->ppid == 2 || t->tid == 2)) { + free_acquired(t); + return NULL; + } + return t; + } + errno = ENOMEM; +next_task: + return NULL; +} + + +////////////////////////////////////////////////////////////////////////////////// +// This finds processes in /proc in the traditional way. +// Return non-zero on success. +static int simple_nextpid(PROCTAB *restrict const PT, proc_t *restrict const p) { + static __thread struct dirent *ent; /* dirent handle */ + char *restrict const path = PT->path; + for (;;) { + ent = readdir(PT->procfs); + if (!ent || !ent->d_name[0]) break; + if (*ent->d_name > '0' && *ent->d_name <= '9') { + errno = 0; + p->tgid = strtoul(ent->d_name, NULL, 10); + if (errno == 0) { + p->tid = p->tgid; + snprintf(path, PROCPATHLEN, "/proc/%d", p->tgid); + return 1; + } + } + } + return 0; +} + + +////////////////////////////////////////////////////////////////////////////////// +// This finds tasks in /proc/*/task/ in the traditional way. +// Return non-zero on success. +static int simple_nexttid(PROCTAB *restrict const PT, const proc_t *restrict const p, proc_t *restrict const t, char *restrict const path) { + static __thread struct dirent *ent; /* dirent handle */ + if(PT->taskdir_user != p->tgid){ + if(PT->taskdir){ + closedir(PT->taskdir); + } + // use "path" as some tmp space + snprintf(path, PROCPATHLEN, "/proc/%d/task", p->tgid); + PT->taskdir = opendir(path); + if(!PT->taskdir) return 0; + PT->taskdir_user = p->tgid; + } + for (;;) { + ent = readdir(PT->taskdir); + if(!ent || !ent->d_name[0]) return 0; + if(*ent->d_name > '0' && *ent->d_name <= '9') break; + } + t->tid = strtoul(ent->d_name, NULL, 10); + t->tgid = p->tgid; +//t->ppid = p->ppid; // cover for kernel behavior? we want both actually...? + snprintf(path, PROCPATHLEN, "/proc/%d/task/%.10s", p->tgid, ent->d_name); + return 1; +} + + +////////////////////////////////////////////////////////////////////////////////// +// This "finds" processes in a list that was given to openproc(). +// Return non-zero on success. (tgid is a real headache) +static int listed_nextpid (PROCTAB *PT, proc_t *p) { + static __thread struct utlbuf_s ub = { NULL, 0 }; + pid_t pid = *(PT->pids)++; + char *path = PT->path; + + if (pid) { + snprintf(path, PROCPATHLEN, "/proc/%d", pid); + p->tid = p->tgid = pid; // this tgid may be a huge fib | + + /* the 'status' directory is the only place where we find the | + task's real tgid. it's a bit expensive, but remember we're | + dealing with fewer processes, unlike the other 'next' guys | + (plus we need not parse the whole thing like status2proc)! | */ + + if (file2str(path, "status", &ub) != -1) { + char *str = strstr(ub.buf, "Tgid:"); + if (str) + p->tgid = atoi(str + 5); // this tgid is the proper one | + } + } + return pid; +} + + +////////////////////////////////////////////////////////////////////////////////// +/* readproc: return a pointer to a proc_t filled with requested info about the + * next process available matching the restriction set. If no more such + * processes are available, return a null pointer (boolean false). Use the + * passed buffer instead of allocating space if it is non-NULL. */ + +/* This is optimized so that if a PID list is given, only those files are + * searched for in /proc. If other lists are given in addition to the PID list, + * the same logic can follow through as for the no-PID list case. This is + * fairly complex, but it does try to not to do any unnecessary work. + */ +proc_t *readproc(PROCTAB *restrict const PT, proc_t *restrict p) { + proc_t *ret; + + free_acquired(p); + + for(;;){ + // fills in the path, plus p->tid and p->tgid + if (!PT->finder(PT,p)) goto out; + + // go read the process data + ret = PT->reader(PT,p); + if(ret) return ret; + } + +out: + return NULL; +} + + +////////////////////////////////////////////////////////////////////////////////// +// readeither: return a pointer to a proc_t filled with requested info about +// the next unique process or task available. If no more are available, +// return a null pointer (boolean false). +proc_t *readeither (PROCTAB *restrict const PT, proc_t *restrict x) { + static __thread proc_t skel_p; // skeleton proc_t, only uses tid + tgid + static __thread proc_t *new_p; // for process/task transitions + static __thread int canary; + char path[PROCPATHLEN]; + proc_t *ret; + + free_acquired(x); + + if (new_p) { + if (new_p->tid != canary) new_p = NULL; + goto next_task; + } + +next_proc: + new_p = NULL; + for (;;) { + // fills in the PT->path, plus skel_p.tid and skel_p.tgid + if (!PT->finder(PT,&skel_p)) goto end_procs; // simple_nextpid + if (!task_dir_missing) break; + if ((ret = PT->reader(PT,x))) return ret; // simple_readproc + } + +next_task: + // fills in our path, plus x->tid and x->tgid + if ((!(PT->taskfinder(PT,&skel_p,x,path))) // simple_nexttid + || (!(ret = PT->taskreader(PT,x,path)))) { // simple_readtask + goto next_proc; + } + if (!new_p) { + new_p = ret; + canary = new_p->tid; + } + return ret; + +end_procs: + return NULL; +} + + +////////////////////////////////////////////////////////////////////////////////// + +// initiate a process table scan +PROCTAB *openproc(unsigned flags, ...) { + va_list ap; + struct stat sbuf; + static __thread int did_stat; + static __thread int hide_kernel = -1; + PROCTAB *PT = calloc(1, sizeof(PROCTAB)); + + if (!PT) + return NULL; + if (hide_kernel < 0) + hide_kernel = (NULL != getenv("LIBPROC_HIDE_KERNEL")); + if (!did_stat){ + task_dir_missing = stat("/proc/self/task", &sbuf); + did_stat = 1; + } + PT->taskdir = NULL; + PT->taskdir_user = -1; + PT->taskfinder = simple_nexttid; + PT->taskreader = simple_readtask; + + PT->reader = simple_readproc; + if (flags & PROC_PID){ + PT->procfs = NULL; + PT->finder = listed_nextpid; + }else{ + PT->procfs = opendir("/proc"); + if (!PT->procfs) { free(PT); return NULL; } + PT->finder = simple_nextpid; + } + PT->flags = flags; + + va_start(ap, flags); + if (flags & PROC_PID) + PT->pids = va_arg(ap, pid_t*); + else if (flags & PROC_UID){ + PT->uids = va_arg(ap, uid_t*); + PT->nuid = va_arg(ap, int); + } + va_end(ap); + + if (hide_kernel > 0) { + PT->hide_kernel = 1; + // we'll need the ppid, ensure it's obtained via cheapest means ... + if (!(PT->flags & (PROC_FILLSTAT | PROC_FILLSTATUS))) + PT->flags |= PROC_FILLSTAT; + } + + if (!src_buffer + && !(src_buffer = malloc(MAX_BUFSZ))) { + closedir(PT->procfs); + free(PT); + return NULL; + } + if (!dst_buffer + && !(dst_buffer = malloc(MAX_BUFSZ))) { + closedir(PT->procfs); + free(src_buffer); + free(PT); + return NULL; + } + + return PT; +} + + +// terminate a process table scan +void closeproc(PROCTAB *PT) { + if (PT){ + if (PT->procfs) closedir(PT->procfs); + if (PT->taskdir) closedir(PT->taskdir); + memset(PT,'#',sizeof(PROCTAB)); + free(PT); + } +} + + +////////////////////////////////////////////////////////////////////////////////// +int look_up_our_self(void) { + struct utlbuf_s ub = { NULL, 0 }; + int rc = 0; + proc_t p; + + memset(&p, 0, sizeof(proc_t)); + if(file2str("/proc/self", "stat", &ub) == -1){ + fprintf(stderr, "Error, do this: mount -t proc proc /proc\n"); + _exit(47); + } + rc = stat2proc(ub.buf, &p); // parse /proc/self/stat + free_acquired(&p); + free(ub.buf); + return !rc; +} + +#undef IS_THREAD +#undef MAX_BUFSZ |