/* Copyright (c) 2011-2016 Dovecot authors, see the included COPYING file */

#include "lib.h"
#include "stats-plugin.h"
#include "mail-stats.h"

#include <sys/resource.h>

#define PROC_IO_PATH "/proc/self/io"

static bool proc_io_disabled = FALSE;
static int proc_io_fd = -1;

static int
process_io_buffer_parse(const char *buf, struct mail_stats *stats)
{
	const char *const *tmp;

	tmp = t_strsplit(buf, "\n");
	for (; *tmp != NULL; tmp++) {
		if (strncmp(*tmp, "rchar: ", 7) == 0) {
			if (str_to_uint64(*tmp + 7, &stats->read_bytes) < 0)
				return -1;
		} else if (strncmp(*tmp, "wchar: ", 7) == 0) {
			if (str_to_uint64(*tmp + 7, &stats->write_bytes) < 0)
				return -1;
		} else if (strncmp(*tmp, "syscr: ", 7) == 0) {
			if (str_to_uint32(*tmp + 7, &stats->read_count) < 0)
				return -1;
		} else if (strncmp(*tmp, "syscw: ", 7) == 0) {
			if (str_to_uint32(*tmp + 7, &stats->write_count) < 0)
				return -1;
		}
	}
	return 0;
}

static int process_io_open(void)
{
	uid_t uid;

	if (proc_io_fd != -1)
		return proc_io_fd;

	if (proc_io_disabled)
		return -1;
	proc_io_fd = open(PROC_IO_PATH, O_RDONLY);
	if (proc_io_fd == -1 && errno == EACCES) {
		/* kludge: if we're running with permissions temporarily
		   dropped, get them temporarily back so we can open
		   /proc/self/io. */
		uid = geteuid();
		if (seteuid(0) == 0) {
			proc_io_fd = open(PROC_IO_PATH, O_RDONLY);
			if (seteuid(uid) < 0) {
				/* oops, this is bad */
				i_fatal("stats: seteuid(%s) failed", dec2str(uid));
			}
		}
		errno = EACCES;
	}
	if (proc_io_fd == -1) {
		if (errno != ENOENT)
			i_error("open(%s) failed: %m", PROC_IO_PATH);
		proc_io_disabled = TRUE;
		return -1;
	}
	return proc_io_fd;
}

static void process_read_io_stats(struct mail_stats *stats)
{
	char buf[1024];
	int fd, ret;

	if ((fd = process_io_open()) == -1)
		return;

	ret = pread(fd, buf, sizeof(buf), 0);
	if (ret <= 0) {
		if (ret == -1)
			i_error("read(%s) failed: %m", PROC_IO_PATH);
		else
			i_error("read(%s) returned EOF", PROC_IO_PATH);
	} else if (ret == sizeof(buf)) {
		/* just shouldn't happen.. */
		i_error("%s is larger than expected", PROC_IO_PATH);
		proc_io_disabled = TRUE;
	} else {
		buf[ret] = '\0';
		T_BEGIN {
			if (process_io_buffer_parse(buf, stats) < 0) {
				i_error("Invalid input in file %s",
					PROC_IO_PATH);
				proc_io_disabled = TRUE;
			}
		} T_END;
	}
}

static void
user_trans_stats_get(struct stats_user *suser, struct mail_stats *dest_r)
{
	struct stats_transaction_context *strans;

	mail_stats_add_transaction(dest_r, &suser->finished_transaction_stats);
	for (strans = suser->transactions; strans != NULL; strans = strans->next)
		mail_stats_add_transaction(dest_r, &strans->trans->stats);
}

void mail_stats_fill(struct stats_user *suser, struct mail_stats *stats_r)
{
	struct rusage usage;

	memset(stats_r, 0, sizeof(*stats_r));
	/* cputime */
	if (getrusage(RUSAGE_SELF, &usage) < 0)
		memset(&usage, 0, sizeof(usage));
	stats_r->user_cpu = usage.ru_utime;
	stats_r->sys_cpu = usage.ru_stime;
	stats_r->min_faults = usage.ru_minflt;
	stats_r->maj_faults = usage.ru_majflt;
	stats_r->vol_cs = usage.ru_nvcsw;
	stats_r->invol_cs = usage.ru_nivcsw;
	stats_r->disk_input = (unsigned long long)usage.ru_inblock * 512ULL;
	stats_r->disk_output = (unsigned long long)usage.ru_oublock * 512ULL;
	(void)gettimeofday(&stats_r->clock_time, NULL);
	process_read_io_stats(stats_r);
	user_trans_stats_get(suser, stats_r);
}
