/*
** Zabbix
** Copyright (C) 2001-2023 Zabbix SIA
**
** This program is free software; you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation; either version 2 of the License, or
** (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program; if not, write to the Free Software
** Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
**/

#include "persistent_state.h"

#include "log.h"
#include "logfiles.h"
#include "zbxjson.h"
#include "zbxcrypto.h"

/* tags for agent persistent storage files */
#define ZBX_PERSIST_TAG_FILENAME		"filename"
#define ZBX_PERSIST_TAG_MTIME			"mtime"
#define ZBX_PERSIST_TAG_SEQ			"seq"
#define ZBX_PERSIST_TAG_INCOMPLETE		"incomplete"
#define ZBX_PERSIST_TAG_COPY_OF			"copy_of"
#define ZBX_PERSIST_TAG_DEVICE			"dev"
#define ZBX_PERSIST_TAG_INODE_LO		"ino_lo"
#define ZBX_PERSIST_TAG_INODE_HI		"ino_hi"
#define ZBX_PERSIST_TAG_SIZE			"size"
#define ZBX_PERSIST_TAG_PROCESSED_SIZE		"processed_size"
#define ZBX_PERSIST_TAG_MD5_BLOCK_SIZE		"md5_block_size"
#define ZBX_PERSIST_TAG_FIRST_BLOCK_MD5		"first_block_md5"
#define ZBX_PERSIST_TAG_LAST_BLOCK_OFFSET	"last_block_offset"
#define ZBX_PERSIST_TAG_LAST_BLOCK_MD5		"last_block_md5"

ZBX_VECTOR_IMPL(pre_persistent, zbx_pre_persistent_t)
ZBX_VECTOR_IMPL(persistent_inactive, zbx_persistent_inactive_t)

#if !defined(_WINDOWS) && !defined(__MINGW32__)
static int	zbx_persistent_inactive_compare_func(const void *d1, const void *d2)
{
	return strcmp(((const zbx_persistent_inactive_t *)d1)->key_orig,
			((const zbx_persistent_inactive_t *)d2)->key_orig);
}

/******************************************************************************
 *                                                                            *
 * Purpose: for the specified string get the part of persistent storage path  *
 *                                                                            *
 * Parameters:                                                                *
 *          str - [IN] string (e.g. host, item key)                           *
 *                                                                            *
 * Return value: dynamically allocated string with the part of path           *
 *               corresponding to the input string                            *
 *                                                                            *
 * Comments: the part of persistent storage path is built of md5 sum of the   *
 *           input string with the length of input string appended            *
 *                                                                            *
 * Example: for input string                                                  *
 *           "logrt[/home/zabbix/test.log,,,,,,,,/home/zabbix/agent_private]" *
 *          the function returns                                              *
 *           "147bd30c08995022fbe78de3c26c082962"                             *
 *                                            \/ - length of input string     *
 *            \------------------------------/   - md5 sum of input string    *
 *                                                                            *
 ******************************************************************************/
static char	*str2file_name_part(const char *str)
{
	md5_state_t	state;
	md5_byte_t	md5[ZBX_MD5_DIGEST_SIZE];
	char		size_buf[21];		/* 20 - max size of printed 'size_t' value, 1 - '\0' */
	char		*md5_text = NULL;
	size_t		str_sz, md5_text_sz, size_buf_len;

	str_sz = strlen(str);

	zbx_md5_init(&state);
	zbx_md5_append(&state, (const md5_byte_t *)str, (int)str_sz);
	zbx_md5_finish(&state, md5);

	size_buf_len = zbx_snprintf(size_buf, sizeof(size_buf), ZBX_FS_SIZE_T, (zbx_fs_size_t)str_sz);

	md5_text_sz = ZBX_MD5_DIGEST_SIZE * 2 + size_buf_len + 1;
	md5_text = (char *)zbx_malloc(NULL, md5_text_sz);

	zbx_md5buf2str(md5, md5_text);
	memcpy(md5_text + ZBX_MD5_DIGEST_SIZE * 2, size_buf, size_buf_len + 1);

	return md5_text;
}

/******************************************************************************
 *                                                                            *
 * Purpose: calculate the part of persistent storage path for the specified   *
 *          server/port pair where the agent is sending active check data     *
 *                                                                            *
 * Parameters:                                                                *
 *          server - [IN] server string                                       *
 *          port   - [IN] port number                                         *
 *                                                                            *
 * Return value: dynamically allocated string with the part of path           *
 *                                                                            *
 * Comments: the part of persistent storage path is built of md5 sum of the   *
 *           input string with the length of input string appended            *
 *                                                                            *
 * Example: for server "127.0.0.1" and port 10091 the function returns        *
 *         "8392b6ea687188e70feac24517d2f9d715"                               *
 *                                          \/ - length  of "127.0.0.1:10091" *
 *          \------------------------------/   - md5 sum of "127.0.0.1:10091" *
 *                                                                            *
 ******************************************************************************/
