/*
 * Copyright (C) 2024 iopsys Software Solutions AB
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License version 2.1
 * as published by the Free Software Foundation
 *
 *	  Author: Amin Ben Romdhane <amin.benromdhane@iopsys.eu>
 *
 */

#include "utils.h"
#include "fwbank.h"

#ifdef SYSMNGR_FWBANK_UBUS_SUPPORT
const char fw_image_dependency[] = "file:/etc/sysmngr/fwbank";
#define FWBANK_FILE_PATH "/etc/sysmngr/fwbank"
#else
const char fw_image_dependency[] = "file:/usr/libexec/rpcd/fwbank";
#define FWBANK_FILE_PATH "/usr/libexec/rpcd/fwbank"
#endif

#define FWBANK_DUMP_CMD FWBANK_FILE_PATH " call dump"
#define FWBANK_SET_BOOTBANK_CMD FWBANK_FILE_PATH " call set_bootbank"
#define FWBANK_UPGRADE_CMD FWBANK_FILE_PATH " call upgrade"

#define FWBANK_DUMP_MAX_RETRIES 10
#define FWBANK_DUMP_RETRY_DELAY 3

struct fwbank_event_data {
	struct ubus_context *ctx;
	struct uloop_timeout tm;
	struct ubus_event_handler ev;
	uint32_t band_id;
	int timeout;
};

struct fwbank_dump_data {
	struct ubus_context *ctx;
	struct uloop_timeout tm;
	struct blob_buf output;
};

static struct fwbank_dump_data g_fwbank_dump = {0};
static int g_retry_count = 0;

typedef void (*sysmngr_task_callback_t)(struct ubus_context *ctx, struct ubus_request_data *req, int *pipe_fds, uint32_t bank_id);

typedef struct sysmngr_task_data {
	struct ubus_context *ctx;
	struct ubus_request_data *req;
	struct ubus_request_data new_req;
	struct uloop_process process; // Used for forked task
	struct uloop_timeout timeoutcb; // Timeout for the task
	sysmngr_task_callback_t finishcb; // Finish callback for parent process
	const char *command; // Command to execute
	uint32_t bank_id;
	int pipe_fds[2];
} sysmngr_task_data_t;

struct blobmsg_policy sysmngr_dump_policy[] = {
	{ "bank", BLOBMSG_TYPE_ARRAY }
};

struct blobmsg_policy sysmngr_bank_policy[] = {
	{ "name", BLOBMSG_TYPE_STRING },
	{ "id", BLOBMSG_TYPE_INT32 },
	{ "active", BLOBMSG_TYPE_BOOL },
	{ "boot", BLOBMSG_TYPE_BOOL },
	{ "upgrade", BLOBMSG_TYPE_BOOL },
	{ "fwver", BLOBMSG_TYPE_STRING },
	{ "swver", BLOBMSG_TYPE_STRING },
	{ "status", BLOBMSG_TYPE_STRING }
};

static void _sysmngr_task_finish_callback(struct uloop_process *p, int ret)
{
	struct sysmngr_task_data *task = NULL;

	task = container_of(p, struct sysmngr_task_data, process);

	if (task == NULL) {
		BBFDM_ERR("Failed to decode forked task");
		return;
	}

	if (task->timeoutcb.pending) {
		// Cancel timeout if the task finishes on its own
		uloop_timeout_cancel(&task->timeoutcb);
	}

	if (task->finishcb) {
		task->finishcb(task->ctx, &task->new_req, task->pipe_fds, task->bank_id);
	}

	if (task->ctx && task->req) {
		ubus_complete_deferred_request(task->ctx, &task->new_req, 0);
	}

	FREE(task);
}

