2 * pid - find the pid of a process given its command name
4 * Same basic idea as Debian's "pidof", in that you type 'pid command'
5 * and it finds a process running that command and gives you the pid;
6 * but differs in details, for example it will search for scripts by
7 * default rather than requiring pidof's -x option, and it will also
8 * look for command-line arguments ('pid make test') and try to find
9 * the parent process of a bunch of forks from the same shell script
12 * Currently tested only on Linux using /proc directly, but I've tried
13 * to set it up so that the logic of what processes to choose is
14 * separated from the mechanism used to iterate over processes and
15 * find their command lines.
24 #include <sys/types.h>
28 #define lenof(x) (sizeof((x))/sizeof(*(x)))
32 /* ----------------------------------------------------------------------
33 * General-purpose code for storing a set of process ids, testing
34 * membership, and iterating over them. Since pids have a very limited
35 * range, we just do this as a giant bitmask.
41 unsigned long procbits
[PIDMAX
/WORDBITS
];
45 static void pidset_init(struct pidset
*p
)
48 for (i
= 0; i
< (int)lenof(p
->procbits
); i
++)
52 static void pidset_add(struct pidset
*p
, int pid
)
54 assert(pid
>= 0 && pid
< PIDMAX
);
55 p
->procbits
[pid
/ WORDBITS
] |= 1 << (pid
% WORDBITS
);
58 static int pidset_in(const struct pidset
*p
, int pid
)
60 assert(pid
>= 0 && pid
< PIDMAX
);
61 return (p
->procbits
[pid
/ WORDBITS
] & (1 << (pid
% WORDBITS
))) != 0;
64 static int pidset_size(const struct pidset
*p
)
69 for (word
= 0; word
< (int)lenof(p
->procbits
); word
++) {
70 unsigned long mask
= p
->procbits
[word
];
80 static int pidset_step(struct pidset
*p
)
82 int word
= p
->next
/ WORDBITS
;
83 int bit
= p
->next
% WORDBITS
;
84 while (word
< (int)lenof(p
->procbits
) && p
->procbits
[word
] >> bit
== 0) {
87 p
->next
= WORDBITS
* word
+ bit
;
90 if (word
>= (int)lenof(p
->procbits
))
93 while (!((p
->procbits
[word
] >> bit
) & 1)) {
95 p
->next
= WORDBITS
* word
+ bit
;
98 assert(bit
< WORDBITS
);
102 static int pidset_first(struct pidset
*p
)
105 return pidset_step(p
);
108 static int pidset_next(struct pidset
*p
)
110 return pidset_step(p
);
113 /* ----------------------------------------------------------------------
114 * Code to scan the list of processes and retrieve all the information
115 * we'll want about each one. This may in future be conditional on the
116 * OS's local mechanism for finding that information (i.e. if we want
117 * to run on kernels that don't provide Linux-style /proc).
123 const char *const *argv
;
126 static struct procdata
*procs
[PIDMAX
];
128 static char *get_contents(const char *filename
, int *returned_len
)
134 FILE *fp
= fopen(filename
, "rb");
142 if (len
>= bufsize
) {
143 bufsize
= len
* 5 / 4 + 4096;
144 buf
= realloc(buf
, bufsize
);
146 fprintf(stderr
, "pid: out of memory\n");
151 readret
= fread(buf
+ len
, 1, bufsize
- len
, fp
);
155 return NULL
; /* I/O error */
156 } else if (readret
== 0) {
160 buf
= realloc(buf
, len
+ 1);
169 static char *get_link_dest(const char *filename
)
179 bufsize
= bufsize
* 5 / 4 + 1024;
180 buf
= realloc(buf
, bufsize
);
182 fprintf(stderr
, "pid: out of memory\n");
186 ret
= readlink(filename
, buf
, (size_t)bufsize
);
189 return NULL
; /* I/O error */
190 } else if (ret
< bufsize
) {
192 * Success! We've read the full link text.
194 buf
= realloc(buf
, ret
+1);
198 /* Overflow. Go round again. */
203 static struct pidset
get_processes(void)
211 d
= opendir("/proc");
213 perror("/proc: open\n");
216 while ((de
= readdir(d
)) != NULL
) {
218 char *cmdline
, *status
, *exe
;
222 struct procdata
*proc
;
224 const char *name
= de
->d_name
;
225 if (name
[strspn(name
, "0123456789")])
229 * The filename is numeric, i.e. we've found a pid. Try to
230 * retrieve all the information we want about it.
232 * We expect this will fail every so often for random reasons,
233 * e.g. if the pid has disappeared between us fetching a list
234 * of them and trying to read their command lines. In that
235 * situation, we won't bother reporting errors: we'll just
236 * drop this pid and silently move on to the next one.
239 assert(pid
>= 0 && pid
< PIDMAX
);
241 sprintf(filename
, "/proc/%d/cmdline", pid
);
242 if ((cmdline
= get_contents(filename
, &cmdlinelen
)) == NULL
)
245 sprintf(filename
, "/proc/%d/status", pid
);
246 if ((status
= get_contents(filename
, NULL
)) == NULL
) {
251 sprintf(filename
, "/proc/%d/exe", pid
);
252 exe
= get_link_dest(filename
);
253 /* This may fail, if the process isn't ours, but we continue
257 * Now we've got all our raw data out of /proc. Process it
258 * into the internal representation we're going to use in the
259 * process-selection logic.
261 proc
= (struct procdata
*)malloc(sizeof(struct procdata
));
263 fprintf(stderr
, "pid: out of memory\n");
270 * cmdline contains a list of NUL-terminated strings. Scan
271 * them to get the argv pointers.
277 /* Count the arguments. */
279 for (p
= cmdline
; p
< cmdline
+ cmdlinelen
; p
+= strlen(p
)+1)
282 /* Allocate space for the pointers. */
283 argv
= (const char **)malloc((nargs
+1) * sizeof(char *));
286 fprintf(stderr
, "pid: out of memory\n");
290 /* Store the pointers. */
292 for (p
= cmdline
; p
< cmdline
+ cmdlinelen
; p
+= strlen(p
)+1)
293 argv
[proc
->argc
++] = p
;
295 /* Trailing NULL to match standard argv lists, just in case. */
296 assert(proc
->argc
== nargs
);
297 argv
[proc
->argc
] = NULL
;
301 * Scan status for the uid and the parent pid. This file
302 * contains a list of \n-terminated lines of text.
306 int got_ppid
= 0, got_uid
= 0;
310 if (!got_ppid
&& sscanf(p
, "PPid: %d", &proc
->ppid
) == 1)
312 if (!got_uid
&& sscanf(p
, "Uid: %*d %d", &proc
->uid
) == 1)
316 * Advance to next line.
322 if (!got_uid
|| !got_ppid
) { /* arrgh, abort everything so far */
333 * If we get here, we've got everything we need. Add the
334 * process to the list of things we can usefully work
338 pidset_add(&ret
, pid
);
345 static const struct procdata
*get_proc(int pid
)
347 assert(pid
>= 0 && pid
< PIDMAX
);
352 /* ----------------------------------------------------------------------
353 * Logic to pick out the set of processes we care about.
356 static int is_an_interpreter(const char *basename
, const char **stop_opt
)
358 if (!strcmp(basename
, "perl") ||
359 !strcmp(basename
, "ruby")) {
363 if (!strcmp(basename
, "python") ||
364 !strcmp(basename
, "bash") ||
365 !strcmp(basename
, "sh") ||
366 !strcmp(basename
, "dash")) {
370 if (!strcmp(basename
, "rep") ||
371 !strcmp(basename
, "lua") ||
372 !strcmp(basename
, "java")) {
379 static const char *find_basename(const char *path
)
381 const char *ret
= path
;
385 p
= ret
+ strcspn(ret
, "/");
394 static int find_command(int pid_argc
, const char *const *pid_argv
,
397 const char *base
, *stop_opt
;
401 base
++; /* skip indicator of login shells */
402 base
= find_basename(base
);
404 if (!strcmp(base
, cmd
)) {
406 * argv[0] matches the supplied command name.
409 } else if (is_an_interpreter(base
, &stop_opt
)) {
411 * argv[0] is an interpreter program of some kind. Look
412 * along its command line for the program it's running,
413 * and see if _that_ matches the command name.
416 while (i
< pid_argc
&& pid_argv
[i
][0] == '-') {
418 * Skip interpreter options, unless they're things which
419 * make the next non-option argument not a script name
420 * (e.g. sh -c, perl -e).
422 if (stop_opt
&& !strncmp(pid_argv
[i
], stop_opt
, strlen(stop_opt
)))
423 return -1; /* no match */
426 if (i
< pid_argc
&& !strcmp(find_basename(pid_argv
[i
]), cmd
))
429 return -1; /* no match */
432 static int strnullcmp(const char *a
, const char *b
)
435 * Like strcmp, but cope with NULL inputs by making them compare
436 * identical to each other and before any non-null string.
439 return (b
!= 0) - (a
!= 0);
444 static int argcmp(const char *const *a
, const char *const *b
)
447 int ret
= strcmp(*a
, *b
);
454 return (*b
!= NULL
) - (*a
!= NULL
);
457 static struct pidset
filter_out_self(struct pidset in
)
460 * Discard our own pid from a set. (Generally we won't want to
461 * return ourself from any search.)
465 int our_pid
= getpid();
468 for (pid
= pidset_first(&in
); pid
>= 0; pid
= pidset_next(&in
)) {
470 pidset_add(&ret
, pid
);
475 static struct pidset
filter_by_uid(struct pidset in
, int uid
)
478 * Return only those processes with a given uid.
484 for (pid
= pidset_first(&in
); pid
>= 0; pid
= pidset_next(&in
)) {
485 const struct procdata
*proc
= get_proc(pid
);
486 if (proc
->uid
== uid
)
487 pidset_add(&ret
, pid
);
492 static struct pidset
filter_by_command(struct pidset in
, const char **words
)
495 * Look for processes matching the user-supplied command name and
496 * subsequent arguments.
502 for (pid
= pidset_first(&in
); pid
>= 0; pid
= pidset_next(&in
)) {
503 const struct procdata
*proc
= get_proc(pid
);
506 if (!proc
->argv
|| proc
->argc
< 1)
509 /* Find the command, whether it's a binary or a script. */
510 i
= find_command(proc
->argc
, proc
->argv
, words
[0]);
514 /* Now check that subsequent arguments match. */
515 for (j
= 1; words
[j
]; j
++)
516 if (!proc
->argv
[i
+j
] || strcmp(proc
->argv
[i
+j
], words
[j
]))
519 /* If we get here, we have a match! */
520 pidset_add(&ret
, pid
);
527 static struct pidset
filter_out_forks(struct pidset in
)
530 * Discard any process whose parent is also in our remaining match
531 * set and looks sufficiently like it for us to decide this one's
532 * an uninteresting fork (e.g. of a shell script executing a
539 for (pid
= pidset_first(&in
); pid
>= 0; pid
= pidset_next(&in
)) {
540 const struct procdata
*proc
= get_proc(pid
);
542 if (pidset_in(&in
, proc
->ppid
)) {
543 /* The parent is in our set too. Is it similar? */
544 const struct procdata
*parent
= get_proc(proc
->ppid
);
545 if (!strnullcmp(parent
->exe
, proc
->exe
) &&
546 !argcmp(parent
->argv
, proc
->argv
)) {
547 /* Yes; don't list it. */
552 pidset_add(&ret
, pid
);
557 /* ----------------------------------------------------------------------
561 const char usagemsg
[] =
562 "usage: pid [options] <search-cmd> [<search-arg>...]\n"
563 "where: -a report all matching pids, not just one\n"
564 " -U report pids of any user, not just ours\n"
565 " also: pid --version report version number\n"
566 " pid --help display this help text\n"
567 " pid --licence display the (MIT) licence text\n"
571 fputs(usagemsg
, stdout
);
574 const char licencemsg
[] =
575 "pid is copyright 2012 Simon Tatham.\n"
577 "Permission is hereby granted, free of charge, to any person\n"
578 "obtaining a copy of this software and associated documentation files\n"
579 "(the \"Software\"), to deal in the Software without restriction,\n"
580 "including without limitation the rights to use, copy, modify, merge,\n"
581 "publish, distribute, sublicense, and/or sell copies of the Software,\n"
582 "and to permit persons to whom the Software is furnished to do so,\n"
583 "subject to the following conditions:\n"
585 "The above copyright notice and this permission notice shall be\n"
586 "included in all copies or substantial portions of the Software.\n"
588 "THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n"
589 "EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n"
590 "MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n"
591 "NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS\n"
592 "BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN\n"
593 "ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\n"
594 "CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n"
599 fputs(licencemsg
, stdout
);
603 #define SVN_REV "$Revision$"
604 char rev
[sizeof(SVN_REV
)];
607 strcpy(rev
, SVN_REV
);
609 for (p
= rev
; *p
&& *p
!= ':'; p
++);
612 while (*p
&& isspace((unsigned char)*p
)) p
++;
613 for (q
= p
; *q
&& *q
!= '$'; q
++);
615 printf("pid revision %s\n", p
);
617 printf("pid: unknown version\n");
621 int main(int argc
, char **argv
)
623 const char **searchwords
;
625 int all
= 0, all_uids
= 0;
629 * Allocate enough space in 'searchwords' that we could shovel the
630 * whole of our argv into it if we had to. Then we won't have to
631 * worry about it later.
633 searchwords
= (const char **)malloc((argc
+1) * sizeof(const char *));
637 * Parse the command line.
641 if (doing_opts
&& *p
== '-') {
642 if (!strcmp(p
, "-a") || !strcmp(p
, "--all")) {
644 } else if (!strcmp(p
, "-U") || !strcmp(p
, "--all-uids")) {
646 } else if (!strcmp(p
, "--version")) {
649 } else if (!strcmp(p
, "--help")) {
652 } else if (!strcmp(p
, "--licence") || !strcmp(p
, "--license")) {
655 } else if (!strcmp(p
, "--")) {
658 fprintf(stderr
, "pid: unrecognised option '%s'\n", p
);
662 searchwords
[nsearchwords
++] = p
;
663 doing_opts
= 0; /* further optionlike args become search terms */
668 fprintf(stderr
, "pid: expected a command to search for; "
669 "type 'pid --help' for help\n");
672 searchwords
[nsearchwords
] = NULL
; /* terminate list */
678 * Construct our list of processes.
680 procs
= get_processes();
682 if (uid
> 0 && !all_uids
)
683 procs
= filter_by_uid(procs
, uid
);
684 procs
= filter_out_self(procs
);
685 procs
= filter_by_command(procs
, searchwords
);
687 procs
= filter_out_forks(procs
);
692 npids
= pidset_size(&procs
);
696 const char *sep
= "";
697 for (pid
= pidset_first(&procs
); pid
>= 0;
698 pid
= pidset_next(&procs
)) {
699 printf("%s%d", sep
, pid
);
705 printf("%d\n", pidset_first(&procs
));
707 printf("MULTIPLE\n");