/*
** Copyright (C) 2001-2026 Zabbix SIA
**
** This program is free software: you can redistribute it and/or modify it under the terms of
** the GNU Affero General Public License as published by the Free Software Foundation, version 3.
**
** 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 Affero General Public License for more details.
**
** You should have received a copy of the GNU Affero General Public License along with this program.
** If not, see <https://www.gnu.org/licenses/>.
**/

#include "vfs_file.h"
#include "../sysinfo.h"

#include "dir.h"

#include "zbxstr.h"
#include "zbxnum.h"
#include "zbxtime.h"
#include "zbxhash.h"
#include "zbxregexp.h"
#include "zbxalgo.h"
#include "zbxfile.h"
#include "zbxjson.h"

#if defined(_WINDOWS) || defined(__MINGW32__)
#include "aclapi.h"
#include "sddl.h"
#include "zbxwin32.h"
#include "zbxlog.h"
#include <sddl.h> /* ConvertSidToStringSid */
#endif


int	vfs_file_size(AGENT_REQUEST *request, AGENT_RESULT *result)
{
	zbx_stat_t	buf;
	char		*filename, *mode;
	int		ret = SYSINFO_RET_FAIL;

	if (2 < request->nparam)
	{
		SET_MSG_RESULT(result, zbx_strdup(NULL, "Too many parameters."));
		goto err;
	}

	filename = get_rparam(request, 0);
	mode = get_rparam(request, 1);

	if (NULL == filename || '\0' == *filename)
	{
		SET_MSG_RESULT(result, zbx_strdup(NULL, "Invalid first parameter."));
		goto err;
	}

	if (NULL != mode && 0 == strcmp(mode, "lines"))
	{
		ssize_t		nbytes;
		char		cbuf[MAX_BUFFER_LEN];
		zbx_uint64_t	lines_num = 0;
		int		f;
		double		ts = zbx_time();

		if (-1 == (f = zbx_open(filename, O_RDONLY)))
		{
			SET_MSG_RESULT(result, zbx_dsprintf(NULL, "Cannot open file: %s", zbx_strerror(errno)));
			goto err;
		}

		while (0 < (nbytes = read(f, cbuf, ARRSIZE(cbuf))))
		{
			char	*p1, *p2;
			size_t	sz = (size_t)nbytes, dif;

			if (sysinfo_get_config_timeout() < zbx_time() - ts)
			{
				SET_MSG_RESULT(result, zbx_strdup(NULL, "Timeout while processing item."));
				close(f);
				goto err;
			}

			p1 = cbuf;

			while (NULL != (p2 = memchr(p1, '\n', sz)))
			{
				lines_num++;
				dif = (size_t)(p2 - p1);
				p1 = p2;

				if (dif < sz)
				{
					sz -= dif + 1;
					p1++;
				}
			}
		}

		close(f);

		if (0 > nbytes)
		{
			SET_MSG_RESULT(result, zbx_dsprintf(NULL, "Cannot read from file: %s", zbx_strerror(errno)));
			goto err;
		}

		SET_UI64_RESULT(result, lines_num);
	}
	else if (NULL != mode && 0 != strcmp(mode, "bytes"))
	{
		SET_MSG_RESULT(result, zbx_strdup(NULL, "Invalid second parameter."));
		goto err;
	}
	else if (0 != zbx_stat(filename, &buf))
	{
		SET_MSG_RESULT(result, zbx_dsprintf(NULL, "Cannot obtain file information: %s", zbx_strerror(errno)));
		goto err;
	}
	else
		SET_UI64_RESULT(result, buf.st_size);

	ret = SYSINFO_RET_OK;
err:
	return ret;
}

int	vfs_file_time(AGENT_REQUEST *request, AGENT_RESULT *result)
{
	zbx_file_time_t	file_time;
	char		*filename, *type;
	int		ret = SYSINFO_RET_FAIL;

	if (2 < request->nparam)
	{
		SET_MSG_RESULT(result, zbx_strdup(NULL, "Too many parameters."));
		goto err;
	}

	filename = get_rparam(request, 0);
	type = get_rparam(request, 1);

	if (NULL == filename || '\0' == *filename)
	{
		SET_MSG_RESULT(result, zbx_strdup(NULL, "Invalid first parameter."));
		goto err;
	}

	if (SUCCEED != zbx_get_file_time(filename, 0, &file_time))
	{
		SET_MSG_RESULT(result, zbx_dsprintf(NULL, "Cannot obtain file information: %s", zbx_strerror(errno)));
		goto err;
	}

	if (NULL == type || '\0' == *type || 0 == strcmp(type, "modify"))	/* default parameter */
		SET_UI64_RESULT(result, file_time.modification_time);
	else if (0 == strcmp(type, "access"))
		SET_UI64_RESULT(result, file_time.access_time);
	else if (0 == strcmp(type, "change"))
		SET_UI64_RESULT(result, file_time.change_time);
	else
	{
		SET_MSG_RESULT(result, zbx_strdup(NULL, "Invalid second parameter."));
		goto err;
	}

	ret = SYSINFO_RET_OK;
err:
	return ret;
}

#if defined(_WINDOWS) || defined(__MINGW32__)
static int	vfs_file_exists_local(AGENT_REQUEST *request, AGENT_RESULT *result)
{
	const char	*filename;
	int		ret = SYSINFO_RET_FAIL, file_exists = 0, types, types_incl, types_excl;
	DWORD		file_attributes;
	wchar_t		*wpath;

	if (3 < request->nparam)
	{
		SET_MSG_RESULT(result, zbx_strdup(NULL, "Too many parameters."));
		goto err;
	}

	filename = get_rparam(request, 0);

	if (NULL == filename || '\0' == *filename)
	{
		SET_MSG_RESULT(result, zbx_strdup(NULL, "Invalid first parameter."));
		goto err;
	}

	if (FAIL == (types_incl = zbx_etypes_to_mask(get_rparam(request, 1), result)) ||
			FAIL == (types_excl = zbx_etypes_to_mask(get_rparam(request, 2), result)))
	{
		goto err;
	}

	if (0 == types_incl)
	{
		if (0 == types_excl)
			types_incl = ZBX_FT_FILE;
		else
			types_incl = ZBX_FT_ALLMASK;
	}

	types = types_incl & (~types_excl) & ZBX_FT_ALLMASK;

	if (NULL == (wpath = zbx_utf8_to_unicode(filename)))
	{
		SET_MSG_RESULT(result, zbx_strdup(NULL, "Cannot convert file name to UTF-16."));
		goto err;
	}

	file_attributes = GetFileAttributesW(wpath);
	zbx_free(wpath);

	if (INVALID_FILE_ATTRIBUTES == file_attributes)
	{
		DWORD	error;

		switch (error = GetLastError())
		{
			case ERROR_FILE_NOT_FOUND:
				goto exit;
			case ERROR_BAD_NETPATH:	/* special case from GetFileAttributesW() documentation */
				SET_MSG_RESULT(result, zbx_dsprintf(NULL, "The specified file is a network share."
						" Use a path to a subfolder on that share."));
				goto err;
			default:
				SET_MSG_RESULT(result, zbx_dsprintf(NULL, "Cannot obtain file information: %s",
						zbx_strerror_from_system(error)));
				goto err;
		}
	}

	switch (file_attributes & (FILE_ATTRIBUTE_REPARSE_POINT | FILE_ATTRIBUTE_DIRECTORY))
	{
		case FILE_ATTRIBUTE_REPARSE_POINT | FILE_ATTRIBUTE_DIRECTORY:
			if (0 != (types & ZBX_FT_SYM) || 0 != (types & ZBX_FT_DIR))
				file_exists = 1;
			break;
		case FILE_ATTRIBUTE_REPARSE_POINT:
						/* not a symlink directory => symlink regular file*/
						/* counting symlink files as MS explorer */
			if (0 != (types & ZBX_FT_FILE))
				file_exists = 1;
			break;
		case FILE_ATTRIBUTE_DIRECTORY:
			if (0 != (types & ZBX_FT_DIR))
				file_exists = 1;
			break;
		default:	/* not a directory => regular file */
			if (0 != (types & ZBX_FT_FILE))
				file_exists = 1;
	}
exit:
	SET_UI64_RESULT(result, file_exists);
	ret = SYSINFO_RET_OK;
err:
	return ret;
}
#else /* not _WINDOWS or __MINGW32__ */
static int	vfs_file_exists_local(AGENT_REQUEST *request, AGENT_RESULT *result)
{
	zbx_stat_t	buf;
	const char	*filename;
	int		types = 0, types_incl, types_excl;

	if (3 < request->nparam)
	{
		SET_MSG_RESULT(result, zbx_strdup(NULL, "Too many parameters."));
		return SYSINFO_RET_FAIL;
	}

	filename = get_rparam(request, 0);

	if (NULL == filename || '\0' == *filename)
	{
		SET_MSG_RESULT(result, zbx_strdup(NULL, "Invalid first parameter."));
		return SYSINFO_RET_FAIL;
	}

	if (FAIL == (types_incl = zbx_etypes_to_mask(get_rparam(request, 1), result)) ||
			FAIL == (types_excl = zbx_etypes_to_mask(get_rparam(request, 2), result)))
	{
		return SYSINFO_RET_FAIL;
	}

	if (0 == types_incl)
	{
		if (0 == types_excl)
			types_incl = ZBX_FT_FILE;
		else
			types_incl = ZBX_FT_ALLMASK;
	}

	if (0 != (types_incl & ZBX_FT_SYM) || 0 != (types_excl & ZBX_FT_SYM))
	{
		if (0 == lstat(filename, &buf))
		{
			if (0 != S_ISLNK(buf.st_mode))
				types |= ZBX_FT_SYM;
		}
		else if (ENOENT != errno)
		{
			SET_MSG_RESULT(result, zbx_dsprintf(NULL, "Cannot obtain file information: %s",
					zbx_strerror(errno)));
			return SYSINFO_RET_FAIL;
		}
	}

	if (0 == zbx_stat(filename, &buf))
	{
		if (0 != S_ISREG(buf.st_mode))
			types |= ZBX_FT_FILE;
		else if (0 != S_ISDIR(buf.st_mode))
			types |= ZBX_FT_DIR;
		else if (0 != S_ISSOCK(buf.st_mode))
			types |= ZBX_FT_SOCK;
		else if (0 != S_ISBLK(buf.st_mode))
			types |= ZBX_FT_BDEV;
		else if (0 != S_ISCHR(buf.st_mode))
			types |= ZBX_FT_CDEV;
		else if (0 != S_ISFIFO(buf.st_mode))
			types |= ZBX_FT_FIFO;
	}
	else if (ENOENT != errno)
	{
		SET_MSG_RESULT(result, zbx_dsprintf(NULL, "Cannot obtain file information: %s", zbx_strerror(errno)));
		return SYSINFO_RET_FAIL;
	}

	if (0 == (types & types_excl) && 0 != (types & types_incl))
		SET_UI64_RESULT(result, 1);
	else
		SET_UI64_RESULT(result, 0);

	return SYSINFO_RET_OK;
}
#endif