static void task_callback(const char *command, int *pipe_fds)
{
	// Redirect stdout to the pipe's write end
	dup2(pipe_fds[1], STDOUT_FILENO);
	close(pipe_fds[0]); // Close unused read end
	close(pipe_fds[1]); // Close write end after dup2

	if (!command)
		exit(EXIT_FAILURE);

	char *line = NULL;
	ssize_t read;
	size_t len;

	FILE *pipe = popen(command, "r"); // flawfinder: ignore
	if (pipe == NULL)
		exit(EXIT_FAILURE);

	while ((read = getline(&line, &len, pipe)) != -1) {
		// Output the command's result to stdout
		line[read - 1] = '\0'; // Remove trailing newline
		printf("%s", line);
	}

	free(line);

	if (pclose(pipe) == -1)
		exit(EXIT_FAILURE);

	exit(EXIT_SUCCESS);
}

static void timeout_callback(struct uloop_timeout *t)
{
	sysmngr_task_data_t *task = container_of(t, sysmngr_task_data_t, timeoutcb);

	if (task && task->process.pid > 0) {
		BBFDM_ERR("Task timed out. Killing process with PID %d\n", task->process.pid);
		kill(task->process.pid, SIGKILL);
	}
}

static int sysmngr_task_fork(sysmngr_task_callback_t finishcb, const char *command, int timeout, struct ubus_request_data *req, uint32_t bank_id)
{
	sysmngr_task_data_t *task;
	pid_t child;

	task = (sysmngr_task_data_t *)calloc(sizeof(sysmngr_task_data_t), 1);
	if (task == NULL) {
		return -1;
	}

	if (pipe(task->pipe_fds) == -1) {
		BBFDM_ERR("pipe failed");
		FREE(task);
		return -1;
	}

	task->command = command;
	task->ctx = g_fwbank_dump.ctx;
	task->req = req;
	task->bank_id = bank_id;

	child = fork();
	if (child == -1) {
		BBFDM_ERR("Failed to fork a child for task");
		FREE(task);
		return -1;
	} else if (child == 0) {
		/* free fd's and memory inherited from parent */
		uloop_done();
		fclose(stdin);
		fclose(stderr);

		task_callback(task->command, task->pipe_fds);

		/* write result and exit */
		exit(EXIT_SUCCESS);
	}

	if (finishcb) {
		task->finishcb = finishcb;
		task->process.pid = child;
		task->process.cb = _sysmngr_task_finish_callback;
		uloop_process_add(&task->process);
	}

	if (task->ctx && task->req) {
		ubus_defer_request(task->ctx, task->req, &task->new_req);
	}

	if (timeout > 0) {
		task->timeoutcb.cb = timeout_callback;
		uloop_timeout_set(&task->timeoutcb, 1000 * timeout);
	}

	return 0;
}

static void fwbank_dump_timer(struct uloop_timeout *timeout)
{
	struct fwbank_dump_data *data = NULL;

	BBFDM_DEBUG("fwbank_dump_timer triggered");

	data = container_of(timeout, struct fwbank_dump_data, tm);
	if (data == NULL)
		return;

	sysmngr_init_fwbank_dump(data->ctx);
}

static int free_global_fwbank_dump(struct blob_buf *fwbank_dump_bb)
{
	if (fwbank_dump_bb->head && blob_len(fwbank_dump_bb->head)) {
		BBFDM_DEBUG("Freeing fwbank dump blob buffer");
		blob_buf_free(fwbank_dump_bb);
	} else {
		BBFDM_DEBUG("fwbank dump blob buffer is already empty");
	}

	return 0;
}

