pkill(1): add '-j jid' flag to restrict matches to jailed processes.
[dragonfly.git] / usr.bin / pkill / pkill.c
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 }