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}