static int validate_global_fwbank_dump(struct blob_buf *fwbank_dump_bb)
{
	if (!fwbank_dump_bb->head || !blob_len(fwbank_dump_bb->head)) {
		BBFDM_ERR("fwbank dump output is empty");
		return -1;
	}

	BBFDM_DEBUG("Validating global fwbank dump");
	struct blob_attr *tb[1] = {0};

	if (blobmsg_parse(sysmngr_dump_policy, 1, tb, blobmsg_data(fwbank_dump_bb->head), blobmsg_len(fwbank_dump_bb->head))) {
		BBFDM_ERR("Failed to parse fwbank dump blob");
		return -1;
	}

	if (!tb[0]) { // bank array is not found
		BBFDM_ERR("Bank array not found in fwbank dump");
		return -1;
	}

	struct blob_attr *entry = NULL;
	int rem = 0, valid = -1;

	blobmsg_for_each_attr(entry, tb[0], rem) { // parse bank array
		struct blob_attr *t[8] = {0};

		if (blobmsg_parse(sysmngr_bank_policy, ARRAY_SIZE(sysmngr_bank_policy), t, blobmsg_data(entry), blobmsg_len(entry))) {
			BBFDM_ERR("Failed to parse bank entry");
			continue;
		}

		// mark the valid flag, if one bank has valid data
		if (t[0] && t[1] && t[2] && t[3] && t[4] && t[5] && t[6] && t[7]) {
			valid = 0;
			break;
		}
	}

	BBFDM_DEBUG("Global fwbank dump validation passed");
	return valid;
}

static int apply_fwbank_buffer(const char *buffer)
{
	struct blob_buf temp_buf = {0};

	memset(&temp_buf, 0, sizeof(struct blob_buf));
	blob_buf_init(&temp_buf, 0);

	if (!blobmsg_add_json_from_string(&temp_buf, buffer)) {
		BBFDM_ERR("Invalid JSON format in buffer");
		blob_buf_free(&temp_buf);
		return -1;
	}

	int res = validate_global_fwbank_dump(&temp_buf);
	if (res) {
		BBFDM_ERR("Failed to validate 'fwbank' output");
		blob_buf_free(&temp_buf);
		return -1;
	}

	free_global_fwbank_dump(&g_fwbank_dump.output);

	memset(&g_fwbank_dump.output, 0, sizeof(struct blob_buf));
	blob_buf_init(&g_fwbank_dump.output, 0);

	blobmsg_add_blob(&g_fwbank_dump.output, blob_data(temp_buf.head));

	blob_buf_free(&temp_buf);

	return 0;
}

static void fwbank_dump_finish_callback(struct ubus_context *ctx, struct ubus_request_data *req, int *pipe_fds, uint32_t bank_id)
{
	BBFDM_DEBUG("Task finished Line=%d && func=%s", __LINE__, __func__);

	close(pipe_fds[1]); // Close unused write end

	char buffer[1024] = {0};
	ssize_t bytes_read;

	BBFDM_DEBUG("Reading script output...");

	// Read the output from the script
	while ((bytes_read = read(pipe_fds[0], buffer, sizeof(buffer) - 1)) > 0) {
		buffer[bytes_read] = '\0'; // Null-terminate the buffer
		BBFDM_DEBUG("Script output: %s", buffer);
	}

	close(pipe_fds[0]); // Close read end

	if (bytes_read < 0 || strlen(buffer) == 0) {
		BBFDM_ERR("Failed to read from pipe");
		goto retry;
	}

	if (apply_fwbank_buffer(buffer))
		goto retry;

	return;

retry:
	if (g_retry_count < FWBANK_DUMP_MAX_RETRIES) {
		g_retry_count++;
		uloop_timeout_set(&g_fwbank_dump.tm, FWBANK_DUMP_RETRY_DELAY * 1000);

		BBFDM_ERR("Attempt %d/%d: fwbank dump blob buf is empty. Retrying in %d second(s)...",
					g_retry_count, FWBANK_DUMP_MAX_RETRIES, FWBANK_DUMP_RETRY_DELAY);
	} else {
		BBFDM_ERR("Max retries (%d) reached: The fwbank dump buffer is empty. Unable to register 'fwbank' ubus object",
				FWBANK_DUMP_MAX_RETRIES);
	}
}

static int init_global_fwbank_dump(void)
{
	BBFDM_DEBUG("Initializing global fwbank dump");

	int res = sysmngr_task_fork(fwbank_dump_finish_callback, FWBANK_DUMP_CMD, 120, NULL, 0);
	if (res) {
		BBFDM_ERR("Failed to start task for fwbank dump command");
		return -1;
	}

	BBFDM_DEBUG("fwbank dump blob initialized successfully");
	return 0;
}

