/*
 * libsysactivity
 * http://sourceforge.net/projects/libsysactivity/
 * Copyright (c) 2009, 2010 Carlos Olmedo Escobar <carlos.olmedo.e@gmail.com>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 */

#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/sysctl.h>
#include <sys/user.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <dirent.h>
#include <ctype.h>

#include "../OpenBSD/utils.h"

static void create_keys();
static void assign(struct sa_process* dst, struct kinfo_proc2* proc) SA_HOT SA_NONNULL;
static void assign_activity(struct sa_process_activity* dst, struct kinfo_proc2* proc) SA_HOT SA_NONNULL;
static void close_keys(void* resource);

long pagesize;
pthread_key_t dir_proc_key;

int sa_open_process() {
	pthread_once_t key_once = PTHREAD_ONCE_INIT;
	if (pthread_once(&key_once, create_keys))
		return ENOSYS;

	DIR* dir_proc = opendir("/proc/");
	if (pthread_setspecific(dir_proc_key, dir_proc))
		return ENOSYS;
	if (dir_proc == NULL)
		return EIO;

	return 0;
}

int sa_count_processes(uint32_t* number) {
	if (number == NULL)
		return EINVAL;

	DIR* dir_proc = pthread_getspecific(dir_proc_key);
	rewinddir(dir_proc);
	*number = 0;

	struct dirent* entry;
	while ((entry = readdir(dir_proc)) != NULL) {
		if (!isdigit((int) entry->d_name[0]))
			continue;

		++(*number);
	}

	return 0;
}

int sa_get_processes_ids(pid_t* dst, uint32_t dst_size, uint32_t* written) {
	if (dst == NULL || dst_size <= 0 || written == NULL)
		return EINVAL;

	DIR* dir_proc = pthread_getspecific(dir_proc_key);
	rewinddir(dir_proc);
	*written = 0;

	struct dirent* entry;
	while ((entry = readdir(dir_proc)) != NULL) {
		if (!isdigit((int) entry->d_name[0]))
			continue;
		if (*written == dst_size)
			return ENOMEM;

		dst[*written] = atoi(entry->d_name);
		++(*written);
	}

	return 0;
}

int sa_get_process(pid_t pid, struct sa_process* dst) {
	if (dst == NULL)
		return EINVAL;

	int mib[6];
	struct kinfo_proc2 proc;
	size_t len = sizeof proc;

	mib[0] = CTL_KERN;
	mib[1] = KERN_PROC2;
	mib[2] = KERN_PROC_PID;
	mib[3] = pid;
	mib[4] = len;
	mib[5] = 1;
	proc.p_paddr = 0;
	if (sysctl(mib, 6, &proc, &len, NULL, 0) == -1)
		return ENOSYS; // TODO Return something more specific in case the process does not exist

	if (proc.p_paddr == 0)
		return ESRCH;

	assign(dst, &proc);
	return 0;
}

int sa_get_process_activity(pid_t pid, struct sa_process_activity* dst) {
	if (dst == NULL)
		return EINVAL;

	int mib[6];
	struct kinfo_proc2 proc;
	size_t len = sizeof proc;

	mib[0] = CTL_KERN;
	mib[1] = KERN_PROC2;
	mib[2] = KERN_PROC_PID;
	mib[3] = pid;
	mib[4] = len;
	mib[5] = 1;
	proc.p_paddr = 0;
	if (sysctl(mib, 6, &proc, &len, NULL, 0) == -1)
		return ENOSYS; // TODO Return something more specific in case the process does not exist

	if (proc.p_paddr == 0)
		return ESRCH;

	assign_activity(dst, &proc);
	return 0;
}

static void create_keys() {
	static short keys_created = 0;
	if (keys_created) {
		return;
	}

	pagesize = sysconf(_SC_PAGESIZE);
	pthread_key_create(&dir_proc_key, close_keys);

	keys_created = 1;
}

static void assign(struct sa_process* dst, struct kinfo_proc2* proc) {
	dst->pid = proc->p_pid;
	dst->uid = proc->p_uid;
	dst->gid = proc->p_gid;
	strlcpy(dst->filename, proc->p_comm, KI_MAXCOMLEN);
	dst->parent_pid = proc->p_ppid;
	dst->pgrp = proc->p__pgid;
	dst->sid = proc->p_sid;
	dst->tty = proc->p_tdev;
	dst->nice = proc->p_nice - 20;
	dst->start_time = proc->p_ustart_sec * 100 + proc->p_ustart_usec / 10000;
	assign_activity(&dst->activity, proc);
}

static void assign_activity(struct sa_process_activity* dst, struct kinfo_proc2* proc) {
	dst->pid = proc->p_pid;
	dst->user_time = proc->p_uticks;
	dst->sys_time = proc->p_sticks;
	dst->vm_size = proc->p_vm_vsize;
	dst->rss = proc->p_vm_rssize * pagesize;
}

static void close_keys(void* resource) {
	closedir(resource);
}
