xref: /dragonfly/usr.bin/pkill/pkill.c (revision 2ee85085)
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.7 2005/02/15 20:31:29 cpressey 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/types.h>
41 #include <sys/param.h>
42 #include <sys/sysctl.h>
43 #include <sys/user.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,
70 	LT_GROUP,
71 	LT_TTY,
72 	LT_PPID,
73 	LT_PGRP,
74 	LT_SID
75 };
76 
77 struct list {
78 	SLIST_ENTRY(list) li_chain;
79 	union {
80 		uid_t	ld_uid;
81 		gid_t	ld_gid;
82 		pid_t	ld_pid;
83 		dev_t	ld_dev;
84 	} li_datum;
85 };
86 
87 SLIST_HEAD(listhead, list);
88 
89 struct kinfo_proc	*plist;
90 char	*selected;
91 const char *delim = "\n";
92 int	nproc;
93 int	pgrep;
94 int	signum = SIGTERM;
95 int	newest;
96 int	inverse;
97 int	longfmt;
98 int	matchargs;
99 int	fullmatch;
100 kvm_t	*kd;
101 pid_t	mypid;
102 
103 struct listhead euidlist = SLIST_HEAD_INITIALIZER(list);
104 struct listhead ruidlist = SLIST_HEAD_INITIALIZER(list);
105 struct listhead rgidlist = SLIST_HEAD_INITIALIZER(list);
106 struct listhead pgrplist = SLIST_HEAD_INITIALIZER(list);
107 struct listhead ppidlist = SLIST_HEAD_INITIALIZER(list);
108 struct listhead tdevlist = SLIST_HEAD_INITIALIZER(list);
109 struct listhead sidlist = SLIST_HEAD_INITIALIZER(list);
110 
111 void	usage(void);
112 void	killact(struct kinfo_proc *, int);
113 void	grepact(struct kinfo_proc *, int);
114 int	parse_pid(const char *, char **, struct list *, pid_t);
115 void	makelist(struct listhead *, enum listtype, char *);
116 
117 int
118 main(int argc, char **argv)
119 {
120 	char buf[_POSIX2_LINE_MAX], *mstr, **pargv, *p, *q;
121 	int i, ch, bestidx, rv, criteria;
122 	unsigned int j;
123 	void (*action)(struct kinfo_proc *, int);
124 	struct kinfo_proc *kp;
125 	struct list *li;
126 	struct timeval best;
127 	regex_t reg;
128 	regmatch_t regmatch;
129 	const char *kvmf = _PATH_DEVNULL;
130 
131 	if (strcmp(getprogname(), "pgrep") == 0) {
132 		action = grepact;
133 		pgrep = 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 				}
152 				if (i != NSIG) {
153 					signum = i;
154 					argv++;
155 					argc--;
156 				}
157 			}
158 		}
159 	}
160 
161 	criteria = 0;
162 
163 	while ((ch = getopt(argc, argv, "G:P:U:d:fg:lns:t:u:vx")) != -1) {
164 		switch (ch) {
165 		case 'G':
166 			makelist(&rgidlist, LT_GROUP, optarg);
167 			criteria = 1;
168 			break;
169 		case 'P':
170 			makelist(&ppidlist, LT_PPID, optarg);
171 			criteria = 1;
172 			break;
173 		case 'U':
174 			makelist(&ruidlist, LT_USER, optarg);
175 			criteria = 1;
176 			break;
177 		case 'd':
178 			if (!pgrep)
179 				usage();
180 			delim = optarg;
181 			break;
182 		case 'f':
183 			matchargs = 1;
184 			break;
185 		case 'g':
186 			makelist(&pgrplist, LT_PGRP, optarg);
187 			criteria = 1;
188 			break;
189 		case 'l':
190 			if (!pgrep)
191 				usage();
192 			longfmt = 1;
193 			break;
194 		case 'n':
195 			newest = 1;
196 			criteria = 1;
197 			break;
198 		case 's':
199 			makelist(&sidlist, LT_SID, optarg);
200 			criteria = 1;
201 			break;
202 		case 't':
203 			makelist(&tdevlist, LT_TTY, optarg);
204 			criteria = 1;
205 			break;
206 		case 'u':
207 			makelist(&euidlist, LT_USER, optarg);
208 			criteria = 1;
209 			break;
210 		case 'v':
211 			inverse = 1;
212 			break;
213 		case 'x':
214 			fullmatch = 1;
215 			break;
216 		default:
217 			usage();
218 			/* NOTREACHED */
219 		}
220 	}
221 
222 	argc -= optind;
223 	argv += optind;
224 	if (argc != 0)
225 		criteria = 1;
226 	if (!criteria)
227 		usage();
228 
229 	mypid = getpid();
230 
231 	/*
232 	 * Retrieve the list of running processes from the kernel.
233 	 */
234 	kd = kvm_openfiles(kvmf, kvmf, NULL, O_RDONLY, buf);
235 	if (kd == NULL)
236 		errx(STATUS_ERROR, "kvm_openfiles(): %s", buf);
237 
238 	plist = kvm_getprocs(kd, KERN_PROC_ALL, 0, &nproc);
239 	if (plist == NULL)
240 		errx(STATUS_ERROR, "cannot list processes");
241 
242 	/*
243 	 * Allocate memory which will be used to keep track of the
244 	 * selection.
245 	 */
246 	if ((selected = malloc(nproc)) == NULL)
247 		errx(STATUS_ERROR, "memory allocation failure");
248 	memset(selected, 0, nproc);
249 
250 	/*
251 	 * Refine the selection.
252 	 */
253 	for (; *argv != NULL; argv++) {
254 		if ((rv = regcomp(&reg, *argv, REG_EXTENDED)) != 0) {
255 			regerror(rv, &reg, buf, sizeof(buf));
256 			errx(STATUS_BADUSAGE, "bad expression: %s", buf);
257 		}
258 
259 		for (i = 0, kp = plist; i < nproc; i++, kp++) {
260 			if ((kp->kp_proc.p_flag & P_SYSTEM) != 0 || kp->kp_proc.p_pid == mypid)
261 				continue;
262 
263 			if (matchargs) {
264 				if ((pargv = kvm_getargv(kd, kp, 0)) == NULL)
265 					continue;
266 
267 				j = 0;
268 				while (j < sizeof(buf) && *pargv != NULL) {
269 					j += snprintf(buf + j, sizeof(buf) - j,
270 					    pargv[1] != NULL ? "%s " : "%s",
271 					    pargv[0]);
272 					pargv++;
273 				}
274 
275 				mstr = buf;
276 			} else
277 				mstr = kp->kp_thread.td_comm;
278 
279 			rv = regexec(&reg, mstr, 1, &regmatch, 0);
280 			if (rv == 0) {
281 				if (fullmatch) {
282 					if (regmatch.rm_so == 0 &&
283 					    regmatch.rm_eo == strlen(mstr))
284 						selected[i] = 1;
285 				} else
286 					selected[i] = 1;
287 			} else if (rv != REG_NOMATCH) {
288 				regerror(rv, &reg, buf, sizeof(buf));
289 				errx(STATUS_ERROR, "regexec(): %s", buf);
290 			}
291 		}
292 
293 		regfree(&reg);
294 	}
295 
296 	for (i = 0, kp = plist; i < nproc; i++, kp++) {
297 		if ((kp->kp_proc.p_flag & P_SYSTEM) != 0)
298 			continue;
299 
300 		SLIST_FOREACH(li, &ruidlist, li_chain) {
301 			if (kp->kp_eproc.e_ucred.cr_ruid == li->li_datum.ld_uid)
302 				break;
303 		}
304 		if (SLIST_FIRST(&ruidlist) != NULL && li == NULL) {
305 			selected[i] = 0;
306 			continue;
307 		}
308 
309 		SLIST_FOREACH(li, &rgidlist, li_chain) {
310 			if (kp->kp_eproc.e_ucred.cr_rgid == li->li_datum.ld_gid)
311 				break;
312 		}
313 		if (SLIST_FIRST(&rgidlist) != NULL && li == NULL) {
314 			selected[i] = 0;
315 			continue;
316 		}
317 
318 		SLIST_FOREACH(li, &euidlist, li_chain) {
319 			if (kp->kp_eproc.e_ucred.cr_uid == li->li_datum.ld_uid)
320 				break;
321 		}
322 		if (SLIST_FIRST(&euidlist) != NULL && li == NULL) {
323 			selected[i] = 0;
324 			continue;
325 		}
326 
327 		SLIST_FOREACH(li, &ppidlist, li_chain) {
328 			if (kp->kp_eproc.e_ppid == li->li_datum.ld_pid)
329 				break;
330 		}
331 		if (SLIST_FIRST(&ppidlist) != NULL && li == NULL) {
332 			selected[i] = 0;
333 			continue;
334 		}
335 
336 		SLIST_FOREACH(li, &pgrplist, li_chain) {
337 			if (kp->kp_eproc.e_pgid == li->li_datum.ld_pid)
338 				break;
339 		}
340 		if (SLIST_FIRST(&pgrplist) != NULL && li == NULL) {
341 			selected[i] = 0;
342 			continue;
343 		}
344 
345 		SLIST_FOREACH(li, &tdevlist, li_chain) {
346 			if (li->li_datum.ld_dev == NODEV &&
347 			    (kp->kp_proc.p_flag & P_CONTROLT) == 0)
348 				break;
349 			if (kp->kp_eproc.e_tdev == li->li_datum.ld_dev)
350 				break;
351 		}
352 		if (SLIST_FIRST(&tdevlist) != NULL && li == NULL) {
353 			selected[i] = 0;
354 			continue;
355 		}
356 
357 		SLIST_FOREACH(li, &sidlist, li_chain) {
358 			if (kp->kp_eproc.e_sess->s_sid == li->li_datum.ld_pid)
359 				break;
360 		}
361 		if (SLIST_FIRST(&sidlist) != NULL && li == NULL) {
362 			selected[i] = 0;
363 			continue;
364 		}
365 
366 		if (argc == 0)
367 			selected[i] = 1;
368 	}
369 
370 	if (newest) {
371 		best.tv_sec = 0;
372 		best.tv_usec = 0;
373 		bestidx = -1;
374 
375 		for (i = 0, kp = plist; i < nproc; i++, kp++) {
376 			if (!selected[i])
377 				continue;
378 
379 			if (kp->kp_thread.td_start.tv_sec > best.tv_sec ||
380 			    (kp->kp_thread.td_start.tv_sec == best.tv_sec
381 			    && kp->kp_thread.td_start.tv_usec > best.tv_usec)) {
382 			    	best.tv_sec = kp->kp_thread.td_start.tv_sec;
383 			    	best.tv_usec = kp->kp_thread.td_start.tv_usec;
384 				bestidx = i;
385 			}
386 		}
387 
388 		memset(selected, 0, nproc);
389 		if (bestidx != -1)
390 			selected[bestidx] = 1;
391 	}
392 
393 	/*
394 	 * Take the appropriate action for each matched process, if any.
395 	 */
396 	for (i = 0, j = 0, rv = 0, kp = plist; i < nproc; i++, kp++) {
397 		if (kp->kp_proc.p_pid == mypid)
398 			continue;
399 		if (selected[i]) {
400 			if (inverse)
401 				continue;
402 		} else if (!inverse)
403 			continue;
404 
405 		if ((kp->kp_proc.p_flag & P_SYSTEM) != 0)
406 			continue;
407 
408 		rv = 1;
409 		(*action)(kp, j++);
410 	}
411 
412 	if (pgrep)
413 		putchar('\n');
414 
415 	exit(rv ? STATUS_MATCH : STATUS_NOMATCH);
416 }
417 
418 void
419 usage(void)
420 {
421 	const char *ustr;
422 
423 	if (pgrep)
424 		ustr = "[-flnvx] [-d delim]";
425 	else
426 		ustr = "[-signal] [-fnvx]";
427 
428 	fprintf(stderr,
429 		"usage: %s %s [-G gid] [-P ppid] [-U uid] [-g pgrp] [-s sid]\n"
430 		"             [-t tty] [-u euid] pattern ...\n", getprogname(),
431 		ustr);
432 
433 	exit(STATUS_ERROR);
434 }
435 
436 void
437 killact(struct kinfo_proc *kp, int dummy)
438 {
439 	dummy = 0; /* unused */
440 
441 	if (kill(kp->kp_proc.p_pid, signum) == -1)
442 		err(STATUS_ERROR, "signalling pid %d", (int)kp->kp_proc.p_pid);
443 }
444 
445 void
446 grepact(struct kinfo_proc *kp, int printdelim)
447 {
448 	char **argv;
449 
450 	if (printdelim)
451 		fputs(delim, stdout);
452 
453 	if (longfmt && matchargs) {
454 		if ((argv = kvm_getargv(kd, kp, 0)) == NULL)
455 			return;
456 
457 		printf("%d ", (int)kp->kp_proc.p_pid);
458 		for (; *argv != NULL; argv++) {
459 			printf("%s", *argv);
460 			if (argv[1] != NULL)
461 				putchar(' ');
462 		}
463 	} else if (longfmt)
464 		printf("%d %s", (int)kp->kp_proc.p_pid, kp->kp_thread.td_comm);
465 	else
466 		printf("%d", (int)kp->kp_proc.p_pid);
467 
468 }
469 
470 int
471 parse_pid(const char *string, char **p, struct list *li, pid_t default_pid)
472 {
473 	long l;
474 
475 	l = strtol(string, p, 0);
476 	li->li_datum.ld_pid = (l == 0 ? default_pid : (pid_t)l);
477 	return(**p == '\0');
478 }
479 
480 void
481 makelist(struct listhead *head, enum listtype type, char *src)
482 {
483 	struct list *li;
484 	struct passwd *pw;
485 	struct group *gr;
486 	struct stat st;
487 	const char *sp, *tty_name;
488 	char *p, buf[MAXPATHLEN];
489 	int empty;
490 
491 	empty = 1;
492 
493 	while ((sp = strsep(&src, ",")) != NULL) {
494 		if (*sp == '\0')
495 			usage();
496 
497 		if ((li = malloc(sizeof(*li))) == NULL)
498 			errx(STATUS_ERROR, "memory allocation failure");
499 		SLIST_INSERT_HEAD(head, li, li_chain);
500 		empty = 0;
501 
502 		switch (type) {
503 		case LT_PPID:
504 			if (!parse_pid(sp, &p, li, (pid_t)0))
505 				usage();
506 			break;
507 		case LT_PGRP:
508 			if (!parse_pid(sp, &p, li, getpgrp()))
509 				usage();
510 			break;
511 		case LT_SID:
512 			if (!parse_pid(sp, &p, li, getsid(mypid)))
513 				usage();
514 			break;
515 		case LT_USER:
516 			li->li_datum.ld_uid = (uid_t)strtol(sp, &p, 0);
517 			if (*p != '\0') {
518 				if ((pw = getpwnam(sp)) == NULL) {
519 					errx(STATUS_BADUSAGE,
520 					     "unknown user `%s'", optarg);
521 				}
522 				li->li_datum.ld_uid = pw->pw_uid;
523 			}
524 			break;
525 		case LT_GROUP:
526 			li->li_datum.ld_gid = (gid_t)strtol(sp, &p, 0);
527 			if (*p != '\0') {
528 				if ((gr = getgrnam(sp)) == NULL) {
529 					errx(STATUS_BADUSAGE,
530 					     "unknown group `%s'", optarg);
531 				}
532 				li->li_datum.ld_gid = gr->gr_gid;
533 			}
534 			break;
535 		case LT_TTY:
536 			if (strcmp(sp, "-") == 0) {
537 				li->li_datum.ld_dev = NODEV;
538 				break;
539 			} else if (strcmp(sp, "co") == 0)
540 				tty_name = "console";
541 			else if (strncmp(sp, "tty", 3) == 0)
542 				tty_name = sp;
543 			else
544 				tty_name = NULL;
545 
546 			if (tty_name == NULL)
547 				snprintf(buf, sizeof(buf), "/dev/tty%s", sp);
548 			else
549 				snprintf(buf, sizeof(buf), "/dev/%s", tty_name);
550 
551 			if (stat(buf, &st) < 0) {
552 				if (errno == ENOENT)
553 					errx(STATUS_BADUSAGE,
554 					    "no such tty: `%s'", sp);
555 				err(STATUS_ERROR, "stat(%s)", sp);
556 			}
557 
558 			if ((st.st_mode & S_IFCHR) == 0)
559 				errx(STATUS_BADUSAGE, "not a tty: `%s'", sp);
560 
561 			li->li_datum.ld_dev = st.st_rdev;
562 			break;
563 		default:
564 			usage();
565 		}
566 	}
567 
568 	if (empty)
569 		usage();
570 }
571