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
65enum listtype {
66	LT_GENERIC,
67	LT_USER,
68	LT_GROUP,
69	LT_TTY,
70	LT_PGRP,
71	LT_SID
72};
73
74struct list {
75	SLIST_ENTRY(list) li_chain;
76	long	li_number;
77};
78
79SLIST_HEAD(listhead, list);
80
81static struct kinfo_proc2	*plist;
82static char	*selected;
83static const char *delim = "\n";
84static int	nproc;
85static int	pgrep;
86static int	prenice;
87static int	signum = SIGTERM;
88static int	nicenum;
89static int	newest;
90static int	quiet;
91static int	inverse;
92static int	longfmt;
93static int	matchargs;
94static int	fullmatch;
95static int	cflags = REG_EXTENDED;
96static kvm_t	*kd;
97static pid_t	mypid;
98
99static struct listhead euidlist = SLIST_HEAD_INITIALIZER(list);
100static struct listhead ruidlist = SLIST_HEAD_INITIALIZER(list);
101static struct listhead rgidlist = SLIST_HEAD_INITIALIZER(list);
102static struct listhead pgrplist = SLIST_HEAD_INITIALIZER(list);
103static struct listhead ppidlist = SLIST_HEAD_INITIALIZER(list);
104static struct listhead tdevlist = SLIST_HEAD_INITIALIZER(list);
105static struct listhead sidlist = SLIST_HEAD_INITIALIZER(list);
106
107static void	usage(void) __dead;
108static int	killact(const struct kinfo_proc2 *);
109static int	reniceact(const struct kinfo_proc2 *);
110static int	grepact(const struct kinfo_proc2 *);
111static void	makelist(struct listhead *, enum listtype, char *);
112
113int
114main(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(&reg, *argv, cflags)) != 0) {
284			(void)regerror(rv, &reg, 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(&reg, buf, 1, &regmatch, 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, &reg, buf, sizeof(buf));
321				errx(STATUS_ERROR,
322				    "Regular expression evaluation error (%s)",
323				    buf);
324			}
325		}
326
327		regfree(&reg);
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
442static void
443usage(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
466static int
467killact(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
492static int
493reniceact(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
518static int
519grepact(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
553static void
554makelist(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