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