static char	*active_server2dir_name_part(const char *server, unsigned short port)
{
	char	*endpoint, *dir_name;

	endpoint = zbx_dsprintf(NULL, "%s:%hu", server, port);
	dir_name = str2file_name_part(endpoint);
	zbx_free(endpoint);

	return dir_name;
}

/******************************************************************************
 *                                                                            *
 * Purpose: make the name of persistent storage directory for the specified   *
 *          server/proxy and port                                             *
 *                                                                            *
 * Parameters:                                                                *
 *          base_path - [IN] initial part of pathname                         *
 *          server    - [IN] server string                                    *
 *          port      - [IN] port number                                      *
 *                                                                            *
 * Return value: dynamically allocated string with the name of directory      *
 *                                                                            *
 * Example: for base_path "/var/tmp/", server "127.0.0.1", port 10091 the     *
 *          function returns "/var/tmp/8392b6ea687188e70feac24517d2f9d715"    *
 *                                                                            *
 ******************************************************************************/
static char	*make_persistent_server_directory_name(const char *base_path, const char *server, unsigned short port)
{
	char	*server_part, *file_name;

	server_part = active_server2dir_name_part(server, port);
	file_name = zbx_dsprintf(NULL, "%s/%s", base_path, server_part);
	zbx_free(server_part);

	return file_name;
}

/******************************************************************************
 *                                                                            *
 * Purpose: check if the directory exists                                     *
 *                                                                            *
 * Parameters:                                                                *
 *          pathname  - [IN] full pathname of directory                       *
 *          error     - [OUT] error message                                   *
 *                                                                            *
 * Return value: SUCCEED or FAIL (with dynamically allocated 'error')         *
 *                                                                            *
 ******************************************************************************/
static int	check_persistent_directory_exists(const char *pathname, char **error)
{
	zbx_stat_t	status;

	if (0 != lstat(pathname, &status))
	{
		*error = zbx_dsprintf(*error, "cannot obtain directory information: %s", zbx_strerror(errno));
		return FAIL;
	}

	if (0 == S_ISDIR(status.st_mode))
	{
		*error = zbx_dsprintf(*error, "file exists but is not a directory");
		return FAIL;
	}

	return SUCCEED;
}

/******************************************************************************
 *                                                                            *
 * Purpose: create directory if it does not exist or check access if it       *
 *          exists                                                            *
 *                                                                            *
 * Parameters:                                                                *
 *          pathname  - [IN] full pathname of directory                       *
 *          error     - [OUT] error message                                   *
 *                                                                            *
 * Return value: SUCCEED - directory created or already exists                *
 *               FAIL    - cannot create directory or it has insufficient     *
 *                         access rights (see dynamically allocated 'error')  *
 *                                                                            *
 ******************************************************************************/
static int	create_persistent_directory(const char *pathname, char **error)
{
	if (0 != mkdir(pathname, S_IRWXU))
	{
		if (EEXIST == errno)
			return check_persistent_directory_exists(pathname, error);

		*error = zbx_dsprintf(*error, "%s(): cannot create directory \"%s\": %s", __func__, pathname,
				zbx_strerror(errno));
		return FAIL;
	}

	zabbix_log(LOG_LEVEL_DEBUG, "%s(): created directory:[%s]", __func__, pathname);

	return SUCCEED;
}

/******************************************************************************
 *                                                                            *
 * Purpose: create all subdirectories in the pathname if they do not exist    *
 *                                                                            *
 * Parameters:                                                                *
 *          pathname - [IN] full pathname of directory, must consist of only  *
 *                          ASCII characters                                  *
 *          error    - [OUT] error message                                    *
 *                                                                            *
 * Return value: SUCCEED - directories created or already exist               *
 *               FAIL    - cannot create directory or it has insufficient     *
 *                         access rights (see dynamically allocated 'error')  *
 *                                                                            *
 ******************************************************************************/
