// Copyright(c) 2018-2020, Intel Corporation
//
// Redistribution  and  use  in source  and  binary  forms,  with  or  without
// modification, are permitted provided that the following conditions are met:
//
// * Redistributions of  source code  must retain the  above copyright notice,
//   this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright notice,
//   this list of conditions and the following disclaimer in the documentation
//   and/or other materials provided with the distribution.
// * Neither the name  of Intel Corporation  nor the names of its contributors
//   may be used to  endorse or promote  products derived  from this  software
//   without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING,  BUT NOT LIMITED TO,  THE
// IMPLIED WARRANTIES OF  MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED.  IN NO EVENT  SHALL THE COPYRIGHT OWNER  OR CONTRIBUTORS BE
// LIABLE  FOR  ANY  DIRECT,  INDIRECT,  INCIDENTAL,  SPECIAL,  EXEMPLARY,  OR
// CONSEQUENTIAL  DAMAGES  (INCLUDING,  BUT  NOT LIMITED  TO,  PROCUREMENT  OF
// SUBSTITUTE GOODS OR SERVICES;  LOSS OF USE,  DATA, OR PROFITS;  OR BUSINESS
// INTERRUPTION)  HOWEVER CAUSED  AND ON ANY THEORY  OF LIABILITY,  WHETHER IN
// CONTRACT,  STRICT LIABILITY,  OR TORT  (INCLUDING NEGLIGENCE  OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,  EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif // HAVE_CONFIG_H

#include <sys/stat.h>
#include <unistd.h>
#include <dirent.h>
#include <stdio.h>

#include "common_int.h"
#include "opae/error.h"

#include "error_int.h"

#define INJECT_ERROR "inject_error"

fpga_result __XFPGA_API__ xfpga_fpgaReadError(fpga_token token, uint32_t error_num, uint64_t *value)
{
	struct _fpga_token *_token = (struct _fpga_token *)token;
	struct stat st;
	uint32_t i = 0;
	fpga_result res = FPGA_OK;

	ASSERT_NOT_NULL(token);
	if (_token->magic != FPGA_TOKEN_MAGIC) {
		OPAE_MSG("Invalid token");
		return FPGA_INVALID_PARAM;
	}

	struct error_list *p = _token->errors;
	while (p) {
		if (i == error_num) {
			// test if file exists
			if (stat(p->error_file, &st) == -1) {
				OPAE_MSG("can't stat %s", p->error_file);
				return FPGA_EXCEPTION;
			}
			res = sysfs_read_u64(p->error_file, value);
			if (res != FPGA_OK) {
				OPAE_MSG("can't read error file '%s'", p->error_file);
				return res;
			}

			return FPGA_OK;
		}
		i++;
		p = p->next;
	}

	OPAE_MSG("error %d not found", error_num);
	return FPGA_NOT_FOUND;
}

fpga_result __XFPGA_API__
xfpga_fpgaClearError(fpga_token token, uint32_t error_num)
{
	struct _fpga_token *_token = (struct _fpga_token *)token;
	struct stat st;
	uint32_t i = 0;
	uint64_t value = 0;
	fpga_result res = FPGA_OK;

	ASSERT_NOT_NULL(token);
	if (_token->magic != FPGA_TOKEN_MAGIC) {
		OPAE_MSG("Invalid token");
		return FPGA_INVALID_PARAM;
	}

	struct error_list *p = _token->errors;
	while (p) {
		if (i == error_num) {
			if (!p->info.can_clear) {
				OPAE_MSG("can't clear error '%s'", p->info.name);
				return FPGA_NOT_SUPPORTED;
			}

			if (strcmp(p->info.name, INJECT_ERROR) == 0) {
				value = 0;
			} else {
				// read current error value
				res = xfpga_fpgaReadError(token, error_num, &value);
				if (res != FPGA_OK)
					return res;
			}

			// write to 'clear' file
			if (stat(p->clear_file, &st) == -1) {
				OPAE_MSG("can't stat %s", p->clear_file);
				return FPGA_EXCEPTION;
			}
			res = sysfs_write_u64(p->clear_file, value);
			if (res != FPGA_OK) {
				OPAE_MSG("can't write clear file '%s'", p->clear_file);
				return res;
			}
			return FPGA_OK;
		}
		i++;
		p = p->next;
	}

	OPAE_MSG("error info %d not found", error_num);
	return FPGA_NOT_FOUND;
}

