/*
 * 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: Rahul Thakur <rahul.thakur@iopsys.eu>
 */

#include <libgen.h>

#include "deviceinfologrotate.h"

/*************************************************************
* ENTRY METHOD
**************************************************************/
static bool check_file_dir(char *name)
{
	struct stat buffer;
	return (stat(name, &buffer) == 0);
}

static int dmmap_synchronizelrotatelfInst(struct dmctx *dmctx, DMNODE *parent_node, void *prev_data, char *prev_instance)
{
	struct uci_section *parent_sec = ((struct dm_data *)prev_data)->config_section, *s = NULL, *stmp = NULL;
	DIR *dir;
	struct dirent *d_file;
	char *file_name = NULL, *tmp_file_name = NULL, *sec_name = NULL;

	// separate file name
	dmuci_get_value_by_section_string(parent_sec, "file_name", &file_name);

	if (DM_STRLEN(file_name) == 0)
		return 0;

	// store full file_name since dirname and basename will truncate it.
	// this variable is used to restore the file name after each operation
	tmp_file_name = dmstrdup(file_name);
	char *f_name = basename(file_name);
	if (DM_STRLEN(f_name) == 0)
		return 0;

	// separate directory path
	file_name = dmstrdup(tmp_file_name);
	char *dir_name = dirname(file_name);
	if (DM_STRLEN(dir_name) == 0)
		return 0;

	sysfs_foreach_file(dir_name, dir, d_file) {
		// logrotate create files as per the following policy:
		// if the original file name is /var/log/messages for example, then it will create
		// files with name /var/log/messages.1 etc on rotation. Hence, its necessary here
		// to loop through the files in the directory and compare names with the file name
		// that is provided in the uci config
		if (strstr(d_file->d_name, f_name) == NULL)
			continue;

		dmasprintf(&tmp_file_name, "%s/%s", dir_name, d_file->d_name);
		if (!is_dmmap_section_exist_eq("dmmap_logmngr", "lrotatelf", "file_name", tmp_file_name)) {
			dmuci_add_section_bbfdm("dmmap_logmngr", "lrotatelf", &s);
			dmuci_set_value_by_section(s, "file_name", tmp_file_name);
			dmuci_set_value_by_section(s, "parent_name", section_name(parent_sec));
		}
	}

	if (dir)
		closedir (dir);

	uci_path_foreach_sections_safe(bbfdm, "dmmap_logmngr", "lrotatelf", stmp, s) {
		dmuci_get_value_by_section_string(s, "parent_name", &sec_name);
		if (DM_STRCMP(section_name(parent_sec), sec_name) != 0)
			continue;

		dmuci_get_value_by_section_string(s, "file_name", &file_name);
		if (check_file_dir(file_name) == 0) {
			dmuci_delete_by_section_bbfdm(s, NULL, NULL);
		}
	}

	return 0;
}


static int browseDeviceInfoLogRotateLogFileInst(struct dmctx *dmctx, DMNODE *parent_node, void *prev_data, char *prev_instance)
{
	struct dm_data curr_data = {0};
	struct uci_section *s = NULL;
	char *inst = NULL, *parent_name = NULL;

	dmmap_synchronizelrotatelfInst(dmctx, parent_node, prev_data, prev_instance);
	uci_path_foreach_sections(bbfdm, "dmmap_logmngr", "lrotatelf", s) {
		struct uci_section *parent_sec = ((struct dm_data *)prev_data)->config_section;
		dmuci_get_value_by_section_string(s, "parent_name", &parent_name);
		if (DM_STRCMP(section_name(parent_sec), parent_name) != 0)
			continue;

                curr_data.config_section = s;

                inst = handle_instance(dmctx, parent_node, s, "lrotatelf_instance", "lrotatelf_alias");

                if (DM_LINK_INST_OBJ(dmctx, parent_node, (void *)&curr_data, inst) == DM_STOP)
                        break;
        }
	return 0;
}

