/*
 * 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 <sys/sysinfo.h>
#include <string.h>

#include "utils.h"

static inline int parse_stats(struct sa_process* dst, const pid_t pid) SA_HOT SA_NONNULL;
static int parse_activity(struct sa_process_activity* dst, const pid_t pid) SA_HOT SA_NONNULL;

extern int kernel_version;
__thread DIR* dir_proc;
__thread char path[32];

int sa_open_process() {
	dir_proc = NULL;

	if (kernel_version < 25)
		return ENOTSUP;

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

	strcpy(path, "/proc/");
	return 0;
}

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

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

	struct sysinfo info;
	if (sysinfo(&info) == -1)
		return errno;

	*number = (pid_t) info.procs;
	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) {
		if (entry->d_type != DT_DIR || (pid = atoi(entry->d_name)) == 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;

	sprintf(&path[6], "%d", pid);
	return parse_stats(dst, pid);
}

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

	sprintf(&path[6], "%d", pid);
	return parse_activity(dst, pid);
}

static inline int parse_stats(struct sa_process* dst, const pid_t pid) {
	char line_buffer[256];

	int aux = strlen(path);
	strcpy(&path[aux], "/stat");
	FILE* file = fopen(path, "r");
	if (file == NULL)
		return ESRCH;
	if (fgets(line_buffer, sizeof line_buffer, file) == NULL) {
		fclose(file);
		return EIO;
	}

	dst->activity.pid = dst->pid = pid;
	char* pos = skip_values(line_buffer, 2);
	switch (*pos) {
	case 'S':
		dst->activity.state = SA_SLEEPING;
		break;
	case 'D':
		dst->activity.state = SA_SLEEPING_UNINTERRUPTIBLE;
		break;
	case 'Z':
		dst->activity.state = SA_ZOMBIE;
		break;
	case 'T':
		dst->activity.state = SA_STOPPED;
		break;
	default:
		dst->activity.state = SA_RUNNING;
	}
	pos = skip_value(pos);
	dst->parent_pid = atoi(pos);
	pos = skip_value(pos);
	dst->pgrp = atoi(pos);
	pos = skip_value(pos);
	dst->sid = atoi(pos);
	pos = skip_value(pos);
	dst->tty = atoi(pos);
	pos = skip_values(pos, 7);
	dst->activity.user_time = strtoull(pos, NULL, 10);
	pos = skip_value(pos);
	dst->activity.sys_time = strtoull(pos, NULL, 10);
	pos = skip_values(pos, 4);
	dst->nice = atoi(pos);
	pos = skip_value(pos);
	dst->activity.threads = atoi(pos);
	pos = skip_values(pos, 2);
	dst->start_time = strtoull(pos, NULL, 10);
	pos = skip_value(pos);
	dst->activity.vm_size = strtoul(pos, NULL, 10);
	pos = skip_value(pos);
	dst->activity.rss = strtoul(pos, NULL, 10);
	fclose(file);

	strcpy(&path[aux], "/cmdline");
	file = fopen(path, "r");
	if (file == NULL)
		return ESRCH;
	if (fgets(dst->cmdline, sizeof dst->cmdline, file) == NULL) // sometimes this file is empty
		dst->cmdline[0] = '\0';
	fclose(file);

	strcpy(&path[aux], "/status");
	file = fopen(path, "r");
	if (file == NULL)
		return ESRCH;

	dst->uid = dst->gid = 0;
	dst->filename[0] = '\0';
	aux = 0; // for better performance
	while (fgets(line_buffer, sizeof line_buffer, file) != NULL) {
		if (aux < 1 && !strncmp(line_buffer, "Name:", sizeof("Name:") - 1)) {
			pos = skip_value(line_buffer);
			strlcpy(dst->filename, pos, sizeof(dst->filename) - 1);
			dst->filename[strlen(dst->filename) - 1] = '\0';
			++aux;
			continue;
		}
		if (aux < 2 && !strncmp(line_buffer, "Uid:", sizeof("Uid:") - 1)) {
			pos = skip_value(line_buffer);
			dst->uid = atoi(pos);
			++aux;
			continue;
		}
		if (aux < 3 && !strncmp(line_buffer, "Gid:", sizeof("Gid:") - 1)) {
			pos = skip_value(line_buffer);
			dst->gid = atoi(pos);
			break;
		}
	}
	fclose(file);

	return 0;
}

static int parse_activity(struct sa_process_activity* dst, const pid_t pid) {
	char line_buffer[256];

	int aux = strlen(path);
	strcpy(&path[aux], "/stat");
	FILE* file = fopen(path, "r");
	if (file == NULL)
		return ESRCH;
	if (fgets(line_buffer, sizeof line_buffer, file) == NULL) {
		fclose(file);
		return EIO;
	}

	dst->pid = pid;
	char* pos = skip_values(line_buffer, 2);
	switch (*pos) {
	case 'S':
		dst->state = SA_SLEEPING;
		break;
	case 'D':
		dst->state = SA_SLEEPING_UNINTERRUPTIBLE;
		break;
	case 'Z':
		dst->state = SA_ZOMBIE;
		break;
	case 'T':
		dst->state = SA_STOPPED;
		break;
	default:
		dst->state = SA_RUNNING;
	}
	pos = skip_values(pos, 11);
	dst->user_time = strtoull(pos, NULL, 10);
	pos = skip_value(pos);
	dst->sys_time = strtoull(pos, NULL, 10);
	pos = skip_values(pos, 5);
	dst->threads = atoi(pos);
	pos = skip_values(pos, 3);
	dst->vm_size = strtoul(pos, NULL, 10);
	pos = skip_value(pos);
	dst->rss = strtoul(pos, NULL, 10);
	fclose(file);
	return 0;
}