int	vfs_file_exists(AGENT_REQUEST *request, AGENT_RESULT *result)
{
	return vfs_file_exists_local(request, result);
}

int	vfs_file_contents(AGENT_REQUEST *request, AGENT_RESULT *result)
{
#define ZBX_MAX_VFS_FILE_SIZE	16 * ZBX_MEBIBYTE	/* file contents larger than 64 KB will be truncated in the database */
	char		*filename, *tmp, encoding[32];
	char		read_buf[MAX_BUFFER_LEN], *utf8, *contents = NULL;
	size_t		contents_alloc = 0, contents_offset = 0;
	int		nbytes, flen, f = -1, ret = SYSINFO_RET_FAIL;
	zbx_stat_t	stat_buf;
	double		ts = zbx_time();

	if (2 < request->nparam)
	{
		SET_MSG_RESULT(result, zbx_strdup(NULL, "Too many parameters."));
		goto err;
	}

	filename = get_rparam(request, 0);
	tmp = get_rparam(request, 1);

	if (NULL == tmp)
		*encoding = '\0';
	else
		zbx_strscpy(encoding, tmp);

	if (NULL == filename || '\0' == *filename)
	{
		SET_MSG_RESULT(result, zbx_strdup(NULL, "Invalid first parameter."));
		goto err;
	}

	if (-1 == (f = zbx_open(filename, O_RDONLY)))
	{
		SET_MSG_RESULT(result, zbx_dsprintf(NULL, "Cannot open file: %s", zbx_strerror(errno)));
		goto err;
	}

	if (sysinfo_get_config_timeout() < zbx_time() - ts)
	{
		SET_MSG_RESULT(result, zbx_strdup(NULL, "Timeout while processing item."));
		goto err;
	}

	if (0 != zbx_fstat(f, &stat_buf))
	{
		SET_MSG_RESULT(result, zbx_dsprintf(NULL, "Cannot obtain file information: %s", zbx_strerror(errno)));
		goto err;
	}

	if (ZBX_MAX_VFS_FILE_SIZE < stat_buf.st_size)
	{
		SET_MSG_RESULT(result, zbx_strdup(NULL, "File is too large for this check."));
		goto err;
	}

	if (sysinfo_get_config_timeout() < zbx_time() - ts)
	{
		SET_MSG_RESULT(result, zbx_strdup(NULL, "Timeout while processing item."));
		goto err;
	}

	flen = 0;

	while (0 < (nbytes = read(f, read_buf, sizeof(read_buf))))
	{
		if (sysinfo_get_config_timeout() < zbx_time() - ts)
		{
			SET_MSG_RESULT(result, zbx_strdup(NULL, "Timeout while processing item."));
			zbx_free(contents);
			goto err;
		}

		if (ZBX_MAX_VFS_FILE_SIZE < (flen += nbytes))
		{
			SET_MSG_RESULT(result, zbx_strdup(NULL, "File is too large for this check."));
			zbx_free(contents);
			goto err;
		}

		zbx_str_memcpy_alloc(&contents, &contents_alloc, &contents_offset, read_buf, nbytes);
	}

	if (-1 == nbytes)	/* error occurred */
	{
		SET_MSG_RESULT(result, zbx_strdup(NULL, "Cannot read from file."));
		zbx_free(contents);
		goto err;
	}

	if (NULL != contents)
	{
		char	*err_msg;

		if (NULL == (utf8 = zbx_convert_to_utf8(contents, contents_offset, encoding, &err_msg)))
		{
			zbx_free(contents);
			SET_MSG_RESULT(result, err_msg);
			goto err;
		}

		zbx_free(contents);
		zbx_rtrim_utf8(utf8, "\r\n");

		SET_TEXT_RESULT(result, utf8);
	}
	else
		SET_TEXT_RESULT(result, zbx_strdup(NULL, ""));

	ret = SYSINFO_RET_OK;
err:
	if (-1 != f)
		close(f);

	return ret;
#undef ZBX_MAX_VFS_FILE_SIZE
}

