xref: /dragonfly/usr.bin/pkill/pkill.c (revision 0dace59e)
1 /*	$NetBSD: pkill.c,v 1.7 2004/02/15 17:03:30 soren Exp $	*/
2 /*	$DragonFly: src/usr.bin/pkill/pkill.c,v 1.9 2007/02/01 10:33:26 corecode Exp $ */
3 
4 /*-
5  * Copyright (c) 2002 The NetBSD Foundation, Inc.
6  * All rights reserved.
7  *
8  * This code is derived from software contributed to The NetBSD Foundation
9  * by Andrew Doran.
10  *
11  * Redistribution and use in source and binary forms, with or without
12  * modification, are permitted provided that the following conditions
13  * are met:
14  * 1. Redistributions of source code must retain the above copyright
15  *    notice, this list of conditions and the following disclaimer.
16  * 2. Redistributions in binary form must reproduce the above copyright
17  *    notice, this list of conditions and the following disclaimer in the
18  *    documentation and/or other materials provided with the distribution.
19  * 3. All advertising materials mentioning features or use of this software
20  *    must display the following acknowledgement:
21  *	This product includes software developed by the NetBSD
22  *	Foundation, Inc. and its contributors.
23  * 4. Neither the name of The NetBSD Foundation nor the names of its
24  *    contributors may be used to endorse or promote products derived
25  *    from this software without specific prior written permission.
26  *
27  * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
28  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
29  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
30  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
31  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
32  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
33  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
34  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
35  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
36  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
37  * POSSIBILITY OF SUCH DAMAGE.
38  */
39 
40 #include <sys/user.h>
41 #include <sys/types.h>
42 #include <sys/param.h>
43 #include <sys/sysctl.h>
44 #include <sys/queue.h>
45 #include <sys/stat.h>
46 #include <sys/fcntl.h>
47 
48 #include <stdio.h>
49 #include <stdlib.h>
50 #include <limits.h>
51 #include <paths.h>
52 #include <string.h>
53 #include <unistd.h>
54 #include <signal.h>
55 #include <regex.h>
56 #include <ctype.h>
57 #include <kvm.h>
58 #include <err.h>
59 #include <pwd.h>
60 #include <grp.h>
61 #include <errno.h>
62 
63 #define	STATUS_MATCH	0
64 #define	STATUS_NOMATCH	1
65 #define	STATUS_BADUSAGE	2
66 #define	STATUS_ERROR	3
67 
68 enum listtype {
69 	LT_USER,		/* real or effective user:	uid_t */
70 	LT_GROUP,		/* group:			gid_t */
71 	LT_TTY,			/* tty:				dev_t */
72 	LT_PPID,		/* parent pid:			pid_t */
73 	LT_PGRP,		/* process group:		pid_t */
74 	LT_SID,			/* session id:			pid_t */
75 	LT_JID			/* jail id:			pid_t */
76 };
77 
78 struct list {
79 	SLIST_ENTRY(list) li_chain;
80 	union {
81 		uid_t	ld_uid;
82 		gid_t	ld_gid;
83 		pid_t	ld_pid;
84 		dev_t	ld_dev;
85 	} li_datum;
86 };
87 
88 SLIST_HEAD(listhead, list);
89 
90 struct kinfo_proc	*plist;
91 char	*selected;
92 const char *delim = "\n";
93 int	nproc;
94 int	pgrep;
95 int	signum = SIGTERM;
96 int	newest;
97 int	inverse;
98 int	longfmt;
99 int	matchargs;
100 int	fullmatch;
101 kvm_t	*kd;
102 pid_t	mypid;
103 
104 struct listhead euidlist = SLIST_HEAD_INITIALIZER(list);
105 struct listhead ruidlist = SLIST_HEAD_INITIALIZER(list);
106 struct listhead rgidlist = SLIST_HEAD_INITIALIZER(list);
107 struct listhead pgrplist = SLIST_HEAD_INITIALIZER(list);
108 struct listhead ppidlist = SLIST_HEAD_INITIALIZER(list);
109 struct listhead tdevlist = SLIST_HEAD_INITIALIZER(list);
110 struct listhead sidlist = SLIST_HEAD_INITIALIZER(list);
111 struct listhead jidlist = SLIST_HEAD_INITIALIZER(list);
112 
113 void	usage(void);
114 void	killact(struct kinfo_proc *, int);
115 void	grepact(struct kinfo_proc *, int);
116 int	parse_pid(const char *, char **, struct list *, pid_t);
117 void	makelist(struct listhead *, enum listtype, char *);
118 
119 /*
120  * pkill - list or signal selected processes based on regular expression.
121  */
122 int
123 main(int argc, char **argv)
124 {
125 	char buf[_POSIX2_LINE_MAX], *mstr, **pargv, *p, *q;
126 	int i, ch, bestidx, rv, criteria;
127 	unsigned int j;
128 	void (*action)(struct kinfo_proc *, int);
129 	struct kinfo_proc *kp;
130 	struct list *li;
131 	struct timeval best;
132 	regex_t reg;
133 	regmatch_t regmatch;
134 	const char *kvmf = _PATH_DEVNULL;
135 
136 	if (strcmp(getprogname(), "pgrep") == 0) {
137 		action = grepact;
138 		pgrep = 1;
139 	} else {
140 		action = killact;
141 		p = argv[1];
142 
143 		/*
144 		 * For pkill only: parse the signal (number or name) to send.
145 		 */
146 		if (argc > 1 && p[0] == '-') {
147 			p++;
148 			i = (int)strtol(p, &q, 10);
149 			if (*q == '\0') {
150 				signum = i;
151 				argv++;
152 				argc--;
153 			} else {
154 				if (strncasecmp(p, "sig", 3) == 0)
155 					p += 3;
156 				for (i = 1; i < NSIG; i++) {
157 					if (strcasecmp(sys_signame[i], p) == 0)
158 						break;
159 				}
160 				if (i != NSIG) {
161 					signum = i;
162 					argv++;
163 					argc--;
164 				}
165 			}
166 		}
167 	}
168 
169 	criteria = 0;
170 
171 	while ((ch = getopt(argc, argv, "G:P:U:d:fg:j:lns:t:u:vx")) != -1) {
172 		switch (ch) {
173 		case 'G':
174 			makelist(&rgidlist, LT_GROUP, optarg);
175 			criteria = 1;
176 			break;
177 		case 'P':
178 			makelist(&ppidlist, LT_PPID, optarg);
179 			criteria = 1;
180 			break;
181 		case 'U':
182 			makelist(&ruidlist, LT_USER, optarg);
183 			criteria = 1;
184 			break;
185 		case 'd':
186 			if (!pgrep)
187 				usage();
188 			delim = optarg;
189 			break;
190 		case 'f':
191 			matchargs = 1;
192 			break;
193 		case 'g':
194 			makelist(&pgrplist, LT_PGRP, optarg);
195 			criteria = 1;
196 			break;
197 		case 'j':
198 			makelist(&jidlist, LT_JID, optarg);
199 			criteria = 1;
200 			break;
201 		case 'l':
202 			if (!pgrep)
203 				usage();
204 			longfmt = 1;
205 			break;
206 		case 'n':
207 			newest = 1;
208 			criteria = 1;
209 			break;
210 		case 's':
211 			makelist(&sidlist, LT_SID, optarg);
212 			criteria = 1;
213 			break;
214 		case 't':
215 			makelist(&tdevlist, LT_TTY, optarg);
216 			criteria = 1;
217 			break;
218 		case 'u':
219 			makelist(&euidlist, LT_USER, optarg);
220 			criteria = 1;
221 			break;
222 		case 'v':
223 			inverse = 1;
224 			break;
225 		case 'x':
226 			fullmatch = 1;
227 			break;
228 		default:
229 			usage();
230 			/* NOTREACHED */
231 		}
232 	}
233 
234 	argc -= optind;
235 	argv += optind;
236 	if (argc != 0)
237 		criteria = 1;
238 	if (!criteria)
239 		usage();
240 
241 	mypid = getpid();
242 
243 	/*
244 	 * Retrieve the list of running processes from the kernel.
245 	 */
246 	kd = kvm_openfiles(kvmf, kvmf, NULL, O_RDONLY, buf);
247 	if (kd == NULL)
248 		errx(STATUS_ERROR, "kvm_openfiles(): %s", buf);
249 
250 	plist = kvm_getprocs(kd, KERN_PROC_ALL, 0, &nproc);
251 	if (plist == NULL)
252 		errx(STATUS_ERROR, "cannot list processes");
253 
254 	/*
255 	 * Allocate memory which will be used to keep track of the
256 	 * selection.
257 	 */
258 	if ((selected = malloc(nproc)) == NULL)
259 		errx(STATUS_ERROR, "memory allocation failure");
260 	memset(selected, 0, nproc);
261 
262 	/*
263 	 * Refine the selection.
264 	 */
265 	for (; *argv != NULL; argv++) {
266 		if ((rv = regcomp(&reg, *argv, REG_EXTENDED)) != 0) {
267 			regerror(rv, &reg, buf, sizeof(buf));
268 			errx(STATUS_BADUSAGE, "bad expression: %s", buf);
269 		}
270 
271 		for (i = 0, kp = plist; i < nproc; i++, kp++) {
272 			if ((kp->kp_flags & P_SYSTEM) != 0 || kp->kp_pid == mypid)
273 				continue;
274 
275 			if (matchargs) {
276 				if ((pargv = kvm_getargv(kd, kp, 0)) == NULL)
277 					continue;
278 
279 				j = 0;
280 				while (j < sizeof(buf) && *pargv != NULL) {
281 					j += snprintf(buf + j, sizeof(buf) - j,
282 					    pargv[1] != NULL ? "%s " : "%s",
283 					    pargv[0]);
284 					pargv++;
285 				}
286 
287 				mstr = buf;
288 			} else
289 				mstr = kp->kp_comm;
290 
291 			rv = regexec(&reg, mstr, 1, &regmatch, 0);
292 			if (rv == 0) {
293 				if (fullmatch) {
294 					if (regmatch.rm_so == 0 &&
295 					    regmatch.rm_eo == (regoff_t)strlen(mstr))
296 						selected[i] = 1;
297 				} else
298 					selected[i] = 1;
299 			} else if (rv != REG_NOMATCH) {
300 				regerror(rv, &reg, buf, sizeof(buf));
301 				errx(STATUS_ERROR, "regexec(): %s", buf);
302 			}
303 		}
304 
305 		regfree(&reg);
306 	}
307 
308 	/*
309 	 * Iterate through the list of processes, deselecting each one
310 	 * if it fails to meet the established criteria.
311 	 */
312 	for (i = 0, kp = plist; i < nproc; i++, kp++) {
313 		if ((kp->kp_flags & P_SYSTEM) != 0)
314 			continue;
315 
316 		SLIST_FOREACH(li, &ruidlist, li_chain) {
317 			if (kp->kp_ruid == li->li_datum.ld_uid)
318 				break;
319 		}
320 		if (SLIST_FIRST(&ruidlist) != NULL && li == NULL) {
321 			selected[i] = 0;
322 			continue;
323 		}
324 
325 		SLIST_FOREACH(li, &rgidlist, li_chain) {
326 			if (kp->kp_rgid == li->li_datum.ld_gid)
327 				break;
328 		}
329 		if (SLIST_FIRST(&rgidlist) != NULL && li == NULL) {
330 			selected[i] = 0;
331 			continue;
332 		}
333 
334 		SLIST_FOREACH(li, &euidlist, li_chain) {
335 			if (kp->kp_uid == li->li_datum.ld_uid)
336 				break;
337 		}
338 		if (SLIST_FIRST(&euidlist) != NULL && li == NULL) {
339 			selected[i] = 0;
340 			continue;
341 		}
342 
343 		SLIST_FOREACH(li, &ppidlist, li_chain) {
344 			if (kp->kp_ppid == li->li_datum.ld_pid)
345 				break;
346 		}
347 		if (SLIST_FIRST(&ppidlist) != NULL && li == NULL) {
348 			selected[i] = 0;
349 			continue;
350 		}
351 
352 		SLIST_FOREACH(li, &pgrplist, li_chain) {
353 			if (kp->kp_pgid == li->li_datum.ld_pid)
354 				break;
355 		}
356 		if (SLIST_FIRST(&pgrplist) != NULL && li == NULL) {
357 			selected[i] = 0;
358 			continue;
359 		}
360 
361 		SLIST_FOREACH(li, &tdevlist, li_chain) {
362 			if (li->li_datum.ld_dev == NODEV &&
363 			    (kp->kp_flags & P_CONTROLT) == 0)
364 				break;
365 			if (kp->kp_tdev == li->li_datum.ld_dev)
366 				break;
367 		}
368 		if (SLIST_FIRST(&tdevlist) != NULL && li == NULL) {
369 			selected[i] = 0;
370 			continue;
371 		}
372 
373 		SLIST_FOREACH(li, &sidlist, li_chain) {
374 			if (kp->kp_sid == li->li_datum.ld_pid)
375 				break;
376 		}
377 		if (SLIST_FIRST(&sidlist) != NULL && li == NULL) {
378 			selected[i] = 0;
379 			continue;
380 		}
381 
382 		SLIST_FOREACH(li, &jidlist, li_chain) {
383 			/* A particular jail ID, including 0 (not in jail) */
384 			if (kp->kp_jailid == li->li_datum.ld_pid)
385 				break;
386 			/* Any jail */
387 			if (kp->kp_jailid > 0 && li->li_datum.ld_pid < 0)
388 				break;
389 		}
390 		if (SLIST_FIRST(&jidlist) != NULL && li == NULL) {
391 			selected[i] = 0;
392 			continue;
393 		}
394 
395 		if (argc == 0)
396 			selected[i] = 1;
397 	}
398 
399 	if (newest) {
400 		best.tv_sec = 0;
401 		best.tv_usec = 0;
402 		bestidx = -1;
403 
404 		for (i = 0, kp = plist; i < nproc; i++, kp++) {
405 			if (!selected[i])
406 				continue;
407 
408 			if (kp->kp_start.tv_sec > best.tv_sec ||
409 			    (kp->kp_start.tv_sec == best.tv_sec
410 			    && kp->kp_start.tv_usec > best.tv_usec)) {
411 			    	best.tv_sec = kp->kp_start.tv_sec;
412 			    	best.tv_usec = kp->kp_start.tv_usec;
413 				bestidx = i;
414 			}
415 		}
416 
417 		memset(selected, 0, nproc);
418 		if (bestidx != -1)
419 			selected[bestidx] = 1;
420 	}
421 
422 	/*
423 	 * Take the appropriate action for each matched process, if any.
424 	 */
425 	for (i = 0, j = 0, rv = 0, kp = plist; i < nproc; i++, kp++) {
426 		if (kp->kp_pid == mypid)
427 			continue;
428 		if (selected[i]) {
429 			if (inverse)
430 				continue;
431 		} else if (!inverse)
432 			continue;
433 
434 		if ((kp->kp_flags & P_SYSTEM) != 0)
435 			continue;
436 
437 		rv = 1;
438 		(*action)(kp, j++);
439 	}
440 
441 	if (pgrep)
442 		putchar('\n');
443 
444 	exit(rv ? STATUS_MATCH : STATUS_NOMATCH);
445 }
446 
447 void
448 usage(void)
449 {
450 	const char *ustr;
451 
452 	if (pgrep)
453 		ustr = "[-flnvx] [-d delim]";
454 	else
455 		ustr = "[-signal] [-fnvx]";
456 
457 	fprintf(stderr,
458 		"usage: %s %s [-G gid] [-P ppid] [-U uid] [-g pgrp] [-s sid]\n"
459 		"             [-t tty] [-u euid] [-j jid] pattern ...\n",
460 		getprogname(), ustr);
461 
462 	exit(STATUS_ERROR);
463 }
464 
465 /*
466  * Action callback to signal the given process (pkill).
467  */
468 void
469 killact(struct kinfo_proc *kp, int dummy __unused)
470 {
471 	if (kill(kp->kp_pid, signum) == -1)
472 		err(STATUS_ERROR, "signalling pid %d", (int)kp->kp_pid);
473 }
474 
475 /*
476  * Action callback to print the pid of the given process (pgrep).
477  */
478 void
479 grepact(struct kinfo_proc *kp, int printdelim)
480 {
481 	char **argv;
482 
483 	if (printdelim)
484 		fputs(delim, stdout);
485 
486 	if (longfmt && matchargs) {
487 		if ((argv = kvm_getargv(kd, kp, 0)) == NULL)
488 			return;
489 
490 		printf("%d ", (int)kp->kp_pid);
491 		for (; *argv != NULL; argv++) {
492 			printf("%s", *argv);
493 			if (argv[1] != NULL)
494 				putchar(' ');
495 		}
496 	} else if (longfmt)
497 		printf("%d %s", (int)kp->kp_pid, kp->kp_comm);
498 	else
499 		printf("%d", (int)kp->kp_pid);
500 
501 }
502 
503 /*
504  * Parse a pid from the given string.  If zero, use a given default.
505  */
506 int
507 parse_pid(const char *string, char **p, struct list *li, pid_t default_pid)
508 {
509 	long l;
510 
511 	l = strtol(string, p, 0);
512 	li->li_datum.ld_pid = (l == 0 ? default_pid : (pid_t)l);
513 	return(**p == '\0');
514 }
515 
516 /*
517  * Populate a list from a comma-seperated string of items.
518  * The possible valid values for each item depends on the type of list.
519  */
520 void
521 makelist(struct listhead *head, enum listtype type, char *src)
522 {
523 	struct list *li;
524 	struct passwd *pw;
525 	struct group *gr;
526 	struct stat st;
527 	const char *sp, *tty_name;
528 	char *p, buf[MAXPATHLEN];
529 	int empty;
530 
531 	empty = 1;
532 
533 	while ((sp = strsep(&src, ",")) != NULL) {
534 		if (*sp == '\0')
535 			usage();
536 
537 		if ((li = malloc(sizeof(*li))) == NULL)
538 			errx(STATUS_ERROR, "memory allocation failure");
539 		SLIST_INSERT_HEAD(head, li, li_chain);
540 		empty = 0;
541 
542 		switch (type) {
543 		case LT_PPID:
544 			if (!parse_pid(sp, &p, li, (pid_t)0))
545 				usage();
546 			break;
547 		case LT_PGRP:
548 			if (!parse_pid(sp, &p, li, getpgrp()))
549 				usage();
550 			break;
551 		case LT_SID:
552 			if (!parse_pid(sp, &p, li, getsid(mypid)))
553 				usage();
554 			break;
555 		case LT_JID:
556 			/* default to no jail */
557 			if (!parse_pid(sp, &p, li, 0))
558 				usage();
559 			if (li->li_datum.ld_pid < -1) {
560 				errx(STATUS_BADUSAGE,
561 				     "Negative jail ID `%s'", sp);
562 			}
563 			break;
564 		case LT_USER:
565 			li->li_datum.ld_uid = (uid_t)strtol(sp, &p, 0);
566 			if (*p != '\0') {
567 				if ((pw = getpwnam(sp)) == NULL) {
568 					errx(STATUS_BADUSAGE,
569 					     "unknown user `%s'", optarg);
570 				}
571 				li->li_datum.ld_uid = pw->pw_uid;
572 			}
573 			break;
574 		case LT_GROUP:
575 			li->li_datum.ld_gid = (gid_t)strtol(sp, &p, 0);
576 			if (*p != '\0') {
577 				if ((gr = getgrnam(sp)) == NULL) {
578 					errx(STATUS_BADUSAGE,
579 					     "unknown group `%s'", optarg);
580 				}
581 				li->li_datum.ld_gid = gr->gr_gid;
582 			}
583 			break;
584 		case LT_TTY:
585 			if (strcmp(sp, "-") == 0) {
586 				li->li_datum.ld_dev = NODEV;
587 				break;
588 			} else if (strcmp(sp, "co") == 0)
589 				tty_name = "console";
590 			else if (strncmp(sp, "tty", 3) == 0)
591 				tty_name = sp;
592 			else
593 				tty_name = NULL;
594 
595 			if (tty_name == NULL)
596 				snprintf(buf, sizeof(buf), "/dev/tty%s", sp);
597 			else
598 				snprintf(buf, sizeof(buf), "/dev/%s", tty_name);
599 
600 			if (stat(buf, &st) < 0) {
601 				if (errno == ENOENT)
602 					errx(STATUS_BADUSAGE,
603 					    "no such tty: `%s'", sp);
604 				err(STATUS_ERROR, "stat(%s)", sp);
605 			}
606 
607 			if ((st.st_mode & S_IFCHR) == 0)
608 				errx(STATUS_BADUSAGE, "not a tty: `%s'", sp);
609 
610 			li->li_datum.ld_dev = st.st_rdev;
611 			break;
612 		default:
613 			usage();
614 		}
615 	}
616 
617 	if (empty)
618 		usage();
619 }
620