| /* libprocstat |
| Copyright 2015-2023 Rivoreo |
| |
| This program is free software; you can redistribute it and/or modify |
| it under the terms of the GNU General Public License as published by |
| the Free Software Foundation; either version 2 of the License, or (at |
| your option) any later version. |
| |
| This program is distributed in the hope that it will be useful, but |
| WITHOUT ANY WARRANTY; without even the implied warranty of |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| General Public License for more details. |
| */ |
| |
| #define _STRUCTURED_PROC 1 |
| #include "procstat.h" |
| #include "procstat-private.h" |
| #include <sys/procfs.h> |
| #include <unistd.h> |
| #include <syncrw.h> |
| #include <dirent.h> |
| #include <string.h> |
| #include <stdlib.h> |
| #include <stdio.h> |
| #include <fcntl.h> |
| #include <errno.h> |
| |
| struct procstat_private_handle { |
| DIR *proc_dir; |
| struct procstat_handle *parent; |
| psinfo_t psinfo; |
| }; |
| |
| static int page_size; |
| static int page_size_kibi; |
| |
| static struct procstat_private_handle *procstat_sysv_open(struct procstat_handle *parent) { |
| DIR *dir = opendir("/proc"); |
| if(!dir) return NULL; |
| struct procstat_private_handle *r = malloc(sizeof(struct procstat_private_handle)); |
| if(!r) { |
| closedir(dir); |
| errno = ENOMEM; |
| return NULL; |
| } |
| r->proc_dir = dir; |
| r->parent = parent; |
| r->psinfo.pr_pid = -1; |
| if(!page_size) { |
| page_size = sysconf(_SC_PAGESIZE); |
| page_size_kibi = page_size / 1024; |
| } |
| return r; |
| } |
| |
| static void procstat_sysv_close(struct procstat_private_handle *handle) { |
| closedir(handle->proc_dir); |
| free(handle); |
| } |
| |
| #define TIMESPEC_TO_MSEC(TS) ((TS).tv_sec * 1000 + (TS).tv_nsec / 1000000) |
| |
| static int read_psinfo(struct procstat_private_handle *handle, pid_t pid) { |
| char path[32]; |
| if(snprintf(path, sizeof path, "%d/psinfo", (int)pid) > sizeof path - 1) { |
| errno = EINVAL; |
| return -1; |
| } |
| int fd = openat(dirfd(handle->proc_dir), path, O_RDONLY); |
| if(fd == -1) return -1; |
| int s = sync_read(fd, &handle->psinfo, sizeof handle->psinfo); |
| if(s < sizeof handle->psinfo) { |
| int e = errno; |
| if(s >= 0 && !e) e = EIO; |
| close(fd); |
| errno = e; |
| return -1; |
| } |
| close(fd); |
| return 0; |
| } |
| |
| static void fill_lwp_info(struct procstat_private_handle *handle, struct procstat *ps, int override_proc_info) { |
| ps->wchan = handle->psinfo.pr_lwp.pr_wchan; |
| ps->state = handle->psinfo.pr_lwp.pr_sname; |
| ps->nice = handle->psinfo.pr_lwp.pr_nice; |
| if(override_proc_info) { |
| ps->start_time = TIMESPEC_TO_MSEC(handle->psinfo.pr_lwp.pr_start); |
| ps->time = TIMESPEC_TO_MSEC(handle->psinfo.pr_lwp.pr_time); |
| if(*handle->psinfo.pr_lwp.pr_name) { |
| strncpy(ps->comm, handle->psinfo.pr_lwp.pr_name, sizeof ps->comm); |
| } |
| } |
| } |
| |
| static int procstat_sysv_get(struct procstat_private_handle *handle, pid_t pid, int flags, struct procstat *ps) { |
| if(handle->psinfo.pr_pid != pid && read_psinfo(handle, pid) < 0) return -1; |
| ps->pid = pid; |
| ps->ppid = handle->psinfo.pr_ppid; |
| ps->pgid = handle->psinfo.pr_pgid; |
| ps->sid = handle->psinfo.pr_sid; |
| ps->tsid = handle->psinfo.pr_sid; |
| ps->tgid = pid; |
| ps->tty = handle->psinfo.pr_ttydev; |
| ps->ruid = handle->psinfo.pr_uid; |
| ps->euid = handle->psinfo.pr_euid; |
| ps->rgid = handle->psinfo.pr_gid; |
| ps->egid = handle->psinfo.pr_egid; |
| ps->size = handle->psinfo.pr_size / page_size_kibi; |
| ps->rssize = handle->psinfo.pr_rssize / page_size_kibi; |
| ps->wchan = 0; |
| ps->ip = 0; |
| ps->is_kernel_process = pid == 0 || (pid != 1 && handle->psinfo.pr_ppid == 0); |
| ps->time = TIMESPEC_TO_MSEC(handle->psinfo.pr_time); |
| ps->child_time = TIMESPEC_TO_MSEC(handle->psinfo.pr_ctime); |
| ps->start_time = TIMESPEC_TO_MSEC(handle->psinfo.pr_start); |
| ps->num_threads = handle->psinfo.pr_nlwp; |
| ps->state = '?'; |
| ps->on_psr = -1; |
| ps->priority = 0; |
| ps->nice = 0; |
| ps->rt_priority = 0; |
| ps->sched_class = 0; |
| strncpy(ps->comm, handle->psinfo.pr_fname, sizeof ps->comm); |
| if(flags & PROCSTAT_MAIN_THREAD_INFO) fill_lwp_info(handle, ps, 0); |
| return 0; |
| } |
| |
| static int procstat_sysv_get_command_line(struct procstat_private_handle *handle, pid_t pid, char **command_line) { |
| if(handle->psinfo.pr_pid != pid && read_psinfo(handle, pid) < 0) return -1; |
| *command_line = strdup(handle->psinfo.pr_psargs); |
| return *command_line ? 0 : -1; |
| } |
| |
| static int procstat_sysv_get_argv(struct procstat_private_handle *handle, pid_t pid, int *argc, char ***argv) { |
| return -1; |
| } |
| |
| static int procstat_sysv_get_path(struct procstat_private_handle *handle, pid_t pid, char **path) { |
| return -1; |
| } |
| |
| static int procstat_sysv_get_mac_label(struct procstat_private_handle *handle, pid_t pid, char **label) { |
| return -1; |
| } |
| |
| // Return boolean |
| static int walk_lwp(struct procstat_private_handle *handle, struct procstat *ps, int (*func)(struct procstat_handle *, struct procstat *, void *), void *arg) { |
| char path[32]; |
| int path_len = snprintf(path, sizeof path, "%d/lwp/", (int)ps->pid); |
| if(path_len > sizeof path - 1) return 1; |
| int dir_fd = openat(dirfd(handle->proc_dir), path, O_RDONLY); |
| if(dir_fd == -1) return 1; |
| DIR *dir = fdopendir(dir_fd); |
| if(!dir) { |
| close(dir_fd); |
| return 1; |
| } |
| struct dirent *e; |
| while((e = readdir(dir))) { |
| if(*e->d_name == '.') continue; |
| size_t len = strlen(e->d_name); |
| #if 0 |
| if(len > sizeof path - path_len - 10) continue; |
| memcpy(path + path_len, e->d_name, len); |
| strcpy(path + path_len + len, "/lwpsinfo"); |
| #else |
| if(len > sizeof path - 10) continue; |
| memcpy(path, e->d_name, len); |
| strcpy(path + len, "/lwpsinfo"); |
| #endif |
| int fd = openat(dir_fd, path, O_RDONLY); |
| if(fd == -1) continue; |
| int s = sync_read(fd, &handle->psinfo.pr_lwp, sizeof handle->psinfo.pr_lwp); |
| close(fd); |
| if(s < sizeof handle->psinfo.pr_lwp) continue; |
| fill_lwp_info(handle, ps, 1); |
| if(!func(handle->parent, ps, arg)) { |
| closedir(dir); |
| return 0; |
| } |
| } |
| closedir(dir); |
| return 1; |
| } |
| |
| static void procstat_sysv_walk(struct procstat_private_handle *handle, int walk_type, int flags, int (*func)(struct procstat_handle *, struct procstat *, void *), void *arg) { |
| rewinddir(handle->proc_dir); |
| struct dirent *e; |
| while((e = readdir(handle->proc_dir))) { |
| char *end_p; |
| struct procstat ps; |
| if(*e->d_name == '.') continue; |
| pid_t pid = strtol(e->d_name, &end_p, 10); |
| if(*end_p) continue; |
| if(procstat_sysv_get(handle, pid, flags, &ps) < 0) continue; |
| switch(walk_type) { |
| case PROCSTAT_WALK_THREADS: |
| if(!walk_lwp(handle, &ps, func, arg)) return; |
| continue; |
| case PROCSTAT_WALK_KERNEL: |
| if(!ps.is_kernel_process) continue; |
| break; |
| case PROCSTAT_WALK_USER: |
| if(ps.is_kernel_process) continue; |
| break; |
| } |
| if(!func(handle->parent, &ps, arg)) return; |
| } |
| return; |
| } |
| |
| #define PROCSTAT_INTERFACE_SYSV \ |
| "sysv", \ |
| procstat_sysv_open, \ |
| procstat_sysv_close, \ |
| procstat_sysv_get, \ |
| procstat_sysv_get_command_line, \ |
| procstat_sysv_get_argv, \ |
| procstat_sysv_get_path, \ |
| procstat_sysv_get_mac_label, \ |
| procstat_sysv_walk |