int	vfs_file_regexp(AGENT_REQUEST *request, AGENT_RESULT *result)
{
	char		*filename, *regexp, encoding[32], *output, *start_line_str, *end_line_str, buf[MAX_BUFFER_LEN],
			*utf8, *tmp, *ptr = NULL, *line;
	int		f = -1, ret = SYSINFO_RET_FAIL;
	zbx_uint32_t	start_line, end_line, current_line = 0;
	double		ts = zbx_time();
	ssize_t		nbytes;
	void		*saveptr = NULL;

	if (6 < request->nparam)
	{
		SET_MSG_RESULT(result, zbx_strdup(NULL, "Too many parameters."));
		goto err;
	}

	filename = get_rparam(request, 0);
	regexp = get_rparam(request, 1);
	tmp = get_rparam(request, 2);
	start_line_str = get_rparam(request, 3);
	end_line_str = get_rparam(request, 4);
	output = get_rparam(request, 5);

	if (NULL == filename || '\0' == *filename)
	{
		SET_MSG_RESULT(result, zbx_strdup(NULL, "Invalid first parameter."));
		goto err;
	}

	if (NULL == regexp || '\0' == *regexp)
	{
		SET_MSG_RESULT(result, zbx_strdup(NULL, "Invalid second parameter."));
		goto err;
	}

	if (NULL == tmp)
		*encoding = '\0';
	else
		zbx_strscpy(encoding, tmp);

	if (NULL == start_line_str || '\0' == *start_line_str)
		start_line = 0;
	else if (FAIL == zbx_is_uint32(start_line_str, &start_line))
	{
		SET_MSG_RESULT(result, zbx_strdup(NULL, "Invalid fourth parameter."));
		goto err;
	}

	if (NULL == end_line_str || '\0' == *end_line_str)
		end_line = 0xffffffff;
	else if (FAIL == zbx_is_uint32(end_line_str, &end_line))
	{
		SET_MSG_RESULT(result, zbx_strdup(NULL, "Invalid fifth parameter."));
		goto err;
	}

	if (start_line > end_line)
	{
		SET_MSG_RESULT(result, zbx_strdup(NULL, "Start line parameter must not exceed end line."));
		goto err;
	}

	if (-1 == (f = zbx_open(filename, O_RDONLY)))
	{
		SET_MSG_RESULT(result, zbx_dsprintf(NULL, "Cannot open file: %s", zbx_strerror(errno)));
		goto err;
	}

	if (sysinfo_get_config_timeout() < zbx_time() - ts)
	{
		SET_MSG_RESULT(result, zbx_strdup(NULL, "Timeout while processing item."));
		goto err;
	}

	while (0 < (nbytes = zbx_buf_readln(f, buf, sizeof(buf), encoding, &line, &saveptr)))
	{
		char	*err_msg;

		if (sysinfo_get_config_timeout() < zbx_time() - ts)
		{
			SET_MSG_RESULT(result, zbx_strdup(NULL, "Timeout while processing item."));
			goto err;
		}

		if (++current_line < start_line)
			continue;

		if (NULL == (utf8 = zbx_convert_to_utf8(line, nbytes, encoding, &err_msg)))
		{
			SET_MSG_RESULT(result, err_msg);
			goto err;
		}

		zbx_rtrim(utf8, "\r\n");
		zbx_regexp_sub(utf8, regexp, output, &ptr);
		zbx_free(utf8);

		if (NULL != ptr)
		{
			SET_STR_RESULT(result, ptr);
			break;
		}

		if (current_line >= end_line)
		{
			/* force EOF state */
			nbytes = 0;
			break;
		}
	}

	if (ZBX_READ_WRONG_ENCODING == nbytes)
	{
		SET_MSG_RESULT(result, zbx_strdup(NULL, "Cannot read from file. Wrong encoding detected."));
		goto err;
	}
	else if (ZBX_READ_ERR == nbytes)	/* general error occurred */
	{
		SET_MSG_RESULT(result, zbx_strdup(NULL, "Cannot read from file."));
		goto err;
	}

	if (0 == nbytes)	/* EOF */
		SET_STR_RESULT(result, zbx_strdup(NULL, ""));

	ret = SYSINFO_RET_OK;
err:
	zbx_free(saveptr);

	if (-1 != f)
		close(f);

	return ret;
}

int	vfs_file_regmatch(AGENT_REQUEST *request, AGENT_RESULT *result)
{
	char		*filename, *regexp, *tmp, encoding[32], *line, buf[MAX_BUFFER_LEN], *utf8, *start_line_str,
			*end_line_str;
	int		res, f = -1, ret = SYSINFO_RET_FAIL;
	zbx_uint32_t	start_line, end_line, current_line = 0;
	double		ts = zbx_time();
	ssize_t		nbytes;
	void		*saveptr = NULL;

	if (5 < request->nparam)
	{
		SET_MSG_RESULT(result, zbx_strdup(NULL, "Too many parameters."));
		goto err;
	}

	filename = get_rparam(request, 0);
	regexp = get_rparam(request, 1);
	tmp = get_rparam(request, 2);
	start_line_str = get_rparam(request, 3);
	end_line_str = get_rparam(request, 4);

	if (NULL == filename || '\0' == *filename)
	{
		SET_MSG_RESULT(result, zbx_strdup(NULL, "Invalid first parameter."));
		goto err;
	}

	if (NULL == regexp || '\0' == *regexp)
	{
		SET_MSG_RESULT(result, zbx_strdup(NULL, "Invalid second parameter."));
		goto err;
	}

	if (NULL == tmp)
		*encoding = '\0';
	else
		zbx_strscpy(encoding, tmp);

	if (NULL == start_line_str || '\0' == *start_line_str)
		start_line = 0;
	else if (FAIL == zbx_is_uint32(start_line_str, &start_line))
	{
		SET_MSG_RESULT(result, zbx_strdup(NULL, "Invalid fourth parameter."));
		goto err;
	}

	if (NULL == end_line_str || '\0' == *end_line_str)
		end_line = 0xffffffff;
	else if (FAIL == zbx_is_uint32(end_line_str, &end_line))
	{
		SET_MSG_RESULT(result, zbx_strdup(NULL, "Invalid fifth parameter."));
		goto err;
	}

	if (start_line > end_line)
	{
		SET_MSG_RESULT(result, zbx_strdup(NULL, "Start line must not exceed end line."));
		goto err;
	}

	if (-1 == (f = zbx_open(filename, O_RDONLY)))
	{
		SET_MSG_RESULT(result, zbx_dsprintf(NULL, "Cannot open file: %s", zbx_strerror(errno)));
		goto err;
	}

	if (sysinfo_get_config_timeout() < zbx_time() - ts)
	{
		SET_MSG_RESULT(result, zbx_strdup(NULL, "Timeout while processing item."));
		goto err;
	}

	res = 0;

	while (0 == res && 0 < (nbytes = zbx_buf_readln(f, buf, sizeof(buf), encoding, &line, &saveptr)))
	{
		char	*err_msg;

		if (sysinfo_get_config_timeout() < zbx_time() - ts)
		{
			SET_MSG_RESULT(result, zbx_strdup(NULL, "Timeout while processing item."));
			goto err;
		}

		if (++current_line < start_line)
			continue;

		if (NULL == (utf8 = zbx_convert_to_utf8(line, nbytes, encoding, &err_msg)))
		{
			SET_MSG_RESULT(result, err_msg);
			goto err;
		}

		zbx_rtrim(utf8, "\r\n");
		if (NULL != zbx_regexp_match(utf8, regexp, NULL))
			res = 1;
		zbx_free(utf8);

		if (current_line >= end_line)
			break;
	}

	if (ZBX_READ_WRONG_ENCODING == nbytes)
	{
		SET_MSG_RESULT(result, zbx_strdup(NULL, "Cannot read from file. Wrong encoding detected."));
		goto err;
	}
	else if (ZBX_READ_ERR == nbytes)	/* error occurred */
	{
		SET_MSG_RESULT(result, zbx_strdup(NULL, "Cannot read from file."));
		goto err;
	}

	SET_UI64_RESULT(result, res);

	ret = SYSINFO_RET_OK;
err:
	zbx_free(saveptr);

	if (-1 != f)
		close(f);

	return ret;
}

static int	vfs_file_cksum_md5(char *filename, AGENT_RESULT *result)
{
	int		nbytes, f, ret = SYSINFO_RET_FAIL;
	md5_state_t	state;
	u_char		buf[16 * ZBX_KIBIBYTE];
	char		*hash_text = NULL;
	size_t		sz;
	md5_byte_t	hash[ZBX_MD5_DIGEST_SIZE];
	double		ts = zbx_time();

	if (-1 == (f = zbx_open(filename, O_RDONLY)))
	{
		SET_MSG_RESULT(result, zbx_dsprintf(NULL, "Cannot open file: %s", zbx_strerror(errno)));
		goto err;
	}

	if (sysinfo_get_config_timeout() < zbx_time() - ts)
	{
		SET_MSG_RESULT(result, zbx_strdup(NULL, "Timeout while processing item."));
		goto err;
	}

	zbx_md5_init(&state);

	while (0 < (nbytes = (int)read(f, buf, sizeof(buf))))
	{
		if (sysinfo_get_config_timeout() < zbx_time() - ts)
		{
			SET_MSG_RESULT(result, zbx_strdup(NULL, "Timeout while processing item."));
			goto err;
		}

		zbx_md5_append(&state, (const md5_byte_t *)buf, nbytes);
	}

	zbx_md5_finish(&state, hash);

	if (0 > nbytes)
	{
		SET_MSG_RESULT(result, zbx_strdup(NULL, "Cannot read from file."));
		goto err;
	}

	/* convert MD5 hash to text form */

	sz = ZBX_MD5_DIGEST_SIZE * 2 + 1;
	hash_text = (char *)zbx_malloc(hash_text, sz);

	for (int i = 0; i < ZBX_MD5_DIGEST_SIZE; i++)
		zbx_snprintf(&hash_text[i << 1], sz - (i << 1), "%02x", hash[i]);

	SET_STR_RESULT(result, hash_text);

	ret = SYSINFO_RET_OK;
err:
	if (-1 != f)
		close(f);

	return ret;
}