fpga_result __XFPGA_API__ xfpga_fpgaClearAllErrors(fpga_token token)
{
	struct _fpga_token *_token = (struct _fpga_token *)token;
	uint32_t i = 0;
	fpga_result res = FPGA_OK;

	ASSERT_NOT_NULL(token);
	if (_token->magic != FPGA_TOKEN_MAGIC) {
		OPAE_MSG("Invalid token");
		return FPGA_INVALID_PARAM;
	}

	struct error_list *p = _token->errors;
	while (p) {
		// if error can be cleared
		if (p->info.can_clear) {
			// clear error
			res = xfpga_fpgaClearError(token, i);
			if (res != FPGA_OK)
				return res;
		}
		i++;
		p = p->next;
	}

	return FPGA_OK;
}

fpga_result __XFPGA_API__ xfpga_fpgaGetErrorInfo(fpga_token token,
			     uint32_t error_num,
			     struct fpga_error_info *error_info)
{
	struct _fpga_token *_token = (struct _fpga_token *)token;
	uint32_t i = 0;

	if (!error_info) {
		OPAE_MSG("error_info is NULL");
		return FPGA_INVALID_PARAM;
	}

	ASSERT_NOT_NULL(token);
	if (_token->magic != FPGA_TOKEN_MAGIC) {
		OPAE_MSG("Invalid token");
		return FPGA_INVALID_PARAM;
	}

	struct error_list *p = _token->errors;
	while (p) {
		if (i == error_num) {
			memcpy(error_info, &p->info, sizeof(struct fpga_error_info));
			return FPGA_OK;
		}
		i++;
		p = p->next;
	}

	OPAE_MSG("error info %d not found", error_num);
	return FPGA_NOT_FOUND;
}

/* files and directories to ignore when looking for errors */
#define NUM_ERRORS_EXCLUDE 4
const char *errors_exclude[NUM_ERRORS_EXCLUDE] = {
	"revision",
	"uevent",
	"power",
	"clear"
};

/* files that can be cleared by writing their value to them */
#define NUM_ERRORS_CLEARABLE 6
const char *errors_clearable[] = {
	"pcie0_errors",
	"pcie1_errors",
	"warning_errors",
	"inject_error",
	"fme_errors",
	"errors"
};

/* Walks the given directory and adds error entries to `list`.
 * This function is called during enumeration when adding tokens to
 * the global tokens list. When tokens are cloned, their error
 * lists are only shallowly copied (which works because errors of
 * a token never change).
 * Note that build_error_list() does not check for dupliates; if
 * called again on the same list, it will add all found errors again.
 * Returns the number of error entries added to `list` */
