#include <string.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <dirent.h>
#include <sys/stat.h>

#include "sysfs.h"
#include "util.h"

/**
 *	enter_dir - Change current working directory
 */

static int enter_dir(struct sysfs_object * so)
{
	int ret;

	dbg("Entering %s\n", so->so_name);
	ret = open(".", O_RDONLY);
	if (ret > 0) {
		so->so_orig_fd = ret;
		
		ret = chdir(so->so_name);
		if (ret < 0) {
			close(so->so_orig_fd);
			so->so_orig_fd = 0;
		}
	}
	return ret;
}


/**
 *	leave_dir - Change back to previous directory.
 */

static void leave_dir(struct sysfs_object * so)
{
	dbg("Leaving %s\n", so->so_name);
	fchdir(so->so_orig_fd);
	close(so->so_orig_fd);
	so->so_orig_fd = 0;
}



/**
 *	prune_object_name - Chop off all but the last directory.
 */

static void prune_object_name(struct sysfs_object * so)
{
	char * str;

	str = strrchr(so->so_name, '/');
	if (str)
		so->so_object_name = ++str;
	else
		so->so_object_name = so->so_name;
}


/**
 *	sysfs_object_init - Initialize object state
 *
 *	Initialize object state and change to that directory in sysfs.
 */

int sysfs_object_init(struct sysfs_object * so, char * name)
{
	int error;

	memset(so, 0, sizeof(struct sysfs_object));
	so->so_name = strdup(name);
	if (!so->so_name)
		return -ENOMEM;

	prune_object_name(so);
	so->so_depth = 1;

	error = enter_dir(so);
	if (error) {	
		free(so->so_name);
		so->so_name = NULL;
		so->so_object_name = NULL;
	}
	return error;
}

void sysfs_object_exit(struct sysfs_object * so)
{
	leave_dir(so);
	free(so->so_name);
	memset(so, 0, sizeof(struct sysfs_object));
}



static int filter_dirs(const struct dirent * d)
{
	struct stat s;

	if (!strcmp(d->d_name,".") || !strcmp(d->d_name,".."))
		return 0;

	if (lstat(d->d_name,&s))
		return 0;
	return S_ISDIR(s.st_mode);
}


/**
 *	sysfs_init_children - Construct a list of child objects
 */

int sysfs_list_children(struct sysfs_object_list * sol)
{
	int ret;
	int i;

	memset(sol, 0, sizeof(struct sysfs_object_list));
	ret = scandir(".", &sol->sol_dirent, filter_dirs, alphasort);
	if (ret > 0) {
		sol->sol_list = calloc(ret, sizeof(char *));
		if (!sol->sol_list) {
			free(sol->sol_dirent);
			ret = -ENOMEM;
			goto Done;
		}
		for (i = 0; i < ret; i++)
			sol->sol_list[i] = sol->sol_dirent[i]->d_name;
		sol->sol_num = ret;
		ret = 0;
	}
 Done:
	return ret;
}


/**
 *	sysfs_free_children - Liberate a group of children.
 */

void sysfs_unlist_children(struct sysfs_object_list * sol)
{
	free(sol->sol_list);
	free(sol->sol_dirent);
	memset(sol, 0, sizeof(struct sysfs_object_list));
}



static int filter_attr(const struct dirent * d)
{
	struct stat s;
	if (lstat(d->d_name,&s))
		return 0;
	return S_ISREG(s.st_mode);
}

/**
 *	sysfs_list_attr - Construct a list of object attributes
 */

int sysfs_list_attr(struct sysfs_attr_list * sal)
{
	int ret;
	int i;

	memset(sal, 0, sizeof(struct sysfs_attr_list));
	ret = scandir(".", &sal->sal_dirent, filter_attr, alphasort);
	if (ret > 0) {
		sal->sal_list = calloc(ret, sizeof(char *));
		if (!sal->sal_list) {
			free(sal->sal_dirent);
			ret = -ENOMEM;
			goto Done;
		}
		sal->sal_attr = calloc(ret, sizeof(struct sysfs_attr));
		if (!sal->sal_attr) {
			free(sal->sal_list);
			free(sal->sal_dirent);
			ret = -ENOMEM;
			goto Done;
		}
		for (i = 0; i < ret; i++) {
			sal->sal_list[i] = sal->sal_dirent[i]->d_name;
			sal->sal_attr[i].a_name = sal->sal_dirent[i]->d_name;
			sal->sal_attr[i].a_name_len = strlen(sal->sal_dirent[i]->d_name);
		}
		sal->sal_num = ret;
		ret = 0;
	}
 Done:
	return ret;
}


/**
 *	sysfs_read_attr - Read all attributes for an object.
 */