int	vfs_file_md5sum(AGENT_REQUEST *request, AGENT_RESULT *result)
{
	char		*filename;

	if (1 < request->nparam)
	{
		SET_MSG_RESULT(result, zbx_strdup(NULL, "Too many parameters."));
		return SYSINFO_RET_FAIL;
	}

	filename = get_rparam(request, 0);

	if (NULL == filename || '\0' == *filename)
	{
		SET_MSG_RESULT(result, zbx_strdup(NULL, "Invalid first parameter."));
		return SYSINFO_RET_FAIL;
	}

	return vfs_file_cksum_md5(filename, result);
}

static u_long	crctab[] =
{
	0x0,
	0x04c11db7, 0x09823b6e, 0x0d4326d9, 0x130476dc, 0x17c56b6b,
	0x1a864db2, 0x1e475005, 0x2608edb8, 0x22c9f00f, 0x2f8ad6d6,
	0x2b4bcb61, 0x350c9b64, 0x31cd86d3, 0x3c8ea00a, 0x384fbdbd,
	0x4c11db70, 0x48d0c6c7, 0x4593e01e, 0x4152fda9, 0x5f15adac,
	0x5bd4b01b, 0x569796c2, 0x52568b75, 0x6a1936c8, 0x6ed82b7f,
	0x639b0da6, 0x675a1011, 0x791d4014, 0x7ddc5da3, 0x709f7b7a,
	0x745e66cd, 0x9823b6e0, 0x9ce2ab57, 0x91a18d8e, 0x95609039,
	0x8b27c03c, 0x8fe6dd8b, 0x82a5fb52, 0x8664e6e5, 0xbe2b5b58,
	0xbaea46ef, 0xb7a96036, 0xb3687d81, 0xad2f2d84, 0xa9ee3033,
	0xa4ad16ea, 0xa06c0b5d, 0xd4326d90, 0xd0f37027, 0xddb056fe,
	0xd9714b49, 0xc7361b4c, 0xc3f706fb, 0xceb42022, 0xca753d95,
	0xf23a8028, 0xf6fb9d9f, 0xfbb8bb46, 0xff79a6f1, 0xe13ef6f4,
	0xe5ffeb43, 0xe8bccd9a, 0xec7dd02d, 0x34867077, 0x30476dc0,
	0x3d044b19, 0x39c556ae, 0x278206ab, 0x23431b1c, 0x2e003dc5,
	0x2ac12072, 0x128e9dcf, 0x164f8078, 0x1b0ca6a1, 0x1fcdbb16,
	0x018aeb13, 0x054bf6a4, 0x0808d07d, 0x0cc9cdca, 0x7897ab07,
	0x7c56b6b0, 0x71159069, 0x75d48dde, 0x6b93dddb, 0x6f52c06c,
	0x6211e6b5, 0x66d0fb02, 0x5e9f46bf, 0x5a5e5b08, 0x571d7dd1,
	0x53dc6066, 0x4d9b3063, 0x495a2dd4, 0x44190b0d, 0x40d816ba,
	0xaca5c697, 0xa864db20, 0xa527fdf9, 0xa1e6e04e, 0xbfa1b04b,
	0xbb60adfc, 0xb6238b25, 0xb2e29692, 0x8aad2b2f, 0x8e6c3698,
	0x832f1041, 0x87ee0df6, 0x99a95df3, 0x9d684044, 0x902b669d,
	0x94ea7b2a, 0xe0b41de7, 0xe4750050, 0xe9362689, 0xedf73b3e,
	0xf3b06b3b, 0xf771768c, 0xfa325055, 0xfef34de2, 0xc6bcf05f,
	0xc27dede8, 0xcf3ecb31, 0xcbffd686, 0xd5b88683, 0xd1799b34,
	0xdc3abded, 0xd8fba05a, 0x690ce0ee, 0x6dcdfd59, 0x608edb80,
	0x644fc637, 0x7a089632, 0x7ec98b85, 0x738aad5c, 0x774bb0eb,
	0x4f040d56, 0x4bc510e1, 0x46863638, 0x42472b8f, 0x5c007b8a,
	0x58c1663d, 0x558240e4, 0x51435d53, 0x251d3b9e, 0x21dc2629,
	0x2c9f00f0, 0x285e1d47, 0x36194d42, 0x32d850f5, 0x3f9b762c,
	0x3b5a6b9b, 0x0315d626, 0x07d4cb91, 0x0a97ed48, 0x0e56f0ff,
	0x1011a0fa, 0x14d0bd4d, 0x19939b94, 0x1d528623, 0xf12f560e,
	0xf5ee4bb9, 0xf8ad6d60, 0xfc6c70d7, 0xe22b20d2, 0xe6ea3d65,
	0xeba91bbc, 0xef68060b, 0xd727bbb6, 0xd3e6a601, 0xdea580d8,
	0xda649d6f, 0xc423cd6a, 0xc0e2d0dd, 0xcda1f604, 0xc960ebb3,
	0xbd3e8d7e, 0xb9ff90c9, 0xb4bcb610, 0xb07daba7, 0xae3afba2,
	0xaafbe615, 0xa7b8c0cc, 0xa379dd7b, 0x9b3660c6, 0x9ff77d71,
	0x92b45ba8, 0x9675461f, 0x8832161a, 0x8cf30bad, 0x81b02d74,
	0x857130c3, 0x5d8a9099, 0x594b8d2e, 0x5408abf7, 0x50c9b640,
	0x4e8ee645, 0x4a4ffbf2, 0x470cdd2b, 0x43cdc09c, 0x7b827d21,
	0x7f436096, 0x7200464f, 0x76c15bf8, 0x68860bfd, 0x6c47164a,
	0x61043093, 0x65c52d24, 0x119b4be9, 0x155a565e, 0x18197087,
	0x1cd86d30, 0x029f3d35, 0x065e2082, 0x0b1d065b, 0x0fdc1bec,
	0x3793a651, 0x3352bbe6, 0x3e119d3f, 0x3ad08088, 0x2497d08d,
	0x2056cd3a, 0x2d15ebe3, 0x29d4f654, 0xc5a92679, 0xc1683bce,
	0xcc2b1d17, 0xc8ea00a0, 0xd6ad50a5, 0xd26c4d12, 0xdf2f6bcb,
	0xdbee767c, 0xe3a1cbc1, 0xe760d676, 0xea23f0af, 0xeee2ed18,
	0xf0a5bd1d, 0xf464a0aa, 0xf9278673, 0xfde69bc4, 0x89b8fd09,
	0x8d79e0be, 0x803ac667, 0x84fbdbd0, 0x9abc8bd5, 0x9e7d9662,
	0x933eb0bb, 0x97ffad0c, 0xafb010b1, 0xab710d06, 0xa6322bdf,
	0xa2f33668, 0xbcb4666d, 0xb8757bda, 0xb5365d03, 0xb1f740b4
};

