pkill.c revision 1.32 1 /* $NetBSD: pkill.c,v 1.32 2022/07/02 20:50:26 ad Exp $ */
2
3 /*-
4 * Copyright (c) 2002, 2022 The NetBSD Foundation, Inc.
5 * All rights reserved.
6 *
7 * This code is derived from software contributed to The NetBSD Foundation
8 * by Andrew Doran.
9 *
10 * Redistribution and use in source and binary forms, with or without
11 * modification, are permitted provided that the following conditions
12 * are met:
13 * 1. Redistributions of source code must retain the above copyright
14 * notice, this list of conditions and the following disclaimer.
15 * 2. Redistributions in binary form must reproduce the above copyright
16 * notice, this list of conditions and the following disclaimer in the
17 * documentation and/or other materials provided with the distribution.
18 *
19 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
20 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
21 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
22 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
23 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29 * POSSIBILITY OF SUCH DAMAGE.
30 */
31
32 #include <sys/cdefs.h>
33 #ifndef lint
34 __RCSID("$NetBSD: pkill.c,v 1.32 2022/07/02 20:50:26 ad Exp $");
35 #endif /* !lint */
36
37 #include <sys/types.h>
38 #include <sys/param.h>
39 #include <sys/sysctl.h>
40 #include <sys/proc.h>
41 #include <sys/queue.h>
42 #include <sys/resource.h>
43 #include <sys/stat.h>
44
45 #include <stdio.h>
46 #include <stdlib.h>
47 #include <limits.h>
48 #include <string.h>
49 #include <unistd.h>
50 #include <signal.h>
51 #include <regex.h>
52 #include <ctype.h>
53 #include <kvm.h>
54 #include <err.h>
55 #include <pwd.h>
56 #include <grp.h>
57 #include <errno.h>
58 #include <paths.h>
59
60 #define STATUS_MATCH 0
61 #define STATUS_NOMATCH 1
62 #define STATUS_BADUSAGE 2
63 #define STATUS_ERROR 3
64
65 enum listtype {
66 LT_GENERIC,
67 LT_USER,
68 LT_GROUP,
69 LT_TTY,
70 LT_PGRP,
71 LT_SID
72 };
73
74 struct list {
75 SLIST_ENTRY(list) li_chain;
76 long li_number;
77 };
78
79 SLIST_HEAD(listhead, list);
80
81 static struct kinfo_proc2 *plist;
82 static char *selected;
83 static const char *delim = "\n";
84 static int nproc;
85 static int pgrep;
86 static int prenice;
87 static int signum = SIGTERM;
88 static int nicenum;
89 static int newest;
90 static int inverse;
91 static int longfmt;
92 static int matchargs;
93 static int fullmatch;
94 static int cflags = REG_EXTENDED;
95 static kvm_t *kd;
96 static pid_t mypid;
97
98 static struct listhead euidlist = SLIST_HEAD_INITIALIZER(list);
99 static struct listhead ruidlist = SLIST_HEAD_INITIALIZER(list);
100 static struct listhead rgidlist = SLIST_HEAD_INITIALIZER(list);
101 static struct listhead pgrplist = SLIST_HEAD_INITIALIZER(list);
102 static struct listhead ppidlist = SLIST_HEAD_INITIALIZER(list);
103 static struct listhead tdevlist = SLIST_HEAD_INITIALIZER(list);
104 static struct listhead sidlist = SLIST_HEAD_INITIALIZER(list);
105
106 static void usage(void) __dead;
107 static int killact(const struct kinfo_proc2 *);
108 static int reniceact(const struct kinfo_proc2 *);
109 static int grepact(const struct kinfo_proc2 *);
110 static void makelist(struct listhead *, enum listtype, char *);
111
112 int
113 main(int argc, char **argv)
114 {
115 char buf[_POSIX2_LINE_MAX], **pargv, *q;
116 int i, j, ch, bestidx, rv, criteria;
117 int (*action)(const struct kinfo_proc2 *);
118 const struct kinfo_proc2 *kp;
119 struct list *li;
120 const char *p;
121 u_int32_t bestsec, bestusec;
122 regex_t reg;
123 regmatch_t regmatch;
124
125 setprogname(argv[0]);
126
127 if (strcmp(getprogname(), "pgrep") == 0) {
128 action = grepact;
129 pgrep = 1;
130 } else if (strcmp(getprogname(), "prenice") == 0) {
131 action = reniceact;
132 prenice = 1;
133 } else {
134 action = killact;
135 p = argv[1];
136
137 if (argc > 1 && p[0] == '-') {
138 p++;
139 i = (int)strtol(p, &q, 10);
140 if (*q == '\0') {
141 signum = i;
142 argv++;
143 argc--;
144 } else {
145 if (strncasecmp(p, "sig", 3) == 0)
146 p += 3;
147 for (i = 1; i < NSIG; i++)
148 if (strcasecmp(sys_signame[i], p) == 0)
149 break;
150 if (i != NSIG) {
151 signum = i;
152 argv++;
153 argc--;
154 }
155 }
156 }
157 }
158
159 criteria = 0;
160
161 if (prenice) {
162 if (argc < 2)
163 usage();
164
165 if (strcmp(argv[1], "-l") == 0) {
166 longfmt = 1;
167 argv++;
168 argc--;
169 }
170
171 if (argc < 2)
172 usage();
173
174 p = argv[1];
175
176 i = (int)strtol(p, &q, 10);
177 if (*q == '\0') {
178 nicenum = i;
179 argv++;
180 argc--;
181 } else
182 usage();
183 } else {
184 while ((ch = getopt(argc, argv, "G:P:U:d:fg:ilns:t:u:vx")) != -1)
185 switch (ch) {
186 case 'G':
187 makelist(&rgidlist, LT_GROUP, optarg);
188 criteria = 1;
189 break;
190 case 'P':
191 makelist(&ppidlist, LT_GENERIC, optarg);
192 criteria = 1;
193 break;
194 case 'U':
195 makelist(&ruidlist, LT_USER, optarg);
196 criteria = 1;
197 break;
198 case 'd':
199 if (!pgrep)
200 usage();
201 delim = optarg;
202 break;
203 case 'f':
204 matchargs = 1;
205 break;
206 case 'g':
207 makelist(&pgrplist, LT_PGRP, optarg);
208 criteria = 1;
209 break;
210 case 'i':
211 cflags |= REG_ICASE;
212 break;
213 case 'l':
214 longfmt = 1;
215 break;
216 case 'n':
217 newest = 1;
218 criteria = 1;
219 break;
220 case 's':
221 makelist(&sidlist, LT_SID, optarg);
222 criteria = 1;
223 break;
224 case 't':
225 makelist(&tdevlist, LT_TTY, optarg);
226 criteria = 1;
227 break;
228 case 'u':
229 makelist(&euidlist, LT_USER, optarg);
230 criteria = 1;
231 break;
232 case 'v':
233 inverse = 1;
234 break;
235 case 'x':
236 fullmatch = 1;
237 break;
238 default:
239 usage();
240 /* NOTREACHED */
241 }
242 argc -= optind;
243 argv += optind;
244 }
245
246 if (argc != 0)
247 criteria = 1;
248 if (!criteria)
249 usage();
250
251 mypid = getpid();
252
253 /*
254 * Retrieve the list of running processes from the kernel.
255 */
256 kd = kvm_openfiles(NULL, NULL, NULL, KVM_NO_FILES, buf);
257 if (kd == NULL)
258 errx(STATUS_ERROR, "Cannot open kernel files (%s)", buf);
259
260 plist = kvm_getproc2(kd, KERN_PROC_ALL, 0, sizeof(*plist), &nproc);
261 if (plist == NULL)
262 errx(STATUS_ERROR, "Cannot get process list (%s)",
263 kvm_geterr(kd));
264
265 /*
266 * Allocate memory which will be used to keep track of the
267 * selection.
268 */
269 if ((selected = calloc(sizeof(*selected), (size_t)nproc)) == NULL)
270 err(STATUS_ERROR, "Cannot allocate memory for %d processes",
271 nproc);
272
273 /*
274 * Refine the selection.
275 */
276 for (; *argv != NULL; argv++) {
277 if ((rv = regcomp(®, *argv, cflags)) != 0) {
278 (void)regerror(rv, ®, buf, sizeof(buf));
279 errx(STATUS_BADUSAGE,
280 "Cannot compile regular expression `%s' (%s)",
281 *argv, buf);
282 }
283
284 for (i = 0, kp = plist; i < nproc; i++, kp++) {
285 if ((kp->p_flag & P_SYSTEM) != 0 || kp->p_pid == mypid)
286 continue;
287
288 if ((pargv = kvm_getargv2(kd, kp, 0)) == NULL)
289 continue;
290 if (matchargs) {
291
292 j = 0;
293 while (j < (int)sizeof(buf) && *pargv != NULL) {
294 j += snprintf(buf + j, sizeof(buf) - j,
295 pargv[1] != NULL ? "%s " : "%s",
296 pargv[0]);
297 pargv++;
298 }
299 } else if (pargv[0] != NULL)
300 strlcpy(buf, pargv[0], sizeof(buf));
301 else
302 strlcpy(buf, kp->p_comm, sizeof(buf));
303
304 rv = regexec(®, buf, 1, ®match, 0);
305 if (rv == 0) {
306 if (fullmatch) {
307 if (regmatch.rm_so == 0 &&
308 regmatch.rm_eo ==
309 (regoff_t)strlen(buf))
310 selected[i] = 1;
311 } else
312 selected[i] = 1;
313 } else if (rv != REG_NOMATCH) {
314 (void)regerror(rv, ®, buf, sizeof(buf));
315 errx(STATUS_ERROR,
316 "Regular expression evaluation error (%s)",
317 buf);
318 }
319 }
320
321 regfree(®);
322 }
323
324 for (i = 0, kp = plist; i < nproc; i++, kp++) {
325 if ((kp->p_flag & P_SYSTEM) != 0)
326 continue;
327
328 SLIST_FOREACH(li, &ruidlist, li_chain)
329 if (kp->p_ruid == (uid_t)li->li_number)
330 break;
331 if (SLIST_FIRST(&ruidlist) != NULL && li == NULL) {
332 selected[i] = 0;
333 continue;
334 }
335
336 SLIST_FOREACH(li, &rgidlist, li_chain)
337 if (kp->p_rgid == (gid_t)li->li_number)
338 break;
339 if (SLIST_FIRST(&rgidlist) != NULL && li == NULL) {
340 selected[i] = 0;
341 continue;
342 }
343
344 SLIST_FOREACH(li, &euidlist, li_chain)
345 if (kp->p_uid == (uid_t)li->li_number)
346 break;
347 if (SLIST_FIRST(&euidlist) != NULL && li == NULL) {
348 selected[i] = 0;
349 continue;
350 }
351
352 SLIST_FOREACH(li, &ppidlist, li_chain)
353 if ((uid_t)kp->p_ppid == (uid_t)li->li_number)
354 break;
355 if (SLIST_FIRST(&ppidlist) != NULL && li == NULL) {
356 selected[i] = 0;
357 continue;
358 }
359
360 SLIST_FOREACH(li, &pgrplist, li_chain)
361 if (kp->p__pgid == (pid_t)li->li_number)
362 break;
363 if (SLIST_FIRST(&pgrplist) != NULL && li == NULL) {
364 selected[i] = 0;
365 continue;
366 }
367
368 SLIST_FOREACH(li, &tdevlist, li_chain) {
369 if (li->li_number == -1 &&
370 (kp->p_flag & P_CONTROLT) == 0)
371 break;
372 if (kp->p_tdev == (uid_t)li->li_number)
373 break;
374 }
375 if (SLIST_FIRST(&tdevlist) != NULL && li == NULL) {
376 selected[i] = 0;
377 continue;
378 }
379
380 SLIST_FOREACH(li, &sidlist, li_chain)
381 if (kp->p_sid == (pid_t)li->li_number)
382 break;
383 if (SLIST_FIRST(&sidlist) != NULL && li == NULL) {
384 selected[i] = 0;
385 continue;
386 }
387
388 if (argc == 0)
389 selected[i] = 1;
390 }
391
392 if (newest) {
393 bestsec = 0;
394 bestusec = 0;
395 bestidx = -1;
396
397 for (i = 0, kp = plist; i < nproc; i++, kp++) {
398 if (!selected[i])
399 continue;
400
401 if (kp->p_ustart_sec > bestsec ||
402 (kp->p_ustart_sec == bestsec
403 && kp->p_ustart_usec > bestusec)) {
404 bestsec = kp->p_ustart_sec;
405 bestusec = kp->p_ustart_usec;
406 bestidx = i;
407 }
408 }
409
410 (void)memset(selected, 0, (size_t)nproc);
411 if (bestidx != -1)
412 selected[bestidx] = 1;
413 }
414
415 /*
416 * Take the appropriate action for each matched process, if any.
417 */
418 for (i = 0, rv = 0, kp = plist; i < nproc; i++, kp++) {
419 if (kp->p_pid == mypid)
420 continue;
421 if (selected[i]) {
422 if (inverse)
423 continue;
424 } else if (!inverse)
425 continue;
426
427 if ((kp->p_flag & P_SYSTEM) != 0)
428 continue;
429
430 rv |= (*action)(kp);
431 }
432
433 return rv ? STATUS_MATCH : STATUS_NOMATCH;
434 }
435
436 static void
437 usage(void)
438 {
439 const char *ustr;
440
441 if (prenice)
442 fprintf(stderr, "Usage: %s [-l] priority pattern ...\n",
443 getprogname());
444 else {
445 if (pgrep)
446 ustr = "[-filnvx] [-d delim]";
447 else
448 ustr = "[-signal] [-filnvx]";
449
450 (void)fprintf(stderr,
451 "Usage: %s %s [-G gid] [-g pgrp] [-P ppid] [-s sid] "
452 "[-t tty]\n"
453 " [-U uid] [-u euid] pattern ...\n",
454 getprogname(), ustr);
455 }
456
457 exit(STATUS_BADUSAGE);
458 }
459
460 static int
461 killact(const struct kinfo_proc2 *kp)
462 {
463 if (longfmt)
464 grepact(kp);
465 if (kill(kp->p_pid, signum) == -1) {
466
467 /*
468 * Check for ESRCH, which indicates that the process
469 * disappeared between us matching it and us
470 * signalling it; don't issue a warning about it.
471 */
472 if (errno != ESRCH)
473 warn("signalling pid %d", (int)kp->p_pid);
474
475 /*
476 * Return 0 to indicate that the process should not be
477 * considered a match, since we didn't actually get to
478 * signal it.
479 */
480 return 0;
481 }
482
483 return 1;
484 }
485
486 static int
487 reniceact(const struct kinfo_proc2 *kp)
488 {
489 int oldprio;
490
491 if (longfmt)
492 grepact(kp);
493
494 errno = 0;
495 if ((oldprio = getpriority(PRIO_PROCESS, kp->p_pid)) == -1 &&
496 errno != 0) {
497 warn("%d: getpriority", kp->p_pid);
498 return 0;
499 }
500
501 if (setpriority(PRIO_PROCESS, kp->p_pid, nicenum) == -1) {
502 warn("%d: setpriority", kp->p_pid);
503 return 0;
504 }
505
506 (void)printf("%d: old priority %d, new priority %d\n",
507 kp->p_pid, oldprio, nicenum);
508
509 return 1;
510 }
511
512 static int
513 grepact(const struct kinfo_proc2 *kp)
514 {
515 char **argv;
516
517 if (longfmt && matchargs) {
518
519 /*
520 * If kvm_getargv2() failed the process has probably
521 * disappeared. Return 0 to indicate that the process
522 * should not be considered a match, since we are no
523 * longer in a position to output it as a match.
524 */
525 if ((argv = kvm_getargv2(kd, kp, 0)) == NULL)
526 return 0;
527
528 (void)printf("%d ", (int)kp->p_pid);
529 for (; *argv != NULL; argv++) {
530 (void)printf("%s", *argv);
531 if (argv[1] != NULL)
532 (void)putchar(' ');
533 }
534 } else if (longfmt)
535 (void)printf("%d %s", (int)kp->p_pid, kp->p_comm);
536 else
537 (void)printf("%d", (int)kp->p_pid);
538
539 (void)printf("%s", delim);
540
541 return 1;
542 }
543
544 static void
545 makelist(struct listhead *head, enum listtype type, char *src)
546 {
547 struct list *li;
548 struct passwd *pw;
549 struct group *gr;
550 struct stat st;
551 char *sp, *ep, buf[MAXPATHLEN];
552 const char *p;
553 int empty;
554 const char *prefix = _PATH_DEV;
555
556 empty = 1;
557
558 while ((sp = strsep(&src, ",")) != NULL) {
559 if (*sp == '\0')
560 usage();
561
562 if ((li = malloc(sizeof(*li))) == NULL)
563 err(STATUS_ERROR, "Cannot allocate %zu bytes",
564 sizeof(*li));
565 SLIST_INSERT_HEAD(head, li, li_chain);
566 empty = 0;
567
568 li->li_number = (uid_t)strtol(sp, &ep, 0);
569 if (*ep == '\0' && type != LT_TTY) {
570 switch (type) {
571 case LT_PGRP:
572 if (li->li_number == 0)
573 li->li_number = getpgrp();
574 break;
575 case LT_SID:
576 if (li->li_number == 0)
577 li->li_number = getsid(mypid);
578 break;
579 default:
580 break;
581 }
582 continue;
583 }
584
585 switch (type) {
586 case LT_USER:
587 if ((pw = getpwnam(sp)) == NULL)
588 errx(STATUS_BADUSAGE, "Unknown user `%s'",
589 sp);
590 li->li_number = pw->pw_uid;
591 break;
592 case LT_GROUP:
593 if ((gr = getgrnam(sp)) == NULL)
594 errx(STATUS_BADUSAGE, "Unknown group `%s'",
595 sp);
596 li->li_number = gr->gr_gid;
597 break;
598 case LT_TTY:
599 p = sp;
600 if (*sp == '/')
601 prefix = "";
602 else if (strcmp(sp, "-") == 0) {
603 li->li_number = -1;
604 break;
605 } else if (strcmp(sp, "co") == 0)
606 p = "console";
607 else if (strncmp(sp, "tty", 3) == 0)
608 /* all set */;
609 else if (strncmp(sp, "pts/", 4) == 0)
610 /* all set */;
611 else if (*ep != '\0' || (strlen(sp) == 2 && *sp == '0'))
612 prefix = _PATH_TTY;
613 else
614 prefix = _PATH_DEV_PTS;
615
616 (void)snprintf(buf, sizeof(buf), "%s%s", prefix, p);
617
618 if (stat(buf, &st) == -1) {
619 if (errno == ENOENT)
620 errx(STATUS_BADUSAGE,
621 "No such tty: `%s'", buf);
622 err(STATUS_ERROR, "Cannot access `%s'", buf);
623 }
624
625 if ((st.st_mode & S_IFCHR) == 0)
626 errx(STATUS_BADUSAGE, "Not a tty: `%s'", buf);
627
628 li->li_number = st.st_rdev;
629 break;
630 default:
631 usage();
632 }
633 }
634
635 if (empty)
636 usage();
637 }
638