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