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