/*	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
