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
< 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
< 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
< lenof(p
->procbits
) && p
->procbits
[word
] >> bit
== 0) {
85 p
->next
= WORDBITS
* word
+ bit
;
88 if (word
>= 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 if ((exe
= get_link_dest(filename
)) == NULL
) {
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
)
358 if (!strcmp(basename
, "perl") ||
359 !strcmp(basename
, "python") ||
360 !strcmp(basename
, "ruby") ||
361 !strcmp(basename
, "rep") ||
362 !strcmp(basename
, "bash") ||
363 !strcmp(basename
, "sh") ||
364 !strcmp(basename
, "dash") ||
365 !strcmp(basename
, "lua") ||
366 !strcmp(basename
, "java"))
372 static const char *find_basename(const char *path
)
374 const char *ret
= path
;
378 p
= ret
+ strcspn(ret
, "/");
387 static int find_command(int pid_argc
, const char *const *pid_argv
,
394 base
++; /* skip indicator of login shells */
395 base
= find_basename(base
);
397 if (!strcmp(base
, cmd
)) {
399 * argv[0] matches the supplied command name.
402 } else if (is_an_interpreter(base
)) {
404 * argv[0] is an interpreter program of some kind. Look
405 * along its command line for the program it's running,
406 * and see if _that_ matches the command name.
409 while (i
< pid_argc
&& pid_argv
[i
][0] == '-')
410 i
++; /* skip interpreter options */
411 if (i
< pid_argc
&& !strcmp(find_basename(pid_argv
[i
]), cmd
))
414 return -1; /* no match */
417 static int argcmp(const char *const *a
, const char *const *b
)
420 int ret
= strcmp(*a
, *b
);
427 return (*b
!= NULL
) - (*a
!= NULL
);
430 static struct pidset
filter_out_self(struct pidset in
)
433 * Discard our own pid from a set. (Generally we won't want to
434 * return ourself from any search.)
438 int our_pid
= getpid();
441 for (pid
= pidset_first(&in
); pid
>= 0; pid
= pidset_next(&in
)) {
443 pidset_add(&ret
, pid
);
448 static struct pidset
filter_by_command(struct pidset in
, const char **words
)
451 * Look for processes matching the user-supplied command name and
452 * subsequent arguments.
458 for (pid
= pidset_first(&in
); pid
>= 0; pid
= pidset_next(&in
)) {
459 const struct procdata
*proc
= get_proc(pid
);
462 if (!proc
->argv
|| proc
->argc
< 1)
465 /* Find the command, whether it's a binary or a script. */
466 i
= find_command(proc
->argc
, proc
->argv
, words
[0]);
470 /* Now check that subsequent arguments match. */
471 for (j
= 1; words
[j
]; j
++)
472 if (!proc
->argv
[i
+j
] || strcmp(proc
->argv
[i
+j
], words
[j
]))
475 /* If we get here, we have a match! */
476 pidset_add(&ret
, pid
);
483 static struct pidset
filter_out_forks(struct pidset in
)
486 * Discard any process whose parent is also in our remaining match
487 * set and looks sufficiently like it for us to decide this one's
488 * an uninteresting fork (e.g. of a shell script executing a
495 for (pid
= pidset_first(&in
); pid
>= 0; pid
= pidset_next(&in
)) {
496 const struct procdata
*proc
= get_proc(pid
);
498 if (pidset_in(&in
, proc
->ppid
)) {
499 /* The parent is in our set too. Is it similar? */
500 const struct procdata
*parent
= get_proc(proc
->ppid
);
501 if (!strcmp(parent
->exe
, proc
->exe
) &&
502 !argcmp(parent
->argv
, proc
->argv
)) {
503 /* Yes; don't list it. */
508 pidset_add(&ret
, pid
);
513 /* ----------------------------------------------------------------------
517 const char usagemsg
[] =
518 "usage: pid [options] <search-cmd> [<search-arg>...]\n"
519 "where: -a report all matching pids, not just one\n"
520 " also: pid --version report version number\n"
521 " pid --help display this help text\n"
522 " pid --licence display the (MIT) licence text\n"
526 fputs(usagemsg
, stdout
);
529 const char licencemsg
[] =
530 "pid is copyright 2012 Simon Tatham.\n"
532 "Permission is hereby granted, free of charge, to any person\n"
533 "obtaining a copy of this software and associated documentation files\n"
534 "(the \"Software\"), to deal in the Software without restriction,\n"
535 "including without limitation the rights to use, copy, modify, merge,\n"
536 "publish, distribute, sublicense, and/or sell copies of the Software,\n"
537 "and to permit persons to whom the Software is furnished to do so,\n"
538 "subject to the following conditions:\n"
540 "The above copyright notice and this permission notice shall be\n"
541 "included in all copies or substantial portions of the Software.\n"
543 "THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n"
544 "EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n"
545 "MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n"
546 "NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS\n"
547 "BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN\n"
548 "ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\n"
549 "CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n"
554 fputs(licencemsg
, stdout
);
558 #define SVN_REV "$Revision$"
559 char rev
[sizeof(SVN_REV
)];
562 strcpy(rev
, SVN_REV
);
564 for (p
= rev
; *p
&& *p
!= ':'; p
++);
567 while (*p
&& isspace((unsigned char)*p
)) p
++;
568 for (q
= p
; *q
&& *q
!= '$'; q
++);
570 printf("pid revision %s\n", p
);
572 printf("pid: unknown version\n");
576 int main(int argc
, char **argv
)
578 const char **searchwords
;
584 * Allocate enough space in 'searchwords' that we could shovel the
585 * whole of our argv into it if we had to. Then we won't have to
586 * worry about it later.
588 searchwords
= (const char **)malloc((argc
+1) * sizeof(const char *));
592 * Parse the command line.
596 if (doing_opts
&& *p
== '-') {
597 if (!strcmp(p
, "-a") || !strcmp(p
, "--all")) {
599 } else if (!strcmp(p
, "--version")) {
602 } else if (!strcmp(p
, "--help")) {
605 } else if (!strcmp(p
, "--licence") || !strcmp(p
, "--license")) {
608 } else if (!strcmp(p
, "--")) {
611 fprintf(stderr
, "pid: unrecognised option '%s'\n", p
);
615 searchwords
[nsearchwords
++] = p
;
616 doing_opts
= 0; /* further optionlike args become search terms */
621 fprintf(stderr
, "pid: expected a command to search for; "
622 "type 'pid --help' for help\n");
625 searchwords
[nsearchwords
] = NULL
; /* terminate list */
631 * Construct our list of processes.
633 procs
= get_processes();
634 procs
= filter_out_self(procs
);
635 procs
= filter_by_command(procs
, searchwords
);
637 procs
= filter_out_forks(procs
);
642 npids
= pidset_size(&procs
);
646 const char *sep
= "";
647 for (pid
= pidset_first(&procs
); pid
>= 0;
648 pid
= pidset_next(&procs
)) {
649 printf("%s%d", sep
, pid
);
655 printf("%d\n", pidset_first(&procs
));
657 printf("MULTIPLE\n");