/*************************************************************
* GET & SET PARAM
**************************************************************/
static int get_DeviceInfoLogRotate_Alias(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value)
{
	return bbf_get_alias(ctx, ((struct dm_data *)data)->dmmap_section, "log_rotate_alias", instance, value);
}

static int set_DeviceInfoLogRotate_Alias(char *refparam, struct dmctx *ctx, void *data, char *instance, char *value, int action)
{
	return bbf_set_alias(ctx, ((struct dm_data *)data)->dmmap_section, "log_rotate_alias", instance, value);
}

static int get_DeviceInfoLogRotate_Enable(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value)
{
	*value = dmuci_get_value_by_section_fallback_def(((struct dm_data *)data)->config_section, "enable", "0");
	return 0;
}

static int set_DeviceInfoLogRotate_Enable(char *refparam, struct dmctx *ctx, void *data, char *instance, char *value, int action)
{
	bool b;

	switch (action)	{
		case VALUECHECK:
			if (bbfdm_validate_boolean(ctx, value))
				return FAULT_9007;
			break;
		case VALUESET:
			string_to_bool(value, &b);
			dmuci_set_value_by_section(((struct dm_data *)data)->config_section, "enable", b ? "1" : "0");
			break;
	}
	return 0;
}

static int get_DeviceInfoLogRotate_Name(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value)
{
	char *lval = dmuci_get_value_by_section_fallback_def(((struct dm_data *)data)->config_section, "file_name", "");

	if (DM_STRLEN(lval) == 0) {
		*value = "";
	} else {
		dmasprintf(value, "file://%s", lval);
	}

	return 0;
}

static int set_DeviceInfoLogRotate_Name(char *refparam, struct dmctx *ctx, void *data, char *instance, char *value, int action)
{
	switch (action)	{
		case VALUECHECK:
			if (bbfdm_validate_string(ctx, value, -1, -1, NULL, NULL))
				return FAULT_9007;

			if (DM_STRNCMP(value, "file://", 7) != 0)
				return FAULT_9007;
			break;
		case VALUESET:
			// value is in uri format, that is, file://, so extact file from value before setting
			dmuci_set_value_by_section(((struct dm_data *)data)->config_section, "file_name", &value[7]);
			break;
	}
	return 0;
}

static int get_DeviceInfoLogRotate_NumberOfFiles(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value)
{
	*value = dmuci_get_value_by_section_fallback_def(((struct dm_data *)data)->config_section, "file_count", "");
	return 0;
}

static int set_DeviceInfoLogRotate_NumberOfFiles(char *refparam, struct dmctx *ctx, void *data, char *instance, char *value, int action)
{
	switch (action)	{
		case VALUECHECK:
			if (bbfdm_validate_unsignedInt(ctx, value, RANGE_ARGS{{"1",NULL}}, 1))
				return FAULT_9007;
			break;
		case VALUESET:
			dmuci_set_value_by_section(((struct dm_data *)data)->config_section, "file_count", value);
			break;
	}
	return 0;
}

static int get_DeviceInfoLogRotate_MaxFileSize(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value)
{
	*value = dmuci_get_value_by_section_fallback_def(((struct dm_data *)data)->config_section, "max_file_size", "");
	return 0;
}

static int set_DeviceInfoLogRotate_MaxFileSize(char *refparam, struct dmctx *ctx, void *data, char *instance, char *value, int action)
{
	switch (action)	{
		case VALUECHECK:
			if (bbfdm_validate_unsignedInt(ctx, value, RANGE_ARGS{{NULL,NULL}}, 1))
				return FAULT_9007;
			break;
		case VALUESET:
			dmuci_set_value_by_section(((struct dm_data *)data)->config_section, "max_file_size", value);
			break;
	}
	return 0;
}

static int get_DeviceInfoLogRotate_RollOver(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value)
{
	*value = dmuci_get_value_by_section_fallback_def(((struct dm_data *)data)->config_section, "duration", "");
	return 0;
}

