2 * No copyright is claimed. This code is in the public domain; do with
5 * Copyright (C) 2021 Karel Zak <kzak@redhat.com>
13 # include "statfs_magic.h"
17 #include "pathnames.h"
19 #include "fileutils.h"
24 static void procfs_process_deinit_path(struct path_cxt
*pc
);
27 * Debug stuff (based on include/debug.h)
29 static UL_DEBUG_DEFINE_MASK(ulprocfs
);
30 UL_DEBUG_DEFINE_MASKNAMES(ulprocfs
) = UL_DEBUG_EMPTY_MASKNAMES
;
32 #define ULPROCFS_DEBUG_INIT (1 << 1)
33 #define ULPROCFS_DEBUG_CXT (1 << 2)
35 #define DBG(m, x) __UL_DBG(ulprocfs, ULPROCFS_DEBUG_, m, x)
36 #define ON_DBG(m, x) __UL_DBG_CALL(ulprocfs, ULPROCFS_DEBUG_, m, x)
38 #define UL_DEBUG_CURRENT_MASK UL_DEBUG_MASK(ulprocfs)
41 void ul_procfs_init_debug(void)
43 if (ulprocfs_debug_mask
)
45 __UL_INIT_DEBUG_FROM_ENV(ulprocfs
, ULPROCFS_DEBUG_
, 0, ULPROCFS_DEBUG
);
48 struct path_cxt
*ul_new_procfs_path(pid_t pid
, const char *prefix
)
50 struct path_cxt
*pc
= ul_new_path(NULL
);
55 ul_path_set_prefix(pc
, prefix
);
57 if (procfs_process_init_path(pc
, pid
) != 0) {
62 DBG(CXT
, ul_debugobj(pc
, "alloc"));
67 * procfs_blkdev_* is procfs extension to ul_path_* API to read info about process.
69 * The function is possible to call in loop and without sysfs_procfs_deinit_path().
70 * The procfs_process_deinit_path() is automatically called by ul_unref_path().
73 int procfs_process_init_path(struct path_cxt
*pc
, pid_t pid
)
75 struct procfs_process
*prc
;
77 char buf
[sizeof(_PATH_PROC
) + sizeof(stringify_value(UINT32_MAX
)) + 2];
79 /* define path to pid stuff */
80 snprintf(buf
, sizeof(buf
), _PATH_PROC
"/%zu", (size_t) pid
);
81 rc
= ul_path_set_dir(pc
, buf
);
85 /* make sure path exists */
86 rc
= ul_path_get_dirfd(pc
);
90 /* initialize procfs specific stuff */
91 prc
= ul_path_get_dialect(pc
);
93 DBG(CXT
, ul_debugobj(pc
, "alloc new procfs handler"));
94 prc
= calloc(1, sizeof(struct procfs_process
));
98 ul_path_set_dialect(pc
, prc
, procfs_process_deinit_path
);
101 DBG(CXT
, ul_debugobj(pc
, "init procfs stuff"));
107 static void procfs_process_deinit_path(struct path_cxt
*pc
)
109 struct procfs_process
*prc
;
114 DBG(CXT
, ul_debugobj(pc
, "deinit"));
116 prc
= ul_path_get_dialect(pc
);
121 ul_path_set_dialect(pc
, NULL
, NULL
);
124 static ssize_t
read_procfs_file(int fd
, char *buf
, size_t bufsz
)
132 sz
= read_all(fd
, buf
, bufsz
);
136 for (i
= 0; i
< (size_t) sz
; i
++) {
144 static ssize_t
procfs_process_get_data_for(struct path_cxt
*pc
, char *buf
, size_t bufsz
,
147 int fd
= ul_path_open(pc
, O_RDONLY
|O_CLOEXEC
, fname
);
150 ssize_t sz
= read_procfs_file(fd
, buf
, bufsz
);
157 ssize_t
procfs_process_get_cmdline(struct path_cxt
*pc
, char *buf
, size_t bufsz
)
159 return procfs_process_get_data_for(pc
, buf
, bufsz
, "cmdline");
162 ssize_t
procfs_process_get_cmdname(struct path_cxt
*pc
, char *buf
, size_t bufsz
)
164 return procfs_process_get_data_for(pc
, buf
, bufsz
, "comm");
167 ssize_t
procfs_process_get_stat(struct path_cxt
*pc
, char *buf
, size_t bufsz
)
169 return procfs_process_get_data_for(pc
, buf
, bufsz
, "stat");
172 ssize_t
procfs_process_get_syscall(struct path_cxt
*pc
, char *buf
, size_t bufsz
)
174 return procfs_process_get_data_for(pc
, buf
, bufsz
, "syscall");
177 int procfs_process_get_stat_nth(struct path_cxt
*pc
, int n
, uintmax_t *re
)
180 char *key
= NULL
, *tok
, *p
;
184 if (n
== 2 || n
== 3) /* process name and status (strings) */
187 rc
= procfs_process_get_data_for(pc
, buf
, sizeof(buf
), "stat");
191 for (i
= 0, tok
= strtok_r(buf
, " ", &key
); tok
;
192 tok
= strtok_r(NULL
, " ", &key
)) {
196 return ul_strtou64(tok
, re
, 10);
198 /* skip rest of the process name */
199 if (i
== 2 && (p
= strrchr(key
, ')')))
206 int procfs_process_get_uid(struct path_cxt
*pc
, uid_t
*uid
)
211 if ((rc
= ul_path_stat(pc
, &sb
, 0, NULL
)) == 0)
217 * returns the next task TID, the @sub is automatically initialized
218 * when called first time and closed after last call or you can
219 * call closedir()* when you need to break the loop.
221 * Returns: <0 on error, 0 on success, >1 done
227 * path_cxt *pc = ul_new_procfs_path(123, NULL);
229 * while (procfs_process_next_tid(pc, &sub, &tid) == 0)
230 * printf("task: %d", (int) tid);
233 int procfs_process_next_tid(struct path_cxt
*pc
, DIR **sub
, pid_t
*tid
)
237 if (!pc
|| !sub
|| !tid
)
241 *sub
= ul_path_opendir(pc
, "task");
246 while ((d
= xreaddir(*sub
))) {
247 if (procfs_dirent_get_pid(d
, tid
) == 0)
256 int procfs_process_next_fd(struct path_cxt
*pc
, DIR **sub
, int *fd
)
260 if (!pc
|| !sub
|| !fd
)
264 *sub
= ul_path_opendir(pc
, "fd");
269 while ((d
= xreaddir(*sub
))) {
271 #ifdef _DIRENT_HAVE_D_TYPE
272 if (d
->d_type
!= DT_LNK
&& d
->d_type
!= DT_UNKNOWN
)
275 if (ul_strtou64(d
->d_name
, &num
, 10) < 0)
287 * Simple 'dirent' based stuff for use-cases where procfs_process_* API is overkill
290 /* stupid, but good enough as a basic filter */
291 int procfs_dirent_is_process(struct dirent
*d
)
293 #ifdef _DIRENT_HAVE_D_TYPE
294 if (d
->d_type
!= DT_DIR
&& d
->d_type
!= DT_UNKNOWN
)
297 if (!isdigit((unsigned char) *d
->d_name
))
303 int procfs_dirent_get_pid(struct dirent
*d
, pid_t
*pid
)
307 if (!procfs_dirent_is_process(d
))
310 if (ul_strtou64(d
->d_name
, &num
, 10) < 0)
317 int procfs_dirent_get_uid(DIR *procfs
, struct dirent
*d
, uid_t
*uid
)
321 if (!procfs_dirent_is_process(d
))
324 if (fstatat(dirfd(procfs
), d
->d_name
, &st
, 0))
331 int procfs_dirent_match_uid(DIR *procfs
, struct dirent
*d
, uid_t uid
)
335 if (procfs_dirent_get_uid(procfs
, d
, &x
) == 0)
341 /* "name" of process; may be truncated, see prctl(2) and PR_SET_NAME.
342 * The minimal of the @buf has to be 32 bytes. */
343 int procfs_dirent_get_name(DIR *procfs
, struct dirent
*d
, char *buf
, size_t bufsz
)
347 char tmp
[1024], *p
, *end
= NULL
;
351 if (!procfs_dirent_is_process(d
))
354 snprintf(tmp
, sizeof(tmp
), "%s/stat", d
->d_name
);
355 f
= fopen_at(dirfd(procfs
), tmp
, O_CLOEXEC
|O_RDONLY
, "r");
359 p
= fgets(tmp
, sizeof(tmp
), f
);
365 while (*p
&& *p
!= '(')
369 while (*p
&& *p
== '(')
373 while (*end
&& *end
!= ')')
386 int procfs_dirent_match_name(DIR *procfs
, struct dirent
*d
, const char *name
)
390 if (procfs_dirent_get_name(procfs
, d
, buf
, sizeof(buf
)) == 0)
391 return strcmp(name
, buf
) == 0;
396 #ifdef HAVE_SYS_VFS_H
397 /* checks if fd is file in a procfs;
398 * returns 1 if true, 0 if false or couldn't determine */
399 int fd_is_procfs(int fd
)
406 ret
= fstatfs(fd
, &st
);
409 if (errno
!= EINTR
&& errno
!= EAGAIN
)
415 return st
.f_type
== STATFS_PROC_MAGIC
;
419 int fd_is_procfs(int fd
__attribute__((__unused__
)))
425 static char *strdup_procfs_file(pid_t pid
, const char *name
)
431 snprintf(buf
, sizeof(buf
), _PATH_PROC
"/%d/%s", (int) pid
, name
);
432 fd
= open(buf
, O_CLOEXEC
|O_RDONLY
);
436 if (read_procfs_file(fd
, buf
, sizeof(buf
)) > 0)
442 char *pid_get_cmdname(pid_t pid
)
444 return strdup_procfs_file(pid
, "comm");
447 char *pid_get_cmdline(pid_t pid
)
449 return strdup_procfs_file(pid
, "cmdline");
452 #ifdef TEST_PROGRAM_PROCFS
454 static int test_tasks(int argc
, char *argv
[], const char *prefix
)
463 pid
= strtol(argv
[1], (char **) NULL
, 10);
464 printf("PID=%d, TIDs:", pid
);
466 pc
= ul_new_procfs_path(pid
, prefix
);
468 err(EXIT_FAILURE
, "alloc procfs handler failed");
470 while (procfs_process_next_tid(pc
, &sub
, &tid
) == 0)
478 static int test_fds(int argc
, char *argv
[], const char *prefix
)
488 pid
= strtol(argv
[1], (char **) NULL
, 10);
489 printf("PID=%d, FDs:", pid
);
491 pc
= ul_new_procfs_path(pid
, prefix
);
493 err(EXIT_FAILURE
, "alloc procfs handler failed");
495 while (procfs_process_next_fd(pc
, &sub
, &fd
) == 0)
503 static int test_processes(int argc
, char *argv
[])
508 uid_t uid
= (uid_t
) -1;
511 if (argc
>= 3 && strcmp(argv
[1], "--name") == 0)
513 if (argc
>= 3 && strcmp(argv
[1], "--uid") == 0)
514 uid
= (uid_t
) atol(argv
[2]);
516 dir
= opendir(_PATH_PROC
);
518 err(EXIT_FAILURE
, "cannot open proc");
520 while ((d
= xreaddir(dir
))) {
523 if (procfs_dirent_get_pid(d
, &pid
) != 0)
525 if (name
&& !procfs_dirent_match_name(dir
, d
, name
))
527 if (uid
!= (uid_t
) -1 && !procfs_dirent_match_uid(dir
, d
, uid
))
529 procfs_dirent_get_name(dir
, d
, buf
, sizeof(buf
));
530 printf(" %d [%s]", pid
, buf
);
538 static int test_one_process(int argc
, char *argv
[], const char *prefix
)
543 uid_t uid
= (uid_t
) -1;
547 pid
= strtol(argv
[1], (char **) NULL
, 10);
549 pc
= ul_new_procfs_path(pid
, prefix
);
551 err(EXIT_FAILURE
, "cannot alloc procfs handler");
553 printf("%d\n", (int) pid
);
555 procfs_process_get_uid(pc
, &uid
);
556 printf(" UID: %zu\n", (size_t) uid
);
558 procfs_process_get_cmdline(pc
, buf
, sizeof(buf
));
559 printf(" CMDLINE: '%s'\n", buf
);
561 procfs_process_get_cmdname(pc
, buf
, sizeof(buf
));
562 printf(" COMM: '%s'\n", buf
);
568 static int test_isprocfs(int argc
, char *argv
[])
570 const char *name
= argc
> 1 ? argv
[1] : "/proc";
571 int fd
= open(name
, O_RDONLY
);
575 is
= fd_is_procfs(fd
);
578 err(EXIT_FAILURE
, "cannot open %s", name
);
580 printf("%s: %s procfs\n", name
, is
? "is" : "is NOT");
581 return is
? EXIT_SUCCESS
: EXIT_FAILURE
;
584 static int test_process_stat_nth(int argc
, char *argv
[], const char *prefix
)
593 pid
= strtol(argv
[1], (char **) NULL
, 10);
594 n
= strtol(argv
[2], (char **) NULL
, 10);
596 pc
= ul_new_procfs_path(pid
, prefix
);
598 err(EXIT_FAILURE
, "cannot alloc procfs handler");
600 ret
= procfs_process_get_stat_nth(pc
, n
, &num
);
602 errx(EXIT_FAILURE
, "read %dth number failed: %s", n
, strerror(-ret
));
604 printf("%d: %dth %ju\n", (int) pid
, n
, num
);
609 int main(int argc
, char *argv
[])
611 const char *prefix
= NULL
;
613 if (argc
> 2 && strcmp(argv
[1], "--prefix") == 0) {
620 fprintf(stderr
, "usage: %1$s [--prefix <prefix>] --tasks <pid>\n"
621 " %1$s [--prefix <prefix>] --fds <pid>\n"
622 " %1$s --is-procfs [<dir>]\n"
623 " %1$s --processes [--name <name>] [--uid <uid>]\n"
624 " %1$s [--prefix <prefix>] --one <pid>\n"
625 " %1$s [--prefix <prefix>] --stat-nth <pid> <n>\n",
626 program_invocation_short_name
);
630 if (strcmp(argv
[1], "--tasks") == 0)
631 return test_tasks(argc
- 1, argv
+ 1, prefix
);
632 if (strcmp(argv
[1], "--fds") == 0)
633 return test_fds(argc
- 1, argv
+ 1, prefix
);
634 if (strcmp(argv
[1], "--processes") == 0)
635 return test_processes(argc
- 1, argv
+ 1);
636 if (strcmp(argv
[1], "--is-procfs") == 0)
637 return test_isprocfs(argc
- 1, argv
+ 1);
638 if (strcmp(argv
[1], "--one") == 0)
639 return test_one_process(argc
- 1, argv
+ 1, prefix
);
640 if (strcmp(argv
[1], "--stat-nth") == 0)
641 return test_process_stat_nth(argc
- 1, argv
+ 1, prefix
);
645 #endif /* TEST_PROGRAM_PROCUTILS */