/*
** Zabbix
** Copyright (C) 2001-2021 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 <procfs.h>
#include "common.h"
#include "sysinfo.h"
#include "zbxregexp.h"
#include "log.h"
#include "stats.h"

#if !defined(HAVE_ZONE_H) && defined(HAVE_SYS_UTSNAME_H)
#	include <sys/utsname.h>
#endif

extern int	CONFIG_TIMEOUT;

typedef struct
{
	pid_t		pid;
	uid_t		uid;

	char		*name;

	/* process name extracted from the first argument (usually executable path) */
	char		*name_arg0;

	/* process command line in format <arg0> <arg1> ... <argN>\0 */
	char		*cmdline;


#ifdef HAVE_ZONE_H
	zoneid_t	zoneid;
#endif
}
zbx_sysinfo_proc_t;

#ifndef HAVE_ZONE_H
/* helper functions for case if agent is compiled on Solaris 9 or earlier where zones are not supported */
/* but is running on a newer Solaris where zones are supported */

/******************************************************************************
 *                                                                            *
 * Function: zbx_solaris_version_get                                          *
 *                                                                            *
 * Purpose: get Solaris version at runtime                                    *
 *                                                                            *
 * Parameters:                                                                *
 *     major_version - [OUT] major version (e.g. 5)                           *
 *     minor_version - [OUT] minor version (e.g. 9 for Solaris 9, 10 for      *
 *                           Solaris 10, 11 for Solaris 11)                   *
 * Return value:                                                              *
 *     SUCCEED - no errors, FAIL - an error occurred                          *
 *                                                                            *
 ******************************************************************************/
static int	zbx_solaris_version_get(unsigned int *major_version, unsigned int *minor_version)
{
	int		res;
	struct utsname	name;

	if (-1 == (res = uname(&name)))
	{
		zabbix_log(LOG_LEVEL_WARNING, "%s(): uname() failed: %s", __func__, zbx_strerror(errno));

		return FAIL;
	}

	/* expected result in name.release: "5.9" - Solaris 9, "5.10" - Solaris 10, "5.11" - Solaris 11 */

	if (2 != sscanf(name.release, "%u.%u", major_version, minor_version))
	{
		zabbix_log(LOG_LEVEL_WARNING, "%s(): sscanf() failed on: \"%s\"", __func__, name.release);
		THIS_SHOULD_NEVER_HAPPEN;

		return FAIL;
	}

	return SUCCEED;
}

/******************************************************************************
 *                                                                            *
 * Function: zbx_detect_zone_support                                          *
 *                                                                            *
 * Purpose: find if zones are supported                                       *
 *                                                                            *
 * Return value:                                                              *
 *     SUCCEED - zones supported                                              *
 *     FAIL - zones not supported or error occurred. For our purposes error   *
 *            counts as no support for zones.                                 *
 *                                                                            *
 ******************************************************************************/
static int	zbx_detect_zone_support(void)
{
#define ZBX_ZONE_SUPPORT_UNKNOWN	0
#define ZBX_ZONE_SUPPORT_YES		1
#define ZBX_ZONE_SUPPORT_NO		2

	static int	zone_support = ZBX_ZONE_SUPPORT_UNKNOWN;
	unsigned int	major, minor;

	switch (zone_support)
	{
		case ZBX_ZONE_SUPPORT_NO:
			return FAIL;
		case ZBX_ZONE_SUPPORT_YES:
			return SUCCEED;
		default:
			/* zones are supported in Solaris 10 and later (minimum version is "5.10") */

			if (SUCCEED == zbx_solaris_version_get(&major, &minor) &&
					((5 == major && 10 <= minor) || 5 < major))
			{
				zone_support = ZBX_ZONE_SUPPORT_YES;
				return SUCCEED;
			}
			else	/* failure to get Solaris version also results in "zones not supported" */
			{
				zone_support = ZBX_ZONE_SUPPORT_NO;
				return FAIL;
			}
	}
}
#endif

static void	zbx_sysinfo_proc_clear(zbx_sysinfo_proc_t *proc)
{
	zbx_free(proc->name);
	zbx_free(proc->cmdline);
	zbx_free(proc->name_arg0);
}

/******************************************************************************
 *                                                                            *
 * Function: zbx_sysinfo_proc_free                                            *
 *                                                                            *
 * Purpose: frees process data structure                                      *
 *                                                                            *
 ******************************************************************************/