int sysfs_read_attr(struct sysfs_attr_list * sal)
{
	char buffer[PAGE_SIZE];
	int error = 0;
	int len;
	int i;

	for (i = 0; i < sal->sal_num; i++) {
		len = sysfs_read_file(sal->sal_attr[i].a_name, buffer);
		if (len < 0) {
			error = len;
			goto ReadFail;
		}
		sal->sal_attr[i].a_data = malloc(len);
		if (!sal->sal_attr[i].a_data) {
			error = -ENOMEM;
			goto ReadFail;
		}
		memcpy(sal->sal_attr[i].a_data, buffer, len);
		sal->sal_attr[i].a_len = len;
	}
	return 0;
 ReadFail:
	while (--i > 0) {
		free(sal->sal_attr[i].a_data);
		sal->sal_attr[i].a_len = 0;
	}
	return error;
}

void sysfs_chop_attr(struct sysfs_attr * a)
{
	/*
	 * Chop off trailing newline.
	 */
	char * tmp = strrchr(a->a_data, '\n');
	if (tmp)
		*tmp = '\0';
}

struct sysfs_attr * sysfs_find_attr(struct sysfs_attr_list * sal, char * name)
{
	int i;

	for (i = 0; i < sal->sal_num; i++) {
		if (!strcmp(sal->sal_attr[i].a_name, name))
			return &sal->sal_attr[i];
	}
	return NULL;
}

/**
 *	sysfs_unlist_attr - Liberate a list of attributes
 */

void sysfs_unlist_attr(struct sysfs_attr_list * sal)
{
	int i;

	for (i = 0; i < sal->sal_num; i++) {
		if (sal->sal_attr[i].a_data)
			free(sal->sal_attr[i].a_data);
	}
	free(sal->sal_attr);
	free(sal->sal_list);
	free(sal->sal_dirent);
	memset(sal, 0, sizeof(struct sysfs_attr_list));
}



static int filter_links(const struct dirent * d)
{
	struct stat s;
	if (lstat(d->d_name,&s))
		return 0;
	return S_ISLNK(s.st_mode);
}

/**
 *	sysfs_list_link - Construct a list of object links.
 */
int sysfs_list_link(struct sysfs_link_list * sll)
{
	char buf[PATH_MAX];
	int error = 0;
	int num;
	int i;
	
	memset(sll, 0, sizeof(struct sysfs_link_list));
	num = scandir(".", &sll->sll_dirent, filter_links, alphasort);
	if (num <= 0) {
		error = num;
		goto Done;
	}

	sll->sll_name = calloc(num, sizeof(char *));

	if (!sll->sll_name)
		goto AllocNameFail;

	sll->sll_target = calloc(num, sizeof(char *));
	if (!sll->sll_target)
		goto AllocTargetFail;

	for (i = 0; i < num; i++) {
		size_t len;

		sll->sll_name[i] = sll->sll_dirent[i]->d_name;
		error = readlink(sll->sll_name[i], buf, PATH_MAX);
		if (error < 0)
			goto ReadlinkFail;
		error = 0;

		/**
		 *	Chop off the beginning "../" occurences.
		 */
		len = strspn(buf, "../");
		sll->sll_target[i] = strdup(buf + len);
		if (!sll->sll_target[i])
			goto StrdupFail;
	}
	sll->sll_num = num;
 Done:
	return error;

 StrdupFail:
	do
		free(sll->sll_target[--i]);
	while (i > 0);
 ReadlinkFail:
	free(sll->sll_target);
 AllocTargetFail:
	free(sll->sll_name);
 AllocNameFail:
	free(sll->sll_dirent);
	if (!error)
		error = -ENOMEM;
	memset(sll, 0, sizeof(struct sysfs_link_list));
	goto Done;
}


void sysfs_unlist_link(struct sysfs_link_list * sll)
{
	free(sll->sll_target);
	free(sll->sll_name);
	free(sll->sll_dirent);
	memset(sll, 0, sizeof(struct sysfs_link_list));
}


int sysfs_read_file(char * file, char * buffer)
{
	int fd;
	int ret = 0;
	
	fd = open(file, O_RDONLY);
	if (fd > 0) {
		ret = read(fd, buffer, PAGE_SIZE);
		close(fd);
	} else
		ret = fd;
	return ret;
}

int sysfs_write_file(char * file, char * buffer, int len)
{
	int ret;
	int fd;

	if (len > PAGE_SIZE)
		return -EINVAL;

	fd = open(file, O_WRONLY);
	if (fd > 0) {
		ret = write(fd, buffer, len);
		close(fd);
	} else
		ret = fd;
	return ret;
}
