master xplshn/aruu / cmd / extra / pgrep.c
  1/* see LICENSE file for copyright and license details */
  2#include <sys/types.h>
  3
  4#include <errno.h>
  5#include <libgen.h>
  6#include <limits.h>
  7#include <pwd.h>
  8#include <regex.h>
  9#include <stdio.h>
 10#include <stdlib.h>
 11#include <string.h>
 12#include <unistd.h>
 13
 14#include "paths.h"
 15#include "proc.h"
 16#include "util.h"
 17
 18enum {
 19  PGREP_fflag = 1 << 0, /* match against full cmdline, not just comm */
 20  PGREP_lflag = 1 << 1, /* list pid and comm */
 21  PGREP_aflag = 1 << 2, /* list pid and full cmdline */
 22  PGREP_xflag = 1 << 3, /* require an exact match */
 23  PGREP_vflag = 1 << 4, /* invert: print non-matching pids */
 24  PGREP_cflag = 1 << 5, /* print a match count, not the pids */
 25};
 26
 27struct hit {
 28  pid_t              pid;
 29  unsigned long long starttime;
 30};
 31
 32static int         flags;
 33static regex_t     preg;
 34static uid_t       user_uid;
 35static int         have_user;
 36static pid_t       self;
 37static struct hit  *hits;
 38static size_t       nhits, hitscap;
 39static int          count;
 40
 41// newest/oldest are mutually exclusive with each other and with
 42// printing every match as it is found, so they buffer every match
 43// instead of streaming it: 0 means "print as found"
 44enum { PGREP_NONE = 0, PGREP_NEWEST, PGREP_OLDEST };
 45static int select_mode;
 46
 47static int
 48matches(const char *target)
 49{
 50  regmatch_t m;
 51  int        r;
 52
 53  r = regexec(&preg, target, 1, &m, 0);
 54  if (r != 0)
 55    return 0;
 56  if (flags & PGREP_xflag)
 57    return m.rm_so == 0 && (size_t)m.rm_eo == strlen(target);
 58  return 1;
 59}
 60
 61static void
 62record(pid_t pid, unsigned long long starttime)
 63{
 64  if (nhits == hitscap) {
 65    hitscap = hitscap ? hitscap * 2 : 64;
 66    hits    = ereallocarray(hits, hitscap, sizeof(*hits));
 67  }
 68  hits[nhits].pid       = pid;
 69  hits[nhits].starttime = starttime;
 70  nhits++;
 71}
 72
 73static void
 74show(pid_t pid, const char *comm)
 75{
 76  char cmdline[BUFSIZ];
 77
 78  count++;
 79  if (flags & PGREP_cflag)
 80    return;
 81  if (flags & PGREP_aflag) {
 82    if (parsecmdline(pid, cmdline, sizeof(cmdline)) < 0)
 83      printf("%d %s\n", pid, comm);
 84    else
 85      printf("%d %s\n", pid, cmdline);
 86  } else if (flags & PGREP_lflag) {
 87    printf("%d %s\n", pid, comm);
 88  } else {
 89    printf("%d\n", pid);
 90  }
 91}
 92
 93static void
 94pgrepr(const char *file)
 95{
 96  char              path[PATH_MAX], *p;
 97  struct procstat   ps;
 98  struct procstatus pstatus;
 99  char              cmdline[BUFSIZ];
100  const char       *target;
101  pid_t             pid;
102  int               matched;
103
104  if (strlcpy(path, file, sizeof(path)) >= sizeof(path))
105    eprintf("path too long\n");
106  p = basename(path);
107  if (pidfile(p) == 0)
108    return;
109  pid = estrtol(p, 10);
110  if (pid == self)
111    return;
112  if (parsestat(pid, &ps) < 0)
113    return;
114
115  if (have_user) {
116    if (parsestatus(pid, &pstatus) < 0)
117      return;
118    if (pstatus.euid != user_uid)
119      return;
120  }
121
122  if (flags & PGREP_fflag) {
123    if (parsecmdline(pid, cmdline, sizeof(cmdline)) < 0)
124      target = ps.comm;
125    else
126      target = cmdline;
127  } else {
128    target = ps.comm;
129  }
130
131  matched = matches(target);
132  if (flags & PGREP_vflag)
133    matched = !matched;
134  if (!matched)
135    return;
136
137  if (select_mode != PGREP_NONE)
138    record(pid, ps.starttime);
139  else
140    show(pid, ps.comm);
141}
142
143static void
144usage(void)
145{
146  eprintf("usage: %s [-flaxvc] [-u user] [-n | -o] pattern\n", argv0);
147}
148
149// ?man pgrep: list processes by name
150// ?man arguments: pattern
151// ?man pgrep matches pattern, a POSIX extended regular expression,
152// ?man against each running process's name (or full command line
153// ?man with -f) and prints the pid of every match, one per line
154int
155main(int argc, char *argv[])
156{
157  char            namebuf[PATH_MAX];
158  struct procstat ps;
159  struct passwd  *pw;
160  const char     *user = NULL;
161  size_t          i, pick;
162
163  ARGBEGIN
164  {
165    // ?man -f: match the full command line instead of just the name
166    case 'f':
167      flags |= PGREP_fflag;
168      break;
169    // ?man -l: list the matched name alongside each pid
170    case 'l':
171      flags |= PGREP_lflag;
172      break;
173    // ?man -a: list the full command line alongside each pid
174    case 'a':
175      flags |= PGREP_aflag;
176      break;
177    // ?man -x: require pattern to match the whole name or cmdline
178    case 'x':
179      flags |= PGREP_xflag;
180      break;
181    // ?man -v: invert the match, printing non-matching pids
182    case 'v':
183      flags |= PGREP_vflag;
184      break;
185    // ?man -c: print a count of matches instead of their pids
186    case 'c':
187      flags |= PGREP_cflag;
188      break;
189    // ?man -u:user only match processes owned by user (name or uid)
190    case 'u':
191      user = EARGF(usage());
192      break;
193    // ?man -n{sel}: select only the most recently started match
194    case 'n':
195      select_mode = PGREP_NEWEST;
196      break;
197    // ?man -o{sel}: select only the oldest match
198    case 'o':
199      select_mode = PGREP_OLDEST;
200      break;
201    default:
202      usage();
203  }
204  ARGEND;
205
206  if (argc != 1)
207    usage();
208
209  if (user) {
210    errno = 0;
211    pw    = getpwnam(user);
212    if (pw) {
213      user_uid = pw->pw_uid;
214    } else {
215      if (errno)
216        eprintf("getpwnam %s:", user);
217      user_uid = (uid_t)estrtonum(user, 0, UINT_MAX);
218    }
219    have_user = 1;
220  }
221
222  eregcomp(&preg, argv[0], REG_EXTENDED);
223
224  self = getpid();
225  recurse_dir(ARUU_LINUX_PATH_PROC, pgrepr);
226
227  if (select_mode != PGREP_NONE && nhits > 0) {
228    pick = 0;
229    for (i = 1; i < nhits; i++) {
230      if (select_mode == PGREP_NEWEST ? hits[i].starttime > hits[pick].starttime
231                                       : hits[i].starttime < hits[pick].starttime)
232        pick = i;
233    }
234    if (parsestat(hits[pick].pid, &ps) == 0)
235      strlcpy(namebuf, ps.comm, sizeof(namebuf));
236    else
237      namebuf[0] = '\0';
238    show(hits[pick].pid, namebuf);
239  }
240
241  if (flags & PGREP_cflag)
242    printf("%d\n", count);
243
244  if (fshut(stdin, "<stdin>") | fshut(stdout, "<stdout>"))
245    return 2;
246  return count > 0 ? 0 : 1;
247}