static int	create_base_path_directories(const char *pathname, char **error)
{
	char	*path, *p;

	zabbix_log(LOG_LEVEL_DEBUG, "%s(): pathname:[%s]", __func__, pathname);

	if ('/' != pathname[0])
	{
		*error = zbx_dsprintf(*error, "persistent directory name is not an absolute path,"
				" it does not start with '/'");
		return FAIL;
	}

	path = zbx_strdup(NULL, pathname);	/* mutable copy */

	for (p = path + 1; ; ++p)
	{
		if ('/' == *p)
		{
			*p = '\0';

			zabbix_log(LOG_LEVEL_DEBUG, "%s(): checking directory:[%s]", __func__, path);

			if (SUCCEED != create_persistent_directory(path, error))
			{
				zbx_free(path);
				return FAIL;
			}

			*p = '/';
		}
		else if ('\0' == *p)
		{
			zabbix_log(LOG_LEVEL_DEBUG, "%s(): checking directory:[%s]", __func__, path);

			if (SUCCEED != create_persistent_directory(path, error))
			{
				zbx_free(path);
				return FAIL;
			}

			break;
		}
	}

	zbx_free(path);

	return SUCCEED;
}

/******************************************************************************
 *                                                                            *
 * Purpose: create directory if it does not exist or check access if it       *
 *          exists. Directory name is derived from host and port.             *
 *                                                                            *
 * Parameters:                                                                *
 *          base_path - [IN] initial part of pathname, must consist of only   *
 *                           ASCII characters                                 *
 *          host      - [IN] host string                                      *
 *          port      - [IN] port number                                      *
 *          error     - [OUT] error message                                   *
 *                                                                            *
 * Return value: on success - dynamically allocated string with the name of   *
 *                            directory,                                      *
 *               on error   - NULL (with dynamically allocated 'error')       *
 *                                                                            *
 ******************************************************************************/
char	*zbx_create_persistent_server_directory(const char *base_path, const char *host, unsigned short port,
		char **error)
{
	char	*server_dir_name, *err_msg = NULL;

	/* persistent storage top-level directory */
	if (SUCCEED != create_base_path_directories(base_path, error))
		return NULL;

	/* directory for server/proxy where the current active check process will be sending data */
	server_dir_name = make_persistent_server_directory_name(base_path, host, port);

	if (SUCCEED != create_persistent_directory(server_dir_name, &err_msg))
	{
		*error = zbx_dsprintf(*error, "cannot create directory \"%s\": %s", server_dir_name, err_msg);
		zbx_free(server_dir_name);
		zbx_free(err_msg);
		return NULL;
	}

	if (0 != access(server_dir_name, R_OK | W_OK | X_OK))
	{
		if (NULL != server_dir_name)	/* only to silence GCC warning "directive argument is null" */
		{
			*error = zbx_dsprintf(*error, "insufficient access rights to directory \"%s\"",
					server_dir_name);
		}
		else
			THIS_SHOULD_NEVER_HAPPEN;

		zbx_free(server_dir_name);
		return NULL;
	}

	return server_dir_name;
}

/******************************************************************************
 *                                                                            *
 * Purpose: make the name of persistent storage directory or file             *
 *                                                                            *
 * Parameters:                                                                *
 *          persistent_server_dir - [IN] initial part of pathname             *
 *          item_key              - [IN] item key (not NULL)                  *
 *                                                                            *
 * Return value: dynamically allocated string with the name of file           *
 *                                                                            *
 ******************************************************************************/
char	*zbx_make_persistent_file_name(const char *persistent_server_dir, const char *item_key)
{
	char	*file_name, *item_part;

	item_part = str2file_name_part(item_key);
	file_name = zbx_dsprintf(NULL, "%s/%s", persistent_server_dir, item_part);
	zbx_free(item_part);

	return file_name;
}

/******************************************************************************
 *                                                                            *
 * Purpose: write metric info into persistent file                            *
 *                                                                            *
 * Parameters:                                                                *
 *          filename  - [IN] file name                                        *
 *          data      - [IN] file content                                     *
 *          error     - [OUT] error message                                   *
 *                                                                            *
 * Return value: SUCCEED - file was successfully written                      *
 *               FAIL    - cannot write the file (see dynamically allocated   *
 *                         'error' message)                                   *
 *                                                                            *
 ******************************************************************************/
static int	zbx_write_persistent_file(const char *filename, const char *data, char **error)
{
	FILE	*fp;
	size_t	sz, alloc_bytes = 0, offset = 0;
	int	ret = SUCCEED;

	zabbix_log(LOG_LEVEL_DEBUG, "%s(): filename:[%s] data:[%s]", __func__, filename, data);

	if (NULL == (fp = fopen(filename, "w")))
	{
		zbx_snprintf_alloc(error, &alloc_bytes, &offset, "cannot open file: %s", zbx_strerror(errno));
		return FAIL;
	}

	sz = strlen(data);

	if (sz != fwrite(data, 1, sz, fp))
	{
		zbx_snprintf_alloc(error, &alloc_bytes, &offset, "cannot write to file: %s", zbx_strerror(errno));
		ret = FAIL;
	}

	if (0 != fclose(fp))
	{
		zbx_snprintf_alloc(error, &alloc_bytes, &offset, "%scannot close file: %s",
				(FAIL == ret) ? "; ": "" , zbx_strerror(errno));
		ret = FAIL;
	}

	return ret;
}