int fill_fwbank_dump(void)
{
	char *line = NULL;
	char buffer[1024] = {0}, cmd[120] = {0};
	ssize_t nread;
	size_t len = 0;
	int offset = 0;

	if (!file_exists(FWBANK_FILE_PATH)) {
		BBFDM_ERR("The fwbank file (%s) is missing", FWBANK_FILE_PATH);
		return -1;
	}

	snprintf(cmd, sizeof(cmd), "timeout 120 %s", FWBANK_DUMP_CMD);
	FILE *pipe = popen(cmd, "r"); // flawfinder: ignore
	if (pipe == NULL) {
		BBFDM_ERR("Failed to read firmware details");
		return -1;
	}

	while ((nread = getline(&line, &len, pipe)) != -1) {
		if (nread > 0 && line[nread - 1] == '\n')
			line[nread - 1] = '\0'; // Remove trailing newline

		int left = sizeof(buffer) - offset;
		int write = snprintf(buffer + offset, left, "%s", line);

		if (write < 0 || write >= left) {
			BBFDM_ERR("fwbank output truncated at %d bytes", offset);
			break;
		}

		offset += write;
	}

	free(line);
	pclose(pipe);

	if (strlen(buffer) == 0 || strcmp(buffer, "Terminated") == 0) {
		BBFDM_ERR("Failed to read from pipe or timedout");
		return -1;
	}

	return apply_fwbank_buffer(buffer);
}

static void fwbank_listen_timeout(struct uloop_timeout *timeout)
{
	struct fwbank_event_data *data = NULL;

	BBFDM_DEBUG("fwbank listen timeout triggered");

	data = container_of(timeout, struct fwbank_event_data, tm);
	if (data == NULL)
		return;

	ubus_unregister_event_handler(data->ctx, &data->ev);
}

static void fwbank_receive_sysupgrade(struct ubus_context *ctx, struct ubus_event_handler *ev,
				const char *type, struct blob_attr *msg)
{
	struct fwbank_event_data *data = NULL;
	struct blob_attr *cur = NULL;
	char bank_id_str[16] = {0};
	int rem;

	if (!msg || !ev) {
		BBFDM_ERR("Invalid event data in sysupgrade handler");
		return;
	}

	data = container_of(ev, struct fwbank_event_data, ev);
	if (data == NULL)
		return;

	if (data->band_id < 0) { // bank_id should be a valid id
		BBFDM_ERR("Invalid bank_id: %d", data->band_id);
		return;
	}

	snprintf(bank_id_str, sizeof(bank_id_str), "%u", data->band_id);

	blobmsg_for_each_attr(cur, msg, rem) {
		if (DM_STRCMP("bank_id", blobmsg_name(cur)) == 0) {
			char *attr_val = (char *)blobmsg_data(cur);
			if (DM_STRCMP(attr_val, bank_id_str) != 0) {
				BBFDM_ERR("Mismatched bank_id (%s != %s)", attr_val, bank_id_str);
				return;
			}
		}

		if (DM_STRCMP("status", blobmsg_name(cur)) == 0) {
			char *attr_val = (char *)blobmsg_data(cur);

			if (DM_STRCMP(attr_val, "Downloading") == 0) {
				BBFDM_DEBUG("Sysupgrade status: Downloading");
				return;
			}

			if (DM_STRCMP(attr_val, "Available") == 0) {
				BBFDM_DEBUG("Sysupgrade status: Available. Refreshing fwbank dump.");
				init_global_fwbank_dump();
				break;
			}
		}
	}

	if (data->tm.pending) {
		// Cancel timeout if the sysupgrade status available is found
		uloop_timeout_cancel(&data->tm);
	}

	ubus_unregister_event_handler(data->ctx, &data->ev);
	return;
}

static struct fwbank_event_data g_fwbank_event_data = {
	.tm.cb = fwbank_listen_timeout,
	.ev.cb = fwbank_receive_sysupgrade,
	.timeout = 120
};

