/*
 * Copyright (c) 2008, Intel Corporation.
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms and conditions of the GNU Lesser General Public License,
 * version 2.1, as published by the Free Software Foundation.
 *
 * This program is distributed in the hope 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 program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA.
 *
 */

#include "utils.h"

/*
 * Read a line from the specified file in the specified directory
 * into the buffer.  The file is opened and closed.
 * Any trailing white space is trimmed off.
 * This is useful for accessing /sys files.
 * Returns 0 or an error number.
 */
int
sa_sys_read_line(const char *dir, const char *file, char *buf, size_t len)
{
	FILE *fp;
	char file_name[256];
	char *cp;
	int rc = 0;

	snprintf(file_name, sizeof(file_name), "%s/%s", dir, file);
	fp = fopen(file_name, "r");
	if (fp == NULL)
		rc = -1;
	else {
		cp = fgets(buf, len, fp);
		if (cp == NULL) {
			fprintf(stderr,
				"%s: read error or empty file %s,"
				" errno=0x%x\n", __func__,
				file_name, errno);
			rc = -1;
		} else {

			/*
			 * Trim off trailing newline or other white space.
			 */
			cp = buf + strlen(buf);
			while (--cp >= buf && isspace(*cp))
				*cp = '\0';
		}
		fclose(fp);
	}
	return rc;
}

/*
 * Write a string to the specified file in the specified directory.
 * The file is opened and closed.
 * The string has a new-line appended to it.
 * This is useful for accessing /sys files.
 * Returns 0 or an error number.
 */
int
sa_sys_write_line(const char *dir, const char *file, const char *string)
{
	FILE *fp;
	char file_name[256];
	int rc = 0;

	snprintf(file_name, sizeof(file_name), "%s/%s", dir, file);
	fp = fopen(file_name, "w");
	if (fp == NULL) {
		fprintf(stderr, "%s: fopen of %s failed, errno=0x%x\n",
			__func__, file_name, errno);
		rc = -1;
	} else {
		rc = fprintf(fp, "%s\n", string);
		if (rc < 0)
			fprintf(stderr,
				"%s: write to %s of %s failed, errno=0x%x\n",
				__func__, file_name, string, errno);
		fclose(fp);
	}
	return rc;
}

int
sa_sys_read_u32(const char *dir, const char *file, u_int32_t *vp)
{
	char buf[256];
	int rc;
	u_int32_t val;
	char *endptr;

	rc = sa_sys_read_line(dir, file, buf, sizeof(buf));
	if (rc == 0) {
		val = strtoul(buf, &endptr, 0);
		if (*endptr != '\0') {
			fprintf(stderr,
				"%s: parse error. file %s/%s line '%s'\n",
				__func__, dir, file, buf);
			rc = -1;
		} else
			*vp = val;
	}
	return rc;
}

int
sa_sys_read_u64(const char *dir, const char *file, u_int64_t *vp)
{
	char buf[256];
	int rc;
	u_int64_t val;
	char *endptr;

	rc = sa_sys_read_line(dir, file, buf, sizeof(buf));
	if (rc == 0) {
		val = strtoull(buf, &endptr, 0);
		if (*endptr != '\0') {
			fprintf(stderr,
				"%s: parse error. file %s/%s line '%s'\n",
				__func__, dir, file, buf);
			rc = -1;
		} else
			*vp = val;
	}
	return rc;
}

/*
 * Make a printable NUL-terminated copy of the string.
 * The source buffer might not be NUL-terminated.
 */
char *
sa_strncpy_safe(char *dest, size_t len, const char *src, size_t src_len)
{
	char *dp = dest;
	const char *sp = src;

	while (len-- > 1 && src_len-- > 0 && *sp != '\0') {
		*dp++ = isprint(*sp) ? *sp : (isspace(*sp) ? ' ' : '.');
		sp++;
	}
	*dp = '\0';

	/*
	 * Take off trailing blanks.
	 */
	while (--dp >= dest && isspace(*dp))
		*dp = '\0';

	return dest;
}

/** sa_enum_encode(tp, name, valp)
 *
 * @param tp pointer to table of names and values, struct sa_nameval.
 * @param name string to be encoded into a value
 * @returns zero on success, non-zero if no matching string found.
 */
int
sa_enum_encode(const struct sa_nameval *tp, const char *name, u_int32_t *valp)
{
	for (; tp->nv_name != NULL; tp++) {
		if (strcasecmp(tp->nv_name, name) == 0) {
			*valp = tp->nv_val;
			return 0;
		}
	}
	return -1;
}

