/* This file is part of genrc Copyryght (C) 2018 Sergey Poznyakoff License GPLv3+: GNU GPL version 3 or later This is free software: you are free to change and redistribute it. There is NO WARRANTY, to the extent permitted by law. */ #include "genrc.h" #include #include char * catfile(char const *dir, char const *file) { char *ret; size_t dlen, flen; dlen = strlen(dir); if (dlen > 0 && dir[dlen-1] == '/') dlen--; flen = strlen(file); ret = xmalloc(dlen + flen + 2); memcpy(ret, dir, dlen); ret[dlen] = '/'; strcpy(ret + dlen + 1, file); return ret; } #define INITIAL_READLINK_SIZE 128 static int areadlink(const char *name, char **pbuf) { size_t status = 0; char *buf; size_t size; ssize_t linklen; size = INITIAL_READLINK_SIZE; buf = malloc(size); if (!buf) return -1; while (1) { char *p; size_t newsize = size << 1; if (newsize < size) { status = ENAMETOOLONG; break; } size = newsize; p = realloc(buf, size); if (!p) free(buf); buf = p; if (!buf) { status = ENOMEM; break; } linklen = readlink(name, buf, size); if (linklen < 0 && errno != ERANGE) { status = errno; break; } if ((size_t) linklen < size) { buf[linklen++] = '\0'; status = 0; break; } } if (status) { free(buf); errno = status; return -1; } *pbuf = buf; return 0; } pid_t strtopid(char const *str) { unsigned long n; char *end; errno = 0; n = strtoul(str, &end, 10); if (errno || *end) return -1; return (pid_t) n; } struct proc_pid_closure { struct genrc_pid_closure generic; PROCSCANBUF buf; }; pid_t parent_pid(pid_t p) { FILE *fp; char *filename = NULL; size_t s = 0; char *line = NULL; ssize_t n; struct wordsplit ws; pid_t pid; grecs_asprintf(&filename, &s, "/proc/%lu/stat", (unsigned long)p); fp = fopen(filename, "r"); if (!fp) { system_error(errno, "can't open file %s", filename); free(filename); return -1; } s = 0; n = grecs_getline(&line, &s, fp); fclose(fp); if (n == -1) { system_error(errno, "error reading from %s", filename); free(filename); return -1; } free(filename); if (n == 0) return 0; ws.ws_delim = " "; ws.ws_error = genrc_error; if (wordsplit(line, &ws, WRDSF_NOCMD|WRDSF_NOVAR|WRDSF_QUOTE| WRDSF_DELIM|WRDSF_ENOMEMABRT|WRDSF_SHOWERR|WRDSF_ERROR)) exit(1); pid = strtopid(ws.ws_wordv[3]); wordsplit_free(&ws); return pid; } static void parent_pids_filter(PIDLIST *plist) { int more; size_t i; do { more = 0; for (i = 0; i < plist->pidc; ) { pid_t ppid = parent_pid(plist->pidv[i]); if (ppid > 1 && pidlist_member(plist, ppid)) { pidlist_remove(plist, i); more = 1; } else i++; } } while (more); } static int match_exe(struct proc_pid_closure *clos, char const *dir) { char *fname = catfile(dir, "exe"); char *exename; struct stat st; int rc = 0; if (lstat(fname, &st)) { if (errno != ENOENT) system_error(errno, "stat %s", fname); rc = -1; } else { if (!S_ISLNK(st.st_mode)) { rc = -1; } else if (areadlink(fname, &exename)) { if (errno != ENOENT) system_error(errno, "readlink %s", fname); rc = -1; } } free(fname); if (rc == 0) { rc = procscan_match(clos->buf, exename); free(exename); } return rc; } static int match_cmdline(struct proc_pid_closure *clos, char const *dir) { char *fname = catfile(dir, "cmdline"); FILE *fp; int c; int nul = 0; grecs_txtacc_t acc; int rc; char *cmdline; fp = fopen(fname, "r"); if (!fp) { system_error(errno, "fopen %s", fname); free(fname); return -1; } acc = grecs_txtacc_create(); while ((c = fgetc(fp)) != EOF) { if (nul) { grecs_txtacc_grow_char(acc, ' '); nul = 0; } if (c == 0) nul = 1; else grecs_txtacc_grow_char(acc, c); } fclose(fp); free(fname); grecs_txtacc_grow_char(acc, 0); cmdline = grecs_txtacc_finish(acc, 0); rc = procscan_match(clos->buf, cmdline); grecs_txtacc_free(acc); return rc; } static int match_argv0(struct proc_pid_closure *clos, char const *dir) { char *fname = catfile(dir, "cmdline"); FILE *fp; int c; grecs_txtacc_t acc; int rc; char *argv0; fp = fopen(fname, "r"); if (!fp) { system_error(errno, "fopen %s", fname); free(fname); return -1; } acc = grecs_txtacc_create(); while ((c = fgetc(fp)) != EOF) { grecs_txtacc_grow_char(acc, c); if (c == 0) break; } fclose(fp); free(fname); argv0 = grecs_txtacc_finish(acc, 0); rc = procscan_match(clos->buf, argv0); grecs_txtacc_free(acc); return rc; } static void scandir(struct proc_pid_closure *clos, char const *dir, PIDLIST *plist) { int rc; struct stat st; if (stat(dir, &st)) { if (errno != ENOENT) system_error(errno, "stat %s", dir); return; } if (!S_ISDIR(st.st_mode)) return; if (clos->buf->flags & PROCF_CMDLINE) { rc = match_cmdline(clos, dir); } else if (clos->buf->flags & PROCF_EXE) { rc = match_exe(clos, dir); } else { rc = match_argv0(clos, dir); } if (rc == 0) { pidlist_add(plist, strtopid(dir + sizeof("/proc"))); } } static int globerr(const char *epath, int ec) { system_error(ec, "%s", epath); return 0; } static int pid_proc_get(GENRC_PID_CLOSURE *clos, PIDLIST *plist) { struct proc_pid_closure *proc_clos = (struct proc_pid_closure *)clos; glob_t gl; char const *dirpat = "/proc/[0-9]*"; int i; switch (glob(dirpat, GLOB_NOSORT|GLOB_ONLYDIR, globerr, &gl)) { case GLOB_NOSPACE: grecs_alloc_die(); case GLOB_ABORTED: genrc_error("read error during glob scan"); exit(1); case GLOB_NOMATCH: return 0; } for (i = 0; i < gl.gl_pathc; i++) { scandir(proc_clos, gl.gl_pathv[i], plist); } globfree(&gl); if (plist->pidc > 1 && !(proc_clos->buf->flags & PROCF_ALL)) parent_pids_filter(plist); return 0; } GENRC_PID_CLOSURE * genrc_pid_proc_init(int argc, char **argv) { struct proc_pid_closure *clos; if (argc > 3) usage_error("expected format: PROC[:[][:]]"); clos = xzalloc(sizeof *clos); clos->buf = procscan_init((argc >= 2 && argv[1][0] != 0) ? argv[1] : NULL, argc > 2 ? argv[2] : NULL); clos->generic.pid = pid_proc_get; return (GENRC_PID_CLOSURE *)clos; }