static void fwbank_wait_for_sysupgrade_event(struct ubus_context *ctx, uint32_t band_id)
{
	BBFDM_DEBUG("Waiting for sysupgrade event for bank_id: %u", band_id);

	g_fwbank_event_data.ctx = ctx;
	g_fwbank_event_data.band_id = band_id;

	ubus_register_event_handler(ctx, &g_fwbank_event_data.ev, "sysupgrade");
	uloop_timeout_set(&g_fwbank_event_data.tm, g_fwbank_event_data.timeout * 1000);
}

static bool is_set_bootbank_success(struct blob_buf *output_bb)
{
	struct blob_attr *tb = NULL;
	struct blobmsg_policy policy = {
		.name = "success",
		.type = BLOBMSG_TYPE_BOOL,
	};

	// Parse the blob buffer for the "success" field
	if (blobmsg_parse(&policy, 1, &tb, blobmsg_data(output_bb->head), blobmsg_len(output_bb->head)) != 0) {
		BBFDM_ERR("Failed to parse blobmsg data");
		return false;
	}

	// Check if the "success" field exists and is of the correct type
	if (tb && blobmsg_type(tb) == BLOBMSG_TYPE_BOOL)
		return blobmsg_get_bool(tb);

	return false;
}

static bool is_upgrade_success(struct blob_buf *output_bb)
{
	struct blob_attr *tb = NULL;
	struct blobmsg_policy policy = {
		.name = "result",
		.type = BLOBMSG_TYPE_STRING,
	};

	// Parse the blob buffer for the "result" field
	if (blobmsg_parse(&policy, 1, &tb, blobmsg_data(output_bb->head), blobmsg_len(output_bb->head)) != 0) {
		BBFDM_INFO("Failed to parse upgrade result, assuming success");
		return true;
	}

	// Check if the "result" field exists and is of the correct type
	if (tb && blobmsg_type(tb) == BLOBMSG_TYPE_STRING)
		return (strcmp(blobmsg_get_string(tb), "failure") == 0) ? false : true;

	return true;
}

struct blob_buf *sysmngr_fwbank_dump(void)
{
	return &g_fwbank_dump.output;
}

static void fwbank_upgrade_finish_callback(struct ubus_context *ctx, struct ubus_request_data *req, int *pipe_fds, uint32_t bank_id)
{
	BBFDM_DEBUG("Task finished Line=%d && func=%s", __LINE__, __func__);

	close(pipe_fds[1]); // Close unused write end

	char buffer[1024] = {0};
	ssize_t bytes_read;

	BBFDM_DEBUG("Reading script output...");

	// Read the output from the script
	while ((bytes_read = read(pipe_fds[0], buffer, sizeof(buffer) - 1)) > 0) {
		buffer[bytes_read] = '\0'; // Null-terminate the buffer
		BBFDM_DEBUG("Script output: %s", buffer);
	}

	close(pipe_fds[0]); // Close read end

	if (bytes_read < 0) {
		BBFDM_ERR("Failed to read from pipe");
		return;
	}

	struct blob_buf upgrade_bb = {0};

	memset(&upgrade_bb, 0, sizeof(struct blob_buf));
	blob_buf_init(&upgrade_bb, 0);

	if (!blobmsg_add_json_from_string(&upgrade_bb, buffer)) {
		BBFDM_ERR("Failed to create blob buf");
		blob_buf_free(&upgrade_bb);
		return;
	}

	bool is_success = is_upgrade_success(&upgrade_bb);

	if (ctx && req) {
		BBFDM_DEBUG("Send ubus output");
		ubus_send_reply(ctx, req, upgrade_bb.head);
	}

	blob_buf_free(&upgrade_bb);

	if (ctx && is_success) {
		// Update fwbank dump output after getting sysupgrade event with status available
		// { "sysupgrade": {"status":"Available","bank_id":"x"} }
		fwbank_wait_for_sysupgrade_event(ctx, bank_id);
	}

	return;
}