static void	zbx_sysinfo_proc_free(zbx_sysinfo_proc_t *proc)
{
	zbx_sysinfo_proc_clear(proc);
	zbx_free(proc);
}

static int	check_procstate(psinfo_t *psinfo, int zbx_proc_stat)
{
	if (zbx_proc_stat == ZBX_PROC_STAT_ALL)
		return SUCCEED;

	switch (zbx_proc_stat)
	{
		case ZBX_PROC_STAT_RUN:
			return (psinfo->pr_lwp.pr_state == SRUN || psinfo->pr_lwp.pr_state == SONPROC) ? SUCCEED : FAIL;
		case ZBX_PROC_STAT_SLEEP:
			return (psinfo->pr_lwp.pr_state == SSLEEP) ? SUCCEED : FAIL;
		case ZBX_PROC_STAT_ZOMB:
			return (psinfo->pr_lwp.pr_state == SZOMB) ? SUCCEED : FAIL;
	}

	return FAIL;
}

static int	get_cmdline(FILE *f_cmd, char **line, size_t *line_offset)
{
	size_t	line_alloc = ZBX_KIBIBYTE, n;

	rewind(f_cmd);

	*line = (char *)zbx_malloc(*line, line_alloc + 2);
	*line_offset = 0;

	while (0 != (n = fread(*line + *line_offset, 1, line_alloc - *line_offset, f_cmd)))
	{
		*line_offset += n;

		if (0 != feof(f_cmd))
			break;

		line_alloc *= 2;
		*line = (char *)zbx_realloc(*line, line_alloc + 2);
	}

	if (0 == ferror(f_cmd))
	{
		if (0 == *line_offset || '\0' != (*line)[*line_offset - 1])
			(*line)[(*line_offset)++] = '\0';
		if (1 == *line_offset || '\0' != (*line)[*line_offset - 2])
			(*line)[(*line_offset)++] = '\0';

		return SUCCEED;
	}

	zbx_free(*line);

	return FAIL;
}

/******************************************************************************
 *                                                                            *
 * Function: proc_get_process_info                                            *
 *                                                                            *
 * Purpose: get single process information                                    *
 *                                                                            *
 * Parameters: pid    - [IN] the process pid                                  *
 *             flags  - [IN] the flags specifying the process properties      *
 *                          that must be returned                             *
 *             proc   - [OUT] the process data                                *
 *             psinfo - [OUT] the raw process information data                *
 * Return value: SUCCEED - the process information was retrieved successfully *
 *               FAIL    - otherwise                                          *
 *                                                                            *
 ******************************************************************************/
static int	proc_get_process_info(const char *pid, unsigned int flags, zbx_sysinfo_proc_t *proc, psinfo_t *psinfo)
{
	int		fd, n;
	char		path[MAX_STRING_LEN];
	FILE		*fp;
	psinfo_t	psinfo_local;

	/* skip entries not containing pids */
	memset(proc, 0, sizeof(zbx_sysinfo_proc_t));
	if (FAIL == is_uint32(pid, &proc->pid))
		return FAIL;

	zbx_snprintf(path, sizeof(path), "/proc/%s/psinfo", pid);

	if (-1 == (fd = open(path, O_RDONLY)))
		return FAIL;

	if (NULL == psinfo)
		psinfo = &psinfo_local;

	n = read(fd, psinfo, sizeof(*psinfo));
	close(fd);

	if (-1 == n)
		return FAIL;

#ifdef HAVE_ZONE_H
	proc->zoneid = psinfo->pr_zoneid;
#endif

	if (0 != (flags & ZBX_SYSINFO_PROC_USER))
		proc->uid = psinfo->pr_uid;

	if (0 != (flags & ZBX_SYSINFO_PROC_NAME))
		proc->name = zbx_strdup(NULL, psinfo->pr_fname);

	if (0 != (flags & ZBX_SYSINFO_PROC_NAME) || 0 != (flags & ZBX_SYSINFO_PROC_CMDLINE))
	{
		zbx_snprintf(path, sizeof(path), "/proc/%s/cmdline", pid);
		if (NULL != (fp = fopen(path, "r")))
		{
			char	*line = NULL, *ptr;
			size_t	l;

			if (SUCCEED == get_cmdline(fp, &line, &l))
			{
				if (0 != (flags & ZBX_SYSINFO_PROC_NAME))
				{
					if (NULL == (ptr = strrchr(line, '/')))
						proc->name_arg0 = zbx_strdup(NULL, line);
					else
						proc->name_arg0 = zbx_strdup(NULL, ptr + 1);
				}

				if (0 != (flags & ZBX_SYSINFO_PROC_CMDLINE))
				{
					int	i;

					for (i = 0, l -= 2; i < l; i++)
						if ('\0' == line[i])
							line[i] = ' ';

					proc->cmdline = zbx_strdup(NULL, line);
				}
				zbx_free(line);
			}
			fclose(fp);
		}

		if (0 != (flags & ZBX_SYSINFO_PROC_CMDLINE) && NULL == proc->cmdline)
			proc->cmdline = zbx_strdup(NULL, psinfo->pr_psargs);
	}

	return SUCCEED;
}