static int set_DeviceInfoLogRotate_RollOver(char *refparam, struct dmctx *ctx, void *data, char *instance, char *value, int action)
{
	switch (action)	{
		case VALUECHECK:
			if (bbfdm_validate_unsignedInt(ctx, value, RANGE_ARGS{{NULL,NULL}}, 1))
				return FAULT_9007;
			break;
		case VALUESET:
			dmuci_set_value_by_section(((struct dm_data *)data)->config_section, "duration", value);
			break;
	}
	return 0;
}

static int get_DeviceInfoLogRotate_Retention(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value)
{
	*value = dmuci_get_value_by_section_fallback_def(((struct dm_data *)data)->config_section, "retention", "");
	return 0;
}

static int set_DeviceInfoLogRotate_Retention(char *refparam, struct dmctx *ctx, void *data, char *instance, char *value, int action)
{
	switch (action)	{
		case VALUECHECK:
			if (bbfdm_validate_unsignedInt(ctx, value, RANGE_ARGS{{NULL,NULL}}, 1))
				return FAULT_9007;
			break;
		case VALUESET:
			dmuci_set_value_by_section(((struct dm_data *)data)->config_section, "retention", value);
			break;
	}
	return 0;
}

static int get_DeviceInfoLogRotate_Compression(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value)
{
	*value = dmuci_get_value_by_section_fallback_def(((struct dm_data *)data)->config_section, "compression", "None");
	return 0;
}

static int set_DeviceInfoLogRotate_Compression(char *refparam, struct dmctx *ctx, void *data, char *instance, char *value, int action)
{
	switch (action)	{
		case VALUECHECK:
			if (bbfdm_validate_string(ctx, value, -1, -1, Compression, NULL))
				return FAULT_9007;
			break;
		case VALUESET:
			dmuci_set_value_by_section(((struct dm_data *)data)->config_section, "compression", value);
			break;
	}
	return 0;
}

static int get_DeviceInfoLogRotate_LogFileNumberOfEntries(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value)
{
	int cnt = get_number_of_entries(ctx, data, instance, browseDeviceInfoLogRotateLogFileInst);
	dmasprintf(value, "%d", cnt);
	return 0;
}

static int get_DeviceInfoLogRotateLogFile_Alias(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value)
{
	return bbf_get_alias(ctx, ((struct dm_data *)data)->config_section, "lrotatelf_alias", instance, value);
}

static int set_DeviceInfoLogRotateLogFile_Alias(char *refparam, struct dmctx *ctx, void *data, char *instance, char *value, int action)
{
	switch (action)	{
		case VALUECHECK:
			if (bbfdm_validate_string(ctx, value, -1, 64, NULL, NULL))
				return FAULT_9007;
			break;
		case VALUESET:
			return bbf_set_alias(ctx, ((struct dm_data *)data)->config_section, "lrotatelf_alias", instance, value);
	}
	return 0;
}

static int get_DeviceInfoLogRotateLogFile_Name(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value)
{
	char *lval = NULL;
	*value = "";
	dmuci_get_value_by_section_string(((struct dm_data *)data)->config_section, "file_name", &lval);
	if (DM_STRLEN(lval) != 0)
		dmasprintf(value, "file://%s", lval);
	return 0;
}

static int get_DeviceInfoLogRotateLogFile_Size(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value)
{
	char *file_name = NULL;
	*value = "0";

	dmuci_get_value_by_section_string(((struct dm_data *)data)->config_section, "file_name", &file_name);
	if (DM_STRLEN(file_name) == 0)
		return 0;

	struct stat attr;

	if (stat(file_name, &attr) != 0)
		return 0;

	dmasprintf(value, "%jd", (intmax_t)attr.st_size);
	return 0;
}

static int get_DeviceInfoLogRotateLogFile_LastChange(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value)
{
	char *file_name = NULL;

	*value = "0001-01-01T00:00:00Z";
	dmuci_get_value_by_section_string(((struct dm_data *)data)->config_section, "file_name", &file_name);
	if (DM_STRLEN(file_name) == 0)
		return 0;

	char date[sizeof("AAAA-MM-JJTHH:MM:SSZ")] = {0};
	struct stat attr;

	if (stat(file_name, &attr) != 0)
		return 0;

	strftime(date, sizeof(date), "%Y-%m-%dT%H:%M:%SZ", gmtime(&attr.st_mtime));
	*value = dmstrdup(date);
	return 0;
}