/******************************************************************************
 *                                                                            *
 * Purpose: read metric info from persistent file. One line is read.          *
 *                                                                            *
 * Parameters:                                                                *
 *          filename  - [IN] file name                                        *
 *          buf       - [OUT] buffer to read file into                        *
 *          buf_size  - [IN] buffer size                                      *
 *          err_msg   - [OUT] error message                                   *
 *                                                                            *
 * Return value: SUCCEED - file was successfully read                         *
 *               FAIL    - cannot read the file (see dynamically allocated    *
 *                         'err_msg' message)                                 *
 *                                                                            *
 ******************************************************************************/
int	zbx_read_persistent_file(const char *filename, char *buf, size_t buf_size, char **err_msg)
{
	int	ret = FAIL;
	FILE	*f;

	if (NULL == (f = fopen(filename, "r")))
	{
		*err_msg = zbx_dsprintf(*err_msg, "cannot open file \"%s\": %s", filename, zbx_strerror(errno));
		return FAIL;
	}

	if (NULL == fgets(buf, (int)buf_size, f))
	{
		*err_msg = zbx_dsprintf(*err_msg, "cannot read from file \"%s\" or file empty", filename);
		goto out;
	}

	buf[strcspn(buf, "\r\n")] = '\0';	/* discard newline at the end of string */

	ret = SUCCEED;
out:
	if (0 != fclose(f))
	{	/* only log, cannot do anything in case of error */
		zabbix_log(LOG_LEVEL_CRIT, "cannot close file \"%s\": %s", filename, zbx_strerror(errno));
	}

	return ret;
}

/******************************************************************************
 *                                                                            *
 * Purpose: remove the specified file                                         *
 *                                                                            *
 * Parameters:                                                                *
 *          pathname  - [IN] file name                                        *
 *          error     - [OUT] error message                                   *
 *                                                                            *
 * Return value: SUCCEED - file was removed or does not exist                 *
 *               FAIL    - cannot remove the specified file (see dynamically  *
 *                         allocated 'error' message)                         *
 *                                                                            *
 ******************************************************************************/
int	zbx_remove_persistent_file(const char *pathname, char **error)
{
	zabbix_log(LOG_LEVEL_DEBUG, "%s(): removing persistent file '%s'", __func__, pathname);

	if (0 == unlink(pathname) || ENOENT == errno)
		return SUCCEED;

	*error = zbx_strdup(*error, zbx_strerror(errno));

	return FAIL;
}

void	zbx_write_persistent_files(zbx_vector_pre_persistent_t *prep_vec)
{
	int	i;

	for (i = 0; i < prep_vec->values_num; i++)
	{
		char		*error = NULL;
		struct zbx_json	json;

		/* prepare JSON */
		zbx_json_init(&json, ZBX_JSON_STAT_BUF_LEN);

		if (NULL != prep_vec->values[i].filename)
		{
			zbx_json_addstring(&json, ZBX_PERSIST_TAG_FILENAME, prep_vec->values[i].filename,
					ZBX_JSON_TYPE_STRING);
		}

		zbx_json_adduint64(&json, ZBX_PERSIST_TAG_MTIME, (zbx_uint64_t)prep_vec->values[i].mtime);

		zbx_json_adduint64(&json, ZBX_PERSIST_TAG_PROCESSED_SIZE, prep_vec->values[i].processed_size);

		if (NULL != prep_vec->values[i].filename)
		{
			char	buf[ZBX_MD5_PRINT_BUF_LEN];

			zbx_json_adduint64(&json, ZBX_PERSIST_TAG_SEQ, (zbx_uint64_t)prep_vec->values[i].seq);
			zbx_json_adduint64(&json, ZBX_PERSIST_TAG_INCOMPLETE,
					(zbx_uint64_t)prep_vec->values[i].incomplete);

			zbx_json_addint64(&json, ZBX_PERSIST_TAG_COPY_OF, prep_vec->values[i].copy_of);
			zbx_json_adduint64(&json, ZBX_PERSIST_TAG_DEVICE, prep_vec->values[i].dev);
			zbx_json_adduint64(&json, ZBX_PERSIST_TAG_INODE_LO, prep_vec->values[i].ino_lo);
			zbx_json_adduint64(&json, ZBX_PERSIST_TAG_INODE_HI, prep_vec->values[i].ino_hi);
			zbx_json_adduint64(&json, ZBX_PERSIST_TAG_SIZE, prep_vec->values[i].size);

			zbx_json_adduint64(&json, ZBX_PERSIST_TAG_MD5_BLOCK_SIZE,
					(zbx_uint64_t)prep_vec->values[i].md5_block_size);

			zbx_md5buf2str(prep_vec->values[i].first_block_md5, buf);
			zbx_json_addstring(&json, ZBX_PERSIST_TAG_FIRST_BLOCK_MD5, buf, ZBX_JSON_TYPE_STRING);

			zbx_json_adduint64(&json, ZBX_PERSIST_TAG_LAST_BLOCK_OFFSET,
					prep_vec->values[i].last_block_offset);

			zbx_md5buf2str(prep_vec->values[i].last_block_md5, buf);
			zbx_json_addstring(&json, ZBX_PERSIST_TAG_LAST_BLOCK_MD5, buf, ZBX_JSON_TYPE_STRING);
		}

		zbx_json_close(&json);

		if (SUCCEED != zbx_write_persistent_file(prep_vec->values[i].persistent_file_name, json.buffer, &error))
		{
			zabbix_log(LOG_LEVEL_WARNING, "cannot write persistent file \"%s\": %s",
					prep_vec->values[i].persistent_file_name, error);
			zbx_free(error);
		}

		zbx_json_free(&json);
	}
}