static int	vfs_file_cksum_crc32(char *filename, AGENT_RESULT *result)
{
	int		i, nr, f, ret = SYSINFO_RET_FAIL;
	zbx_uint32_t	crc, flen;
	u_char		buf[16 * ZBX_KIBIBYTE];
	u_long		cval;
	double		ts;

	ts = zbx_time();

	if (-1 == (f = zbx_open(filename, O_RDONLY)))
	{
		SET_MSG_RESULT(result, zbx_dsprintf(NULL, "Cannot open file: %s", zbx_strerror(errno)));
		goto err;
	}

	if (sysinfo_get_config_timeout() < zbx_time() - ts)
	{
		SET_MSG_RESULT(result, zbx_strdup(NULL, "Timeout while processing item."));
		goto err;
	}

	crc = flen = 0;

	while (0 < (nr = (int)read(f, buf, sizeof(buf))))
	{
		if (sysinfo_get_config_timeout() < zbx_time() - ts)
		{
			SET_MSG_RESULT(result, zbx_strdup(NULL, "Timeout while processing item."));
			goto err;
		}

		flen += nr;

		for (i = 0; i < nr; i++)
			crc = (crc << 8) ^ crctab[((crc >> 24) ^ buf[i]) & 0xff];
	}

	if (0 > nr)
	{
		SET_MSG_RESULT(result, zbx_strdup(NULL, "Cannot read from file."));
		goto err;
	}

	/* include the length of the file */
	for (; 0 != flen; flen >>= 8)
		crc = (crc << 8) ^ crctab[((crc >> 24) ^ flen) & 0xff];

	cval = ~crc;

	SET_UI64_RESULT(result, cval);

	ret = SYSINFO_RET_OK;
err:
	if (-1 != f)
		close(f);

	return ret;
}

static int	vfs_file_cksum_sha256(char *filename, AGENT_RESULT *result)
{
	/* On HP-UX zbx_sha256_finish() requires buffer specified in 2nd argument to */
	/* be uint32_t-aligned to avoid crash (ZBX-23471). */
	typedef union	{
		zbx_uint32_t	ui[ZBX_SHA256_DIGEST_SIZE / sizeof(zbx_uint32_t)];
		char		ch[ZBX_SHA256_DIGEST_SIZE];
	} aligned_buf_t;

	int		i, f, ret = SYSINFO_RET_FAIL;
	char		buf[16 * ZBX_KIBIBYTE];
	aligned_buf_t	hash_res;
	char		hash_res_stringhexes[ZBX_SHA256_DIGEST_SIZE * 2 + 1];
	double		ts;
	ssize_t		nr;
	sha256_ctx	ctx;

	ts = zbx_time();

	if (-1 == (f = zbx_open(filename, O_RDONLY)))
	{
		SET_MSG_RESULT(result, zbx_dsprintf(NULL, "Cannot open file: %s", zbx_strerror(errno)));
		goto err;
	}

	if (sysinfo_get_config_timeout() < zbx_time() - ts)
	{
		SET_MSG_RESULT(result, zbx_strdup(NULL, "Timeout while processing item."));
		goto err;
	}

	zbx_sha256_init(&ctx);

	while (0 < (nr = read(f, buf, sizeof(buf))))
	{
		if (sysinfo_get_config_timeout() < zbx_time() - ts)
		{
			SET_MSG_RESULT(result, zbx_strdup(NULL, "Timeout while processing item."));
			goto err;
		}

		zbx_sha256_process_bytes(buf, (size_t)nr, &ctx);
	}

	if (0 > nr)
	{
		SET_MSG_RESULT(result, zbx_strdup(NULL, "Cannot read from file."));
		goto err;
	}

	zbx_sha256_finish(&ctx, hash_res.ch);

	for (i = 0 ; i < ZBX_SHA256_DIGEST_SIZE; i++)
	{
		char z[3];

		zbx_snprintf(z, 3, "%02x", (unsigned char)hash_res.ch[i]);
		hash_res_stringhexes[i * 2] = z[0];
		hash_res_stringhexes[i * 2 + 1] = z[1];
	}

	hash_res_stringhexes[ZBX_SHA256_DIGEST_SIZE * 2] = '\0';

	SET_STR_RESULT(result, zbx_strdup(NULL, hash_res_stringhexes));

	ret = SYSINFO_RET_OK;
err:
	if (-1 != f)
		close(f);

	return ret;
}

/******************************************************************************
 *                                                                            *
 * Comments: computes POSIX 1003.2 checksum                                   *
 *                                                                            *
 ******************************************************************************/
int	vfs_file_cksum(AGENT_REQUEST *request, AGENT_RESULT *result)
{
	char	*filename, *method;
	int	ret = SYSINFO_RET_FAIL;

	if (2 < request->nparam)
	{
		SET_MSG_RESULT(result, zbx_strdup(NULL, "Too many parameters."));
		goto err;
	}

	filename = get_rparam(request, 0);
	method = get_rparam(request, 1);

	if (NULL == filename || '\0' == *filename)
	{
		SET_MSG_RESULT(result, zbx_strdup(NULL, "Invalid first parameter."));
		goto err;
	}

	if (NULL == method || '\0' == *method || 0 == strcmp(method, "crc32"))
		ret = vfs_file_cksum_crc32(filename, result);
	else if (0 == strcmp(method, "md5"))
		ret = vfs_file_cksum_md5(filename, result);
	else if (0 == strcmp(method, "sha256"))
		ret = vfs_file_cksum_sha256(filename, result);
	else
		SET_MSG_RESULT(result, zbx_strdup(NULL, "Invalid second parameter."));
err:
	return ret;
}

#if defined(_WINDOWS) || defined(__MINGW32__)
int	vfs_file_owner(AGENT_REQUEST *request, AGENT_RESULT *result)
{
	char			*filename, *ownertype, *resulttype;
	int			ret = SYSINFO_RET_FAIL;
	wchar_t			*wpath;
	PSECURITY_DESCRIPTOR	sec = NULL;
	PSID			sid = NULL;

	if (3 < request->nparam)
	{
		SET_MSG_RESULT(result, zbx_strdup(NULL, "Too many parameters."));
		goto err;
	}

	filename = get_rparam(request, 0);
	ownertype = get_rparam(request, 1);
	resulttype = get_rparam(request, 2);

	if (NULL == filename || '\0' == *filename || NULL == (wpath = zbx_utf8_to_unicode(filename)))
	{
		SET_MSG_RESULT(result, zbx_strdup(NULL, "Invalid first parameter."));
		goto err;
	}

	if (NULL != ownertype && '\0' != *ownertype && 0 != strcmp(ownertype, "user"))
	{
		SET_MSG_RESULT(result, zbx_strdup(NULL, "Invalid second parameter."));
		goto err;
	}

	if (NULL != resulttype && '\0' != *resulttype &&
			(0 != strcmp(resulttype, "id") && 0 != strcmp(resulttype, "name")))
	{
		SET_MSG_RESULT(result, zbx_strdup(NULL, "Invalid third parameter."));
		goto err;
	}

	if (ERROR_SUCCESS != GetNamedSecurityInfo(wpath, SE_FILE_OBJECT, OWNER_SECURITY_INFORMATION, &sid, NULL, NULL,
			NULL, &sec) || NULL == sid)
	{
		SET_MSG_RESULT(result, zbx_strdup(NULL, "Cannot obtain security information."));
		goto err;
	}

	if (NULL != resulttype && 0 == strcmp(resulttype, "id"))
	{
		wchar_t	*sid_string = NULL;

		if (TRUE != ConvertSidToStringSid(sid, &sid_string))
		{
			SET_MSG_RESULT(result, zbx_strdup(NULL, "Cannot obtain SID."));
			goto err;
		}

		SET_STR_RESULT(result, zbx_unicode_to_utf8(sid_string));
		LocalFree(sid_string);
	}
	else
	{
		DWORD		acc_sz = 0, dmn_sz = 0;
		wchar_t		*acc_name = NULL, *dmn_name = NULL;
		SID_NAME_USE	acc_type = SidTypeUnknown;
		char		*acc_utf8, *dmn_ut8;

		LookupAccountSid(NULL, sid, acc_name, (LPDWORD)&acc_sz, dmn_name, (LPDWORD)&dmn_sz, &acc_type);

		acc_name = (wchar_t *)zbx_malloc(acc_name, acc_sz * sizeof(wchar_t));
		dmn_name = (wchar_t *)zbx_malloc(dmn_name, dmn_sz * sizeof(wchar_t));

		if (TRUE != LookupAccountSid(NULL, sid, acc_name, (LPDWORD)&acc_sz, dmn_name, (LPDWORD)&dmn_sz,
				&acc_type))
		{
			SET_MSG_RESULT(result, zbx_strdup(NULL, "Cannot obtain user name."));
			zbx_free(acc_name);
			zbx_free(dmn_name);
			goto err;
		}

		acc_utf8 = (zbx_unicode_to_utf8(acc_name));
		dmn_ut8 = (zbx_unicode_to_utf8(dmn_name));

		if (0 == strlen(dmn_ut8))
			SET_STR_RESULT(result, zbx_dsprintf(NULL, "%s", acc_utf8));
		else
			SET_STR_RESULT(result, zbx_dsprintf(NULL, "%s\\%s", dmn_ut8, acc_utf8));

		zbx_free(acc_name);
		zbx_free(dmn_name);
		zbx_free(acc_utf8);
		zbx_free(dmn_ut8);
	}

	ret = SYSINFO_RET_OK;
err:
	if (NULL != sec)
		LocalFree(sec);

	return ret;
}
#else
int	vfs_file_owner(AGENT_REQUEST *request, AGENT_RESULT *result)
{
	char		*filename, *ownertype, *resulttype;
	int		ret = SYSINFO_RET_FAIL, type;
	zbx_stat_t	st;

	if (3 < request->nparam)
	{
		SET_MSG_RESULT(result, zbx_strdup(NULL, "Too many parameters."));
		goto err;
	}

	filename = get_rparam(request, 0);
	ownertype = get_rparam(request, 1);
	resulttype = get_rparam(request, 2);

	if (NULL == filename || '\0' == *filename)
	{
		SET_MSG_RESULT(result, zbx_strdup(NULL, "Invalid first parameter."));
		goto err;
	}

	if (0 != lstat(filename, &st))
	{
		SET_MSG_RESULT(result, zbx_dsprintf(NULL, "Cannot obtain file information: %s", zbx_strerror(errno)));
		goto err;
	}

	if (NULL != ownertype && 0 == strcmp(ownertype, "group"))
	{
		type = 1;
	}
	else if (NULL == ownertype || '\0' == *ownertype || 0 == strcmp(ownertype, "user"))
	{
		type = 0;
	}
	else
	{
		SET_MSG_RESULT(result, zbx_strdup(NULL, "Invalid second parameter."));
		goto err;
	}

	if (NULL != resulttype && 0 == strcmp(resulttype, "id"))
	{
		if (1 == type)
			SET_STR_RESULT(result, zbx_dsprintf(NULL, ZBX_FS_UI64, (zbx_uint64_t)st.st_gid));
		else
			SET_STR_RESULT(result, zbx_dsprintf(NULL, ZBX_FS_UI64, (zbx_uint64_t)st.st_uid));
	}
	else if (NULL == resulttype || '\0' == *resulttype || 0 == strcmp(resulttype, "name"))
	{
		if (1 == type)
		{
			struct group	*grp;

			if (NULL == (grp = getgrgid(st.st_gid)))
				SET_STR_RESULT(result, zbx_dsprintf(NULL, ZBX_FS_UI64, (zbx_uint64_t)st.st_gid));
			else
				SET_STR_RESULT(result, zbx_strdup(NULL, grp->gr_name));
		}
		else
		{
			struct passwd	*pwd;

			if (NULL == (pwd = getpwuid(st.st_uid)))
				SET_STR_RESULT(result, zbx_dsprintf(NULL, ZBX_FS_UI64, (zbx_uint64_t)st.st_uid));
			else
				SET_STR_RESULT(result, zbx_strdup(NULL, pwd->pw_name));
		}
	}
	else
	{
		SET_MSG_RESULT(result, zbx_strdup(NULL, "Invalid third parameter."));
		goto err;
	}

	ret = SYSINFO_RET_OK;
err:
	return ret;
}
#endif