/******************************************************************************
 *                                                                            *
 * Function: proc_match_name                                                  *
 *                                                                            *
 * Purpose: checks if the process name matches filter                         *
 *                                                                            *
 ******************************************************************************/
static int	proc_match_name(const zbx_sysinfo_proc_t *proc, const char *procname)
{
	if (NULL == procname)
		return SUCCEED;

	if (NULL != proc->name && 0 == strcmp(procname, proc->name))
		return SUCCEED;

	if (NULL != proc->name_arg0 && 0 == strcmp(procname, proc->name_arg0))
		return SUCCEED;

	return FAIL;
}

/******************************************************************************
 *                                                                            *
 * Function: proc_match_user                                                  *
 *                                                                            *
 * Purpose: checks if the process user matches filter                         *
 *                                                                            *
 ******************************************************************************/
static int	proc_match_user(const zbx_sysinfo_proc_t *proc, const struct passwd *usrinfo)
{
	if (NULL == usrinfo)
		return SUCCEED;

	if (proc->uid == usrinfo->pw_uid)
		return SUCCEED;

	return FAIL;
}

/******************************************************************************
 *                                                                            *
 * Function: proc_match_cmdline                                               *
 *                                                                            *
 * Purpose: checks if the process command line matches filter                 *
 *                                                                            *
 ******************************************************************************/
static int	proc_match_cmdline(const zbx_sysinfo_proc_t *proc, const char *cmdline)
{
	if (NULL == cmdline)
		return SUCCEED;

	if (NULL != proc->cmdline && NULL != zbx_regexp_match(proc->cmdline, cmdline, NULL))
		return SUCCEED;

	return FAIL;
}

#ifdef HAVE_ZONE_H
/******************************************************************************
 *                                                                            *
 * Function: proc_match_zone                                                  *
 *                                                                            *
 * Purpose: checks if the process zone matches filter                         *
 *                                                                            *
 ******************************************************************************/
static int	proc_match_zone(const zbx_sysinfo_proc_t *proc, zbx_uint64_t flags, zoneid_t zoneid)
{
	if (0 != (ZBX_PROCSTAT_FLAGS_ZONE_ALL & flags))
		return SUCCEED;

	if (proc->zoneid == zoneid)
		return SUCCEED;

	return FAIL;
}
#endif

/******************************************************************************
 *                                                                            *
 * Function: proc_match_props                                                 *
 *                                                                            *
 * Purpose: checks if the process properties (except zone) matches filter     *
 *                                                                            *
 ******************************************************************************/
static int	proc_match_props(const zbx_sysinfo_proc_t *proc, const struct passwd *usrinfo, const char *procname,
		const char *cmdline)
{
	if (SUCCEED != proc_match_user(proc, usrinfo))
		return FAIL;

	if (SUCCEED != proc_match_name(proc, procname))
		return FAIL;

	if (SUCCEED != proc_match_cmdline(proc, cmdline))
		return FAIL;

	return SUCCEED;
}

