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 souped up with various pragmatic features such as recognising
7 * well known interpreters (so you can search for, say, 'pid
8 * script.sh' as well as 'pid bash' and have it do what you meant).
10 * Currently tested only on Linux using /proc directly, but I've tried
11 * to set it up so that the logic of what processes to choose is
12 * separated from the mechanism used to iterate over processes and
13 * find their command lines.
22 #include <sys/types.h>
26 #define lenof(x) (sizeof((x))/sizeof(*(x)))
30 /* ----------------------------------------------------------------------
31 * General-purpose code for storing a set of process ids, testing
32 * membership, and iterating over them. Since pids have a very limited
33 * range, we just do this as a giant bitmask.
39 unsigned long procbits
[PIDMAX
/WORDBITS
];
43 static void pidset_init(struct pidset
*p
)
46 for (i
= 0; i
< (int)lenof(p
->procbits
); i
++)
50 static void pidset_add(struct pidset
*p
, int pid
)
52 assert(pid
>= 0 && pid
< PIDMAX
);
53 p
->procbits
[pid
/ WORDBITS
] |= 1 << (pid
% WORDBITS
);
56 static int pidset_in(const struct pidset
*p
, int pid
)
58 assert(pid
>= 0 && pid
< PIDMAX
);
59 return (p
->procbits
[pid
/ WORDBITS
] & (1 << (pid
% WORDBITS
))) != 0;
62 static int pidset_size(const struct pidset
*p
)
67 for (word
= 0; word
< (int)lenof(p
->procbits
); word
++) {
68 unsigned long mask
= p
->procbits
[word
];
78 static int pidset_step(struct pidset
*p
)
80 int word
= p
->next
/ WORDBITS
;
81 int bit
= p
->next
% WORDBITS
;
82 while (word
< (int)lenof(p
->procbits
) && p
->procbits
[word
] >> bit
== 0) {
85 p
->next
= WORDBITS
* word
+ bit
;
88 if (word
>= (int)lenof(p
->procbits
))
91 while (!((p
->procbits
[word
] >> bit
) & 1)) {
93 p
->next
= WORDBITS
* word
+ bit
;
96 assert(bit
< WORDBITS
);
100 static int pidset_first(struct pidset
*p
)
103 return pidset_step(p
);
106 static int pidset_next(struct pidset
*p
)
108 return pidset_step(p
);
111 /* ----------------------------------------------------------------------
112 * Code to scan the list of processes and retrieve all the information
113 * we'll want about each one. This may in future be conditional on the
114 * OS's local mechanism for finding that information (i.e. if we want
115 * to run on kernels that don't provide Linux-style /proc).
121 const char *const *argv
;
124 static struct procdata
*procs
[PIDMAX
];
126 static char *get_contents(const char *filename
, int *returned_len
)
132 FILE *fp
= fopen(filename
, "rb");
140 if (len
>= bufsize
) {
141 bufsize
= len
* 5 / 4 + 4096;
142 buf
= realloc(buf
, bufsize
);
144 fprintf(stderr
, "pid: out of memory\n");
149 readret
= fread(buf
+ len
, 1, bufsize
- len
, fp
);
153 return NULL
; /* I/O error */
154 } else if (readret
== 0) {
158 buf
= realloc(buf
, len
+ 1);
167 static char *get_link_dest(const char *filename
)
177 bufsize
= bufsize
* 5 / 4 + 1024;
178 buf
= realloc(buf
, bufsize
);
180 fprintf(stderr
, "pid: out of memory\n");
184 ret
= readlink(filename
, buf
, (size_t)bufsize
);
187 return NULL
; /* I/O error */
188 } else if (ret
< bufsize
) {
190 * Success! We've read the full link text.
192 buf
= realloc(buf
, ret
+1);
196 /* Overflow. Go round again. */
201 static struct pidset
get_processes(void)
209 d
= opendir("/proc");
211 perror("/proc: open\n");
214 while ((de
= readdir(d
)) != NULL
) {
216 char *cmdline
, *status
, *exe
;
220 struct procdata
*proc
;
222 const char *name
= de
->d_name
;
223 if (name
[strspn(name
, "0123456789")])
227 * The filename is numeric, i.e. we've found a pid. Try to
228 * retrieve all the information we want about it.
230 * We expect this will fail every so often for random reasons,
231 * e.g. if the pid has disappeared between us fetching a list
232 * of them and trying to read their command lines. In that
233 * situation, we won't bother reporting errors: we'll just
234 * drop this pid and silently move on to the next one.
237 assert(pid
>= 0 && pid
< PIDMAX
);
239 sprintf(filename
, "/proc/%d/cmdline", pid
);
240 if ((cmdline
= get_contents(filename
, &cmdlinelen
)) == NULL
)
243 sprintf(filename
, "/proc/%d/status", pid
);
244 if ((status
= get_contents(filename
, NULL
)) == NULL
) {
249 sprintf(filename
, "/proc/%d/exe", pid
);
250 exe
= get_link_dest(filename
);
251 /* This may fail, if the process isn't ours, but we continue
255 * Now we've got all our raw data out of /proc. Process it
256 * into the internal representation we're going to use in the
257 * process-selection logic.
259 proc
= (struct procdata
*)malloc(sizeof(struct procdata
));
261 fprintf(stderr
, "pid: out of memory\n");
268 * cmdline contains a list of NUL-terminated strings. Scan
269 * them to get the argv pointers.
275 /* Count the arguments. */
277 for (p
= cmdline
; p
< cmdline
+ cmdlinelen
; p
+= strlen(p
)+1)
280 /* Allocate space for the pointers. */
281 argv
= (const char **)malloc((nargs
+1) * sizeof(char *));
284 fprintf(stderr
, "pid: out of memory\n");
288 /* Store the pointers. */
290 for (p
= cmdline
; p
< cmdline
+ cmdlinelen
; p
+= strlen(p
)+1)
291 argv
[proc
->argc
++] = p
;
293 /* Trailing NULL to match standard argv lists, just in case. */
294 assert(proc
->argc
== nargs
);
295 argv
[proc
->argc
] = NULL
;
299 * Scan status for the uid and the parent pid. This file
300 * contains a list of \n-terminated lines of text.
304 int got_ppid
= 0, got_uid
= 0;
308 if (!got_ppid
&& sscanf(p
, "PPid: %d", &proc
->ppid
) == 1)
310 if (!got_uid
&& sscanf(p
, "Uid: %*d %d", &proc
->uid
) == 1)
314 * Advance to next line.
320 if (!got_uid
|| !got_ppid
) { /* arrgh, abort everything so far */
331 * If we get here, we've got everything we need. Add the
332 * process to the list of things we can usefully work
336 pidset_add(&ret
, pid
);
343 static const struct procdata
*get_proc(int pid
)
345 assert(pid
>= 0 && pid
< PIDMAX
);
350 /* ----------------------------------------------------------------------
351 * Logic to pick out the set of processes we care about.
354 static int is_an_interpreter(const char *basename
, const char **stop_opt
)
356 if (!strcmp(basename
, "perl") ||
357 !strcmp(basename
, "ruby")) {
361 if (!strcmp(basename
, "python") ||
362 !strcmp(basename
, "bash") ||
363 !strcmp(basename
, "sh") ||
364 !strcmp(basename
, "dash")) {
368 if (!strcmp(basename
, "rep") ||
369 !strcmp(basename
, "lua") ||
370 !strcmp(basename
, "java")) {
377 static const char *find_basename(const char *path
)
379 const char *ret
= path
;
383 p
= ret
+ strcspn(ret
, "/");
392 static int find_command(int pid_argc
, const char *const *pid_argv
,
395 const char *base
, *stop_opt
;
399 base
++; /* skip indicator of login shells */
400 base
= find_basename(base
);
402 if (!strcmp(base
, cmd
)) {
404 * argv[0] matches the supplied command name.
407 } else if (is_an_interpreter(base
, &stop_opt
)) {
409 * argv[0] is an interpreter program of some kind. Look
410 * along its command line for the program it's running,
411 * and see if _that_ matches the command name.
414 while (i
< pid_argc
&& pid_argv
[i
][0] == '-') {
416 * Skip interpreter options, unless they're things which
417 * make the next non-option argument not a script name
418 * (e.g. sh -c, perl -e).
420 if (stop_opt
&& !strncmp(pid_argv
[i
], stop_opt
, strlen(stop_opt
)))
421 return -1; /* no match */
424 if (i
< pid_argc
&& !strcmp(find_basename(pid_argv
[i
]), cmd
))
427 return -1; /* no match */
430 static int strnullcmp(const char *a
, const char *b
)
433 * Like strcmp, but cope with NULL inputs by making them compare
434 * identical to each other and before any non-null string.
437 return (b
!= 0) - (a
!= 0);
442 static int argcmp(const char *const *a
, const char *const *b
)
445 int ret
= strcmp(*a
, *b
);
452 return (*b
!= NULL
) - (*a
!= NULL
);
455 static struct pidset
filter_out_self(struct pidset in
)
458 * Discard our own pid from a set. (Generally we won't want to
459 * return ourself from any search.)
463 int our_pid
= getpid();
466 for (pid
= pidset_first(&in
); pid
>= 0; pid
= pidset_next(&in
)) {
468 pidset_add(&ret
, pid
);
473 static struct pidset
filter_by_uid(struct pidset in
, int uid
)
476 * Return only those processes with a given uid.
482 for (pid
= pidset_first(&in
); pid
>= 0; pid
= pidset_next(&in
)) {
483 const struct procdata
*proc
= get_proc(pid
);
484 if (proc
->uid
== uid
)
485 pidset_add(&ret
, pid
);
490 static struct pidset
filter_by_command(struct pidset in
, const char **words
)
493 * Look for processes matching the user-supplied command name and
494 * subsequent arguments.
500 for (pid
= pidset_first(&in
); pid
>= 0; pid
= pidset_next(&in
)) {
501 const struct procdata
*proc
= get_proc(pid
);
504 if (!proc
->argv
|| proc
->argc
< 1)
507 /* Find the command, whether it's a binary or a script. */
508 i
= find_command(proc
->argc
, proc
->argv
, words
[0]);
512 /* Now check that subsequent arguments match. */
513 for (j
= 1; words
[j
]; j
++)
514 if (!proc
->argv
[i
+j
] || strcmp(proc
->argv
[i
+j
], words
[j
]))
517 /* If we get here, we have a match! */
518 pidset_add(&ret
, pid
);
525 static struct pidset
filter_out_forks(struct pidset in
)
528 * Discard any process whose parent is also in our remaining match
529 * set and looks sufficiently like it for us to decide this one's
530 * an uninteresting fork (e.g. of a shell script executing a
537 for (pid
= pidset_first(&in
); pid
>= 0; pid
= pidset_next(&in
)) {
538 const struct procdata
*proc
= get_proc(pid
);
540 if (pidset_in(&in
, proc
->ppid
)) {
541 /* The parent is in our set too. Is it similar? */
542 const struct procdata
*parent
= get_proc(proc
->ppid
);
543 if (!strnullcmp(parent
->exe
, proc
->exe
) &&
544 !argcmp(parent
->argv
, proc
->argv
)) {
545 /* Yes; don't list it. */
550 pidset_add(&ret
, pid
);
555 /* ----------------------------------------------------------------------
559 const char usagemsg
[] =
560 "usage: pid [options] <search-cmd> [<search-arg>...]\n"
561 "where: -a report all matching pids, not just one\n"
562 " -U report pids of any user, not just ours\n"
563 " also: pid --version report version number\n"
564 " pid --help display this help text\n"
565 " pid --licence display the (MIT) licence text\n"
569 fputs(usagemsg
, stdout
);
572 const char licencemsg
[] =
573 "pid is copyright 2012 Simon Tatham.\n"
575 "Permission is hereby granted, free of charge, to any person\n"
576 "obtaining a copy of this software and associated documentation files\n"
577 "(the \"Software\"), to deal in the Software without restriction,\n"
578 "including without limitation the rights to use, copy, modify, merge,\n"
579 "publish, distribute, sublicense, and/or sell copies of the Software,\n"
580 "and to permit persons to whom the Software is furnished to do so,\n"
581 "subject to the following conditions:\n"
583 "The above copyright notice and this permission notice shall be\n"
584 "included in all copies or substantial portions of the Software.\n"
586 "THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n"
587 "EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n"
588 "MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n"
589 "NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS\n"
590 "BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN\n"
591 "ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\n"
592 "CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n"
597 fputs(licencemsg
, stdout
);
601 #define SVN_REV "$Revision$"
602 char rev
[sizeof(SVN_REV
)];
605 strcpy(rev
, SVN_REV
);
607 for (p
= rev
; *p
&& *p
!= ':'; p
++);
610 while (*p
&& isspace((unsigned char)*p
)) p
++;
611 for (q
= p
; *q
&& *q
!= '$'; q
++);
613 printf("pid revision %s\n", p
);
615 printf("pid: unknown version\n");
619 int main(int argc
, char **argv
)
621 const char **searchwords
;
623 int all
= 0, all_uids
= 0;
627 * Allocate enough space in 'searchwords' that we could shovel the
628 * whole of our argv into it if we had to. Then we won't have to
629 * worry about it later.
631 searchwords
= (const char **)malloc((argc
+1) * sizeof(const char *));
635 * Parse the command line.
639 if (doing_opts
&& *p
== '-') {
640 if (!strcmp(p
, "-a") || !strcmp(p
, "--all")) {
642 } else if (!strcmp(p
, "-U") || !strcmp(p
, "--all-uids")) {
644 } else if (!strcmp(p
, "--version")) {
647 } else if (!strcmp(p
, "--help")) {
650 } else if (!strcmp(p
, "--licence") || !strcmp(p
, "--license")) {
653 } else if (!strcmp(p
, "--")) {
656 fprintf(stderr
, "pid: unrecognised option '%s'\n", p
);
660 searchwords
[nsearchwords
++] = p
;
661 doing_opts
= 0; /* further optionlike args become search terms */
666 fprintf(stderr
, "pid: expected a command to search for; "
667 "type 'pid --help' for help\n");
670 searchwords
[nsearchwords
] = NULL
; /* terminate list */
676 * Construct our list of processes.
678 procs
= get_processes();
680 if (uid
> 0 && !all_uids
)
681 procs
= filter_by_uid(procs
, uid
);
682 procs
= filter_out_self(procs
);
683 procs
= filter_by_command(procs
, searchwords
);
685 procs
= filter_out_forks(procs
);
690 npids
= pidset_size(&procs
);
694 const char *sep
= "";
695 for (pid
= pidset_first(&procs
); pid
>= 0;
696 pid
= pidset_next(&procs
)) {
697 printf("%s%d", sep
, pid
);
703 printf("%d\n", pidset_first(&procs
));
705 printf("MULTIPLE\n");