#if defined(_WINDOWS) || defined(__MINGW32__)
int	vfs_file_permissions(AGENT_REQUEST *request, AGENT_RESULT *result)
{
	ZBX_UNUSED(request);
	SET_MSG_RESULT(result, zbx_strdup(NULL, "Item is not supported on Windows."));

	return SYSINFO_RET_FAIL;
}
#else
static char	*get_file_permissions(zbx_stat_t *st)
{
	return zbx_dsprintf(NULL, "%x%x%x%x", ((S_ISUID | S_ISGID | S_ISVTX) & st->st_mode) >> 9,
				(S_IRWXU & st->st_mode) >> 6, (S_IRWXG & st->st_mode) >> 3, S_IRWXO & st->st_mode);
}

int	vfs_file_permissions(AGENT_REQUEST *request, AGENT_RESULT *result)
{
	char		*filename;
	int		ret = SYSINFO_RET_FAIL;
	zbx_stat_t	st;

	if (1 < request->nparam)
	{
		SET_MSG_RESULT(result, zbx_strdup(NULL, "Too many parameters."));
		goto err;
	}

	filename = get_rparam(request, 0);

	if (NULL == filename || '\0' == *filename)
	{
		SET_MSG_RESULT(result, zbx_strdup(NULL, "Invalid first parameter."));
		goto err;
	}

	if (SUCCEED != zbx_stat(filename, &st))
	{
		SET_MSG_RESULT(result, zbx_dsprintf(NULL, "Cannot obtain file information: %s", zbx_strerror(errno)));
		goto err;
	}

	SET_STR_RESULT(result, get_file_permissions(&st));

	ret = SYSINFO_RET_OK;
err:
	return ret;
}
#endif

static char	*get_print_time(time_t st_raw)
{
#define MAX_TIME_STR_LEN	26
	struct tm	st;
	char		*st_str;

	st_str = zbx_malloc(NULL, MAX_TIME_STR_LEN);
	st = *zbx_localtime(&st_raw, NULL);
	strftime(st_str, MAX_TIME_STR_LEN, "%Y-%m-%dT%T%z", &st);

	return st_str;
#undef MAX_TIME_STR_LEN
}

#define VFS_FILE_ADD_TIME(time, tag)						\
	do									\
	{									\
		char	*tmp;							\
										\
		if (0 <= time)							\
			tmp = get_print_time((time_t)time);			\
		else								\
			tmp = get_print_time(0);				\
										\
		zbx_json_addstring(j, tag, tmp, ZBX_JSON_TYPE_STRING);		\
		zbx_free(tmp);							\
	} while (0)

#define VFS_FILE_ADD_TS(time, tag)						\
	do									\
	{									\
		if (0 <= time)							\
			zbx_json_adduint64(j, tag, (zbx_uint64_t)time);		\
		else								\
			zbx_json_adduint64(j, tag, 0);				\
	} while (0)

#if defined(_WINDOWS) || defined(__MINGW32__)
#	define ZBX_DIR_DELIMITER	"\\"
#else
#	define ZBX_DIR_DELIMITER	"/"

static char	*canonicalize_path(const char *fullname)
{
	int			up_level = 0;
	char			*name;
	const char		*p_start = &fullname[1], *p_to_delimiter;
	size_t			name_alloc = 0, name_offset = 0;
	zbx_vector_str_t	names;

	zbx_vector_str_create(&names);

	do
	{
		if (NULL != (p_to_delimiter = strchr(p_start, '/')))
		{
			name = zbx_dsprintf(NULL, "%.*s", (int)(p_to_delimiter - p_start), p_start);
			p_start = p_to_delimiter + 1;
		}
		else
			name = zbx_strdup(NULL, p_start);

		zbx_vector_str_append(&names, name);
	}
	while (NULL != p_to_delimiter);

	name = NULL;

	for (int i = names.values_num - 1; 0 <= i; i--)
	{
		char	*ptr = names.values[i];

		if (0 == strcmp(ptr, ".") || 0 == strlen(ptr))
		{
			zbx_free(ptr);
			zbx_vector_str_remove(&names, i);
		}
		else if (0 == strcmp(ptr, ".."))
		{
			zbx_free(ptr);
			zbx_vector_str_remove(&names, i);
			up_level++;
		}
		else if (0 < up_level)
		{
			zbx_free(ptr);
			zbx_vector_str_remove(&names, i);
			up_level--;
		}
	}

	if (0 < names.values_num)
	{
		for (int i = 0; i < names.values_num; i++)
			zbx_snprintf_alloc(&name, &name_alloc, &name_offset, "/%s", names.values[i]);
	}
	else
		zbx_snprintf_alloc(&name, &name_alloc, &name_offset, "/");

	zbx_vector_str_clear_ext(&names, zbx_str_free);
	zbx_vector_str_destroy(&names);

	return name;
}
#endif