uint32_t
build_error_list(const char *path, struct error_list **list)
{
	struct dirent *de;
	DIR *dir;
	struct stat st;
	char basedir[FILENAME_MAX] = { 0, };
	int len;
	int subpath_len = 0;
	uint32_t n = 0;
	unsigned int i;
	struct error_list **el = list;

	len = strnlen(path, FILENAME_MAX - 1);

	// add 3 to the len
	// 1 for the '/' char
	// 1 for the minimum length of a file appended
	// 1 for null string to terminate
	// if we go over now, then leave without doing anything else
	if (len+3 > FILENAME_MAX) {
		OPAE_MSG("path too long");
		return 0;
	}

	len = snprintf(basedir, sizeof(basedir),
		       "%s/", path);

	// now we've added one to length

	dir = opendir(path);
	if (!dir) {
		OPAE_MSG("unable to open %s", path);
		return 0;
	}

	while ((de = readdir(dir))) {
		size_t blen;
		size_t dlen;

		// skip hidden ('.*') files (includes "." and "..")
		if (de->d_name[0] == '.')
			continue;

		// skip names on blacklist
		for (i = 0; i < NUM_ERRORS_EXCLUDE; i++) {
			if (strcmp(de->d_name, errors_exclude[i]) == 0) {
				break;
			}
		}
		if (i < NUM_ERRORS_EXCLUDE)
			continue;

		subpath_len = strnlen(de->d_name, sizeof(de->d_name) - 1);

		// check if the result abs path is longer than  our max
		if (len + subpath_len > FILENAME_MAX) {
			OPAE_MSG("Error path length is too long");
			continue;
		}

		// build absolute path
		// dmax (arg2) is restricted max length of resulting dest,
		// including null - it must also be at least smax+1 (arg4)
		strncpy(basedir + len, de->d_name, subpath_len + 1);

		// try accessing file/dir
		if (lstat(basedir, &st) == -1) {
			OPAE_MSG("can't stat %s", basedir);
			continue;
		}

		// skip symlinks
		if (S_ISLNK(st.st_mode))
			continue;

		// recursively dive into subdirectories
		if (S_ISDIR(st.st_mode)) {
			n += build_error_list(basedir, el);
			continue;
		}

		// not blacklisted, not hidden, accessible, no symlink, no dir -> count and append it!
		n++;
		if (!el)	// no list
			continue;

		// append error info to list
		struct error_list *new_entry = malloc(sizeof(struct error_list));
		if (!new_entry) {
			OPAE_MSG("can't allocate memory");
			n--;
			break;
		}

		dlen = strnlen(de->d_name, sizeof(new_entry->info.name) - 1);
		memcpy(new_entry->info.name, de->d_name, dlen);
		new_entry->info.name[dlen] = '\0';

		blen = strnlen(basedir, sizeof(new_entry->error_file) - 1);
		memcpy(new_entry->error_file, basedir, blen);
		new_entry->error_file[blen] = '\0';

		new_entry->next = NULL;
		// Errors can be cleared:
		//   * if the name is "errors" and there is a file called "clear" (generic case), OR
		//   * if the name is in the "errors_clearable" table
		new_entry->info.can_clear = false;
		if (strcmp(de->d_name, "errors") == 0 &&
			!stat(FPGA_SYSFS_CLASS_PATH_INTEL, &st)) {
			strncpy(basedir + len, "clear", 6);
			// try accessing clear file
			if (lstat(basedir, &st) != -1) {
				new_entry->info.can_clear = true;
				memcpy(new_entry->clear_file, basedir, blen);
				new_entry->clear_file[blen] = '\0';
			}
		} else {
			for (i = 0; i < NUM_ERRORS_CLEARABLE; i++) {
				if (strcmp(de->d_name, errors_clearable[i]) == 0) {
					memcpy(basedir + len, de->d_name, dlen);
					*(basedir + len + dlen) = '\0';
					// try accessing clear file
					if (lstat(basedir, &st) != -1) {
						new_entry->info.can_clear = true;
						memcpy(new_entry->clear_file, basedir, blen);
						new_entry->clear_file[blen] = '\0';
					}
				}
			}
		}

		if (new_entry && !new_entry->info.can_clear) {
			memset(new_entry->clear_file, 0, sizeof(new_entry->clear_file));
		}

		// find end of list
		while (*el)
			el = &(*el)->next;

		// append
		if (new_entry)
			*el = new_entry;
		el = &new_entry->next;
	}
	closedir(dir);

	return n;
}

uint32_t count_error_files(const char *path)
{
	return build_error_list(path, NULL);
}
