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