void	zbx_clean_pre_persistent_elements(zbx_vector_pre_persistent_t *prep_vec)
{
	int	i;

	/* clean only element data and number of elements, do not reduce vector size */

	for (i = 0; i < prep_vec->values_num; i++)
	{
		zbx_free(prep_vec->values[i].key_orig);
		zbx_free(prep_vec->values[i].persistent_file_name);
		zbx_free(prep_vec->values[i].filename);
	}

	zbx_vector_pre_persistent_clear(prep_vec);
}

void	zbx_add_to_persistent_inactive_list(zbx_vector_persistent_inactive_t *inactive_vec, char *key,
		const char *filename)
{
	zbx_persistent_inactive_t	el;

	el.key_orig = key;

	if (FAIL == zbx_vector_persistent_inactive_search(inactive_vec, el,
			zbx_persistent_inactive_compare_func))
	{
		/* create and initialize a new vector element */

		el.key_orig = zbx_strdup(NULL, key);
		el.not_received_time = time(NULL);
		el.persistent_file_name = zbx_strdup(NULL, filename);

		zbx_vector_persistent_inactive_append(inactive_vec, el);

		zabbix_log(LOG_LEVEL_DEBUG, "%s(): added element %d with key '%s' for file '%s'", __func__,
				inactive_vec->values_num - 1, key, filename);
	}
}

void	zbx_remove_from_persistent_inactive_list(zbx_vector_persistent_inactive_t *inactive_vec, char *key)
{
	zbx_persistent_inactive_t	el;
	int				idx;

	el.key_orig = key;

	if (FAIL == (idx = zbx_vector_persistent_inactive_search(inactive_vec, el,
			zbx_persistent_inactive_compare_func)))
	{
		return;
	}

	zbx_free(inactive_vec->values[idx].key_orig);
	zbx_free(inactive_vec->values[idx].persistent_file_name);
	zbx_vector_persistent_inactive_remove(inactive_vec, idx);

	zabbix_log(LOG_LEVEL_DEBUG, "%s(): removed element %d with key '%s'", __func__, idx, key);
}

void	zbx_remove_inactive_persistent_files(zbx_vector_persistent_inactive_t *inactive_vec)
{
	int	i;
	time_t	now;

	now = time(NULL);

	for (i = 0; i < inactive_vec->values_num; i++)
	{
		zbx_persistent_inactive_t	*el = inactive_vec->values + i;

		if (ZBX_PERSIST_INACTIVITY_PERIOD <= now - el->not_received_time)
		{
			char	*err_msg = NULL;

			zabbix_log(LOG_LEVEL_DEBUG, "%s(): removing element %d with key '%s'", __func__, i,
					el->key_orig);

			if (SUCCEED != zbx_remove_persistent_file(el->persistent_file_name, &err_msg))
			{
				zabbix_log(LOG_LEVEL_WARNING, "cannot remove persistent file \"%s\": %s",
						el->persistent_file_name, err_msg);
				zbx_free(err_msg);
			}

			zbx_free(el->key_orig);
			zbx_free(el->persistent_file_name);
			zbx_vector_persistent_inactive_remove(inactive_vec, i);
		}
	}
}

static int	zbx_pre_persistent_compare_func(const void *d1, const void *d2)
{
	return strcmp(((const zbx_pre_persistent_t *)d1)->key_orig, ((const zbx_pre_persistent_t *)d2)->key_orig);
}

