diff options
Diffstat (limited to 'lib/procfs.c')
-rw-r--r-- | lib/procfs.c | 564 |
1 files changed, 564 insertions, 0 deletions
diff --git a/lib/procfs.c b/lib/procfs.c new file mode 100644 index 0000000..4d6d25b --- /dev/null +++ b/lib/procfs.c @@ -0,0 +1,564 @@ +/* + * No copyright is claimed. This code is in the public domain; do with + * it what you wish. + * + * Written by Karel Zak <kzak@redhat.com> + */ +#include <ctype.h> +#include <unistd.h> +#include <sys/vfs.h> +#include <errno.h> + +#include "c.h" +#include "pathnames.h" +#include "procfs.h" +#include "fileutils.h" +#include "all-io.h" +#include "debug.h" +#include "strutils.h" +#include "statfs_magic.h" + +static void procfs_process_deinit_path(struct path_cxt *pc); + +/* + * Debug stuff (based on include/debug.h) + */ +static UL_DEBUG_DEFINE_MASK(ulprocfs); +UL_DEBUG_DEFINE_MASKNAMES(ulprocfs) = UL_DEBUG_EMPTY_MASKNAMES; + +#define ULPROCFS_DEBUG_INIT (1 << 1) +#define ULPROCFS_DEBUG_CXT (1 << 2) + +#define DBG(m, x) __UL_DBG(ulprocfs, ULPROCFS_DEBUG_, m, x) +#define ON_DBG(m, x) __UL_DBG_CALL(ulprocfs, ULPROCFS_DEBUG_, m, x) + +#define UL_DEBUG_CURRENT_MASK UL_DEBUG_MASK(ulprocfs) +#include "debugobj.h" + +void ul_procfs_init_debug(void) +{ + if (ulprocfs_debug_mask) + return; + __UL_INIT_DEBUG_FROM_ENV(ulprocfs, ULPROCFS_DEBUG_, 0, ULPROCFS_DEBUG); +} + +struct path_cxt *ul_new_procfs_path(pid_t pid, const char *prefix) +{ + struct path_cxt *pc = ul_new_path(NULL); + + if (!pc) + return NULL; + if (prefix) + ul_path_set_prefix(pc, prefix); + + if (procfs_process_init_path(pc, pid) != 0) { + ul_unref_path(pc); + return NULL; + } + + DBG(CXT, ul_debugobj(pc, "alloc")); + return pc; +} + +/* + * procfs_blkdev_* is procfs extension to ul_path_* API to read info about process. + * + * The function is possible to call in loop and without sysfs_procfs_deinit_path(). + * The procfs_process_deinit_path() is automatically called by ul_unref_path(). + * + */ +int procfs_process_init_path(struct path_cxt *pc, pid_t pid) +{ + struct procfs_process *prc; + int rc; + char buf[sizeof(_PATH_PROC) + sizeof(stringify_value(UINT32_MAX)) + 2]; + + /* define path to pid stuff */ + snprintf(buf, sizeof(buf), _PATH_PROC "/%zu", (size_t) pid); + rc = ul_path_set_dir(pc, buf); + if (rc) + return rc; + + /* make sure path exists */ + rc = ul_path_get_dirfd(pc); + if (rc < 0) + return rc; + + /* initialize procfs specific stuff */ + prc = ul_path_get_dialect(pc); + if (!prc) { + DBG(CXT, ul_debugobj(pc, "alloc new procfs handler")); + prc = calloc(1, sizeof(struct procfs_process)); + if (!prc) + return -ENOMEM; + + ul_path_set_dialect(pc, prc, procfs_process_deinit_path); + } + + DBG(CXT, ul_debugobj(pc, "init procfs stuff")); + + prc->pid = pid; + return 0; +} + +static void procfs_process_deinit_path(struct path_cxt *pc) +{ + struct procfs_process *prc; + + if (!pc) + return; + + DBG(CXT, ul_debugobj(pc, "deinit")); + + prc = ul_path_get_dialect(pc); + if (!prc) + return; + + free(prc); + ul_path_set_dialect(pc, NULL, NULL); +} + +static ssize_t read_procfs_file(int fd, char *buf, size_t bufsz) +{ + ssize_t sz = 0; + size_t i; + + if (fd < 0) + return -EINVAL; + + sz = read_all(fd, buf, bufsz); + if (sz <= 0) + return sz; + + for (i = 0; i < (size_t) sz; i++) { + if (buf[i] == '\0') + buf[i] = ' '; + } + buf[sz - 1] = '\0'; + return sz; +} + +static ssize_t procfs_process_get_line_for(struct path_cxt *pc, char *buf, size_t bufsz, + const char *fname) +{ + int fd = ul_path_open(pc, O_RDONLY|O_CLOEXEC, fname); + + if (fd >= 0) { + ssize_t sz = read_procfs_file(fd, buf, bufsz); + close(fd); + return sz; + } + return -errno; +} + +ssize_t procfs_process_get_cmdline(struct path_cxt *pc, char *buf, size_t bufsz) +{ + return procfs_process_get_line_for(pc, buf, bufsz, "cmdline"); +} + +ssize_t procfs_process_get_cmdname(struct path_cxt *pc, char *buf, size_t bufsz) +{ + return procfs_process_get_line_for(pc, buf, bufsz, "comm"); +} + +ssize_t procfs_process_get_stat(struct path_cxt *pc, char *buf, size_t bufsz) +{ + return procfs_process_get_line_for(pc, buf, bufsz, "stat"); +} + +int procfs_process_get_uid(struct path_cxt *pc, uid_t *uid) +{ + struct stat sb; + int rc; + + if ((rc = ul_path_stat(pc, &sb, 0, NULL)) == 0) + *uid = sb.st_uid; + return rc; +} + +/* + * returns the next task TID, the @sub is automatically initialized + * when called first time and closed after last call or you can + * call closedir()* when you need to break the loop. + * + * Returns: <0 on error, 0 on success, >1 done + * + * Example: + * + * pid_t tid; + * DIR *sub = NULL; + * path_cxt *pc = ul_new_procfs_path(123, NULL); + * + * while (procfs_process_next_tid(pc, &sub, &tid) == 0) + * printf("task: %d", (int) tid); + * + */ +int procfs_process_next_tid(struct path_cxt *pc, DIR **sub, pid_t *tid) +{ + struct dirent *d; + + if (!pc || !sub || !tid) + return -EINVAL; + + if (!*sub) { + *sub = ul_path_opendir(pc, "task"); + if (!*sub) + return -errno; + } + + while ((d = xreaddir(*sub))) { + if (procfs_dirent_get_pid(d, tid) == 0) + return 0; + } + + closedir(*sub); + *sub = NULL; + return 1; +} + +int procfs_process_next_fd(struct path_cxt *pc, DIR **sub, int *fd) +{ + struct dirent *d; + + if (!pc || !sub || !fd) + return -EINVAL; + + if (!*sub) { + *sub = ul_path_opendir(pc, "fd"); + if (!*sub) + return -errno; + } + + while ((d = xreaddir(*sub))) { + uint64_t num; +#ifdef _DIRENT_HAVE_D_TYPE + if (d->d_type != DT_LNK && d->d_type != DT_UNKNOWN) + continue; +#endif + if (ul_strtou64(d->d_name, &num, 10) < 0) + continue; + *fd = num; + return 0; + } + + closedir(*sub); + *sub = NULL; + return 1; +} + +/* + * Simple 'dirent' based stuff for use-cases where procfs_process_* API is overkill + */ + +/* stupid, but good enough as a basic filter */ +int procfs_dirent_is_process(struct dirent *d) +{ +#ifdef _DIRENT_HAVE_D_TYPE + if (d->d_type != DT_DIR && d->d_type != DT_UNKNOWN) + return 0; +#endif + if (!isdigit((unsigned char) *d->d_name)) + return 0; + + return 1; +} + +int procfs_dirent_get_pid(struct dirent *d, pid_t *pid) +{ + uint64_t num; + + if (!procfs_dirent_is_process(d)) + return -EINVAL; + + if (ul_strtou64(d->d_name, &num, 10) < 0) + return -EINVAL; + + *pid = (pid_t) num; + return 0; +} + +int procfs_dirent_get_uid(DIR *procfs, struct dirent *d, uid_t *uid) +{ + struct stat st; + + if (!procfs_dirent_is_process(d)) + return -EINVAL; + + if (fstatat(dirfd(procfs), d->d_name, &st, 0)) + return -EINVAL; + + *uid = st.st_uid; + return 0; +} + +int procfs_dirent_match_uid(DIR *procfs, struct dirent *d, uid_t uid) +{ + uid_t x; + + if (procfs_dirent_get_uid(procfs, d, &x) == 0) + return x == uid; + + return 0; +} + +/* "name" of process; may be truncated, see prctl(2) and PR_SET_NAME. + * The minimal of the @buf has to be 32 bytes. */ +int procfs_dirent_get_name(DIR *procfs, struct dirent *d, char *buf, size_t bufsz) +{ + FILE *f; + size_t sz; + char tmp[1024], *p, *end = NULL; + + if (bufsz < 32) + return -EINVAL; + if (!procfs_dirent_is_process(d)) + return -EINVAL; + + snprintf(tmp, sizeof(tmp), "%s/stat", d->d_name); + f = fopen_at(dirfd(procfs), tmp, O_CLOEXEC|O_RDONLY, "r"); + if (!f) + return -errno; + + p = fgets(tmp, sizeof(tmp), f); + fclose(f); + if (!p) + return -errno; + + /* skip PID */ + while (*p && *p != '(') + p++; + + /* skip extra '(' */ + while (*p && *p == '(') + p++; + + end = p; + while (*end && *end != ')') + end++; + + sz = end - p; + if (sz > bufsz) + sz = bufsz - 1; + + memcpy(buf, p, sz); + buf[sz] = '\0'; + + return 0; +} + +int procfs_dirent_match_name(DIR *procfs, struct dirent *d, const char *name) +{ + char buf[33]; + + if (procfs_dirent_get_name(procfs, d, buf, sizeof(buf)) == 0) + return strcmp(name, buf) == 0; + + return 0; +} + +/* checks if fd is file in a procfs; + * returns 1 if true, 0 if false or couldn't determine */ +int fd_is_procfs(int fd) +{ + struct statfs st; + int ret; + + do { + errno = 0; + ret = fstatfs(fd, &st); + + if (ret < 0) { + if (errno != EINTR && errno != EAGAIN) + return 0; + xusleep(250000); + } + } while (ret != 0); + + return st.f_type == STATFS_PROC_MAGIC; +} + +static char *strdup_procfs_file(pid_t pid, const char *name) +{ + char buf[BUFSIZ]; + char *re = NULL; + int fd; + + snprintf(buf, sizeof(buf), _PATH_PROC "/%d/%s", (int) pid, name); + fd = open(buf, O_CLOEXEC|O_RDONLY); + if (fd < 0) + return NULL; + + if (read_procfs_file(fd, buf, sizeof(buf)) > 0) + re = strdup(buf); + close(fd); + return re; +} + +char *pid_get_cmdname(pid_t pid) +{ + return strdup_procfs_file(pid, "comm"); +} + +char *pid_get_cmdline(pid_t pid) +{ + return strdup_procfs_file(pid, "cmdline"); +} + +#ifdef TEST_PROGRAM_PROCFS + +static int test_tasks(int argc, char *argv[]) +{ + DIR *sub = NULL; + struct path_cxt *pc; + pid_t tid = 0, pid; + + if (argc != 2) + return EXIT_FAILURE; + + pid = strtol(argv[1], (char **) NULL, 10); + printf("PID=%d, TIDs:", pid); + + pc = ul_new_procfs_path(pid, NULL); + if (!pc) + err(EXIT_FAILURE, "alloc procfs handler failed"); + + while (procfs_process_next_tid(pc, &sub, &tid) == 0) + printf(" %d", tid); + + printf("\n"); + ul_unref_path(pc); + return EXIT_SUCCESS; +} + +static int test_fds(int argc, char *argv[]) +{ + DIR *sub = NULL; + struct path_cxt *pc; + pid_t pid; + int fd = -1; + + if (argc != 2) + return EXIT_FAILURE; + + pid = strtol(argv[1], (char **) NULL, 10); + printf("PID=%d, FDs:", pid); + + pc = ul_new_procfs_path(pid, NULL); + if (!pc) + err(EXIT_FAILURE, "alloc procfs handler failed"); + + while (procfs_process_next_fd(pc, &sub, &fd) == 0) + printf(" %d", fd); + + fputc('\n', stdout); + ul_unref_path(pc); + return EXIT_SUCCESS; +} + +static int test_processes(int argc, char *argv[]) +{ + DIR *dir; + struct dirent *d; + char *name = NULL; + uid_t uid = (uid_t) -1; + char buf[128]; + + if (argc >= 3 && strcmp(argv[1], "--name") == 0) + name = argv[2]; + if (argc >= 3 && strcmp(argv[1], "--uid") == 0) + uid = (uid_t) atol(argv[2]); + + dir = opendir(_PATH_PROC); + if (!dir) + err(EXIT_FAILURE, "cannot open proc"); + + while ((d = xreaddir(dir))) { + pid_t pid = 0; + + if (procfs_dirent_get_pid(d, &pid) != 0) + continue; + if (name && !procfs_dirent_match_name(dir, d, name)) + continue; + if (uid != (uid_t) -1 && !procfs_dirent_match_uid(dir, d, uid)) + continue; + procfs_dirent_get_name(dir, d, buf, sizeof(buf)); + printf(" %d [%s]", pid, buf); + } + + fputc('\n', stdout); + closedir(dir); + return EXIT_SUCCESS; +} + +static int test_one_process(int argc, char *argv[]) +{ + pid_t pid; + struct path_cxt *pc; + char buf[BUFSIZ]; + uid_t uid = (uid_t) -1; + + if (argc != 2) + return EXIT_FAILURE; + pid = strtol(argv[1], (char **) NULL, 10); + + pc = ul_new_procfs_path(pid, NULL); + if (!pc) + err(EXIT_FAILURE, "cannot alloc procfs handler"); + + printf("%d\n", (int) pid); + + procfs_process_get_uid(pc, &uid); + printf(" UID: %zu\n", (size_t) uid); + + procfs_process_get_cmdline(pc, buf, sizeof(buf)); + printf(" CMDLINE: '%s'\n", buf); + + procfs_process_get_cmdname(pc, buf, sizeof(buf)); + printf(" COMM: '%s'\n", buf); + + ul_unref_path(pc); + return EXIT_SUCCESS; +} + +static int test_isprocfs(int argc, char *argv[]) +{ + const char *name = argc > 1 ? argv[1] : "/proc"; + int fd = open(name, O_RDONLY); + int is = 0; + + if (fd >= 0) { + is = fd_is_procfs(fd); + close(fd); + } else + err(EXIT_FAILURE, "cannot open %s", name); + + printf("%s: %s procfs\n", name, is ? "is" : "is NOT"); + return is ? EXIT_SUCCESS : EXIT_FAILURE; +} + +int main(int argc, char *argv[]) +{ + if (argc < 2) { + fprintf(stderr, "usage: %1$s --tasks <pid>\n" + " %1$s --fds <pid>\n" + " %1$s --is-procfs [<dir>]\n" + " %1$s --processes [---name <name>] [--uid <uid>]\n" + " %1$s --one <pid>\n", + program_invocation_short_name); + return EXIT_FAILURE; + } + + if (strcmp(argv[1], "--tasks") == 0) + return test_tasks(argc - 1, argv + 1); + if (strcmp(argv[1], "--fds") == 0) + return test_fds(argc - 1, argv + 1); + if (strcmp(argv[1], "--processes") == 0) + return test_processes(argc - 1, argv + 1); + if (strcmp(argv[1], "--is-procfs") == 0) + return test_isprocfs(argc - 1, argv + 1); + if (strcmp(argv[1], "--one") == 0) + return test_one_process(argc - 1, argv + 1); + + return EXIT_FAILURE; +} +#endif /* TEST_PROGRAM_PROCUTILS */ |