/** sa_enum_decode(buf, len, tp, val)
 *
 * @param buf buffer for result (may be used or not).
 * @param len size of buffer (at least 32 bytes recommended).
 * @param tp pointer to table of names and values, struct sa_nameval.
 * @param val value to be decoded into a name.
 * @returns pointer to name string.  Unknown values are put into buffer in hex.
 */
const char *
sa_enum_decode(char *buf, size_t len,
		const struct sa_nameval *tp, u_int32_t val)
{
	for (; tp->nv_name != NULL; tp++) {
		if (tp->nv_val == val)
			return tp->nv_name;
	}
	snprintf(buf, len, "Unknown (code 0x%X)", val);
	return buf;
}

/*
 * Read through a directory and call a function for each entry.
 */
int
sa_dir_read(char *dir_name, int (*func)(struct dirent *dp, void *), void *arg)
{
	DIR *dir;
	struct dirent *dp;
	int error = 0;

	dir = opendir(dir_name);
	if (dir == NULL)
		error = errno;
	else {
		while ((dp = readdir(dir)) != NULL && error == 0) {
			if (dp->d_name[0] == '.' && (dp->d_name[1] == '\0' ||
			   (dp->d_name[1] == '.' && dp->d_name[2] == '\0')))
				continue;
			error = (*func)(dp, arg);
		}
		closedir(dir);
	}
	return error;
}

/*
 * Size of on-stack line buffers.
 * These shouldn't be to large for a kernel stack frame.
 */
#define SA_LOG_BUF_LEN  200	/* on-stack line buffer size */

static const u_int32_t sa_table_growth = 16;        /* entries to grow by */

/** sa_table_grow(tp, index) - add space to a table for index.
 *
 * @param tp pointer to sa_table structure.
 * @param index - new index past the end of the current table.
 * @returns new index, or -1 if table couldn't be grown.
 *
 * Note: if the table has never been used, and is still all zero, this works.
 *
 * Note: perhaps not safe for multithreading.  Caller can lock the table
 * externally, but reallocation can take a while, during which time the
 * caller may not wish to hold the lock.
 */
int
sa_table_grow(struct sa_table *tp, u_int32_t index)
{
	u_int32_t new_size;
	void **ap;

	if (index >= tp->st_size) {
		new_size = index + sa_table_growth;
		ap = realloc(tp->st_table, new_size * sizeof(*ap));
		if (ap == NULL)
			return -1;
		memset(ap + tp->st_size, 0,
			(new_size - tp->st_size) * sizeof(*ap));
		tp->st_table = ap;
		tp->st_size = new_size;
	}
	tp->st_limit = index + 1;
	return index;
}

/** sa_table_destroy(tp) - free memory used by table.
 *
 * @param tp pointer to sa_table structure.
 */
void
sa_table_destroy(struct sa_table *tp)
{
	if (tp->st_table) {
		free(tp->st_table);
		tp->st_table = NULL;
	}
	tp->st_limit = 0;
	tp->st_size = 0;
}

/** sa_table_destroy_all(tp) - free memory used by table, including entries.
 *
 * @param tp pointer to sa_table structure.
 */
void
sa_table_destroy_all(struct sa_table *tp)
{
	int  i;

	if (tp->st_table) {
		for (i = 0; i < tp->st_limit; i++) {
			if (tp->st_table[i]) {
				free(tp->st_table[i]);
				tp->st_table[i] = NULL;
			}
		}
	}
	sa_table_destroy(tp);
}

/** sa_table_iterate(tp, handler, arg)
 *
 * @param tp pointer to sa_table structure.
 * @param handler function to be called for each non-NULL entry.
 * @param arg argument for function.
 */
void
sa_table_iterate(struct sa_table *tp,
		 void (*handler)(void *ep, void *arg),
		 void *arg)
{
	int i;
	void *ep;

	for (i = 0; i < tp->st_limit; i++) {
		ep = tp->st_table[i];
		if (ep != NULL)
			(*handler)(ep, arg);
	}
}

/** sa_table_search(tp, match, arg)
 *
 * @param tp pointer to sa_table structure.
 * @param match function to compare entries with arg and
 *	 return non-NULL if match.
 * @param arg argument for match function.
 *
 * Note that the value found could actually be something not in the table
 * if the match function is doing something clever, like returning a
 * sub-structure of the table entry.
 */
void *
sa_table_search(struct sa_table *tp, void *(*match)(void *ep, void *arg),
	void *arg)
{
	int i;
	void *found = NULL;
	void *ep;

	for (i = 0; i < tp->st_limit; i++) {
		ep = tp->st_table[i];
		if (ep != NULL) {
			found = (*match)(ep, arg);
			if (found != NULL)
				break;
		}
	}
	return found;
}