int	PROC_MEM(AGENT_REQUEST *request, AGENT_RESULT *result)
{
	char			tmp[MAX_STRING_LEN], *procname, *proccomm, *param, *memtype = NULL;
	DIR			*dir;
	struct dirent		*entries;
	struct passwd		*usrinfo;
	psinfo_t		psinfo;	/* In the correct procfs.h, the structure name is psinfo_t */
	int			do_task, proccount = 0, invalid_user = 0, proc_props = 0;
	zbx_uint64_t		mem_size = 0, byte_value = 0;
	double			pct_size = 0.0, pct_value = 0.0;
	size_t			*p_value;

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

	if (NULL != (procname = get_rparam(request, 0)) && '\0' != *procname)
		proc_props |= ZBX_SYSINFO_PROC_NAME;
	else
		procname = NULL;

	param = get_rparam(request, 1);

	if (NULL != param && '\0' != *param)
	{
		proc_props |= ZBX_SYSINFO_PROC_USER;
		errno = 0;

		if (NULL == (usrinfo = getpwnam(param)))
		{
			if (0 != errno)
			{
				SET_MSG_RESULT(result, zbx_dsprintf(NULL, "Cannot obtain user information: %s",
						zbx_strerror(errno)));
				return SYSINFO_RET_FAIL;
			}

			invalid_user = 1;
		}
	}
	else
		usrinfo = NULL;

	param = get_rparam(request, 2);

	if (NULL == param || '\0' == *param || 0 == strcmp(param, "sum"))
		do_task = ZBX_DO_SUM;
	else if (0 == strcmp(param, "avg"))
		do_task = ZBX_DO_AVG;
	else if (0 == strcmp(param, "max"))
		do_task = ZBX_DO_MAX;
	else if (0 == strcmp(param, "min"))
		do_task = ZBX_DO_MIN;
	else
	{
		SET_MSG_RESULT(result, zbx_strdup(NULL, "Invalid third parameter."));
		return SYSINFO_RET_FAIL;
	}

	if (NULL != (proccomm = get_rparam(request, 3)) && '\0' != *proccomm)
		proc_props |= ZBX_SYSINFO_PROC_CMDLINE;
	else
		proccomm = NULL;

	memtype = get_rparam(request, 4);

	if (NULL == memtype || '\0' == *memtype || 0 == strcmp(memtype, "vsize"))
	{
		p_value = &psinfo.pr_size;	/* size of process image in Kbytes */
	}
	else if (0 == strcmp(memtype, "rss"))
	{
		p_value = &psinfo.pr_rssize;	/* resident set size in Kbytes */
	}
	else if (0 == strcmp(memtype, "pmem"))
	{
		p_value = NULL;			/* for % of system memory used by process */
	}
	else
	{
		SET_MSG_RESULT(result, zbx_strdup(NULL, "Invalid fifth parameter."));
		return SYSINFO_RET_FAIL;
	}

	if (1 == invalid_user)	/* handle 0 for non-existent user after all parameters have been parsed and validated */
		goto out;

	if (NULL == (dir = opendir("/proc")))
	{
		SET_MSG_RESULT(result, zbx_dsprintf(NULL, "Cannot open /proc: %s", zbx_strerror(errno)));
		return SYSINFO_RET_FAIL;
	}

	while (NULL != (entries = readdir(dir)))
	{
		zbx_sysinfo_proc_t	proc;

		if (SUCCEED != proc_get_process_info(entries->d_name, proc_props, &proc, &psinfo))
			continue;

		if (SUCCEED == proc_match_props(&proc, usrinfo, procname, proccomm))
		{
			if (NULL != p_value)
			{
				/* pr_size or pr_rssize in Kbytes */
				byte_value = *p_value << 10;	/* kB to Byte */

				if (0 != proccount++)
				{
					if (ZBX_DO_MAX == do_task)
						mem_size = MAX(mem_size, byte_value);
					else if (ZBX_DO_MIN == do_task)
						mem_size = MIN(mem_size, byte_value);
					else
						mem_size += byte_value;
				}
				else
					mem_size = byte_value;
			}
			else
			{
				/* % of system memory used by process, measured in 16-bit binary fractions in the range */
				/* 0.0 - 1.0 with the binary point to the right of the most significant bit. 1.0 == 0x8000 */
				pct_value = (double)((int)psinfo.pr_pctmem * 100) / 32768.0;

				if (0 != proccount++)
				{
					if (ZBX_DO_MAX == do_task)
						pct_size = MAX(pct_size, pct_value);
					else if (ZBX_DO_MIN == do_task)
						pct_size = MIN(pct_size, pct_value);
					else
						pct_size += pct_value;
				}
				else
					pct_size = pct_value;
			}
		}

		zbx_sysinfo_proc_clear(&proc);
	}

	closedir(dir);
out:
	if (NULL != p_value)
	{
		if (ZBX_DO_AVG == do_task)
			SET_DBL_RESULT(result, 0 == proccount ? 0.0 : (double)mem_size / (double)proccount);
		else
			SET_UI64_RESULT(result, mem_size);
	}
	else
	{
		if (ZBX_DO_AVG == do_task)
			SET_DBL_RESULT(result, 0 == proccount ? 0.0 : pct_size / (double)proccount);
		else
			SET_DBL_RESULT(result, pct_size);
	}

	return SYSINFO_RET_OK;
}

