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 int procfs_process_get_stat_nth(struct path_cxt
*pc
, int n
, uintmax_t *re
)
175 char *key
= NULL
, *tok
, *p
;
179 if (n
== 2 || n
== 3) /* process name and status (strings) */
182 rc
= procfs_process_get_data_for(pc
, buf
, sizeof(buf
), "stat");
186 for (i
= 0, tok
= strtok_r(buf
, " ", &key
); tok
;
187 tok
= strtok_r(NULL
, " ", &key
)) {
191 return ul_strtou64(tok
, re
, 10);
193 /* skip rest of the process name */
194 if (i
== 2 && (p
= strrchr(key
, ')')))
201 int procfs_process_get_uid(struct path_cxt
*pc
, uid_t
*uid
)
206 if ((rc
= ul_path_stat(pc
, &sb
, 0, NULL
)) == 0)
212 * returns the next task TID, the @sub is automatically initialized
213 * when called first time and closed after last call or you can
214 * call closedir()* when you need to break the loop.
216 * Returns: <0 on error, 0 on success, >1 done
222 * path_cxt *pc = ul_new_procfs_path(123, NULL);
224 * while (procfs_process_next_tid(pc, &sub, &tid) == 0)
225 * printf("task: %d", (int) tid);
228 int procfs_process_next_tid(struct path_cxt
*pc
, DIR **sub
, pid_t
*tid
)
232 if (!pc
|| !sub
|| !tid
)
236 *sub
= ul_path_opendir(pc
, "task");
241 while ((d
= xreaddir(*sub
))) {
242 if (procfs_dirent_get_pid(d
, tid
) == 0)
251 int procfs_process_next_fd(struct path_cxt
*pc
, DIR **sub
, int *fd
)
255 if (!pc
|| !sub
|| !fd
)
259 *sub
= ul_path_opendir(pc
, "fd");
264 while ((d
= xreaddir(*sub
))) {
266 #ifdef _DIRENT_HAVE_D_TYPE
267 if (d
->d_type
!= DT_LNK
&& d
->d_type
!= DT_UNKNOWN
)
270 if (ul_strtou64(d
->d_name
, &num
, 10) < 0)
282 * Simple 'dirent' based stuff for use-cases where procfs_process_* API is overkill
285 /* stupid, but good enough as a basic filter */
286 int procfs_dirent_is_process(struct dirent
*d
)
288 #ifdef _DIRENT_HAVE_D_TYPE
289 if (d
->d_type
!= DT_DIR
&& d
->d_type
!= DT_UNKNOWN
)
292 if (!isdigit((unsigned char) *d
->d_name
))
298 int procfs_dirent_get_pid(struct dirent
*d
, pid_t
*pid
)
302 if (!procfs_dirent_is_process(d
))
305 if (ul_strtou64(d
->d_name
, &num
, 10) < 0)
312 int procfs_dirent_get_uid(DIR *procfs
, struct dirent
*d
, uid_t
*uid
)
316 if (!procfs_dirent_is_process(d
))
319 if (fstatat(dirfd(procfs
), d
->d_name
, &st
, 0))
326 int procfs_dirent_match_uid(DIR *procfs
, struct dirent
*d
, uid_t uid
)
330 if (procfs_dirent_get_uid(procfs
, d
, &x
) == 0)
336 /* "name" of process; may be truncated, see prctl(2) and PR_SET_NAME.
337 * The minimal of the @buf has to be 32 bytes. */
338 int procfs_dirent_get_name(DIR *procfs
, struct dirent
*d
, char *buf
, size_t bufsz
)
342 char tmp
[1024], *p
, *end
= NULL
;
346 if (!procfs_dirent_is_process(d
))
349 snprintf(tmp
, sizeof(tmp
), "%s/stat", d
->d_name
);
350 f
= fopen_at(dirfd(procfs
), tmp
, O_CLOEXEC
|O_RDONLY
, "r");
354 p
= fgets(tmp
, sizeof(tmp
), f
);
360 while (*p
&& *p
!= '(')
364 while (*p
&& *p
== '(')
368 while (*end
&& *end
!= ')')
381 int procfs_dirent_match_name(DIR *procfs
, struct dirent
*d
, const char *name
)
385 if (procfs_dirent_get_name(procfs
, d
, buf
, sizeof(buf
)) == 0)
386 return strcmp(name
, buf
) == 0;
391 #ifdef HAVE_SYS_VFS_H
392 /* checks if fd is file in a procfs;
393 * returns 1 if true, 0 if false or couldn't determine */
394 int fd_is_procfs(int fd
)
401 ret
= fstatfs(fd
, &st
);
404 if (errno
!= EINTR
&& errno
!= EAGAIN
)
410 return st
.f_type
== STATFS_PROC_MAGIC
;
414 int fd_is_procfs(int fd
__attribute__((__unused__
)))
420 static char *strdup_procfs_file(pid_t pid
, const char *name
)
426 snprintf(buf
, sizeof(buf
), _PATH_PROC
"/%d/%s", (int) pid
, name
);
427 fd
= open(buf
, O_CLOEXEC
|O_RDONLY
);
431 if (read_procfs_file(fd
, buf
, sizeof(buf
)) > 0)
437 char *pid_get_cmdname(pid_t pid
)
439 return strdup_procfs_file(pid
, "comm");
442 char *pid_get_cmdline(pid_t pid
)
444 return strdup_procfs_file(pid
, "cmdline");
447 #ifdef TEST_PROGRAM_PROCFS
449 static int test_tasks(int argc
, char *argv
[], const char *prefix
)
458 pid
= strtol(argv
[1], (char **) NULL
, 10);
459 printf("PID=%d, TIDs:", pid
);
461 pc
= ul_new_procfs_path(pid
, prefix
);
463 err(EXIT_FAILURE
, "alloc procfs handler failed");
465 while (procfs_process_next_tid(pc
, &sub
, &tid
) == 0)
473 static int test_fds(int argc
, char *argv
[], const char *prefix
)
483 pid
= strtol(argv
[1], (char **) NULL
, 10);
484 printf("PID=%d, FDs:", pid
);
486 pc
= ul_new_procfs_path(pid
, prefix
);
488 err(EXIT_FAILURE
, "alloc procfs handler failed");
490 while (procfs_process_next_fd(pc
, &sub
, &fd
) == 0)
498 static int test_processes(int argc
, char *argv
[])
503 uid_t uid
= (uid_t
) -1;
506 if (argc
>= 3 && strcmp(argv
[1], "--name") == 0)
508 if (argc
>= 3 && strcmp(argv
[1], "--uid") == 0)
509 uid
= (uid_t
) atol(argv
[2]);
511 dir
= opendir(_PATH_PROC
);
513 err(EXIT_FAILURE
, "cannot open proc");
515 while ((d
= xreaddir(dir
))) {
518 if (procfs_dirent_get_pid(d
, &pid
) != 0)
520 if (name
&& !procfs_dirent_match_name(dir
, d
, name
))
522 if (uid
!= (uid_t
) -1 && !procfs_dirent_match_uid(dir
, d
, uid
))
524 procfs_dirent_get_name(dir
, d
, buf
, sizeof(buf
));
525 printf(" %d [%s]", pid
, buf
);
533 static int test_one_process(int argc
, char *argv
[], const char *prefix
)
538 uid_t uid
= (uid_t
) -1;
542 pid
= strtol(argv
[1], (char **) NULL
, 10);
544 pc
= ul_new_procfs_path(pid
, prefix
);
546 err(EXIT_FAILURE
, "cannot alloc procfs handler");
548 printf("%d\n", (int) pid
);
550 procfs_process_get_uid(pc
, &uid
);
551 printf(" UID: %zu\n", (size_t) uid
);
553 procfs_process_get_cmdline(pc
, buf
, sizeof(buf
));
554 printf(" CMDLINE: '%s'\n", buf
);
556 procfs_process_get_cmdname(pc
, buf
, sizeof(buf
));
557 printf(" COMM: '%s'\n", buf
);
563 static int test_isprocfs(int argc
, char *argv
[])
565 const char *name
= argc
> 1 ? argv
[1] : "/proc";
566 int fd
= open(name
, O_RDONLY
);
570 is
= fd_is_procfs(fd
);
573 err(EXIT_FAILURE
, "cannot open %s", name
);
575 printf("%s: %s procfs\n", name
, is
? "is" : "is NOT");
576 return is
? EXIT_SUCCESS
: EXIT_FAILURE
;
579 static int test_process_stat_nth(int argc
, char *argv
[], const char *prefix
)
588 pid
= strtol(argv
[1], (char **) NULL
, 10);
589 n
= strtol(argv
[2], (char **) NULL
, 10);
591 pc
= ul_new_procfs_path(pid
, prefix
);
593 err(EXIT_FAILURE
, "cannot alloc procfs handler");
595 ret
= procfs_process_get_stat_nth(pc
, n
, &num
);
597 errx(EXIT_FAILURE
, "read %dth number failed: %s", n
, strerror(-ret
));
599 printf("%d: %dth %ju\n", (int) pid
, n
, num
);
604 int main(int argc
, char *argv
[])
606 const char *prefix
= NULL
;
608 if (argc
> 2 && strcmp(argv
[1], "--prefix") == 0) {
615 fprintf(stderr
, "usage: %1$s [--prefix <prefix>] --tasks <pid>\n"
616 " %1$s [--prefix <prefix>] --fds <pid>\n"
617 " %1$s --is-procfs [<dir>]\n"
618 " %1$s --processes [--name <name>] [--uid <uid>]\n"
619 " %1$s [--prefix <prefix>] --one <pid>\n"
620 " %1$s [--prefix <prefix>] --stat-nth <pid> <n>\n",
621 program_invocation_short_name
);
625 if (strcmp(argv
[1], "--tasks") == 0)
626 return test_tasks(argc
- 1, argv
+ 1, prefix
);
627 if (strcmp(argv
[1], "--fds") == 0)
628 return test_fds(argc
- 1, argv
+ 1, prefix
);
629 if (strcmp(argv
[1], "--processes") == 0)
630 return test_processes(argc
- 1, argv
+ 1);
631 if (strcmp(argv
[1], "--is-procfs") == 0)
632 return test_isprocfs(argc
- 1, argv
+ 1);
633 if (strcmp(argv
[1], "--one") == 0)
634 return test_one_process(argc
- 1, argv
+ 1, prefix
);
635 if (strcmp(argv
[1], "--stat-nth") == 0)
636 return test_process_stat_nth(argc
- 1, argv
+ 1, prefix
);
640 #endif /* TEST_PROGRAM_PROCUTILS */