blob: 0148cfd1e937770feaafe98d95b02ec245e81b7b [file] [log] [blame] [raw]
/* 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