int	PROC_NUM(AGENT_REQUEST *request, AGENT_RESULT *result)
{
	char			tmp[MAX_STRING_LEN], *procname, *proccomm, *param, *zone_parameter;
	DIR			*dir;
	struct dirent		*entries;
	zbx_stat_t		buf;
	struct passwd		*usrinfo;
	psinfo_t		psinfo;	/* In the correct procfs.h, the structure name is psinfo_t */
	int			proccount = 0, invalid_user = 0, proc_props = 0, zbx_proc_stat;
#ifdef HAVE_ZONE_H
	zoneid_t		zoneid;
	int			zoneflag;
#endif

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

	if (NULL != (procname = get_rparam(request, 0)) && '\0' != *procname)
		proc_props |= ZBX_SYSINFO_PROC_NAME;
	else
		procname = NULL;

	param = get_rparam(request, 1);

	if (NULL != param && '\0' != *param)
	{
		proc_props |= ZBX_SYSINFO_PROC_USER;
		errno = 0;

		if (NULL == (usrinfo = getpwnam(param)))
		{
			if (0 != errno)
			{
				SET_MSG_RESULT(result, zbx_dsprintf(NULL, "Cannot obtain user information: %s",
						zbx_strerror(errno)));
				return SYSINFO_RET_FAIL;
			}

			invalid_user = 1;
		}
	}
	else
		usrinfo = NULL;

	param = get_rparam(request, 2);

	if (NULL == param || '\0' == *param || 0 == strcmp(param, "all"))
		zbx_proc_stat = ZBX_PROC_STAT_ALL;
	else if (0 == strcmp(param, "run"))
		zbx_proc_stat = ZBX_PROC_STAT_RUN;
	else if (0 == strcmp(param, "sleep"))
		zbx_proc_stat = ZBX_PROC_STAT_SLEEP;
	else if (0 == strcmp(param, "zomb"))
		zbx_proc_stat = ZBX_PROC_STAT_ZOMB;
	else
	{
		SET_MSG_RESULT(result, zbx_strdup(NULL, "Invalid third parameter."));
		return SYSINFO_RET_FAIL;
	}

	if (NULL != (proccomm = get_rparam(request, 3)) && '\0' != *proccomm)
		proc_props |= ZBX_SYSINFO_PROC_CMDLINE;
	else
		proccomm = NULL;

	if (NULL == (zone_parameter = get_rparam(request, 4)) || '\0' == *zone_parameter
			|| 0 == strcmp(zone_parameter, "current"))
	{
#ifdef HAVE_ZONE_H
		zoneflag = ZBX_PROCSTAT_FLAGS_ZONE_CURRENT;
#else
		if (SUCCEED == zbx_detect_zone_support())
		{
			/* Agent has been compiled on Solaris 9 or earlier where zones are not supported */
			/* but now it is running on a system with zone support. This agent cannot limit */
			/* results to only current zone. */

			SET_MSG_RESULT(result, zbx_strdup(NULL, "The fifth parameter value \"current\" cannot be used"
					" with agent running on a Solaris version with zone support, but compiled on"
					" a Solaris version without zone support. Consider using \"all\" or install"
					" agent with Solaris zone support."));
			return SYSINFO_RET_FAIL;
		}
#endif
	}
	else if (0 == strcmp(zone_parameter, "all"))
	{
#ifdef HAVE_ZONE_H
		zoneflag = ZBX_PROCSTAT_FLAGS_ZONE_ALL;
#endif
	}
	else
	{
		SET_MSG_RESULT(result, zbx_strdup(NULL, "Invalid fifth parameter."));
		return SYSINFO_RET_FAIL;
	}
#ifdef HAVE_ZONE_H
	zoneid = getzoneid();
#endif

	if (1 == invalid_user)	/* handle 0 for non-existent user after all parameters have been parsed and validated */
		goto out;

	if (NULL == (dir = opendir("/proc")))
	{
		SET_MSG_RESULT(result, zbx_dsprintf(NULL, "Cannot open /proc: %s", zbx_strerror(errno)));
		return SYSINFO_RET_FAIL;
	}

	while (NULL != (entries = readdir(dir)))
	{
		zbx_sysinfo_proc_t	proc;

		if (SUCCEED != proc_get_process_info(entries->d_name, proc_props, &proc, &psinfo))
			continue;

		if (SUCCEED == proc_match_props(&proc, usrinfo, procname, proccomm))
		{
#ifdef HAVE_ZONE_H
			if (SUCCEED != proc_match_zone(&proc, zoneflag, zoneid))
			{
				zbx_sysinfo_proc_clear(&proc);
				continue;
			}

#endif
			proccount++;
		}

		zbx_sysinfo_proc_clear(&proc);
	}

	closedir(dir);
out:
	SET_UI64_RESULT(result, proccount);

	return SYSINFO_RET_OK;
}