static int sysmngr_fwbank_upgrade(struct blob_buf *bbp, int bank_id, struct ubus_request_data *req)
{
	char *str = NULL;
	char cmd[1024] = {0};
	int res = 0;

	str = blobmsg_format_json(bbp->head, true);
	if (str == NULL) {
		return -1;
	}

	snprintf(cmd, sizeof(cmd), "echo '%s' | %s 2>/dev/null", str, FWBANK_UPGRADE_CMD);
	res = sysmngr_task_fork(fwbank_upgrade_finish_callback, cmd, 60, req, bank_id);
	if (res) {
		BBFDM_ERR("Failed to start task for fwbank upgrade command");
		res = -1;
	}

	FREE(str);
	return res;
}

enum {
	DUMP_REFRESH,
	__DUMP_MAX
};

static const struct blobmsg_policy dump_policy[] = {
	[DUMP_REFRESH] = { .name = "refresh", .type = BLOBMSG_TYPE_BOOL }
};

static int dump_handler(struct ubus_context *ctx, struct ubus_object *obj,
		    struct ubus_request_data *req, const char *method,
		    struct blob_attr *msg)
{
	struct blob_attr *tb[__DUMP_MAX];
	bool refresh = false;

	if (blob_len(msg)) {
		if (blobmsg_parse(dump_policy, __DUMP_MAX, tb, blob_data(msg), blob_len(msg))) {
			BBFDM_ERR("Failed to parse the 'dump' message");
			return UBUS_STATUS_UNKNOWN_ERROR;
		}
		if (tb[DUMP_REFRESH]) {
			refresh = blobmsg_get_bool(tb[DUMP_REFRESH]);
		}
	}
	if (refresh == true) {
		init_global_fwbank_dump();
	}

	ubus_send_reply(ctx, req, g_fwbank_dump.output.head);
	return 0;
}

bool sysmngr_fwbank_set_bootbank(uint32_t bank_id)
{
	char cmd[128] = {0};
	char output[256] = {0};
	struct blob_buf bb = {0};
	bool res = false;

	snprintf(cmd, sizeof(cmd), "echo '{\"bank\":%u}' | %s 2>/dev/null", bank_id, FWBANK_SET_BOOTBANK_CMD);
	res = run_cmd(cmd, output, sizeof(output));
	if (res || (DM_STRLEN(output) == 0)) {
		BBFDM_ERR("Failed to run [%s], ret[%d], output[%s]", cmd, res, output);
		return false;
	}

	memset(&bb, 0, sizeof(struct blob_buf));
	blob_buf_init(&bb, 0);

	if (!blobmsg_add_json_from_string(&bb, output)) {
		BBFDM_ERR("Failed to create blob, from json str[%s]", output);
		blob_buf_free(&bb);
		return false;
	}

	res = is_set_bootbank_success(&bb);
	if (res == true) {
		init_global_fwbank_dump();
	}

	blob_buf_free(&bb);
	return res;
}

enum {
	SET_BOOT_BANK,
	__SET_BOOT_MAX
};

static const struct blobmsg_policy set_bootbank_policy[] = {
	[SET_BOOT_BANK] = { .name = "bank", .type = BLOBMSG_TYPE_INT32 },
};

static int set_bootbank_handler(struct ubus_context *ctx, struct ubus_object *obj,
		    struct ubus_request_data *req, const char *method,
		    struct blob_attr *msg)
{
	struct blob_attr *tb[__SET_BOOT_MAX];
	uint32_t bank_id = 0;
	int res = 0;
	struct blob_buf bb = {0};

	if (blobmsg_parse(set_bootbank_policy, __SET_BOOT_MAX, tb, blob_data(msg), blob_len(msg))) {
		BBFDM_ERR("Failed to parse the 'set_bootbank' message");
		return UBUS_STATUS_UNKNOWN_ERROR;
	}

	memset(&bb, 0, sizeof(struct blob_buf));
	blob_buf_init(&bb, 0);

	if (tb[SET_BOOT_BANK]) {
		bank_id = blobmsg_get_u32(tb[SET_BOOT_BANK]);
	}