static int browseLogRotateInst(struct dmctx *dmctx, DMNODE *parent_node, void *prev_data, char *prev_instance)
{
	struct dm_data *curr_data = NULL;
	LIST_HEAD(dup_list);
	char *inst = NULL;

	synchronize_specific_config_sections_with_dmmap("logmngr", "log_rotate", "dmmap_logmngr", &dup_list);
	list_for_each_entry(curr_data, &dup_list, list) {

		inst = handle_instance(dmctx, parent_node, curr_data->dmmap_section, "log_rotate_instance", "log_rotate_alias");

		if (DM_LINK_INST_OBJ(dmctx, parent_node, curr_data, inst) == DM_STOP)
			break;
	}
	free_dmmap_config_dup_list(&dup_list);
	return 0;
}

static int get_DeviceInfo_LogRotateNumberOfEntries(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value)
{
	int cnt = get_number_of_entries(ctx, data, instance, browseLogRotateInst);
	dmasprintf(value, "%d", cnt);
	return 0;
}

static int addObjLogRotate(char *refparam, struct dmctx *ctx, void *data, char **instance)
{
	struct uci_section *s = NULL, *dmmap = NULL;
	char lrotate_name[16] = {0};

	snprintf(lrotate_name, sizeof(lrotate_name), "bbf_lrotate_%s", *instance);

	dmuci_add_section("logmngr", "log_rotate", &s);
	dmuci_rename_section_by_section(s, lrotate_name);

	dmuci_add_section_bbfdm("dmmap_logmngr", "log_rotate", &dmmap);
	dmuci_set_value_by_section(dmmap, "log_rotate_instance", *instance);
	dmuci_set_value_by_section(dmmap, "section_name", lrotate_name);

	return 0;
}

static int delObjLogRotate(char *refparam, struct dmctx *ctx, void *data, char *instance, unsigned char del_action)
{
	struct uci_section *s = NULL, *stmp = NULL;

	switch (del_action) {
		case DEL_INST:
			// cleanup dmmap file for all LogRotate.LogFile instances associated with this
			// uci section corresponding to the LogRotate object
			uci_path_foreach_sections_safe(bbfdm, "dmmap_logmngr", "lrotatelf", stmp, s) {
				char *name;

				dmuci_get_value_by_section_string(s, "parent_name", &name);
				if (DM_STRCMP(name, section_name(((struct dm_data *)data)->config_section)) == 0)
					dmuci_delete_by_section_bbfdm(s, NULL, NULL);
			}

			// now delete the log_rotate uci section itself
			dmuci_delete_by_section(((struct dm_data *)data)->config_section, NULL, NULL);

			break;
		case DEL_ALL:
			// delete all logrotate sections in the uci config
			s = NULL;
			uci_foreach_sections_safe("logmngr", "log_rotate", stmp, s) {
				dmuci_delete_by_section(s, NULL, NULL);
			}

			// cleanup dmmap file for all LogRotate.LogFile instances
			uci_path_foreach_sections_safe(bbfdm, "dmmap_logmngr", "lrotatelf", stmp, s) {
				dmuci_delete_by_section_bbfdm(s, NULL, NULL);
			}
			break;
	}

	return 0;
}
/**********************************************************************************************************************************
*                                            OBJ & PARAM DEFINITION
***********************************************************************************************************************************/
/* *** Device.DeviceInfo.LogRotate.{i}. *** */
DMOBJ tDeviceInfoLogRotateObj[] = {
/* OBJ, permission, addobj, delobj, checkdep, browseinstobj, nextdynamicobj, dynamicleaf, nextobj, leaf, linker, bbfdm_type, uniqueKeys */
{"LogRotate", &DMWRITE, addObjLogRotate, delObjLogRotate, NULL, browseLogRotateInst, NULL, NULL, tLogRotateLogFileObj, tDeviceInfoLogRotateParams, NULL, BBFDM_BOTH, NULL},
{0}
};