/******************************************************************************
 *                                                                            *
 * Function: proc_read_cpu_util                                               *
 *                                                                            *
 * Purpose: reads process cpu utilization values from /proc/[pid]/usage file  *
 *                                                                            *
 * Parameters: procutil - [IN/OUT] the process cpu utilization data           *
 *                                                                            *
 * Return value: SUCCEED - the process cpu utilization data was read          *
 *                         successfully                                       *
 *               <0      - otherwise, -errno code is returned                 *
 *                                                                            *
 * Comments: we use /proc/[pid]/usage since /proc/[pid]/status contains       *
 *           sensitive information and by default can only be read by the     *
 *           owner or privileged user.                                        *
 *                                                                            *
 *           In addition to user and system-call CPU time the                 *
 *           /proc/[pid]/usage also contains CPU time spent in trap context   *
 *           Currently trap CPU time is not taken into account.               *
 *                                                                            *
 *           prstat(1) skips processes 0 (sched), 2 (pageout) and 3 (fsflush) *
 *           however we take them into account.                               *
 *                                                                            *
 ******************************************************************************/
static int	proc_read_cpu_util(zbx_procstat_util_t *procutil)
{
	int		fd, n;
	char		tmp[MAX_STRING_LEN];
	psinfo_t	psinfo;
	prusage_t	prusage;

	zbx_snprintf(tmp, sizeof(tmp), "/proc/%d/psinfo", (int)procutil->pid);

	if (-1 == (fd = open(tmp, O_RDONLY)))
		return -errno;

	n = read(fd, &psinfo, sizeof(psinfo));
	close(fd);

	if (-1 == n)
		return -errno;

	procutil->starttime = psinfo.pr_start.tv_sec;

	zbx_snprintf(tmp, sizeof(tmp), "/proc/%d/usage", (int)procutil->pid);

	if (-1 == (fd = open(tmp, O_RDONLY)))
		return -errno;

	n = read(fd, &prusage, sizeof(prusage));
	close(fd);

	if (-1 == n)
		return -errno;

	/* convert cpu utilization time to clock ticks */
	procutil->utime = ((zbx_uint64_t)prusage.pr_utime.tv_sec * 1e9 + prusage.pr_utime.tv_nsec) *
			sysconf(_SC_CLK_TCK) / 1e9;

	procutil->stime = ((zbx_uint64_t)prusage.pr_stime.tv_sec * 1e9 + prusage.pr_stime.tv_nsec) *
			sysconf(_SC_CLK_TCK) / 1e9;

	return SUCCEED;
}

/******************************************************************************
 *                                                                            *
 * Function: zbx_proc_get_process_stats                                       *
 *                                                                            *
 * Purpose: get process cpu utilization data                                  *
 *                                                                            *
 * Parameters: procs     - [IN/OUT] an array of process utilization data      *
 *             procs_num - [IN] the number of items in procs array            *
 *                                                                            *
 ******************************************************************************/
void	zbx_proc_get_process_stats(zbx_procstat_util_t *procs, int procs_num)
{
	int	i;

	zabbix_log(LOG_LEVEL_TRACE, "In %s() procs_num:%d", __func__, procs_num);

	for (i = 0; i < procs_num; i++)
		procs[i].error = proc_read_cpu_util(&procs[i]);

	zabbix_log(LOG_LEVEL_TRACE, "End of %s()", __func__);
}

/******************************************************************************
 *                                                                            *
 * Function: zbx_proc_get_processes                                           *
 *                                                                            *
 * Purpose: get system processes                                              *
 *                                                                            *
 * Parameters: processes - [OUT] the system processes                         *
 *             flags     - [IN] the flags specifying the process properties   *
 *                              that must be returned                         *
 *                                                                            *
 * Return value: SUCCEED - the system processes were retrieved successfully   *
 *               FAIL    - failed to open /proc directory                     *
 *                                                                            *
 ******************************************************************************/