	if (sysmngr_fwbank_set_bootbank(bank_id)) {
		blobmsg_add_u8(&bb, "success", true);
	} else {
		blobmsg_add_u8(&bb, "success", false);
	}
	ubus_send_reply(ctx, req, bb.head);
	blob_buf_free(&bb);

	return res;
}

enum {
	UPGRADE_PATH,
	UPGRADE_AUTO_ACTIVATE,
	UPGRADE_BANK,
	UPGRADE_KEEP_SETTINGS,
	UPGRADE_KEEP_OPCONF,
	UPGRADE_CONFIG_MODE,
	__UPGRADE_MAX
};

static const struct blobmsg_policy upgrade_policy[] = {
	[UPGRADE_PATH] = { .name = "path", .type = BLOBMSG_TYPE_STRING },
	[UPGRADE_AUTO_ACTIVATE] = { .name = "auto_activate", .type = BLOBMSG_TYPE_BOOL },
	[UPGRADE_BANK] = { .name = "bank", .type = BLOBMSG_TYPE_INT32 },
	[UPGRADE_KEEP_SETTINGS] = { .name = "keep_settings", .type = BLOBMSG_TYPE_BOOL},
	[UPGRADE_KEEP_OPCONF] = { .name = "keep_opconf", .type = BLOBMSG_TYPE_BOOL},
	[UPGRADE_CONFIG_MODE] = { .name = "config_scope", .type = BLOBMSG_TYPE_STRING},
};

static int upgrade_handler(struct ubus_context *ctx, struct ubus_object *obj,
		    struct ubus_request_data *req, const char *method,
		    struct blob_attr *msg)
{
	struct blob_attr *tb[__UPGRADE_MAX];
	uint32_t bank_id = 0;
	char *tmp_string = NULL;
	bool tmp_boolean = false;
	int res = 0;
	struct blob_buf bb = {0};

	if (blobmsg_parse(upgrade_policy, __UPGRADE_MAX, tb, blob_data(msg), blob_len(msg))) {
		BBFDM_ERR("Failed to parse the 'upgrade' message");
		return UBUS_STATUS_UNKNOWN_ERROR;
	}

	if (tb[UPGRADE_PATH])
		tmp_string = blobmsg_get_string(tb[UPGRADE_PATH]);

	if (DM_STRLEN(tmp_string) == 0) {
		BBFDM_ERR("Path missing, aborting upgrade!!!");
		return UBUS_STATUS_UNKNOWN_ERROR;
	}

	if (tb[UPGRADE_BANK]) {
		bank_id = blobmsg_get_u32(tb[UPGRADE_BANK]);
	} else {
		BBFDM_ERR("bank missing, aborting upgrade!!!");
		return UBUS_STATUS_UNKNOWN_ERROR;
	}

	memset(&bb, 0, sizeof(struct blob_buf));
	blob_buf_init(&bb, 0);

	blobmsg_add_string(&bb, "path", tmp_string);
	blobmsg_add_u32(&bb, "bank", bank_id);

	// default values will be enforced in fwbank script
	if (tb[UPGRADE_AUTO_ACTIVATE]) {
		tmp_boolean = blobmsg_get_bool(tb[UPGRADE_AUTO_ACTIVATE]);
		blobmsg_add_u8(&bb, "auto_activate", tmp_boolean);
	} else {
		blobmsg_add_u8(&bb, "auto_activate", false);
	}

	if (tb[UPGRADE_KEEP_SETTINGS]) {
		tmp_boolean = blobmsg_get_bool(tb[UPGRADE_KEEP_SETTINGS]);
		blobmsg_add_u8(&bb, "keep_settings", tmp_boolean);
	}

	if (tb[UPGRADE_KEEP_OPCONF]) {
		tmp_boolean = blobmsg_get_bool(tb[UPGRADE_KEEP_OPCONF]);
		blobmsg_add_u8(&bb, "keep_opconf", tmp_boolean);
	}