/******************************************************************************
 *                                                                            *
 * Purpose: search preparation vector to find element with the specified key. *
 *          If not found then create the element.                             *
 *                                                                            *
 * Parameters:                                                                *
 *    prep_vec             - [IN/OUT] preparation vector for persistent data  *
 *                                files                                       *
 *    key                  - [IN] log*[] item key                             *
 *    persistent_file_name - [IN] file name for copying into new element      *
 *                                                                            *
 ******************************************************************************/
int	zbx_find_or_create_prep_vec_element(zbx_vector_pre_persistent_t *prep_vec, const char *key,
		const char *persistent_file_name)
{
	zbx_pre_persistent_t	prep_element;
	int			prep_vec_idx;

	prep_element.key_orig = (char *)key;

	if (FAIL == (prep_vec_idx = zbx_vector_pre_persistent_search(prep_vec, prep_element,
			zbx_pre_persistent_compare_func)))
	{
		/* create and initialize a new vector element */
		memset(&prep_element, 0, sizeof(prep_element));

		zbx_vector_pre_persistent_append(prep_vec, prep_element);
		prep_vec_idx = prep_vec->values_num - 1;

		/* fill in 'key_orig' and 'persistent_file_name' values - they never change for the specified */
		/* log*[] item (otherwise it is not the same item anymore) */
		prep_vec->values[prep_vec_idx].key_orig = zbx_strdup(NULL, key);
		prep_vec->values[prep_vec_idx].persistent_file_name = zbx_strdup(NULL, persistent_file_name);

		zabbix_log(LOG_LEVEL_DEBUG, "%s(): key:[%s] created element %d", __func__, key, prep_vec_idx);
	}
	else
		zabbix_log(LOG_LEVEL_DEBUG, "%s(): key:[%s] found element %d", __func__, key, prep_vec_idx);

	return prep_vec_idx;
}

void	zbx_init_prep_vec_data(const struct st_logfile *logfile, zbx_pre_persistent_t *prep_vec_elem)
{
	/* copy attributes which are stable within one check of the specified log file but */
	/* may change in the next check */

	if (NULL == prep_vec_elem->filename || 0 != strcmp(prep_vec_elem->filename, logfile->filename))
		prep_vec_elem->filename = zbx_strdup(prep_vec_elem->filename, logfile->filename);

	prep_vec_elem->mtime = logfile->mtime;
	prep_vec_elem->seq = logfile->seq;
	prep_vec_elem->copy_of = logfile->copy_of;
	prep_vec_elem->dev = logfile->dev;
	prep_vec_elem->ino_lo = logfile->ino_lo;
	prep_vec_elem->ino_hi = logfile->ino_hi;
	prep_vec_elem->size = logfile->size;

	prep_vec_elem->md5_block_size = logfile->md5_block_size;
	memcpy(prep_vec_elem->first_block_md5, logfile->first_block_md5, sizeof(logfile->first_block_md5));

	prep_vec_elem->last_block_offset = logfile->last_block_offset;
	memcpy(prep_vec_elem->last_block_md5, logfile->last_block_md5, sizeof(logfile->last_block_md5));
}

void	zbx_update_prep_vec_data(const struct st_logfile *logfile, zbx_uint64_t processed_size,
		zbx_pre_persistent_t *prep_vec_elem)
{
	/* copy attributes which (may) change with every log file record */
	prep_vec_elem->processed_size = processed_size;
	prep_vec_elem->incomplete = logfile->incomplete;
}

/******************************************************************************
 *                                                                            *
 * Purpose: create the 'old log file list' and restore log file attributes    *
 *          from JSON string which was read from persistent file              *
 *                                                                            *
 * Parameters:                                                                *
 *     str            - [IN] JSON string                                      *
 *     logfiles       - [OUT] log file list (vector)                          *
 *     logfiles_num   - [OUT] number of elements in the log file list         *
 *     processed_size - [OUT] processed size attribute                        *
 *     mtime          - [OUT] mtime                                           *
 *     err_msg        - [OUT] dynamically allocated error message             *
 *                                                                            *
 * Return value: SUCCEED or FAIL                                              *
 *                                                                            *
 * Examples of valid JSON 'str' (one text line but here it is split for       *
 * readability):                                                              *
 *     {"mtime":1623174047,                                                   *
 *      "processed_size":0}                                                   *
 * or                                                                         *
 *     {"filename":"/home/zabbix/test.log",                                   *
 *      "mtime":0,                                                            *
 *      "processed_size":324,                                                 *
 *      "seq":0,                                                              *
 *      "incomplete":0,                                                       *
 *      "copy_of":-1,                                                         *
 *      "dev":65027,                                                          *
 *      "ino_lo":17043636,                                                    *
 *      "ino_hi":0,                                                           *
 *      "size":635,                                                           *
 *      "md5_block_size":512,                                                 *
 *      "first_block_md5":"7f2d0cf871384671c51359ce8c90e475",                 *
 *      "last_block_offset":123,                                              *
 *      "last_block_md5":"86985e105f79b95d6bc918fb45ec7727"}                  *
 ******************************************************************************/