int	zbx_proc_get_processes(zbx_vector_ptr_t *processes, unsigned int flags)
{
	DIR			*dir;
	struct dirent		*entries;
	char			tmp[MAX_STRING_LEN];
	int			pid, ret = FAIL, fd = -1, n;
	zbx_sysinfo_proc_t	*proc = NULL;

	zabbix_log(LOG_LEVEL_TRACE, "In %s()", __func__);

	if (NULL == (dir = opendir("/proc")))
		goto out;

	while (NULL != (entries = readdir(dir)))
	{
		if (NULL == proc)
		{
			proc = (zbx_sysinfo_proc_t *)zbx_malloc(NULL, sizeof(zbx_sysinfo_proc_t));
			memset(proc, 0, sizeof(zbx_sysinfo_proc_t));
		}

		if (SUCCEED == proc_get_process_info(entries->d_name, flags, proc, NULL))
		{
			zbx_vector_ptr_append(processes, proc);
			proc = NULL;
		}
	}
	closedir(dir);

	zbx_free(proc);

	ret = SUCCEED;
out:
	zabbix_log(LOG_LEVEL_TRACE, "End of %s(): %s", __func__, zbx_result_string(ret));

	return ret;
}

/******************************************************************************
 *                                                                            *
 * Function: zbx_proc_free_processes                                          *
 *                                                                            *
 * Purpose: frees process vector read by zbx_proc_get_processes function      *
 *                                                                            *
 * Parameters: processes - [IN/OUT] the process vector to free                *
 *                                                                            *
 ******************************************************************************/
void	zbx_proc_free_processes(zbx_vector_ptr_t *processes)
{
	zbx_vector_ptr_clear_ext(processes, (zbx_mem_free_func_t)zbx_sysinfo_proc_free);
}

/******************************************************************************
 *                                                                            *
 * Function: zbx_proc_get_matching_pids                                       *
 *                                                                            *
 * Purpose: get pids matching the specified process name, user name and       *
 *          command line                                                      *
 *                                                                            *
 * Parameters: procname    - [IN] the process name, NULL - all                *
 *             username    - [IN] the user name, NULL - all                   *
 *             cmdline     - [IN] the command line, NULL - all                *
 *             pids        - [OUT] the vector of matching pids                *
 *                                                                            *
 * Return value: SUCCEED   - the pids were read successfully                  *
 *               -errno    - failed to read pids                              *
 *                                                                            *
 ******************************************************************************/
void	zbx_proc_get_matching_pids(const zbx_vector_ptr_t *processes, const char *procname, const char *username,
		const char *cmdline, zbx_uint64_t flags, zbx_vector_uint64_t *pids)
{
	struct passwd		*usrinfo;
	int			i;
	zbx_sysinfo_proc_t	*proc;
#ifdef HAVE_ZONE_H
	zoneid_t		zoneid;
#endif

	zabbix_log(LOG_LEVEL_TRACE, "In %s() procname:%s username:%s cmdline:%s zone:%d", __func__,
			ZBX_NULL2EMPTY_STR(procname), ZBX_NULL2EMPTY_STR(username), ZBX_NULL2EMPTY_STR(cmdline), flags);

	if (NULL != username)
	{
		/* in the case of invalid user there are no matching processes, return empty vector */
		if (NULL == (usrinfo = getpwnam(username)))
			goto out;
	}
	else
		usrinfo = NULL;

#ifdef HAVE_ZONE_H
	zoneid = getzoneid();
#endif

	for (i = 0; i < processes->values_num; i++)
	{
		proc = (zbx_sysinfo_proc_t *)processes->values[i];

		if (SUCCEED != proc_match_props(proc, usrinfo, procname, cmdline))
			continue;

#ifdef HAVE_ZONE_H
		if (SUCCEED != proc_match_zone(proc, flags, zoneid))
			continue;
#endif

		zbx_vector_uint64_append(pids, (zbx_uint64_t)proc->pid);
	}
out:
	zabbix_log(LOG_LEVEL_TRACE, "End of %s()", __func__);
}

