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
)
356 if (!strcmp(basename
, "perl") ||
357 !strcmp(basename
, "python") ||
358 !strcmp(basename
, "ruby") ||
359 !strcmp(basename
, "rep") ||
360 !strcmp(basename
, "bash") ||
361 !strcmp(basename
, "sh") ||
362 !strcmp(basename
, "dash") ||
363 !strcmp(basename
, "lua") ||
364 !strcmp(basename
, "java"))
370 static const char *find_basename(const char *path
)
372 const char *ret
= path
;
376 p
= ret
+ strcspn(ret
, "/");
385 static int find_command(int pid_argc
, const char *const *pid_argv
,
392 base
++; /* skip indicator of login shells */
393 base
= find_basename(base
);
395 if (!strcmp(base
, cmd
)) {
397 * argv[0] matches the supplied command name.
400 } else if (is_an_interpreter(base
)) {
402 * argv[0] is an interpreter program of some kind. Look
403 * along its command line for the program it's running,
404 * and see if _that_ matches the command name.
407 while (i
< pid_argc
&& pid_argv
[i
][0] == '-')
408 i
++; /* skip interpreter options */
409 if (i
< pid_argc
&& !strcmp(find_basename(pid_argv
[i
]), cmd
))
412 return -1; /* no match */
415 static int strnullcmp(const char *a
, const char *b
)
418 * Like strcmp, but cope with NULL inputs by making them compare
419 * identical to each other and before any non-null string.
422 return (b
!= 0) - (a
!= 0);
427 static int argcmp(const char *const *a
, const char *const *b
)
430 int ret
= strcmp(*a
, *b
);
437 return (*b
!= NULL
) - (*a
!= NULL
);
440 static struct pidset
filter_out_self(struct pidset in
)
443 * Discard our own pid from a set. (Generally we won't want to
444 * return ourself from any search.)
448 int our_pid
= getpid();
451 for (pid
= pidset_first(&in
); pid
>= 0; pid
= pidset_next(&in
)) {
453 pidset_add(&ret
, pid
);
458 static struct pidset
filter_by_uid(struct pidset in
, int uid
)
461 * Return only those processes with a given uid.
467 for (pid
= pidset_first(&in
); pid
>= 0; pid
= pidset_next(&in
)) {
468 const struct procdata
*proc
= get_proc(pid
);
469 if (proc
->uid
== uid
)
470 pidset_add(&ret
, pid
);
475 static struct pidset
filter_by_command(struct pidset in
, const char **words
)
478 * Look for processes matching the user-supplied command name and
479 * subsequent arguments.
485 for (pid
= pidset_first(&in
); pid
>= 0; pid
= pidset_next(&in
)) {
486 const struct procdata
*proc
= get_proc(pid
);
489 if (!proc
->argv
|| proc
->argc
< 1)
492 /* Find the command, whether it's a binary or a script. */
493 i
= find_command(proc
->argc
, proc
->argv
, words
[0]);
497 /* Now check that subsequent arguments match. */
498 for (j
= 1; words
[j
]; j
++)
499 if (!proc
->argv
[i
+j
] || strcmp(proc
->argv
[i
+j
], words
[j
]))
502 /* If we get here, we have a match! */
503 pidset_add(&ret
, pid
);
510 static struct pidset
filter_out_forks(struct pidset in
)
513 * Discard any process whose parent is also in our remaining match
514 * set and looks sufficiently like it for us to decide this one's
515 * an uninteresting fork (e.g. of a shell script executing a
522 for (pid
= pidset_first(&in
); pid
>= 0; pid
= pidset_next(&in
)) {
523 const struct procdata
*proc
= get_proc(pid
);
525 if (pidset_in(&in
, proc
->ppid
)) {
526 /* The parent is in our set too. Is it similar? */
527 const struct procdata
*parent
= get_proc(proc
->ppid
);
528 if (!strnullcmp(parent
->exe
, proc
->exe
) &&
529 !argcmp(parent
->argv
, proc
->argv
)) {
530 /* Yes; don't list it. */
535 pidset_add(&ret
, pid
);
540 /* ----------------------------------------------------------------------
544 const char usagemsg
[] =
545 "usage: pid [options] <search-cmd> [<search-arg>...]\n"
546 "where: -a report all matching pids, not just one\n"
547 " -U report pids of any user, not just ours\n"
548 " also: pid --version report version number\n"
549 " pid --help display this help text\n"
550 " pid --licence display the (MIT) licence text\n"
554 fputs(usagemsg
, stdout
);
557 const char licencemsg
[] =
558 "pid is copyright 2012 Simon Tatham.\n"
560 "Permission is hereby granted, free of charge, to any person\n"
561 "obtaining a copy of this software and associated documentation files\n"
562 "(the \"Software\"), to deal in the Software without restriction,\n"
563 "including without limitation the rights to use, copy, modify, merge,\n"
564 "publish, distribute, sublicense, and/or sell copies of the Software,\n"
565 "and to permit persons to whom the Software is furnished to do so,\n"
566 "subject to the following conditions:\n"
568 "The above copyright notice and this permission notice shall be\n"
569 "included in all copies or substantial portions of the Software.\n"
571 "THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n"
572 "EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n"
573 "MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n"
574 "NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS\n"
575 "BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN\n"
576 "ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\n"
577 "CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n"
582 fputs(licencemsg
, stdout
);
586 #define SVN_REV "$Revision$"
587 char rev
[sizeof(SVN_REV
)];
590 strcpy(rev
, SVN_REV
);
592 for (p
= rev
; *p
&& *p
!= ':'; p
++);
595 while (*p
&& isspace((unsigned char)*p
)) p
++;
596 for (q
= p
; *q
&& *q
!= '$'; q
++);
598 printf("pid revision %s\n", p
);
600 printf("pid: unknown version\n");
604 int main(int argc
, char **argv
)
606 const char **searchwords
;
608 int all
= 0, all_uids
= 0;
612 * Allocate enough space in 'searchwords' that we could shovel the
613 * whole of our argv into it if we had to. Then we won't have to
614 * worry about it later.
616 searchwords
= (const char **)malloc((argc
+1) * sizeof(const char *));
620 * Parse the command line.
624 if (doing_opts
&& *p
== '-') {
625 if (!strcmp(p
, "-a") || !strcmp(p
, "--all")) {
627 } else if (!strcmp(p
, "-U") || !strcmp(p
, "--all-uids")) {
629 } else if (!strcmp(p
, "--version")) {
632 } else if (!strcmp(p
, "--help")) {
635 } else if (!strcmp(p
, "--licence") || !strcmp(p
, "--license")) {
638 } else if (!strcmp(p
, "--")) {
641 fprintf(stderr
, "pid: unrecognised option '%s'\n", p
);
645 searchwords
[nsearchwords
++] = p
;
646 doing_opts
= 0; /* further optionlike args become search terms */
651 fprintf(stderr
, "pid: expected a command to search for; "
652 "type 'pid --help' for help\n");
655 searchwords
[nsearchwords
] = NULL
; /* terminate list */
661 * Construct our list of processes.
663 procs
= get_processes();
665 if (uid
> 0 && !all_uids
)
666 procs
= filter_by_uid(procs
, uid
);
667 procs
= filter_out_self(procs
);
668 procs
= filter_by_command(procs
, searchwords
);
670 procs
= filter_out_forks(procs
);
675 npids
= pidset_size(&procs
);
679 const char *sep
= "";
680 for (pid
= pidset_first(&procs
); pid
>= 0;
681 pid
= pidset_next(&procs
)) {
682 printf("%s%d", sep
, pid
);
688 printf("%d\n", pidset_first(&procs
));
690 printf("MULTIPLE\n");