	if (tb[UPGRADE_CONFIG_MODE]) {
		tmp_string = blobmsg_get_string(tb[UPGRADE_CONFIG_MODE]);
		blobmsg_add_string(&bb, "config_scope", tmp_string);
	}

	res = sysmngr_fwbank_upgrade(&bb, bank_id, req);
	blob_buf_free(&bb);

	if (res) {
		memset(&bb, 0, sizeof(struct blob_buf));
		blob_buf_init(&bb, 0);
		blobmsg_add_string(&bb, "result", "failure");
		ubus_send_reply(ctx, req, bb.head);
		blob_buf_free(&bb);
	}

	init_global_fwbank_dump();
	return 0;
}

static struct ubus_method fwbank_methods[] = {
	UBUS_METHOD("dump", dump_handler, dump_policy),
	UBUS_METHOD("set_bootbank", set_bootbank_handler, set_bootbank_policy),
	UBUS_METHOD("upgrade", upgrade_handler, upgrade_policy),
};

static struct ubus_object_type fwbank_type = UBUS_OBJECT_TYPE("fwbank", fwbank_methods);

static struct ubus_object fwbank_object = {
	.name = "fwbank",
	.type = &fwbank_type,
	.methods = fwbank_methods,
	.n_methods = ARRAY_SIZE(fwbank_methods)
};

int sysmngr_register_fwbank(struct ubus_context *ubus_ctx)
{
	int res = ubus_add_object(ubus_ctx, &fwbank_object);
	if (res) {
		BBFDM_ERR("Failed to register 'fwbank' ubus object!!!!!!");
		return -1;
	}

	BBFDM_INFO("'fwbank' ubus object was registered");
	return res;
}

int sysmngr_unregister_fwbank(struct ubus_context *ubus_ctx)
{
	ubus_remove_object(ubus_ctx, &fwbank_object);

	BBFDM_INFO("'fwbank' ubus object was unregistered, and resources were freed");
	return 0;
}

int sysmngr_init_fwbank_dump(struct ubus_context *ubus_ctx)
{
	int res = 0;

	g_fwbank_dump.ctx = ubus_ctx;
	g_fwbank_dump.tm.cb = fwbank_dump_timer;

	if (!file_exists(FWBANK_FILE_PATH)) {
		BBFDM_ERR("The fwbank file (%s) is missing", FWBANK_FILE_PATH);
		return -1;
	}

	res = init_global_fwbank_dump();
	if (res) {
		BBFDM_ERR("Failed to fetch 'fwbank' output or no data available");
		return -1;
	}

	return 0;
}

int sysmngr_clean_fwbank_dump(struct ubus_context *ubus_ctx)
{
	free_global_fwbank_dump(&g_fwbank_dump.output);
	return 0;
}

#ifdef SYSMNGR_FWBANK_UBUS_SUPPORT
static void schedule_fwbank_dump_refresh(struct uloop_timeout *timeout);
struct uloop_timeout upgrade_event_timer = { .cb = schedule_fwbank_dump_refresh };

void schedule_fwbank_dump_refresh(struct uloop_timeout *tm __attribute__((unused)))
{
	init_global_fwbank_dump();
}

void sysmngr_process_sysupgrade(struct ubus_context *ctx, struct ubus_event_handler *ev,
				const char *type, struct blob_attr *msg)
{
	struct blob_attr *cur = NULL;
	int rem;

	if (!msg || !ev)
		return;

	BBFDM_INFO("Received sysupgrade event");
	blobmsg_for_each_attr(cur, msg, rem) {
		if (DM_STRCMP("status", blobmsg_name(cur)) == 0) {
			char *attr_val = (char *)blobmsg_data(cur);

			if (DM_STRCMP(attr_val, "Available") != 0) {
				BBFDM_DEBUG("Sysupgrade status not Available. Refresh fwbank dump after 10sec.");
				uloop_timeout_set(&upgrade_event_timer, 10 * 1000);
				break;
			} else {
				init_global_fwbank_dump();
				uloop_timeout_cancel(&upgrade_event_timer);
				break;
			}
		}
	}
	return;
}
#endif