static int	get_dir_names(const char *filename, char **basename, char **dirname, char **pathname)
{
	char	*ptr1, *ptr2;
	size_t	len;

#if defined(_WINDOWS) || defined(__MINGW32__)
	if (NULL == (*pathname = _fullpath(NULL, filename, 0)))
		return FAIL;
#else
	if ( '/' != filename[0])
	{
#ifdef PATH_MAX
#	define MAX_PATH_BUFFER	PATH_MAX
#else
#	define MAX_PATH_BUFFER	4096
#endif
		char	resolved_path[MAX_PATH_BUFFER + 1], *name = NULL;
		size_t	name_alloc = 0, name_offset = 0;

#define ZBX_UNREACHABLE_STR		"(unreachable)"
#define ZBX_UNREACHABLE_STR_LEN		ZBX_CONST_STRLEN(ZBX_UNREACHABLE_STR)

		if (NULL == getcwd(resolved_path, MAX_PATH_BUFFER) || 0 == strncmp(ZBX_UNREACHABLE_STR, resolved_path,
				ZBX_UNREACHABLE_STR_LEN))
		{
			return FAIL;
		}

		zbx_snprintf_alloc(&name, &name_alloc, &name_offset, "%s/%s", resolved_path, filename);

		*pathname = canonicalize_path(name);
		zbx_free(name);
#undef ZBX_UNREACHABLE_STR_LEN
#undef ZBX_UNREACHABLE_STR
#undef MAX_PATH_BUFFER
	}
	else
		*pathname = canonicalize_path(filename);
#endif

	ptr1 = *pathname;
	ptr2 = strstr(ptr1, ZBX_DIR_DELIMITER);

	while (NULL != ptr2)
	{
		len = strlen(ptr2) + 1;
		ptr1 = &ptr1[strlen(ptr1) - len + 2];
		ptr2 = strstr(ptr1, ZBX_DIR_DELIMITER);
	}

	if (0 == strlen(ptr1))
		*basename = zbx_strdup(NULL, *pathname);
	else
		*basename = zbx_strdup(NULL, ptr1);

	ptr2 = zbx_strdup(NULL, *pathname);
	len = strlen(*pathname) - strlen(ptr1);
#if defined(_WINDOWS) || defined(__MINGW32__)
	if (3 == len)
#else
	if (1 == len)
#endif
		len++;
	ptr2[len - 1] = '\0';
	*dirname = zbx_dsprintf(NULL, "%s", ptr2);
	zbx_free(ptr2);

	return SUCCEED;
}

