/*
 * 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 <stdio.h>
#include <dirent.h>
#include <errno.h>
#include <stdlib.h>
#include <inttypes.h>
#include <procfs.h>
#include <string.h>
#include <vm/as.h>
#include <kstat.h>

#include "utils.h"

static int get_psinfo(psinfo_t* p_info, pid_t pid) SA_HOT SA_NONNULL;
static int read_stats(struct sa_process* dst, pid_t pid) SA_HOT SA_NONNULL;
static int read_stats_activity(struct sa_process_activity* dst, psinfo_t* p_info) SA_HOT SA_NONNULL;

__thread DIR* dir_proc;
__thread kstat_ctl_t* process_kc;
__thread uint32_t boot_time = 0; // seconds from epoch
__thread char usage_path[32];

int sa_open_process() {
	process_kc = NULL;

	dir_proc = opendir("/proc/");
	if (dir_proc == NULL)
		return EIO;

	process_kc = kstat_open();
	if (process_kc == NULL)
		return ENOSYS;

	kstat_t* ks;
	if (get_ks(process_kc, &ks, "unix", "system_misc") != 0)
		return ENOSYS;
	kstat_named_t* kn = kstat_data_lookup(ks, "boot_time");
	if (kn == NULL)
		return ENOSYS;

	boot_time = kn->value.ui32;
	strcpy(usage_path, "/proc/");
	return 0;
}

int sa_close_process() {
	if (dir_proc != NULL)
		closedir(dir_proc);
	if (process_kc != NULL)
		kstat_close(process_kc);
	return 0;
}

int sa_count_processes(uint32_t* number) {
	kstat_t* ks;
	if (get_ks(process_kc, &ks, "unix", "system_misc") != 0)
		return ENOSYS;
	kstat_named_t* kn = kstat_data_lookup(ks, "nproc");
	if (kn == NULL)
		return ENOSYS;

	*number = kn->value.ui32;
	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;

	rewinddir(dir_proc);
	*written = 0;

	struct dirent* entry;
	pid_t pid;
	while ((entry = readdir(dir_proc)) != NULL) {
		pid = atoi(entry->d_name);
		if (pid == 0)
			continue;
		if (*written == dst_size)
			return ENOMEM;

		dst[*written] = pid;
		++(*written);
	}

	return 0;
}

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

	return read_stats(dst, pid);
}

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

	psinfo_t p_info;
	if (get_psinfo(&p_info, pid) != 0)
		return ESRCH;

	return read_stats_activity(dst, &p_info);
}

static int get_psinfo(psinfo_t* p_info, pid_t pid) {
	char line_buffer[32];
	FILE* psinfo;

	sprintf(line_buffer, "/proc/%d/psinfo", (int) pid);
	psinfo = fopen(line_buffer, "r");
	if (psinfo == NULL)
		return ESRCH;

	if (fread(p_info, sizeof(psinfo_t), 1, psinfo) == 0) {
		fclose(psinfo);
		return ENOSYS;
	}

	fclose(psinfo);
	return 0;
}

static int read_stats(struct sa_process* dst, pid_t pid) {
	psinfo_t p_info; // psinfo_t is defined at procfs.h
	int ret;

	ret = get_psinfo(&p_info, pid);
	if (ret != 0)
		return ret;

	dst->pid = pid;
	dst->uid = p_info.pr_uid;
	dst->gid = p_info.pr_gid;
	strncpy(dst->filename, p_info.pr_fname, PRFNSZ);
	strncpy(dst->cmdline, p_info.pr_psargs, PRARGSZ);
	dst->parent_pid = p_info.pr_ppid;
	dst->pgrp = p_info.pr_pgid;
	dst->sid = p_info.pr_sid;
	dst->tty = p_info.pr_ttydev;
	dst->nice = p_info.pr_lwp.pr_nice - 20;
	dst->start_time = (uint64_t) (p_info.pr_start.tv_sec - boot_time) * 100;
	return read_stats_activity(&dst->activity, &p_info);
}

static int read_stats_activity(struct sa_process_activity* dst, psinfo_t* p_info) {
	dst->pid = p_info->pr_pid;

	sprintf(&usage_path[6], "%d/usage", (int) p_info->pr_pid);
	FILE* file_usage = fopen(usage_path, "r");
	if (file_usage == NULL)
		return ESRCH;
	prusage_t prusage;
	if (fread(&prusage, sizeof prusage, 1, file_usage) != 1) {
		fclose(file_usage);
		return ESRCH;
	}
	fclose(file_usage);
	dst->threads = prusage.pr_count;
	dst->user_time = prusage.pr_utime.tv_sec * 1000 + prusage.pr_utime.tv_nsec / 1000000;
	dst->sys_time = prusage.pr_stime.tv_sec * 1000 + prusage.pr_stime.tv_nsec / 1000000;

	switch (p_info->pr_lwp.pr_state) {
	case SZOMB:
		dst->state = SA_ZOMBIE;
		break;
	case SSTOP:
		dst->state = SA_STOPPED;
		break;
	case SIDL:
	case SONPROC:
		dst->state = SA_RUNNING;
		break;
	default:
		dst->state = SA_SLEEPING;
		break;
	}
	dst->vm_size = p_info->pr_size * 1024;
	dst->rss = p_info->pr_rssize * 1024;
	return 0;
}