int	zbx_restore_file_details(const char *str, struct st_logfile **logfiles, int *logfiles_num,
		zbx_uint64_t *processed_size, int *mtime, char **err_msg)
{
	struct zbx_json_parse	jp;
	/* temporary variables before filling in 'st_logfile' elements */
	char		filename[MAX_STRING_LEN];
	int		mtime_tmp = 0;
	int		seq = 0;
	int		incomplete = 0;
	int		copy_of = 0;
	zbx_uint64_t	dev;
	zbx_uint64_t	ino_lo;
	zbx_uint64_t	ino_hi;
	zbx_uint64_t	size;
	zbx_uint64_t	processed_size_tmp = 0;
	int		md5_block_size = 0;
	md5_byte_t	first_block_md5[ZBX_MD5_DIGEST_SIZE];
	zbx_uint64_t	last_block_offset = 0;
	md5_byte_t	last_block_md5[ZBX_MD5_DIGEST_SIZE];
	/* flags to check missing attributes */
	int		got_filename = 0, got_mtime = 0, got_seq = 0, got_incomplete = 0, got_copy_of = 0, got_dev = 0,
			got_ino_lo = 0, got_ino_hi = 0, got_size = 0, got_processed_size = 0, got_md5_block_size = 0,
			got_first_block_md5 = 0, got_last_block_offset = 0, got_last_block_md5 = 0, sum;
	char		tmp[MAX_STRING_LEN];

	if (0 != *logfiles_num || NULL != *logfiles)
	{
		THIS_SHOULD_NEVER_HAPPEN;
		*err_msg = zbx_dsprintf(*err_msg, "%s(): non-empty log file list, logfiles_num:%d",
				__func__, *logfiles_num);
		zabbix_log(LOG_LEVEL_WARNING, "%s(): non-empty log file list, logfiles_num:%d",
				__func__, *logfiles_num);
		return FAIL;
	}

	if (SUCCEED != zbx_json_open(str, &jp))
	{
		*err_msg = zbx_dsprintf(*err_msg, "cannot parse persistent data: %s", zbx_json_strerror());
		return FAIL;
	}

	if (SUCCEED == zbx_json_value_by_name(&jp, ZBX_PERSIST_TAG_FILENAME, filename, sizeof(filename), NULL))
		got_filename = 1;

	if (SUCCEED == zbx_json_value_by_name(&jp, ZBX_PERSIST_TAG_MTIME, tmp, sizeof(tmp), NULL))
	{
		mtime_tmp = atoi(tmp);
		got_mtime = 1;
	}

	if (SUCCEED == zbx_json_value_by_name(&jp, ZBX_PERSIST_TAG_SEQ, tmp, sizeof(tmp), NULL))
	{
		seq = atoi(tmp);
		got_seq = 1;
	}

	if (SUCCEED == zbx_json_value_by_name(&jp, ZBX_PERSIST_TAG_INCOMPLETE, tmp, sizeof(tmp), NULL))
	{
		incomplete = atoi(tmp);
		got_incomplete = 1;
	}

	if (SUCCEED == zbx_json_value_by_name(&jp, ZBX_PERSIST_TAG_COPY_OF, tmp, sizeof(tmp), NULL))
	{
		copy_of = atoi(tmp);
		got_copy_of = 1;
	}

	if (SUCCEED == zbx_json_value_by_name(&jp, ZBX_PERSIST_TAG_DEVICE, tmp, sizeof(tmp), NULL))
	{
		if (SUCCEED == zbx_is_uint64(tmp, &dev))
			got_dev = 1;
	}

	if (SUCCEED == zbx_json_value_by_name(&jp, ZBX_PERSIST_TAG_INODE_LO, tmp, sizeof(tmp), NULL))
	{
		if (SUCCEED == zbx_is_uint64(tmp, &ino_lo))
			got_ino_lo = 1;
	}

	if (SUCCEED == zbx_json_value_by_name(&jp, ZBX_PERSIST_TAG_INODE_HI, tmp, sizeof(tmp), NULL))
	{
		if (SUCCEED == zbx_is_uint64(tmp, &ino_hi))
			got_ino_hi = 1;
	}

	if (SUCCEED == zbx_json_value_by_name(&jp, ZBX_PERSIST_TAG_SIZE, tmp, sizeof(tmp), NULL))
	{
		if (SUCCEED == zbx_is_uint64(tmp, &size))
			got_size = 1;
	}

	if (SUCCEED == zbx_json_value_by_name(&jp, ZBX_PERSIST_TAG_PROCESSED_SIZE, tmp, sizeof(tmp), NULL))
	{
		if (SUCCEED == zbx_is_uint64(tmp, &processed_size_tmp))
			got_processed_size = 1;
	}

	if (SUCCEED == zbx_json_value_by_name(&jp, ZBX_PERSIST_TAG_MD5_BLOCK_SIZE, tmp, sizeof(tmp), NULL))
	{
		md5_block_size = atoi(tmp);
		got_md5_block_size = 1;
	}

	if (SUCCEED == zbx_json_value_by_name(&jp, ZBX_PERSIST_TAG_FIRST_BLOCK_MD5, tmp, sizeof(tmp), NULL))
	{
		if (sizeof(first_block_md5) == zbx_hex2bin((const unsigned char *)tmp, first_block_md5,
				sizeof(first_block_md5)))
		{
			got_first_block_md5 = 1;
		}
	}

	if (SUCCEED == zbx_json_value_by_name(&jp, ZBX_PERSIST_TAG_LAST_BLOCK_OFFSET, tmp, sizeof(tmp), NULL))
	{
		if (SUCCEED == zbx_is_uint64(tmp, &last_block_offset))
			got_last_block_offset = 1;
	}

	if (SUCCEED == zbx_json_value_by_name(&jp, ZBX_PERSIST_TAG_LAST_BLOCK_MD5, tmp, sizeof(tmp), NULL))
	{
		if (sizeof(last_block_md5) == zbx_hex2bin((const unsigned char *)tmp, last_block_md5,
				sizeof(last_block_md5)))
		{
			got_last_block_md5 = 1;
		}
	}

	/* 'mtime' and 'processed_size' should always be present */
	if (0 == got_mtime || 0 == got_processed_size)
	{
		*err_msg = zbx_dsprintf(*err_msg, "corrupted data: 'mtime' or 'processed_size' attribute missing");
		return FAIL;
	}

	/* all other 12 variables should be present or none of them */
	sum = got_filename + got_seq + got_incomplete + got_copy_of + got_dev + got_ino_lo + got_ino_hi + got_size +
			got_md5_block_size + got_first_block_md5 + got_last_block_offset + got_last_block_md5;

	if (0 == sum)	/* got only metadata 'mtime' and 'processed_size' */
	{
		*mtime = mtime_tmp;
		*processed_size = processed_size_tmp;
		return SUCCEED;
	}

	if (12 != sum)
	{
		*err_msg = zbx_dsprintf(*err_msg, "present/missing attributes: filename:%d seq:%d incomplete:%d"
				" copy_of:%d dev:%d ino_lo:%d ino_hi:%d size:%d md5_block_size:%d first_block_md5:%d"
				" last_block_offset:%d last_block_md5:%d", got_filename, got_seq,
				got_incomplete, got_copy_of, got_dev, got_ino_lo, got_ino_hi, got_size,
				got_md5_block_size, got_first_block_md5, got_last_block_offset, got_last_block_md5);
		return FAIL;
	}

	/* All attributes present. Create log file list with one element. It will be used as the 'old log file list', */
	/* it does not need to be resizable. */
	*logfiles = (struct st_logfile *)zbx_malloc(NULL, sizeof(struct st_logfile));
	*logfiles_num = 1;

	(*logfiles)[0].filename = zbx_strdup(NULL, filename);
	(*logfiles)[0].mtime = mtime_tmp;
	(*logfiles)[0].seq = seq;
	(*logfiles)[0].retry = 0;
	(*logfiles)[0].incomplete = incomplete;
	(*logfiles)[0].copy_of = copy_of;
	(*logfiles)[0].dev = dev;
	(*logfiles)[0].ino_lo = ino_lo;
	(*logfiles)[0].ino_hi = ino_hi;
	(*logfiles)[0].size = size;
	(*logfiles)[0].processed_size = processed_size_tmp;

	(*logfiles)[0].md5_block_size = md5_block_size;
	memcpy((*logfiles)[0].first_block_md5, first_block_md5, sizeof(first_block_md5));

	(*logfiles)[0].last_block_offset = last_block_offset;
	memcpy((*logfiles)[0].last_block_md5, last_block_md5, sizeof(last_block_md5));

	*mtime = mtime_tmp;
	*processed_size = processed_size_tmp;

	return SUCCEED;
}
#endif	/* not WINDOWS */