#if defined(_WINDOWS) || defined(__MINGW32__)
int	zbx_vfs_file_info(const char *filename, struct zbx_json *j, int array, char **error)
{
	int			ret = FAIL;
	DWORD			file_attributes, acc_sz = 0, dmn_sz = 0;
	wchar_t			*wpath = NULL, *sid_string = NULL, *acc_name = NULL, *dmn_name = NULL;
	char			*type = NULL, *basename = NULL, *dirname = NULL,
				*pathname = NULL, *user = NULL, *sidbuf = NULL;
	PSID			sid = NULL;
	PSECURITY_DESCRIPTOR	sec = NULL;
	SID_NAME_USE		acc_type = SidTypeUnknown;
	zbx_stat_t		buf;
	zbx_file_time_t		file_time;
	zbx_uint64_t		size;

	if (NULL == (wpath = zbx_utf8_to_unicode(filename)))
	{
		*error = zbx_strdup(NULL, "Cannot convert file name to UTF-16.");
		goto err;
	}

	file_attributes = GetFileAttributesW(wpath);

	if (INVALID_FILE_ATTRIBUTES == file_attributes)
	{
		DWORD	last_error;

		switch (last_error = GetLastError())
		{
			case ERROR_FILE_NOT_FOUND:
				goto err;
			case ERROR_BAD_NETPATH:	/* special case from GetFileAttributesW() documentation */
				*error = zbx_dsprintf(NULL, "The specified file is a network share."
						" Use a path to a subfolder on that share.");
				goto err;
			default:
				*error = zbx_dsprintf(NULL, "Cannot obtain file information: %s",
						zbx_strerror_from_system(last_error));
				goto err;
		}
	}

	if (0 != (file_attributes & FILE_ATTRIBUTE_REPARSE_POINT))
		type = zbx_strdup(NULL, "sym");
	else if (0 != (file_attributes & FILE_ATTRIBUTE_DIRECTORY))
		type = zbx_strdup(NULL, "dir");
	else
		type = zbx_strdup(NULL, "file");

	if (SUCCEED != get_dir_names(filename, &basename, &dirname, &pathname))
	{
		*error = zbx_strdup(NULL, "Cannot obtain file or directory name.");
		goto err;
	}

	if (ERROR_SUCCESS != GetNamedSecurityInfo(wpath, SE_FILE_OBJECT, OWNER_SECURITY_INFORMATION, &sid, NULL, NULL,
			NULL, &sec))
	{
		*error = zbx_strdup(NULL, "Cannot obtain security information.");
		goto err;
	}

	LookupAccountSid(NULL, sid, acc_name, (LPDWORD)&acc_sz, dmn_name, (LPDWORD)&dmn_sz, &acc_type);

	acc_name = (wchar_t *)zbx_malloc(acc_name, acc_sz * sizeof(wchar_t));
	dmn_name = (wchar_t *)zbx_malloc(dmn_name, dmn_sz * sizeof(wchar_t));

	if (TRUE == LookupAccountSid(NULL, sid, acc_name, (LPDWORD)&acc_sz, dmn_name, (LPDWORD)&dmn_sz,
			&acc_type))
	{
		char	*acc_ut8, *dmn_ut8;

		acc_ut8 = zbx_unicode_to_utf8(acc_name);
		dmn_ut8 = zbx_unicode_to_utf8(dmn_name);

		if (0 == strlen(dmn_ut8))
			user = zbx_dsprintf(NULL, "%s", acc_ut8);
		else
			user = zbx_dsprintf(NULL, "%s\\%s", dmn_ut8, acc_ut8);

		zbx_free(acc_ut8);
		zbx_free(dmn_ut8);
	}

	zbx_free(acc_name);
	zbx_free(dmn_name);

	if (TRUE != ConvertSidToStringSid(sid, &sid_string))
	{
		*error = zbx_strdup(NULL, "Cannot obtain SID.");
		goto err;
	}

	sidbuf = zbx_unicode_to_utf8(sid_string);
	LocalFree(sid_string);

	if (0 != (file_attributes & (FILE_ATTRIBUTE_REPARSE_POINT | FILE_ATTRIBUTE_DIRECTORY)))
	{
		size = 0;
	}
	else if (0 != zbx_stat(filename, &buf))
	{
		*error = zbx_dsprintf(NULL, "Cannot obtain file information: %s", zbx_strerror(errno));
		goto err;
	}
	else
		size = (zbx_uint64_t)buf.st_size;

	/* name */

	if (0 != array)
		zbx_json_addobject(j, NULL);

	zbx_json_addstring(j, ZBX_SYSINFO_FILE_TAG_BASENAME, basename, ZBX_JSON_TYPE_STRING);
	zbx_json_addstring(j, ZBX_SYSINFO_FILE_TAG_PATHNAME, pathname, ZBX_JSON_TYPE_STRING);
	zbx_json_addstring(j, ZBX_SYSINFO_FILE_TAG_DIRNAME, dirname, ZBX_JSON_TYPE_STRING);

	/* type */
	zbx_json_addstring(j, ZBX_SYSINFO_FILE_TAG_TYPE, type, ZBX_JSON_TYPE_STRING);

	/* User name */
	zbx_json_addstring(j, ZBX_SYSINFO_FILE_TAG_USER, user, ZBX_JSON_TYPE_STRING);

	/* SID */
	zbx_json_addstring(j, ZBX_SYSINFO_FILE_TAG_SID, sidbuf, ZBX_JSON_TYPE_STRING);

	/* size */
	zbx_json_adduint64(j, ZBX_SYSINFO_FILE_TAG_SIZE, size);

	/* time */

	if (SUCCEED != zbx_get_file_time(filename, 1, &file_time))
	{
		memset(&file_time, 0 ,sizeof(file_time));
	}

	zbx_json_addobject(j, ZBX_SYSINFO_FILE_TAG_TIME);
	VFS_FILE_ADD_TIME(file_time.access_time, ZBX_SYSINFO_FILE_TAG_TIME_ACCESS);
	VFS_FILE_ADD_TIME(file_time.modification_time, ZBX_SYSINFO_FILE_TAG_TIME_MODIFY);
	VFS_FILE_ADD_TIME(file_time.change_time, ZBX_SYSINFO_FILE_TAG_TIME_CHANGE);
	zbx_json_close(j);

	zbx_json_addobject(j, ZBX_SYSINFO_FILE_TAG_TIMESTAMP);
	VFS_FILE_ADD_TS(file_time.access_time, ZBX_SYSINFO_FILE_TAG_TIME_ACCESS);
	VFS_FILE_ADD_TS(file_time.modification_time, ZBX_SYSINFO_FILE_TAG_TIME_MODIFY);
	VFS_FILE_ADD_TS(file_time.change_time, ZBX_SYSINFO_FILE_TAG_TIME_CHANGE);
	zbx_json_close(j);

	/* close object*/
	zbx_json_close(j);

	ret =  SUCCEED;
err:
	if (NULL != sec)
		LocalFree(sec);

	zbx_free(wpath);

	zbx_free(type);
	zbx_free(basename);
	zbx_free(dirname);
	zbx_free(pathname);
	zbx_free(user);
	zbx_free(sidbuf);

	return ret;
}
#else /* not _WINDOWS or __MINGW32__ */
int	zbx_vfs_file_info(const char *filename, struct zbx_json *j, int array, char **error)
{
	int		ret = FAIL;
	char		*permissions, *type = NULL, *basename = NULL, *dirname = NULL, *pathname = NULL;
	zbx_file_time_t	file_time;
	zbx_stat_t	buf;
	struct group	*grp;
	struct passwd	*pwd;

	if (0 != lstat(filename, &buf))
	{
		*error = zbx_dsprintf(NULL, "Cannot obtain file information: %s", zbx_strerror(errno));
		return FAIL;
	}

	if (0 != S_ISLNK(buf.st_mode))
		type = zbx_strdup(NULL, "sym");
	else if (0 != S_ISREG(buf.st_mode))
		type = zbx_strdup(NULL, "file");
	else if (0 != S_ISDIR(buf.st_mode))
		type = zbx_strdup(NULL, "dir");
	else if (0 != S_ISSOCK(buf.st_mode))
		type = zbx_strdup(NULL, "sock");
	else if (0 != S_ISBLK(buf.st_mode))
		type = zbx_strdup(NULL, "bdev");
	else if (0 != S_ISCHR(buf.st_mode))
		type = zbx_strdup(NULL, "cdev");
	else if (0 != S_ISFIFO(buf.st_mode))
		type = zbx_strdup(NULL, "fifo");

	if (SUCCEED != get_dir_names(filename, &basename, &dirname, &pathname))
	{
		*error = zbx_strdup(NULL, "Cannot obtain file or directory name.");
		goto err;
	}

	if (SUCCEED != zbx_get_file_time(filename, 1, &file_time))
	{
		*error = zbx_dsprintf(NULL, "Cannot obtain file information: %s", zbx_strerror(errno));
		goto err;
	}

	/* name */

	if (0 != array)
		zbx_json_addobject(j, NULL);

	zbx_json_addstring(j, ZBX_SYSINFO_FILE_TAG_BASENAME, basename, ZBX_JSON_TYPE_STRING);
	zbx_json_addstring(j, ZBX_SYSINFO_FILE_TAG_PATHNAME, pathname, ZBX_JSON_TYPE_STRING);
	zbx_json_addstring(j, ZBX_SYSINFO_FILE_TAG_DIRNAME, dirname, ZBX_JSON_TYPE_STRING);

	/* type */
	zbx_json_addstring(j, ZBX_SYSINFO_FILE_TAG_TYPE, type, ZBX_JSON_TYPE_STRING);

	/* user */
	if (NULL == (pwd = getpwuid(buf.st_uid)))
	{
		char	buffer[MAX_ID_LEN];

		zbx_snprintf(buffer, sizeof(buffer), ZBX_FS_UI64, (zbx_uint64_t)buf.st_uid);
		zbx_json_addstring(j, ZBX_SYSINFO_FILE_TAG_USER, buffer, ZBX_JSON_TYPE_STRING);
	}
	else
		zbx_json_addstring(j, ZBX_SYSINFO_FILE_TAG_USER, pwd->pw_name, ZBX_JSON_TYPE_STRING);

	/* group */
	if (NULL == (grp = getgrgid(buf.st_gid)))
	{
		char	buffer[MAX_ID_LEN];

		zbx_snprintf(buffer, sizeof(buffer), ZBX_FS_UI64, (zbx_uint64_t)buf.st_gid);
		zbx_json_addstring(j, ZBX_SYSINFO_FILE_TAG_GROUP, buffer, ZBX_JSON_TYPE_STRING);
	}
	else
		zbx_json_addstring(j, ZBX_SYSINFO_FILE_TAG_GROUP, grp->gr_name, ZBX_JSON_TYPE_STRING);

	/* permissions */
	permissions = get_file_permissions(&buf);
	zbx_json_addstring(j, ZBX_SYSINFO_FILE_TAG_PERMISSIONS, permissions, ZBX_JSON_TYPE_STRING);
	zbx_free(permissions);

	/* uid */
	zbx_json_adduint64(j, ZBX_SYSINFO_FILE_TAG_UID, (zbx_uint64_t)buf.st_uid);

	/* gid */
	zbx_json_adduint64(j, ZBX_SYSINFO_FILE_TAG_GID, (zbx_uint64_t)buf.st_gid);

	/* size */
	zbx_json_adduint64(j, ZBX_SYSINFO_FILE_TAG_SIZE, (zbx_uint64_t)buf.st_size);

	/* time */

	zbx_json_addobject(j, ZBX_SYSINFO_FILE_TAG_TIME);
	VFS_FILE_ADD_TIME(file_time.access_time, ZBX_SYSINFO_FILE_TAG_TIME_ACCESS);
	VFS_FILE_ADD_TIME(file_time.modification_time, ZBX_SYSINFO_FILE_TAG_TIME_MODIFY);
	VFS_FILE_ADD_TIME(file_time.change_time, ZBX_SYSINFO_FILE_TAG_TIME_CHANGE);
	zbx_json_close(j);

	zbx_json_addobject(j, ZBX_SYSINFO_FILE_TAG_TIMESTAMP);
	VFS_FILE_ADD_TS(file_time.access_time, ZBX_SYSINFO_FILE_TAG_TIME_ACCESS);
	VFS_FILE_ADD_TS(file_time.modification_time, ZBX_SYSINFO_FILE_TAG_TIME_MODIFY);
	VFS_FILE_ADD_TS(file_time.change_time, ZBX_SYSINFO_FILE_TAG_TIME_CHANGE);
	zbx_json_close(j);

	zbx_json_close(j);

	ret =  SUCCEED;
err:
	zbx_free(type);
	zbx_free(basename);
	zbx_free(dirname);
	zbx_free(pathname);

	return ret;
}
#endif

static int	vfs_file_get_local(const char *filename, AGENT_RESULT *result)
{
	int		ret = SYSINFO_RET_FAIL;
	char		*error = NULL;
	struct zbx_json	j;

	zbx_json_init(&j, ZBX_JSON_STAT_BUF_LEN);

	if (SUCCEED == zbx_vfs_file_info(filename, &j, 0, &error))
	{
		SET_STR_RESULT(result, zbx_strdup(NULL, j.buffer));

		ret =  SYSINFO_RET_OK;
	}
	else
		SET_MSG_RESULT(result, error);

	zbx_json_close(&j);

	zbx_json_free(&j);

	return ret;
}

#undef VFS_FILE_ADD_TIME
#undef VFS_FILE_ADD_TS

int	vfs_file_get(AGENT_REQUEST *request, AGENT_RESULT *result)
{
	const char	*filename;

	if (1 < request->nparam)
	{
		SET_MSG_RESULT(result, zbx_strdup(NULL, "Too many parameters."));
		return SYSINFO_RET_FAIL;
	}

	filename = get_rparam(request, 0);

	if (NULL == filename || '\0' == *filename)
	{
		SET_MSG_RESULT(result, zbx_strdup(NULL, "Invalid first parameter."));
		return SYSINFO_RET_FAIL;
	}

	return vfs_file_get_local(filename, result);
}