DMOBJ tLogRotateLogFileObj[] = {
/* OBJ, permission, addobj, delobj, checkdep, browseinstobj, nextdynamicobj, dynamicleaf, nextobj, leaf, linker, bbfdm_type, uniqueKeys */
{"LogFile", &DMREAD, NULL, NULL, NULL, browseDeviceInfoLogRotateLogFileInst, NULL, NULL, NULL, tDeviceInfoLogRotateLogFileParams, NULL, BBFDM_BOTH, NULL},
{0}
};

DMLEAF tDeviceInfoLogRotateParams[] = {
/* PARAM, permission, type, getvalue, setvalue, bbfdm_type */
{"Alias", &DMWRITE, DMT_STRING, get_DeviceInfoLogRotate_Alias, set_DeviceInfoLogRotate_Alias, BBFDM_BOTH, DM_FLAG_UNIQUE},
{"Enable", &DMWRITE, DMT_BOOL, get_DeviceInfoLogRotate_Enable, set_DeviceInfoLogRotate_Enable, BBFDM_BOTH},
{"Name", &DMWRITE, DMT_STRING, get_DeviceInfoLogRotate_Name, set_DeviceInfoLogRotate_Name, BBFDM_BOTH, DM_FLAG_UNIQUE},
{"NumberOfFiles", &DMWRITE, DMT_UNINT, get_DeviceInfoLogRotate_NumberOfFiles, set_DeviceInfoLogRotate_NumberOfFiles, BBFDM_BOTH},
{"MaxFileSize", &DMWRITE, DMT_UNINT, get_DeviceInfoLogRotate_MaxFileSize, set_DeviceInfoLogRotate_MaxFileSize, BBFDM_BOTH},
{"RollOver", &DMWRITE, DMT_UNINT, get_DeviceInfoLogRotate_RollOver, set_DeviceInfoLogRotate_RollOver, BBFDM_BOTH},
{"Retention", &DMWRITE, DMT_UNINT, get_DeviceInfoLogRotate_Retention, set_DeviceInfoLogRotate_Retention, BBFDM_BOTH},
{"Compression", &DMWRITE, DMT_STRING, get_DeviceInfoLogRotate_Compression, set_DeviceInfoLogRotate_Compression, BBFDM_BOTH},
{"LogFileNumberOfEntries", &DMREAD, DMT_UNINT, get_DeviceInfoLogRotate_LogFileNumberOfEntries, NULL, BBFDM_BOTH},
{0}
};

/* *** Device.DeviceInfo.LogRotate.{i}.LogFile.{i}. *** */
DMLEAF tDeviceInfoLogRotateLogFileParams[] = {
/* PARAM, permission, type, getvalue, setvalue, bbfdm_type */
{"Alias", &DMWRITE, DMT_STRING, get_DeviceInfoLogRotateLogFile_Alias, set_DeviceInfoLogRotateLogFile_Alias, BBFDM_BOTH, DM_FLAG_UNIQUE},
{"Name", &DMREAD, DMT_STRING, get_DeviceInfoLogRotateLogFile_Name, NULL, BBFDM_BOTH, DM_FLAG_UNIQUE},
{"Size", &DMREAD, DMT_UNINT, get_DeviceInfoLogRotateLogFile_Size, NULL, BBFDM_BOTH},
{"LastChange", &DMREAD, DMT_TIME, get_DeviceInfoLogRotateLogFile_LastChange, NULL, BBFDM_BOTH},
{0}
};

DMLEAF tLogRotateNumberofEntries[] = {
{"LogRotateNumberOfEntries", &DMREAD, DMT_UNINT, get_DeviceInfo_LogRotateNumberOfEntries, NULL, BBFDM_BOTH},
{0}
};

/* ********** DynamicObj ********** */
DM_MAP_OBJ tDynamicObj[] = {
/* parentobj, nextobject, parameter */
{"Device.DeviceInfo.", tDeviceInfoLogRotateObj, tLogRotateNumberofEntries},
{0}
};
