/*
 * libsysactivity
 * http://sourceforge.net/projects/libsysactivity/
 * Copyright (c) 2009-2011 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 <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/sysctl.h>
#include <stdlib.h>
#include <sys/devicestat.h>
#include <stdio.h>
#include "utils.h"

#define MIB_SIZE 3

static void assign(struct sa_disk* __restrict__ dst, struct devstat* __restrict__ src) SA_NONNULL;

__thread void* buffer;
__thread size_t buffer_size = 0;
__thread int ds_mib[MIB_SIZE];
__thread int number_devs;
__thread struct devstat* devs;

int sa_open_disk() {
	buffer = NULL;
	buffer_size = 0;

	int version;
	size_t len = sizeof version;
	if (sysctlbyname("kern.devstat.version", &version, &len, NULL, 0) == -1)
		return ENOSYS;
	if (version != DEVSTAT_VERSION)
		return ENOSYS;

	len = MIB_SIZE;
	if (sysctlnametomib("kern.devstat.numdevs", ds_mib, &len) == -1)
		return ENOSYS;

	return 0;
}

int sa_close_disk() {
	if (buffer != NULL)
		free(buffer);
	return 0;
}

int sa_count_disks(uint16_t* number) {
	if (number == NULL)
		return EINVAL;

	int amount;
	size_t len = sizeof amount;
	if (sysctl(ds_mib, MIB_SIZE, &amount, &len, NULL, 0) == -1)
		return ENOSYS;

	*number = amount;
	return 0;
}

int sa_reset_disks() {
	size_t len;
	do {
		len = sizeof number_devs;
		if (sysctl(ds_mib, MIB_SIZE, &number_devs, &len, NULL, 0) == -1)
			return ENOSYS;

		len = (number_devs * sizeof(struct devstat)) + sizeof(long);
		if (len > buffer_size) {
			safe_realloc(&buffer, &len);
			buffer_size = len;
			if (buffer == NULL)
				return ENOMEM;
		}

		errno = 0;
		if (sysctlbyname("kern.devstat.all", buffer, &len, NULL, 0) == -1 && errno != ENOMEM)
			return ENOSYS;
	} while (errno == ENOMEM);

	devs = (struct devstat *) (buffer + sizeof(long));
	return 0;
}

int sa_get_disks_ids(char* dst, uint16_t dst_size, uint16_t* written) {
	if (dst == NULL || dst_size == 0 || written == NULL)
		return EINVAL;

	*written = 0;
	uint16_t i;
	for (i = 0; i < number_devs; ++i) {
		if (i == dst_size)
			return ENOMEM;

		snprintf(&dst[i * SA_DISK_NAME], SA_DISK_NAME, "%s%d", devs[i].device_name, devs[i].unit_number);
		++(*written);
	}
	return 0;
}

int sa_get_disk(char* name, struct sa_disk* dst) {
	if (name == NULL || dst == NULL)
		return EINVAL;

	uint16_t i;
	char tmp_name[sizeof dst->name];
	for (i = 0; i < number_devs; ++i) {
		snprintf(tmp_name, sizeof tmp_name, "%s%d", devs[i].device_name, devs[i].unit_number);
		if (strncmp(tmp_name, name, sizeof tmp_name) != 0)
			continue;

		assign(dst, &devs[i]);
		return 0;
	}
	return ENODEV;
}

int sa_get_disks(struct sa_disk* dst, uint16_t dst_size, uint16_t* written) {
	if (dst == NULL || dst_size == 0 || written == NULL)
		return EINVAL;

	*written = 0;
	uint16_t i;
	for (i = 0; i < number_devs; ++i) {
		if (i == dst_size)
			return ENOMEM;

		assign(&dst[i], &devs[i]);
		++(*written);
	}
	return 0;
}

static void assign(struct sa_disk* __restrict__ dst, struct devstat* __restrict__ src) {
	snprintf(dst->name, sizeof dst->name, "%s%d", src->device_name, src->unit_number);
#if DEVSTAT_VERSION == 4
	dst->reads = src->num_reads;
	dst->writes = src->num_writes;
	dst->bytes_read = src->bytes_read;
	dst->bytes_written = src->bytes_written;
#elif DEVSTAT_VERSION == 6
	dst->reads = src->operations[DEVSTAT_READ];
	dst->time_spent_reading = src->duration[DEVSTAT_READ].sec;
	dst->writes = src->operations[DEVSTAT_WRITE];
	dst->time_spent_writing = src->duration[DEVSTAT_WRITE].sec;
	dst->bytes_read = src->bytes[DEVSTAT_READ];
	dst->bytes_written = src->bytes[DEVSTAT_WRITE];
#endif
}
