Updated with Tizen:Base source codes
[external/procps.git] / pgrep.c
1 /* emacs settings:  -*- c-basic-offset: 8 tab-width: 8 -*-
2  *
3  * pgrep/pkill -- utilities to filter the process table
4  *
5  * Copyright 2000 Kjetil Torgrim Homme <kjetilho@ifi.uio.no>
6  *
7  * May be distributed under the conditions of the
8  * GNU General Public License; a copy is in COPYING
9  *
10  * Changes by Albert Cahalan, 2002,2006.
11  * 
12  */
13
14
15 #include <stdio.h>
16 #include <stdlib.h>
17 #include <limits.h>
18 #include <unistd.h>
19 #include <ctype.h>
20 #include <string.h>
21 #include <sys/types.h>
22 #include <sys/stat.h>
23 #include <fcntl.h>
24 #include <signal.h>
25 #include <pwd.h>
26 #include <grp.h>
27 #include <regex.h>
28 #include <errno.h>
29
30 #include "proc/readproc.h"
31 #include "proc/sig.h"
32 #include "proc/devname.h"
33 #include "proc/sysinfo.h"
34 #include "proc/version.h" /* procps_version */
35
36 // EXIT_SUCCESS is 0
37 // EXIT_FAILURE is 1
38 #define EXIT_USAGE 2
39 #define EXIT_FATAL 3
40
41 static int i_am_pkill = 0;
42 static const char *progname = "pgrep";
43
44 union el {
45         long    num;
46         char *  str;
47 };
48
49 /* User supplied arguments */
50
51 static int opt_full = 0;
52 static int opt_long = 0;
53 static int opt_oldest = 0;
54 static int opt_newest = 0;
55 static int opt_negate = 0;
56 static int opt_exact = 0;
57 static int opt_signal = SIGTERM;
58 static int opt_lock = 0;
59 static int opt_case = 0;
60
61 static const char *opt_delim = "\n";
62 static union el *opt_pgrp = NULL;
63 static union el *opt_rgid = NULL;
64 static union el *opt_pid = NULL;
65 static union el *opt_ppid = NULL;
66 static union el *opt_sid = NULL;
67 static union el *opt_term = NULL;
68 static union el *opt_euid = NULL;
69 static union el *opt_ruid = NULL;
70 static char *opt_pattern = NULL;
71 static char *opt_pidfile = NULL;
72
73 static int usage (int opt) NORETURN;
74 static int usage (int opt)
75 {
76         int err = (opt=='?'); /* getopt() uses '?' to mark an error */
77         FILE *fp = err ? stderr : stdout;
78
79         if (i_am_pkill)
80                 fprintf (fp, "Usage: pkill [-SIGNAL] [-fvx] ");
81         else
82                 fprintf (fp, "Usage: pgrep [-flvx] [-d DELIM] ");
83         fprintf (fp, "[-n|-o] [-P PPIDLIST] [-g PGRPLIST] [-s SIDLIST]\n"
84                  "\t[-u EUIDLIST] [-U UIDLIST] [-G GIDLIST] [-t TERMLIST] "
85                  "[PATTERN]\n");
86
87         exit(err ? EXIT_USAGE : EXIT_SUCCESS);
88 }
89
90
91 static union el *split_list (const char *restrict str, int (*convert)(const char *, union el *))
92 {
93         char *copy = strdup (str);
94         char *ptr = copy;
95         char *sep_pos;
96         int i = 0;
97         int size = 0;
98         union el *list = NULL;
99
100         do {
101                 if (i == size) {
102                         size = size * 5 / 4 + 4;
103                         // add 1 because slot zero is a count
104                         list = realloc (list, 1 + size * sizeof *list);
105                         if (list == NULL)
106                                 exit (EXIT_FATAL);
107                 }
108                 sep_pos = strchr (ptr, ',');
109                 if (sep_pos)
110                         *sep_pos = 0;
111                 // Use ++i instead of i++ because slot zero is a count
112                 if (!convert (ptr, &list[++i]))
113                         exit (EXIT_USAGE);
114                 if (sep_pos)
115                         ptr = sep_pos + 1;
116         } while (sep_pos);
117
118         free (copy);
119         if (!i) {
120                 free (list);
121                 list = NULL;
122         } else {
123                 list[0].num = i;
124         }
125         return list;
126 }
127
128 // strict_atol returns a Boolean: TRUE if the input string
129 // contains a plain number, FALSE if there are any non-digits.
130 static int strict_atol (const char *restrict str, long *restrict value)
131 {
132         int res = 0;
133         int sign = 1;
134
135         if (*str == '+')
136                 ++str;
137         else if (*str == '-') {
138                 ++str;
139                 sign = -1;
140         }
141
142         for ( ; *str; ++str) {
143                 if (! isdigit (*str))
144                         return (0);
145                 res *= 10;
146                 res += *str - '0';
147         }
148         *value = sign * res;
149         return 1;
150 }
151
152 #include <sys/file.h>
153
154 // Seen non-BSD code do this:
155 //
156 //if (fcntl_lock(pid_fd, F_SETLK, F_WRLCK, SEEK_SET, 0, 0) == -1)
157 //                return -1;
158 int fcntl_lock(int fd, int cmd, int type, int whence, int start, int len)
159 {
160         struct flock lock[1];
161
162         lock->l_type = type;
163         lock->l_whence = whence;
164         lock->l_start = start;
165         lock->l_len = len;
166
167         return fcntl(fd, cmd, lock);
168 }
169                                                 
170
171 // We try a read lock. The daemon should have a write lock.
172 // Seen using flock: FreeBSD code
173 static int has_flock(int fd)
174 {
175         return flock(fd, LOCK_SH|LOCK_NB)==-1 && errno==EWOULDBLOCK;
176 }
177
178 // We try a read lock. The daemon should have a write lock.
179 // Seen using fcntl: libslack
180 static int has_fcntl(int fd)
181 {
182         struct flock f;  // seriously, struct flock is for a fnctl lock!
183         f.l_type = F_RDLCK;
184         f.l_whence = SEEK_SET;
185         f.l_start = 0;
186         f.l_len = 0;
187         return fcntl(fd,F_SETLK,&f)==-1 && (errno==EACCES || errno==EAGAIN);
188 }
189
190 static union el *read_pidfile(void)
191 {
192         char buf[12];
193         int fd;
194         struct stat sbuf;
195         char *endp;
196         int pid;
197         union el *list = NULL;
198
199         fd = open(opt_pidfile, O_RDONLY|O_NOCTTY|O_NONBLOCK);
200         if(fd<0)
201                 goto out;
202         if(fstat(fd,&sbuf) || !S_ISREG(sbuf.st_mode) || sbuf.st_size<1)
203                 goto out;
204         // type of lock, if any, is not standardized on Linux
205         if(opt_lock && !has_flock(fd) && !has_fcntl(fd))
206                         goto out;
207         memset(buf,'\0',sizeof buf);
208         buf[read(fd,buf+1,sizeof buf-2)] = '\0';
209         pid = strtoul(buf+1,&endp,10);
210         if(endp<=buf+1 || pid<1 || pid>0x7fffffff)
211                 goto out;
212         if(*endp && !isspace(*endp))
213                 goto out;
214         list = malloc(2 * sizeof *list);
215         list[0].num = 1;
216         list[1].num = pid;
217 out:
218         close(fd);
219         return list;
220 }
221
222 static int conv_uid (const char *restrict name, union el *restrict e)
223 {
224         struct passwd *pwd;
225
226         if (strict_atol (name, &e->num))
227                 return (1);
228
229         pwd = getpwnam (name);
230         if (pwd == NULL) {
231                 fprintf (stderr, "%s: invalid user name: %s\n",
232                          progname, name);
233                 return 0;
234         }
235         e->num = pwd->pw_uid;
236         return 1;
237 }
238
239
240 static int conv_gid (const char *restrict name, union el *restrict e)
241 {
242         struct group *grp;
243
244         if (strict_atol (name, &e->num))
245                 return 1;
246
247         grp = getgrnam (name);
248         if (grp == NULL) {
249                 fprintf (stderr, "%s: invalid group name: %s\n",
250                          progname, name);
251                 return 0;
252         }
253         e->num = grp->gr_gid;
254         return 1;
255 }
256
257
258 static int conv_pgrp (const char *restrict name, union el *restrict e)
259 {
260         if (! strict_atol (name, &e->num)) {
261                 fprintf (stderr, "%s: invalid process group: %s\n",
262                          progname, name);
263                 return 0;
264         }
265         if (e->num == 0)
266                 e->num = getpgrp ();
267         return 1;
268 }
269
270
271 static int conv_sid (const char *restrict name, union el *restrict e)
272 {
273         if (! strict_atol (name, &e->num)) {
274                 fprintf (stderr, "%s: invalid session id: %s\n",
275                          progname, name);
276                 return 0;
277         }
278         if (e->num == 0)
279                 e->num = getsid (0);
280         return 1;
281 }
282
283
284 static int conv_num (const char *restrict name, union el *restrict e)
285 {
286         if (! strict_atol (name, &e->num)) {
287                 fprintf (stderr, "%s: not a number: %s\n",
288                          progname, name);
289                 return 0;
290         }
291         return 1;
292 }
293
294
295 static int conv_str (const char *restrict name, union el *restrict e)
296 {
297         e->str = strdup (name);
298         return 1;
299 }
300
301
302 static int match_numlist (long value, const union el *restrict list)
303 {
304         int found = 0;
305         if (list == NULL)
306                 found = 0;
307         else {
308                 int i;
309                 for (i = list[0].num; i > 0; i--) {
310                         if (list[i].num == value)
311                                 found = 1;
312                 }
313         }
314         return found;
315 }
316
317 static int match_strlist (const char *restrict value, const union el *restrict list)
318 {
319         int found = 0;
320         if (list == NULL)
321                 found = 0;
322         else {
323                 int i;
324                 for (i = list[0].num; i > 0; i--) {
325                         if (! strcmp (list[i].str, value))
326                                 found = 1;
327                 }
328         }
329         return found;
330 }
331
332 static void output_numlist (const union el *restrict list, int num)
333 {
334         int i;
335         const char *delim = opt_delim;
336         for (i = 0; i < num; i++) {
337                 if(i+1==num)
338                         delim = "\n";
339                 printf ("%ld%s", list[i].num, delim);
340         }
341 }
342
343 static void output_strlist (const union el *restrict list, int num)
344 {
345 // FIXME: escape codes
346         int i;
347         const char *delim = opt_delim;
348         for (i = 0; i < num; i++) {
349                 if(i+1==num)
350                         delim = "\n";
351                 printf ("%s%s", list[i].str, delim);
352         }
353 }
354
355 static PROCTAB *do_openproc (void)
356 {
357         PROCTAB *ptp;
358         int flags = 0;
359
360         if (opt_pattern || opt_full)
361                 flags |= PROC_FILLCOM;
362         if (opt_ruid || opt_rgid)
363                 flags |= PROC_FILLSTATUS;
364         if (opt_oldest || opt_newest || opt_pgrp || opt_sid || opt_term)
365                 flags |= PROC_FILLSTAT;
366         if (!(flags & PROC_FILLSTAT))
367                 flags |= PROC_FILLSTATUS;  // FIXME: need one, and PROC_FILLANY broken
368         if (opt_euid && !opt_negate) {
369                 int num = opt_euid[0].num;
370                 int i = num;
371                 uid_t *uids = malloc (num * sizeof (uid_t));
372                 if (uids == NULL)
373                         exit (EXIT_FATAL);
374                 while (i-- > 0) {
375                         uids[i] = opt_euid[i+1].num;
376                 }
377                 flags |= PROC_UID;
378                 ptp = openproc (flags, uids, num);
379         } else {
380                 ptp = openproc (flags);
381         }
382         return ptp;
383 }
384
385 static regex_t * do_regcomp (void)
386 {
387         regex_t *preg = NULL;
388
389         if (opt_pattern) {
390                 char *re;
391                 char errbuf[256];
392                 int re_err;
393
394                 preg = malloc (sizeof (regex_t));
395                 if (preg == NULL)
396                         exit (EXIT_FATAL);
397                 if (opt_exact) {
398                         re = malloc (strlen (opt_pattern) + 5);
399                         if (re == NULL)
400                                 exit (EXIT_FATAL);
401                         sprintf (re, "^(%s)$", opt_pattern);
402                 } else {
403                         re = opt_pattern;
404                 }
405
406                 re_err = regcomp (preg, re, REG_EXTENDED | REG_NOSUB | opt_case);
407                 if (re_err) {
408                         regerror (re_err, preg, errbuf, sizeof(errbuf));
409                         fputs(errbuf,stderr);
410                         exit (EXIT_USAGE);
411                 }
412         }
413         return preg;
414 }
415
416 static union el * select_procs (int *num)
417 {
418         PROCTAB *ptp;
419         proc_t task;
420         unsigned long long saved_start_time;      // for new/old support
421         pid_t saved_pid = 0;                      // for new/old support
422         int matches = 0;
423         int size = 0;
424         regex_t *preg;
425         pid_t myself = getpid();
426         union el *list = NULL;
427         char cmd[4096];
428
429         ptp = do_openproc();
430         preg = do_regcomp();
431
432         if (opt_newest) saved_start_time =  0ULL;
433         if (opt_oldest) saved_start_time = ~0ULL;
434         if (opt_newest) saved_pid = 0;
435         if (opt_oldest) saved_pid = INT_MAX;
436         
437         memset(&task, 0, sizeof (task));
438         while(readproc(ptp, &task)) {
439                 int match = 1;
440
441                 if (task.XXXID == myself)
442                         continue;
443                 else if (opt_newest && task.start_time < saved_start_time)
444                         match = 0;
445                 else if (opt_oldest && task.start_time > saved_start_time)
446                         match = 0;
447                 else if (opt_ppid && ! match_numlist (task.ppid, opt_ppid))
448                         match = 0;
449                 else if (opt_pid && ! match_numlist (task.tgid, opt_pid))
450                         match = 0;
451                 else if (opt_pgrp && ! match_numlist (task.pgrp, opt_pgrp))
452                         match = 0;
453                 else if (opt_euid && ! match_numlist (task.euid, opt_euid))
454                         match = 0;
455                 else if (opt_ruid && ! match_numlist (task.ruid, opt_ruid))
456                         match = 0;
457                 else if (opt_rgid && ! match_numlist (task.rgid, opt_rgid))
458                         match = 0;
459                 else if (opt_sid && ! match_numlist (task.session, opt_sid))
460                         match = 0;
461                 else if (opt_term) {
462                         if (task.tty == 0) {
463                                 match = 0;
464                         } else {
465                                 char tty[256];
466                                 dev_to_tty (tty, sizeof(tty) - 1,
467                                             task.tty, task.XXXID, ABBREV_DEV);
468                                 match = match_strlist (tty, opt_term);
469                         }
470                 }
471                 if (opt_long || (match && opt_pattern)) {
472                         if (opt_full && task.cmdline) {
473                                 int i = 0;
474                                 int bytes = sizeof (cmd) - 1;
475
476                                 /* make sure it is always NUL-terminated */
477                                 cmd[bytes] = 0;
478                                 /* make room for SPC in loop below */
479                                 --bytes;
480
481                                 strncpy (cmd, task.cmdline[i], bytes);
482                                 bytes -= strlen (task.cmdline[i++]);
483                                 while (task.cmdline[i] && bytes > 0) {
484                                         strncat (cmd, " ", bytes);
485                                         strncat (cmd, task.cmdline[i], bytes);
486                                         bytes -= strlen (task.cmdline[i++]) + 1;
487                                 }
488                         } else {
489                                 strcpy (cmd, task.cmd);
490                         }
491                 }
492
493                 if (match && opt_pattern) {
494                         if (regexec (preg, cmd, 0, NULL, 0) != 0)
495                                 match = 0;
496                 }
497
498                 if (match ^ opt_negate) {       /* Exclusive OR is neat */
499                         if (opt_newest) {
500                                 if (saved_start_time == task.start_time &&
501                                     saved_pid > task.XXXID)
502                                         continue;
503                                 saved_start_time = task.start_time;
504                                 saved_pid = task.XXXID;
505                                 matches = 0;
506                         }
507                         if (opt_oldest) {
508                                 if (saved_start_time == task.start_time &&
509                                     saved_pid < task.XXXID)
510                                         continue;
511                                 saved_start_time = task.start_time;
512                                 saved_pid = task.XXXID;
513                                 matches = 0;
514                         }
515                         if (matches == size) {
516                                 size = size * 5 / 4 + 4;
517                                 list = realloc(list, size * sizeof *list);
518                                 if (list == NULL)
519                                         exit (EXIT_FATAL);
520                         }
521                         if (opt_long) {
522                                 char buff[5096];  // FIXME
523                                 sprintf (buff, "%d %s", task.XXXID, cmd);
524                                 list[matches++].str = strdup (buff);
525                         } else {
526                                 list[matches++].num = task.XXXID;
527                         }
528                 }
529                 
530                 memset (&task, 0, sizeof (task));
531         }
532         closeproc (ptp);
533
534         *num = matches;
535         return list;
536 }
537
538
539 static void parse_opts (int argc, char **argv)
540 {
541         char opts[32] = "";
542         int opt;
543         int criteria_count = 0;
544
545         if (strstr (argv[0], "pkill")) {
546                 i_am_pkill = 1;
547                 progname = "pkill";
548                 /* Look for a signal name or number as first argument */
549                 if (argc > 1 && argv[1][0] == '-') {
550                         int sig;
551                         sig = signal_name_to_number (argv[1] + 1);
552                         if (sig == -1 && isdigit (argv[1][1]))
553                                 sig = atoi (argv[1] + 1);
554                         if (sig != -1) {
555                                 int i;
556                                 for (i = 2; i < argc; i++)
557                                         argv[i-1] = argv[i];
558                                 --argc;
559                                 opt_signal = sig;
560                         }
561                 }
562         } else {
563                 /* These options are for pgrep only */
564                 strcat (opts, "ld:");
565         }
566                         
567         strcat (opts, "LF:fnovxP:g:s:u:U:G:t:?V");
568         
569         while ((opt = getopt (argc, argv, opts)) != -1) {
570                 switch (opt) {
571 //              case 'D':   // FreeBSD: print info about non-matches for debugging
572 //                      break;
573                 case 'F':   // FreeBSD: the arg is a file containing a PID to match
574                         opt_pidfile = strdup (optarg);
575                         ++criteria_count;
576                         break;
577                 case 'G':   // Solaris: match rgid/rgroup
578                         opt_rgid = split_list (optarg, conv_gid);
579                         if (opt_rgid == NULL)
580                                 usage (opt);
581                         ++criteria_count;
582                         break;
583 //              case 'I':   // FreeBSD: require confirmation before killing
584 //                      break;
585 //              case 'J':   // Solaris: match by project ID (name or number)
586 //                      break;
587                 case 'L':   // FreeBSD: fail if pidfile (see -F) not locked
588                         opt_lock++;
589                         break;
590 //              case 'M':   // FreeBSD: specify core (OS crash dump) file
591 //                      break;
592 //              case 'N':   // FreeBSD: specify alternate namelist file (for us, System.map -- but we don't need it)
593 //                      break;
594                 case 'P':   // Solaris: match by PPID
595                         opt_ppid = split_list (optarg, conv_num);
596                         if (opt_ppid == NULL)
597                                 usage (opt);
598                         ++criteria_count;
599                         break;
600 //              case 'S':   // FreeBSD: don't ignore the built-in kernel tasks
601 //                      break;
602 //              case 'T':   // Solaris: match by "task ID" (probably not a Linux task)
603 //                      break;
604                 case 'U':   // Solaris: match by ruid/rgroup
605                         opt_ruid = split_list (optarg, conv_uid);
606                         if (opt_ruid == NULL)
607                                 usage (opt);
608                         ++criteria_count;
609                         break;
610                 case 'V':
611                         fprintf(stdout, "%s (%s)\n", progname, procps_version);
612                         exit(EXIT_SUCCESS);
613 //              case 'c':   // Solaris: match by contract ID
614 //                      break;
615                 case 'd':   // Solaris: change the delimiter
616                         opt_delim = strdup (optarg);
617                         break;
618                 case 'f':   // Solaris: match full process name (as in "ps -f")
619                         opt_full = 1;
620                         break;
621                 case 'g':   // Solaris: match pgrp
622                         opt_pgrp = split_list (optarg, conv_pgrp);
623                         if (opt_pgrp == NULL)
624                                 usage (opt);
625                         ++criteria_count;
626                         break;
627 //              case 'i':   // FreeBSD: ignore case. OpenBSD: withdrawn. See -I. This sucks.
628 //                      if (opt_case)
629 //                              usage (opt);
630 //                      opt_case = REG_ICASE;
631 //                      break;
632 //              case 'j':   // FreeBSD: restricted to the given jail ID
633 //                      break;
634                 case 'l':   // Solaris: long output format (pgrep only) Should require -f for beyond argv[0] maybe?
635                         opt_long = 1;
636                         break;
637                 case 'n':   // Solaris: match only the newest
638                         if (opt_oldest|opt_negate|opt_newest)
639                                 usage (opt);
640                         opt_newest = 1;
641                         ++criteria_count;
642                         break;
643                 case 'o':   // Solaris: match only the oldest
644                         if (opt_oldest|opt_negate|opt_newest)
645                                 usage (opt);
646                         opt_oldest = 1;
647                         ++criteria_count;
648                         break;
649                 case 's':   // Solaris: match by session ID -- zero means self
650                         opt_sid = split_list (optarg, conv_sid);
651                         if (opt_sid == NULL)
652                                 usage (opt);
653                         ++criteria_count;
654                         break;
655                 case 't':   // Solaris: match by tty
656                         opt_term = split_list (optarg, conv_str);
657                         if (opt_term == NULL)
658                                 usage (opt);
659                         ++criteria_count;
660                         break;
661                 case 'u':   // Solaris: match by euid/egroup
662                         opt_euid = split_list (optarg, conv_uid);
663                         if (opt_euid == NULL)
664                                 usage (opt);
665                         ++criteria_count;
666                         break;
667                 case 'v':   // Solaris: as in grep, invert the matching (uh... applied after selection I think)
668                         if (opt_oldest|opt_negate|opt_newest)
669                                 usage (opt);
670                         opt_negate = 1;
671                         break;
672                 // OpenBSD -x, being broken, does a plain string
673                 case 'x':   // Solaris: use ^(regexp)$ in place of regexp (FreeBSD too)
674                         opt_exact = 1;
675                         break;
676 //              case 'z':   // Solaris: match by zone ID
677 //                      break;
678                 case '?':
679                         usage (opt);
680                         break;
681                 }
682         }
683
684         if(opt_lock && !opt_pidfile){
685                 fprintf(stderr, "%s: -L without -F makes no sense\n",progname);
686                 usage(0);
687         }
688
689         if(opt_pidfile){
690                 opt_pid = read_pidfile();
691                 if(!opt_pid){
692                         fprintf(stderr, "%s: pidfile not valid\n",progname);
693                         usage(0);
694                 }
695         }
696
697         if (argc - optind == 1)
698                 opt_pattern = argv[optind];
699         else if (argc - optind > 1)
700                 usage (0);
701         else if (criteria_count == 0) {
702                 fprintf (stderr, "%s: No matching criteria specified\n",
703                          progname);
704                 usage (0);
705         }
706 }
707
708
709 int main (int argc, char *argv[])
710 {
711         union el *procs;
712         int num;
713
714         parse_opts (argc, argv);
715
716         procs = select_procs (&num);
717         if (i_am_pkill) {
718                 int i;
719                 for (i = 0; i < num; i++) {
720                         if (kill (procs[i].num, opt_signal) != -1) continue;
721                         if (errno==ESRCH) continue; // gone now, which is OK
722                         fprintf (stderr, "pkill: %ld - %s\n",
723                                  procs[i].num, strerror (errno));
724                 }
725         } else {
726                 if (opt_long)
727                         output_strlist(procs,num);
728                 else
729                         output_numlist(procs,num);
730         }
731         return !num; // exit(EXIT_SUCCESS) if match, otherwise exit(EXIT_FAILURE)
732 }