/*
 * libsysactivity
 * http://sourceforge.net/projects/libsysactivity/
 * Copyright (c) 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 "utils.h"

static int refresh_processes(size_t* len) SA_HOT SA_NONNULL;
static void assign(struct sa_process* dst, struct kinfo_proc* kinfo_proc) SA_NONNULL;
static void assign_activity(struct sa_process_activity* dst, struct kinfo_proc* kinfo_proc) SA_NONNULL;

int mib[3] = { CTL_KERN, KERN_PROC, KERN_PROC_ALL };
extern long pagesize;
__thread struct kinfo_proc* processes = NULL;
__thread size_t processes_size = 0;
__thread int cmd_mib[4] = { CTL_KERN, KERN_PROC, KERN_PROC_ARGS, 0 };

int sa_close_process() {
	if (processes != NULL) {
		free(processes);
		processes = NULL;
		processes_size = 0;
	}

	return 0;
}

int sa_count_processes(uint32_t* number) {
	size_t len;
	if (sysctl(mib, 3, NULL, &len, NULL, 0) == -1)
		return ENOSYS;

	*number = len / sizeof(struct kinfo_proc);
	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;

	size_t len;
	if (refresh_processes(&len) != 0)
		return ENOSYS;

	uint32_t i;
	*written = 0;
	len /= sizeof(struct kinfo_proc);
	for (i = 0; i < len; ++i) {
		if (i == dst_size)
			return ENOMEM;

		dst[i] = processes[i].kp_pid;
		++(*written);
	}

	return 0;
}

int sa_get_process(pid_t pid, struct sa_process* dst) {
	if (dst == NULL) // A pid with value 0 is valid here
		return EINVAL;

	struct kinfo_proc proc;
	size_t len = sizeof proc;
	int mib_pid[4] = { CTL_KERN, KERN_PROC, KERN_PROC_PID };
	mib_pid[3] = pid;
	if (sysctl(mib_pid, 4, &proc, &len, NULL, 0) == -1)
		return ENOSYS;

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

int sa_get_process_activity(pid_t pid, struct sa_process_activity* dst) {
	if (dst == NULL) // A pid with value 0 is valid here
		return EINVAL;

	struct kinfo_proc proc;
	size_t len = sizeof proc;
	int mib_pid[4] = { CTL_KERN, KERN_PROC, KERN_PROC_PID };
	mib_pid[3] = pid;
	if (sysctl(mib_pid, 4, &proc, &len, NULL, 0) == -1)
		return ENOSYS;

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

static int refresh_processes(size_t* len) {
	do {
		if (sysctl(mib, 3, NULL, len, NULL, 0) == -1)
			return ENOSYS;

		if (*len > processes_size) {
			safe_realloc((void*) &processes, len);
			processes_size = *len;
			if (processes == NULL)
				return ENOMEM;
		}

		errno = 0;
		if (sysctl(mib, 3, processes, len, NULL, 0) == -1 && errno != ENOMEM)
			return ENOSYS;

	} while (errno == ENOMEM);

	return 0;
}

static void assign(struct sa_process* dst, struct kinfo_proc* kinfo_proc) {
	dst->pid = kinfo_proc->kp_pid;
	dst->uid = kinfo_proc->kp_uid;
	dst->gid = kinfo_proc->kp_rgid;
	strlcpy(dst->filename, kinfo_proc->kp_comm, SA_PROCESS_FILENAME);

	cmd_mib[3] = kinfo_proc->kp_pid;
	size_t len = sizeof dst->cmdline;
	sysctl(cmd_mib, 4, dst->cmdline, &len, 0, 0);

	dst->parent_pid = kinfo_proc->kp_ppid;
	dst->pgrp = kinfo_proc->kp_pgid;
	dst->sid = kinfo_proc->kp_sid;
	dst->tty = kinfo_proc->kp_tpgid;
	dst->nice = kinfo_proc->kp_nice;
	assign_activity(&dst->activity, kinfo_proc);
}

static void assign_activity(struct sa_process_activity* dst, struct kinfo_proc* kinfo_proc) {
	dst->pid = kinfo_proc->kp_pid;
	switch (kinfo_proc->kp_stat) {
	case 3: // 3 = idle but, sleeping = idle?
	case 1:
		dst->state = SA_SLEEPING;
		break;
	case 2:
		dst->state = SA_RUNNING;
		break;
	case 4:
		dst->state = SA_STOPPED;
		break;
	case 5:
		dst->state = SA_ZOMBIE;
		break;
	default:
		dst->state = SA_UNKNOWN;
	}
	dst->user_time = kinfo_proc->kp_lwp.kl_uticks;
	dst->sys_time = kinfo_proc->kp_lwp.kl_sticks;
	dst->threads = kinfo_proc->kp_nthreads;
	dst->vm_size = kinfo_proc->kp_vm_map_size;
	dst->rss = kinfo_proc->kp_vm_rssize * pagesize;
}