int	PROC_CPU_UTIL(AGENT_REQUEST *request, AGENT_RESULT *result)
{
	const char	*procname, *username, *cmdline, *tmp, *flags;
	char		*errmsg = NULL;
	int		period, type;
	double		value;
	zbx_uint64_t	zoneflag;
	zbx_timespec_t	ts_timeout, ts;

	/* proc.cpu.util[<procname>,<username>,(user|system),<cmdline>,(avg1|avg5|avg15),(current|all)] */
	if (6 < request->nparam)
	{
		SET_MSG_RESULT(result, zbx_strdup(NULL, "Too many parameters."));
		return SYSINFO_RET_FAIL;
	}

	/* zbx_procstat_get_* functions expect NULL for default values -       */
	/* convert empty procname, username and cmdline strings to NULL values */
	if (NULL != (procname = get_rparam(request, 0)) && '\0' == *procname)
		procname = NULL;

	if (NULL != (username = get_rparam(request, 1)) && '\0' == *username)
		username = NULL;

	if (NULL != (cmdline = get_rparam(request, 3)) && '\0' == *cmdline)
		cmdline = NULL;

	/* utilization type parameter (user|system) */
	if (NULL == (tmp = get_rparam(request, 2)) || '\0' == *tmp || 0 == strcmp(tmp, "total"))
	{
		type = ZBX_PROCSTAT_CPU_TOTAL;
	}
	else if (0 == strcmp(tmp, "user"))
	{
		type = ZBX_PROCSTAT_CPU_USER;
	}
	else if (0 == strcmp(tmp, "system"))
	{
		type = ZBX_PROCSTAT_CPU_SYSTEM;
	}
	else
	{
		SET_MSG_RESULT(result, zbx_strdup(NULL, "Invalid third parameter."));
		return SYSINFO_RET_FAIL;
	}

	/* mode parameter (avg1|avg5|avg15) */
	if (NULL == (tmp = get_rparam(request, 4)) || '\0' == *tmp || 0 == strcmp(tmp, "avg1"))
	{
		period = SEC_PER_MIN;
	}
	else if (0 == strcmp(tmp, "avg5"))
	{
		period = SEC_PER_MIN * 5;
	}
	else if (0 == strcmp(tmp, "avg15"))
	{
		period = SEC_PER_MIN * 15;
	}
	else
	{
		SET_MSG_RESULT(result, zbx_strdup(NULL, "Invalid fifth parameter."));
		return SYSINFO_RET_FAIL;
	}

	if (NULL == (flags = get_rparam(request, 5)) || '\0' == *flags || 0 == strcmp(flags, "current"))
	{
#ifndef HAVE_ZONE_H
		if (SUCCEED == zbx_detect_zone_support())
		{
			/* Agent has been compiled on Solaris 9 or earlier where zones are not supported */
			/* but now it is running on a system with zone support. This agent cannot limit */
			/* results to only current zone. */

			SET_MSG_RESULT(result, zbx_strdup(NULL, "The sixth parameter value \"current\" cannot be used"
					" with agent running on a Solaris version with zone support, but compiled on"
					" a Solaris version without zone support. Consider using \"all\" or install"
					" agent with Solaris zone support."));
			return SYSINFO_RET_FAIL;
		}

		/* zones are not supported, the agent can accept 6th parameter with default value "current" */
#endif
		zoneflag = ZBX_PROCSTAT_FLAGS_ZONE_CURRENT;
	}
	else if (0 == strcmp(flags, "all"))
	{
		zoneflag = ZBX_PROCSTAT_FLAGS_ZONE_ALL;
	}
	else
	{
		SET_MSG_RESULT(result, zbx_strdup(NULL, "Invalid sixth parameter."));
		return SYSINFO_RET_FAIL;
	}

	if (SUCCEED != zbx_procstat_collector_started())
	{
		SET_MSG_RESULT(result, zbx_strdup(NULL, "Collector is not started."));
		return SYSINFO_RET_FAIL;
	}

	zbx_timespec(&ts_timeout);
	ts_timeout.sec += CONFIG_TIMEOUT;

	while (SUCCEED != zbx_procstat_get_util(procname, username, cmdline, zoneflag, period, type, &value, &errmsg))
	{
		/* zbx_procstat_get_* functions will return FAIL when either a collection   */
		/* error was registered or if less than 2 data samples were collected.      */
		/* In the first case the errmsg will contain error message.                 */
		if (NULL != errmsg)
		{
			SET_MSG_RESULT(result, errmsg);
			return SYSINFO_RET_FAIL;
		}

		zbx_timespec(&ts);

		if (0 > zbx_timespec_compare(&ts_timeout, &ts))
		{
			SET_MSG_RESULT(result, zbx_strdup(NULL, "Timeout while waiting for collector data."));
			return SYSINFO_RET_FAIL;
		}

		sleep(1);
	}

	SET_DBL_RESULT(result, value);

	return SYSINFO_RET_OK;
}
