pkill.c revision 143878
1/*	$NetBSD: pkill.c,v 1.8 2005/03/02 15:31:44 abs 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 143878 2005-03-20 11:42:26Z pjd $");
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/time.h>
49#include <sys/user.h>
50
51#include <stdio.h>
52#include <stdlib.h>
53#include <limits.h>
54#include <paths.h>
55#include <string.h>
56#include <unistd.h>
57#include <signal.h>
58#include <regex.h>
59#include <ctype.h>
60#include <fcntl.h>
61#include <kvm.h>
62#include <err.h>
63#include <pwd.h>
64#include <grp.h>
65#include <errno.h>
66#include <locale.h>
67
68#define	STATUS_MATCH	0
69#define	STATUS_NOMATCH	1
70#define	STATUS_BADUSAGE	2
71#define	STATUS_ERROR	3
72
73#define	MIN_PID	5
74#define	MAX_PID	99999
75
76/* Ignore system-processes (if '-S' flag is not specified) and myself. */
77#define	PSKIP(kp)	((kp)->ki_pid == mypid ||			\
78			 (!kthreads && ((kp)->ki_flag & P_KTHREAD) != 0))
79
80enum listtype {
81	LT_GENERIC,
82	LT_USER,
83	LT_GROUP,
84	LT_TTY,
85	LT_PGRP,
86	LT_SID
87};
88
89struct list {
90	SLIST_ENTRY(list) li_chain;
91	long	li_number;
92};
93
94SLIST_HEAD(listhead, list);
95
96struct kinfo_proc	*plist;
97char	*selected;
98const char	*delim = "\n";
99int	nproc;
100int	pgrep;
101int	signum = SIGTERM;
102int	newest;
103int	oldest;
104int	inverse;
105int	longfmt;
106int	matchargs;
107int	fullmatch;
108int	kthreads;
109int	cflags = REG_EXTENDED;
110kvm_t	*kd;
111pid_t	mypid;
112
113struct listhead euidlist = SLIST_HEAD_INITIALIZER(list);
114struct listhead ruidlist = SLIST_HEAD_INITIALIZER(list);
115struct listhead rgidlist = SLIST_HEAD_INITIALIZER(list);
116struct listhead pgrplist = SLIST_HEAD_INITIALIZER(list);
117struct listhead ppidlist = SLIST_HEAD_INITIALIZER(list);
118struct listhead tdevlist = SLIST_HEAD_INITIALIZER(list);
119struct listhead sidlist = SLIST_HEAD_INITIALIZER(list);
120struct listhead jidlist = SLIST_HEAD_INITIALIZER(list);
121
122int	main(int, char **);
123void	usage(void);
124void	killact(struct kinfo_proc *);
125void	grepact(struct kinfo_proc *);
126void	makelist(struct listhead *, enum listtype, char *);
127int	takepid(const char *);
128
129int
130main(int argc, char **argv)
131{
132	extern char *optarg;
133	extern int optind;
134	char buf[_POSIX2_LINE_MAX], *mstr, **pargv, *p, *q;
135	const char *execf, *coref;
136	int debug_opt;
137	int i, ch, bestidx, rv, criteria, pidfromfile;
138	size_t jsz;
139	void (*action)(struct kinfo_proc *);
140	struct kinfo_proc *kp;
141	struct list *li;
142	struct timeval best_tval;
143	regex_t reg;
144	regmatch_t regmatch;
145
146	setlocale(LC_ALL, "");
147
148	if (strcmp(getprogname(), "pgrep") == 0) {
149		action = grepact;
150		pgrep = 1;
151	} else {
152		action = killact;
153		p = argv[1];
154
155		if (argc > 1 && p[0] == '-') {
156			p++;
157			i = (int)strtol(p, &q, 10);
158			if (*q == '\0') {
159				signum = i;
160				argv++;
161				argc--;
162			} else {
163				if (strncasecmp(p, "sig", 3) == 0)
164					p += 3;
165				for (i = 1; i < NSIG; i++)
166					if (strcasecmp(sys_signame[i], p) == 0)
167						break;
168				if (i != NSIG) {
169					signum = i;
170					argv++;
171					argc--;
172				}
173			}
174		}
175	}
176
177	criteria = 0;
178	debug_opt = 0;
179	pidfromfile = -1;
180	execf = coref = _PATH_DEVNULL;
181
182	while ((ch = getopt(argc, argv, "DF:G:M:N:P:SU:d:fg:ij:lnos:t:u:vx")) != -1)
183		switch (ch) {
184		case 'D':
185			debug_opt++;
186			break;
187		case 'F':
188			pidfromfile = takepid(optarg);
189			criteria = 1;
190			break;
191		case 'G':
192			makelist(&rgidlist, LT_GROUP, optarg);
193			criteria = 1;
194			break;
195		case 'M':
196			coref = optarg;
197			break;
198		case 'N':
199			execf = optarg;
200			break;
201		case 'P':
202			makelist(&ppidlist, LT_GENERIC, optarg);
203			criteria = 1;
204			break;
205		case 'S':
206			if (!pgrep)
207				usage();
208			kthreads = 1;
209			break;
210		case 'U':
211			makelist(&ruidlist, LT_USER, optarg);
212			criteria = 1;
213			break;
214		case 'd':
215			if (!pgrep)
216				usage();
217			delim = optarg;
218			break;
219		case 'f':
220			matchargs = 1;
221			break;
222		case 'g':
223			makelist(&pgrplist, LT_PGRP, optarg);
224			criteria = 1;
225			break;
226		case 'i':
227			cflags |= REG_ICASE;
228			break;
229		case 'j':
230			makelist(&jidlist, LT_GENERIC, optarg);
231			criteria = 1;
232			break;
233		case 'l':
234			if (!pgrep)
235				usage();
236			longfmt = 1;
237			break;
238		case 'n':
239			newest = 1;
240			criteria = 1;
241			break;
242		case 'o':
243			oldest = 1;
244			criteria = 1;
245			break;
246		case 's':
247			makelist(&sidlist, LT_SID, optarg);
248			criteria = 1;
249			break;
250		case 't':
251			makelist(&tdevlist, LT_TTY, optarg);
252			criteria = 1;
253			break;
254		case 'u':
255			makelist(&euidlist, LT_USER, optarg);
256			criteria = 1;
257			break;
258		case 'v':
259			inverse = 1;
260			break;
261		case 'x':
262			fullmatch = 1;
263			break;
264		default:
265			usage();
266			/* NOTREACHED */
267		}
268
269	argc -= optind;
270	argv += optind;
271	if (argc != 0)
272		criteria = 1;
273	if (!criteria)
274		usage();
275	if (newest && oldest)
276		errx(STATUS_ERROR, "-n and -o are mutually exclusive");
277
278	mypid = getpid();
279
280	/*
281	 * Retrieve the list of running processes from the kernel.
282	 */
283	kd = kvm_openfiles(execf, coref, NULL, O_RDONLY, buf);
284	if (kd == NULL)
285		errx(STATUS_ERROR, "kvm_openfiles(): %s", buf);
286
287	/*
288	 * Use KERN_PROC_PROC instead of KERN_PROC_ALL, since we
289	 * just want processes and not individual kernel threads.
290	 */
291	plist = kvm_getprocs(kd, KERN_PROC_PROC, 0, &nproc);
292	if (plist == NULL)
293		errx(STATUS_ERROR, "kvm_getprocs() failed");
294
295	/*
296	 * Allocate memory which will be used to keep track of the
297	 * selection.
298	 */
299	if ((selected = malloc(nproc)) == NULL)
300		errx(STATUS_ERROR, "memory allocation failure");
301	memset(selected, 0, nproc);
302
303	/*
304	 * Refine the selection.
305	 */
306	for (; *argv != NULL; argv++) {
307		if ((rv = regcomp(&reg, *argv, cflags)) != 0) {
308			regerror(rv, &reg, buf, sizeof(buf));
309			errx(STATUS_BADUSAGE, "bad expression: %s", buf);
310		}
311
312		for (i = 0, kp = plist; i < nproc; i++, kp++) {
313			if (PSKIP(kp)) {
314				if (debug_opt > 0)
315				    fprintf(stderr, "* Skipped %5d %3d %s\n",
316					kp->ki_pid, kp->ki_uid, kp->ki_comm);
317				continue;
318			}
319
320			if (matchargs &&
321			    (pargv = kvm_getargv(kd, kp, 0)) != NULL) {
322				jsz = 0;
323				while (jsz < sizeof(buf) && *pargv != NULL) {
324					jsz += snprintf(buf + jsz,
325					    sizeof(buf) - jsz,
326					    pargv[1] != NULL ? "%s " : "%s",
327					    pargv[0]);
328					pargv++;
329				}
330				mstr = buf;
331			} else
332				mstr = kp->ki_comm;
333
334			rv = regexec(&reg, mstr, 1, &regmatch, 0);
335			if (rv == 0) {
336				if (fullmatch) {
337					if (regmatch.rm_so == 0 &&
338					    regmatch.rm_eo ==
339					    (off_t)strlen(mstr))
340						selected[i] = 1;
341				} else
342					selected[i] = 1;
343			} else if (rv != REG_NOMATCH) {
344				regerror(rv, &reg, buf, sizeof(buf));
345				errx(STATUS_ERROR, "regexec(): %s", buf);
346			}
347			if (debug_opt > 1) {
348				const char *rv_res = "NoMatch";
349				if (selected[i])
350					rv_res = "Matched";
351				fprintf(stderr, "* %s %5d %3d %s\n", rv_res,
352				    kp->ki_pid, kp->ki_uid, mstr);
353			}
354		}
355
356		regfree(&reg);
357	}
358
359	for (i = 0, kp = plist; i < nproc; i++, kp++) {
360		if (PSKIP(kp))
361			continue;
362
363		if (pidfromfile >= 0 && kp->ki_pid != pidfromfile) {
364			selected[i] = 0;
365			continue;
366		}
367
368		SLIST_FOREACH(li, &ruidlist, li_chain)
369			if (kp->ki_ruid == (uid_t)li->li_number)
370				break;
371		if (SLIST_FIRST(&ruidlist) != NULL && li == NULL) {
372			selected[i] = 0;
373			continue;
374		}
375
376		SLIST_FOREACH(li, &rgidlist, li_chain)
377			if (kp->ki_rgid == (gid_t)li->li_number)
378				break;
379		if (SLIST_FIRST(&rgidlist) != NULL && li == NULL) {
380			selected[i] = 0;
381			continue;
382		}
383
384		SLIST_FOREACH(li, &euidlist, li_chain)
385			if (kp->ki_uid == (uid_t)li->li_number)
386				break;
387		if (SLIST_FIRST(&euidlist) != NULL && li == NULL) {
388			selected[i] = 0;
389			continue;
390		}
391
392		SLIST_FOREACH(li, &ppidlist, li_chain)
393			if (kp->ki_ppid == (pid_t)li->li_number)
394				break;
395		if (SLIST_FIRST(&ppidlist) != NULL && li == NULL) {
396			selected[i] = 0;
397			continue;
398		}
399
400		SLIST_FOREACH(li, &pgrplist, li_chain)
401			if (kp->ki_pgid == (pid_t)li->li_number)
402				break;
403		if (SLIST_FIRST(&pgrplist) != NULL && li == NULL) {
404			selected[i] = 0;
405			continue;
406		}
407
408		SLIST_FOREACH(li, &tdevlist, li_chain) {
409			if (li->li_number == -1 &&
410			    (kp->ki_flag & P_CONTROLT) == 0)
411				break;
412			if (kp->ki_tdev == (dev_t)li->li_number)
413				break;
414		}
415		if (SLIST_FIRST(&tdevlist) != NULL && li == NULL) {
416			selected[i] = 0;
417			continue;
418		}
419
420		SLIST_FOREACH(li, &sidlist, li_chain)
421			if (kp->ki_sid == (pid_t)li->li_number)
422				break;
423		if (SLIST_FIRST(&sidlist) != NULL && li == NULL) {
424			selected[i] = 0;
425			continue;
426		}
427
428		SLIST_FOREACH(li, &jidlist, li_chain) {
429			if (kp->ki_jid > 0) {
430				if (li->li_number == 0)
431					break;
432				if (kp->ki_jid == (int)li->li_number)
433					break;
434			}
435		}
436		if (SLIST_FIRST(&jidlist) != NULL && li == NULL) {
437			selected[i] = 0;
438			continue;
439		}
440
441		if (argc == 0)
442			selected[i] = 1;
443	}
444
445	if (newest || oldest) {
446		best_tval.tv_sec = 0;
447		best_tval.tv_usec = 0;
448		bestidx = -1;
449
450		for (i = 0, kp = plist; i < nproc; i++, kp++) {
451			if (!selected[i])
452				continue;
453
454			if (bestidx == -1) {
455				/* The first entry of the list which matched. */
456				;
457			} else if (timercmp(&kp->ki_start, &best_tval, >)) {
458				/* This entry is newer than previous "best". */
459				if (oldest)     /* but we want the oldest */
460					continue;
461			} else {
462				/* This entry is older than previous "best". */
463				if (newest)     /* but we want the newest */
464					continue;
465			}
466			/* This entry is better than previous "best" entry. */
467			best_tval.tv_sec = kp->ki_start.tv_sec;
468			best_tval.tv_usec = kp->ki_start.tv_usec;
469			bestidx = i;
470		}
471
472		memset(selected, 0, nproc);
473		if (bestidx != -1)
474			selected[bestidx] = 1;
475	}
476
477	/*
478	 * Take the appropriate action for each matched process, if any.
479	 */
480	for (i = 0, rv = 0, kp = plist; i < nproc; i++, kp++) {
481		if (PSKIP(kp))
482			continue;
483		if (selected[i]) {
484			if (inverse)
485				continue;
486		} else if (!inverse)
487			continue;
488		rv = 1;
489		(*action)(kp);
490	}
491
492	exit(rv ? STATUS_MATCH : STATUS_NOMATCH);
493}
494
495void
496usage(void)
497{
498	const char *ustr;
499
500	if (pgrep)
501		ustr = "[-Sfilnovx] [-d delim]";
502	else
503		ustr = "[-signal] [-finovx]";
504
505	fprintf(stderr,
506		"usage: %s %s [-F pidfile] [-G gid] [-M core] [-N system]\n"
507		"             [-P ppid] [-U uid] [-g pgrp] [-j jid] [-s sid]\n"
508		"             [-t tty] [-u euid] pattern ...\n", getprogname(),
509		ustr);
510
511	exit(STATUS_ERROR);
512}
513
514void
515killact(struct kinfo_proc *kp)
516{
517
518	if (kill(kp->ki_pid, signum) == -1)
519		err(STATUS_ERROR, "signalling pid %d", (int)kp->ki_pid);
520}
521
522void
523grepact(struct kinfo_proc *kp)
524{
525	char **argv;
526
527	if (longfmt && matchargs &&
528	    (argv = kvm_getargv(kd, kp, 0)) != NULL) {
529		printf("%d ", (int)kp->ki_pid);
530		for (; *argv != NULL; argv++) {
531			printf("%s", *argv);
532			if (argv[1] != NULL)
533				putchar(' ');
534		}
535	} else if (longfmt)
536		printf("%d %s", (int)kp->ki_pid, kp->ki_comm);
537	else
538		printf("%d", (int)kp->ki_pid);
539
540	printf("%s", delim);
541}
542
543void
544makelist(struct listhead *head, enum listtype type, char *src)
545{
546	struct list *li;
547	struct passwd *pw;
548	struct group *gr;
549	struct stat st;
550	const char *cp;
551	char *sp, *p, buf[MAXPATHLEN];
552	int empty;
553
554	empty = 1;
555
556	while ((sp = strsep(&src, ",")) != NULL) {
557		if (*sp == '\0')
558			usage();
559
560		if ((li = malloc(sizeof(*li))) == NULL)
561			errx(STATUS_ERROR, "memory allocation failure");
562		SLIST_INSERT_HEAD(head, li, li_chain);
563		empty = 0;
564
565		li->li_number = (uid_t)strtol(sp, &p, 0);
566		if (*p == '\0') {
567			switch (type) {
568			case LT_PGRP:
569				if (li->li_number == 0)
570					li->li_number = getpgrp();
571				break;
572			case LT_SID:
573				if (li->li_number == 0)
574					li->li_number = getsid(mypid);
575				break;
576			case LT_TTY:
577				usage();
578			default:
579				break;
580			}
581			continue;
582		}
583
584		switch (type) {
585		case LT_USER:
586			if ((pw = getpwnam(sp)) == NULL)
587				errx(STATUS_BADUSAGE, "unknown user `%s'",
588				    sp);
589			li->li_number = pw->pw_uid;
590			break;
591		case LT_GROUP:
592			if ((gr = getgrnam(sp)) == NULL)
593				errx(STATUS_BADUSAGE, "unknown group `%s'",
594				    sp);
595			li->li_number = gr->gr_gid;
596			break;
597		case LT_TTY:
598			if (strcmp(sp, "-") == 0) {
599				li->li_number = -1;
600				break;
601			} else if (strcmp(sp, "co") == 0)
602				cp = "console";
603			else if (strncmp(sp, "tty", 3) == 0)
604				cp = sp;
605			else
606				cp = NULL;
607
608			if (cp == NULL)
609				snprintf(buf, sizeof(buf), "/dev/tty%s", sp);
610			else
611				snprintf(buf, sizeof(buf), "/dev/%s", cp);
612
613			if (stat(buf, &st) < 0) {
614				if (errno == ENOENT)
615					errx(STATUS_BADUSAGE,
616					    "no such tty: `%s'", sp);
617				err(STATUS_ERROR, "stat(%s)", sp);
618			}
619
620			if ((st.st_mode & S_IFCHR) == 0)
621				errx(STATUS_BADUSAGE, "not a tty: `%s'", sp);
622
623			li->li_number = st.st_rdev;
624			break;
625		default:
626			usage();
627		};
628	}
629
630	if (empty)
631		usage();
632}
633
634int
635takepid(const char *pidfile)
636{
637	char *endp, line[BUFSIZ];
638	FILE *fh;
639	long rval;
640
641	fh = fopen(pidfile, "r");
642	if (fh == NULL)
643		err(STATUS_ERROR, "can't open pid file `%s'", pidfile);
644
645	if (fgets(line, sizeof(line), fh) == NULL) {
646		if (feof(fh)) {
647			(void)fclose(fh);
648			errx(STATUS_ERROR, "pid file `%s' is empty", pidfile);
649		}
650		(void)fclose(fh);
651		err(STATUS_ERROR, "can't read from pid file `%s'", pidfile);
652	}
653	(void)fclose(fh);
654
655	rval = strtol(line, &endp, 10);
656	if (*endp != '\0' && !isspace((unsigned char)*endp))
657		errx(STATUS_ERROR, "invalid pid in file `%s'", pidfile);
658	else if (rval < MIN_PID || rval > MAX_PID)
659		errx(STATUS_ERROR, "invalid pid in file `%s'", pidfile);
660	return (rval);
661}
662