pkill.c revision 1.33 1 /* $NetBSD: pkill.c,v 1.33 2022/10/29 08:17:16 simonb 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.33 2022/10/29 08:17:16 simonb 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 quiet;
91 static int inverse;
92 static int longfmt;
93 static int matchargs;
94 static int fullmatch;
95 static int cflags = REG_EXTENDED;
96 static kvm_t *kd;
97 static pid_t mypid;
98
99 static struct listhead euidlist = SLIST_HEAD_INITIALIZER(list);
100 static struct listhead ruidlist = SLIST_HEAD_INITIALIZER(list);
101 static struct listhead rgidlist = SLIST_HEAD_INITIALIZER(list);
102 static struct listhead pgrplist = SLIST_HEAD_INITIALIZER(list);
103 static struct listhead ppidlist = SLIST_HEAD_INITIALIZER(list);
104 static struct listhead tdevlist = SLIST_HEAD_INITIALIZER(list);
105 static struct listhead sidlist = SLIST_HEAD_INITIALIZER(list);
106
107 static void usage(void) __dead;
108 static int killact(const struct kinfo_proc2 *);
109 static int reniceact(const struct kinfo_proc2 *);
110 static int grepact(const struct kinfo_proc2 *);
111 static void makelist(struct listhead *, enum listtype, char *);
112
113 int
114 main(int argc, char **argv)
115 {
116 char buf[_POSIX2_LINE_MAX], **pargv, *q;
117 int i, j, ch, bestidx, rv, criteria;
118 int (*action)(const struct kinfo_proc2 *);
119 const struct kinfo_proc2 *kp;
120 struct list *li;
121 const char *p;
122 u_int32_t bestsec, bestusec;
123 regex_t reg;
124 regmatch_t regmatch;
125
126 setprogname(argv[0]);
127
128 if (strcmp(getprogname(), "pgrep") == 0) {
129 action = grepact;
130 pgrep = 1;
131 } else if (strcmp(getprogname(), "prenice") == 0) {
132 action = reniceact;
133 prenice = 1;
134 } else {
135 action = killact;
136 p = argv[1];
137
138 if (argc > 1 && p[0] == '-') {
139 p++;
140 i = (int)strtol(p, &q, 10);
141 if (*q == '\0') {
142 signum = i;
143 argv++;
144 argc--;
145 } else {
146 if (strncasecmp(p, "sig", 3) == 0)
147 p += 3;
148 for (i = 1; i < NSIG; i++)
149 if (strcasecmp(sys_signame[i], p) == 0)
150 break;
151 if (i != NSIG) {
152 signum = i;
153 argv++;
154 argc--;
155 }
156 }
157 }
158 }
159
160 criteria = 0;
161
162 if (prenice) {
163 if (argc < 2)
164 usage();
165
166 if (strcmp(argv[1], "-l") == 0) {
167 longfmt = 1;
168 argv++;
169 argc--;
170 }
171
172 if (argc < 2)
173 usage();
174
175 p = argv[1];
176
177 i = (int)strtol(p, &q, 10);
178 if (*q == '\0') {
179 nicenum = i;
180 argv++;
181 argc--;
182 } else
183 usage();
184 } else {
185 while ((ch = getopt(argc, argv, "G:P:U:d:fg:ilnqs:t:u:vx")) != -1)
186 switch (ch) {
187 case 'G':
188 makelist(&rgidlist, LT_GROUP, optarg);
189 criteria = 1;
190 break;
191 case 'P':
192 makelist(&ppidlist, LT_GENERIC, optarg);
193 criteria = 1;
194 break;
195 case 'U':
196 makelist(&ruidlist, LT_USER, optarg);
197 criteria = 1;
198 break;
199 case 'd':
200 if (!pgrep)
201 usage();
202 delim = optarg;
203 break;
204 case 'f':
205 matchargs = 1;
206 break;
207 case 'g':
208 makelist(&pgrplist, LT_PGRP, optarg);
209 criteria = 1;
210 break;
211 case 'i':
212 cflags |= REG_ICASE;
213 break;
214 case 'l':
215 longfmt = 1;
216 break;
217 case 'n':
218 newest = 1;
219 criteria = 1;
220 break;
221 case 'q':
222 if (!pgrep)
223 usage();
224 quiet = 1;
225 break;
226 case 's':
227 makelist(&sidlist, LT_SID, optarg);
228 criteria = 1;
229 break;
230 case 't':
231 makelist(&tdevlist, LT_TTY, optarg);
232 criteria = 1;
233 break;
234 case 'u':
235 makelist(&euidlist, LT_USER, optarg);
236 criteria = 1;
237 break;
238 case 'v':
239 inverse = 1;
240 break;
241 case 'x':
242 fullmatch = 1;
243 break;
244 default:
245 usage();
246 /* NOTREACHED */
247 }
248 argc -= optind;
249 argv += optind;
250 }
251
252 if (argc != 0)
253 criteria = 1;
254 if (!criteria)
255 usage();
256
257 mypid = getpid();
258
259 /*
260 * Retrieve the list of running processes from the kernel.
261 */
262 kd = kvm_openfiles(NULL, NULL, NULL, KVM_NO_FILES, buf);
263 if (kd == NULL)
264 errx(STATUS_ERROR, "Cannot open kernel files (%s)", buf);
265
266 plist = kvm_getproc2(kd, KERN_PROC_ALL, 0, sizeof(*plist), &nproc);
267 if (plist == NULL)
268 errx(STATUS_ERROR, "Cannot get process list (%s)",
269 kvm_geterr(kd));
270
271 /*
272 * Allocate memory which will be used to keep track of the
273 * selection.
274 */
275 if ((selected = calloc(sizeof(*selected), (size_t)nproc)) == NULL)
276 err(STATUS_ERROR, "Cannot allocate memory for %d processes",
277 nproc);
278
279 /*
280 * Refine the selection.
281 */
282 for (; *argv != NULL; argv++) {
283 if ((rv = regcomp(®, *argv, cflags)) != 0) {
284 (void)regerror(rv, ®, buf, sizeof(buf));
285 errx(STATUS_BADUSAGE,
286 "Cannot compile regular expression `%s' (%s)",
287 *argv, buf);
288 }
289
290 for (i = 0, kp = plist; i < nproc; i++, kp++) {
291 if ((kp->p_flag & P_SYSTEM) != 0 || kp->p_pid == mypid)
292 continue;
293
294 if ((pargv = kvm_getargv2(kd, kp, 0)) == NULL)
295 continue;
296 if (matchargs) {
297
298 j = 0;
299 while (j < (int)sizeof(buf) && *pargv != NULL) {
300 j += snprintf(buf + j, sizeof(buf) - j,
301 pargv[1] != NULL ? "%s " : "%s",
302 pargv[0]);
303 pargv++;
304 }
305 } else if (pargv[0] != NULL)
306 strlcpy(buf, pargv[0], sizeof(buf));
307 else
308 strlcpy(buf, kp->p_comm, sizeof(buf));
309
310 rv = regexec(®, buf, 1, ®match, 0);
311 if (rv == 0) {
312 if (fullmatch) {
313 if (regmatch.rm_so == 0 &&
314 regmatch.rm_eo ==
315 (regoff_t)strlen(buf))
316 selected[i] = 1;
317 } else
318 selected[i] = 1;
319 } else if (rv != REG_NOMATCH) {
320 (void)regerror(rv, ®, buf, sizeof(buf));
321 errx(STATUS_ERROR,
322 "Regular expression evaluation error (%s)",
323 buf);
324 }
325 }
326
327 regfree(®);
328 }
329
330 for (i = 0, kp = plist; i < nproc; i++, kp++) {
331 if ((kp->p_flag & P_SYSTEM) != 0)
332 continue;
333
334 SLIST_FOREACH(li, &ruidlist, li_chain)
335 if (kp->p_ruid == (uid_t)li->li_number)
336 break;
337 if (SLIST_FIRST(&ruidlist) != NULL && li == NULL) {
338 selected[i] = 0;
339 continue;
340 }
341
342 SLIST_FOREACH(li, &rgidlist, li_chain)
343 if (kp->p_rgid == (gid_t)li->li_number)
344 break;
345 if (SLIST_FIRST(&rgidlist) != NULL && li == NULL) {
346 selected[i] = 0;
347 continue;
348 }
349
350 SLIST_FOREACH(li, &euidlist, li_chain)
351 if (kp->p_uid == (uid_t)li->li_number)
352 break;
353 if (SLIST_FIRST(&euidlist) != NULL && li == NULL) {
354 selected[i] = 0;
355 continue;
356 }
357
358 SLIST_FOREACH(li, &ppidlist, li_chain)
359 if ((uid_t)kp->p_ppid == (uid_t)li->li_number)
360 break;
361 if (SLIST_FIRST(&ppidlist) != NULL && li == NULL) {
362 selected[i] = 0;
363 continue;
364 }
365
366 SLIST_FOREACH(li, &pgrplist, li_chain)
367 if (kp->p__pgid == (pid_t)li->li_number)
368 break;
369 if (SLIST_FIRST(&pgrplist) != NULL && li == NULL) {
370 selected[i] = 0;
371 continue;
372 }
373
374 SLIST_FOREACH(li, &tdevlist, li_chain) {
375 if (li->li_number == -1 &&
376 (kp->p_flag & P_CONTROLT) == 0)
377 break;
378 if (kp->p_tdev == (uid_t)li->li_number)
379 break;
380 }
381 if (SLIST_FIRST(&tdevlist) != NULL && li == NULL) {
382 selected[i] = 0;
383 continue;
384 }
385
386 SLIST_FOREACH(li, &sidlist, li_chain)
387 if (kp->p_sid == (pid_t)li->li_number)
388 break;
389 if (SLIST_FIRST(&sidlist) != NULL && li == NULL) {
390 selected[i] = 0;
391 continue;
392 }
393
394 if (argc == 0)
395 selected[i] = 1;
396 }
397
398 if (newest) {
399 bestsec = 0;
400 bestusec = 0;
401 bestidx = -1;
402
403 for (i = 0, kp = plist; i < nproc; i++, kp++) {
404 if (!selected[i])
405 continue;
406
407 if (kp->p_ustart_sec > bestsec ||
408 (kp->p_ustart_sec == bestsec
409 && kp->p_ustart_usec > bestusec)) {
410 bestsec = kp->p_ustart_sec;
411 bestusec = kp->p_ustart_usec;
412 bestidx = i;
413 }
414 }
415
416 (void)memset(selected, 0, (size_t)nproc);
417 if (bestidx != -1)
418 selected[bestidx] = 1;
419 }
420
421 /*
422 * Take the appropriate action for each matched process, if any.
423 */
424 for (i = 0, rv = 0, kp = plist; i < nproc; i++, kp++) {
425 if (kp->p_pid == mypid)
426 continue;
427 if (selected[i]) {
428 if (inverse)
429 continue;
430 } else if (!inverse)
431 continue;
432
433 if ((kp->p_flag & P_SYSTEM) != 0)
434 continue;
435
436 rv |= (*action)(kp);
437 }
438
439 return rv ? STATUS_MATCH : STATUS_NOMATCH;
440 }
441
442 static void
443 usage(void)
444 {
445 const char *ustr;
446
447 if (prenice)
448 fprintf(stderr, "Usage: %s [-l] priority pattern ...\n",
449 getprogname());
450 else {
451 if (pgrep)
452 ustr = "[-filnqvx] [-d delim]";
453 else
454 ustr = "[-signal] [-filnvx]";
455
456 (void)fprintf(stderr,
457 "Usage: %s %s [-G gid] [-g pgrp] [-P ppid] [-s sid] "
458 "[-t tty]\n"
459 " [-U uid] [-u euid] pattern ...\n",
460 getprogname(), ustr);
461 }
462
463 exit(STATUS_BADUSAGE);
464 }
465
466 static int
467 killact(const struct kinfo_proc2 *kp)
468 {
469 if (longfmt)
470 grepact(kp);
471 if (kill(kp->p_pid, signum) == -1) {
472
473 /*
474 * Check for ESRCH, which indicates that the process
475 * disappeared between us matching it and us
476 * signalling it; don't issue a warning about it.
477 */
478 if (errno != ESRCH)
479 warn("signalling pid %d", (int)kp->p_pid);
480
481 /*
482 * Return 0 to indicate that the process should not be
483 * considered a match, since we didn't actually get to
484 * signal it.
485 */
486 return 0;
487 }
488
489 return 1;
490 }
491
492 static int
493 reniceact(const struct kinfo_proc2 *kp)
494 {
495 int oldprio;
496
497 if (longfmt)
498 grepact(kp);
499
500 errno = 0;
501 if ((oldprio = getpriority(PRIO_PROCESS, kp->p_pid)) == -1 &&
502 errno != 0) {
503 warn("%d: getpriority", kp->p_pid);
504 return 0;
505 }
506
507 if (setpriority(PRIO_PROCESS, kp->p_pid, nicenum) == -1) {
508 warn("%d: setpriority", kp->p_pid);
509 return 0;
510 }
511
512 (void)printf("%d: old priority %d, new priority %d\n",
513 kp->p_pid, oldprio, nicenum);
514
515 return 1;
516 }
517
518 static int
519 grepact(const struct kinfo_proc2 *kp)
520 {
521 char **argv;
522
523 if (quiet)
524 return 1;
525
526 if (longfmt && matchargs) {
527
528 /*
529 * If kvm_getargv2() failed the process has probably
530 * disappeared. Return 0 to indicate that the process
531 * should not be considered a match, since we are no
532 * longer in a position to output it as a match.
533 */
534 if ((argv = kvm_getargv2(kd, kp, 0)) == NULL)
535 return 0;
536
537 (void)printf("%d ", (int)kp->p_pid);
538 for (; *argv != NULL; argv++) {
539 (void)printf("%s", *argv);
540 if (argv[1] != NULL)
541 (void)putchar(' ');
542 }
543 } else if (longfmt)
544 (void)printf("%d %s", (int)kp->p_pid, kp->p_comm);
545 else
546 (void)printf("%d", (int)kp->p_pid);
547
548 (void)printf("%s", delim);
549
550 return 1;
551 }
552
553 static void
554 makelist(struct listhead *head, enum listtype type, char *src)
555 {
556 struct list *li;
557 struct passwd *pw;
558 struct group *gr;
559 struct stat st;
560 char *sp, *ep, buf[MAXPATHLEN];
561 const char *p;
562 int empty;
563 const char *prefix = _PATH_DEV;
564
565 empty = 1;
566
567 while ((sp = strsep(&src, ",")) != NULL) {
568 if (*sp == '\0')
569 usage();
570
571 if ((li = malloc(sizeof(*li))) == NULL)
572 err(STATUS_ERROR, "Cannot allocate %zu bytes",
573 sizeof(*li));
574 SLIST_INSERT_HEAD(head, li, li_chain);
575 empty = 0;
576
577 li->li_number = (uid_t)strtol(sp, &ep, 0);
578 if (*ep == '\0' && type != LT_TTY) {
579 switch (type) {
580 case LT_PGRP:
581 if (li->li_number == 0)
582 li->li_number = getpgrp();
583 break;
584 case LT_SID:
585 if (li->li_number == 0)
586 li->li_number = getsid(mypid);
587 break;
588 default:
589 break;
590 }
591 continue;
592 }
593
594 switch (type) {
595 case LT_USER:
596 if ((pw = getpwnam(sp)) == NULL)
597 errx(STATUS_BADUSAGE, "Unknown user `%s'",
598 sp);
599 li->li_number = pw->pw_uid;
600 break;
601 case LT_GROUP:
602 if ((gr = getgrnam(sp)) == NULL)
603 errx(STATUS_BADUSAGE, "Unknown group `%s'",
604 sp);
605 li->li_number = gr->gr_gid;
606 break;
607 case LT_TTY:
608 p = sp;
609 if (*sp == '/')
610 prefix = "";
611 else if (strcmp(sp, "-") == 0) {
612 li->li_number = -1;
613 break;
614 } else if (strcmp(sp, "co") == 0)
615 p = "console";
616 else if (strncmp(sp, "tty", 3) == 0)
617 /* all set */;
618 else if (strncmp(sp, "pts/", 4) == 0)
619 /* all set */;
620 else if (*ep != '\0' || (strlen(sp) == 2 && *sp == '0'))
621 prefix = _PATH_TTY;
622 else
623 prefix = _PATH_DEV_PTS;
624
625 (void)snprintf(buf, sizeof(buf), "%s%s", prefix, p);
626
627 if (stat(buf, &st) == -1) {
628 if (errno == ENOENT)
629 errx(STATUS_BADUSAGE,
630 "No such tty: `%s'", buf);
631 err(STATUS_ERROR, "Cannot access `%s'", buf);
632 }
633
634 if ((st.st_mode & S_IFCHR) == 0)
635 errx(STATUS_BADUSAGE, "Not a tty: `%s'", buf);
636
637 li->li_number = st.st_rdev;
638 break;
639 default:
640 usage();
641 }
642 }
643
644 if (empty)
645 usage();
646 }
647