/*
** Zabbix
** Copyright (C) 2001-2017 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 "common.h"
#include "sysinc.h"
#include "module.h"
#include "zbxjson.h"
#include <mqueue.h>
#include <sys/stat.h>
#include <pwd.h>
#include <grp.h>

/* the variable keeps timeout setting for item processing */
static int      item_timeout = 0;

/* module SHOULD define internal functions as static and use a naming pattern different from Zabbix internal */
/* symbols (zbx_*) and loadable module API functions (zbx_module_*) to avoid conflicts                       */
static int      mqueue_getattr(AGENT_REQUEST *request, AGENT_RESULT *result);
static int      mqueue_info(AGENT_REQUEST *request, AGENT_RESULT *result);
static int      mqueue_discovery(AGENT_REQUEST *request, AGENT_RESULT *result);
static int      mqueue_sumattr(AGENT_REQUEST *request, AGENT_RESULT *result);

static ZBX_METRIC keys[] =
/*      KEY                     FLAG            FUNCTION                TEST PARAMETERS */
{
        {"mqueue.getattr",      CF_HAVEPARAMS,  mqueue_getattr,         "curmsgs"},
        {"mqueue.info",         CF_HAVEPARAMS,  mqueue_info,            "size"},
        {"mqueue.discovery",    0,              mqueue_discovery,       NULL},
        {"mqueue.sumattr",      CF_HAVEPARAMS,  mqueue_sumattr,         "malloc"},
        {NULL}
};

/******************************************************************************
 *                                                                            *
 * Function: zbx_module_api_version                                           *
 *                                                                            *
 * Purpose: returns version number of the module interface                    *
 *                                                                            *
 * Return value: ZBX_MODULE_API_VERSION - version of module.h module is       *
 *               compiled with, in order to load module successfully Zabbix   *
 *               MUST be compiled with the same version of this header file   *
 *                                                                            *
 ******************************************************************************/
int     zbx_module_api_version(void)
{
        return ZBX_MODULE_API_VERSION;
}

/******************************************************************************
 *                                                                            *
 * Function: zbx_module_item_timeout                                          *
 *                                                                            *
 * Purpose: set timeout value for processing of items                         *
 *                                                                            *
 * Parameters: timeout - timeout in seconds, 0 - no timeout set               *
 *                                                                            *
 ******************************************************************************/
void    zbx_module_item_timeout(int timeout)
{
        item_timeout = timeout;
}

/******************************************************************************
 *                                                                            *
 * Function: zbx_module_item_list                                             *
 *                                                                            *
 * Purpose: returns list of item keys supported by the module                 *
 *                                                                            *
 * Return value: list of item keys                                            *
 *                                                                            *
 ******************************************************************************/
ZBX_METRIC      *zbx_module_item_list(void)
{
        return keys;
}

/******************************************************************************
 *                                                                            *
 * Function: zbx_module_init                                                  *
 *                                                                            *
 * Purpose: the function is called on agent startup                           *
 *          It should be used to call any initialization routines             *
 *                                                                            *
 * Return value: ZBX_MODULE_OK - success                                      *
 *               ZBX_MODULE_FAIL - module initialization failed               *
 *                                                                            *
 * Comment: the module won't be loaded in case of ZBX_MODULE_FAIL             *
 *                                                                            *
 ******************************************************************************/
int     zbx_module_init(void)
{
        /* initialization for dummy.random */
        srand(time(NULL));

        return ZBX_MODULE_OK;
}

/******************************************************************************
 *                                                                            *
 * Function: zbx_module_uninit                                                *
 *                                                                            *
 * Purpose: the function is called on agent shutdown                          *
 *          It should be used to cleanup used resources if there are any      *
 *                                                                            *
 * Return value: ZBX_MODULE_OK - success                                      *
 *               ZBX_MODULE_FAIL - function failed                            *
 *                                                                            *
 ******************************************************************************/
int     zbx_module_uninit(void)
{
        return ZBX_MODULE_OK;
}

int     mqueue_getattr(AGENT_REQUEST *request, AGENT_RESULT *result)
{
        char *qname, *qattr;
        mqd_t mqd;
        struct mq_attr attr;
        int ret = SYSINFO_RET_FAIL;

        if (2 != request->nparam)
        {
                SET_MSG_RESULT(result, zbx_strdup(NULL, "Invalid number of parameters"));
                return SYSINFO_RET_FAIL;
        }

        qname = get_rparam(request,0);

        if (NULL == qname || '\0' == *qname)
        {
                SET_MSG_RESULT(result, zbx_strdup(NULL, "First parameter missing"));
                return SYSINFO_RET_FAIL;
        }

        qattr = get_rparam(request,1);

        if (NULL == qattr || '\0' == *qattr)
        {
                SET_MSG_RESULT(result, zbx_strdup(NULL, "Second parameter missing"));
                return SYSINFO_RET_FAIL;
        }

        mqd = mq_open(qname, O_RDONLY);

        if (mqd == (mqd_t) -1)
        {
                SET_MSG_RESULT(result, zbx_dsprintf(NULL, "Could not open queue:%s", qname));
                return SYSINFO_RET_FAIL;
        }

        if (mq_getattr(mqd, &attr) == -1)
        {
                SET_MSG_RESULT(result, zbx_dsprintf(NULL, "Could not get attributes of queue:%s", qname));
                goto err;
        }

        if (0 == strcmp(qattr, "maxmsg"))
        {
                SET_UI64_RESULT(result,attr.mq_maxmsg);
        }
        else if (0 == strcmp(qattr, "msgsize"))
        {
                SET_UI64_RESULT(result,attr.mq_msgsize);
        }
        else if (0 == strcmp(qattr, "curmsgs"))
        {
                SET_UI64_RESULT(result,attr.mq_curmsgs);
        }
        else if (0 == strcmp(qattr, "malloc"))
        {
                SET_UI64_RESULT(result,attr.mq_maxmsg * attr.mq_msgsize);
        }
        else
        {
                SET_MSG_RESULT(result, zbx_strdup(NULL, "Invalid second parameter"));
                goto err;
        }

        ret = SYSINFO_RET_OK;
err:
        mq_close(mqd);
        return ret;
}

int     mqueue_info(AGENT_REQUEST *request, AGENT_RESULT *result)
{
        char            *qname, *qinfo;
        char            qfile[MAX_STRING_LEN];
        zbx_uint64_t    qsize;
        FILE            *f_queue = NULL;
        int             ret = SYSINFO_RET_FAIL;

        if (2 != request->nparam)
        {
                SET_MSG_RESULT(result, zbx_strdup(NULL, "Invalid number of parameters"));
                return SYSINFO_RET_FAIL;
        }

        qname = get_rparam(request,0);

        if (NULL == qname || '\0' == *qname)
        {
                SET_MSG_RESULT(result, zbx_strdup(NULL, "First parameter missing"));
                return SYSINFO_RET_FAIL;
        }

        qinfo = get_rparam(request,1);

        if (NULL == qinfo || '\0' == *qinfo)
        {
                SET_MSG_RESULT(result, zbx_strdup(NULL, "Second parameter missing"));
                return SYSINFO_RET_FAIL;
        }

        zbx_snprintf(qfile, sizeof(qfile), "/dev/mqueue%s", qname);

        if (NULL == (f_queue = fopen(qfile, "r")))
        {
                SET_MSG_RESULT(result, zbx_dsprintf(NULL, "Could not open queue:%s", qfile));
                return SYSINFO_RET_FAIL;
        }

        if (0 == strcmp(qinfo, "size"))
        {
                fscanf(f_queue, "QSIZE:%llu", &qsize);
                SET_UI64_RESULT(result, qsize);
        }
        else
        {
                SET_MSG_RESULT(result, zbx_strdup(NULL, "Invalid second parameter"));
                goto err;
        }

        ret = SYSINFO_RET_OK;
err:
        zbx_fclose(f_queue);
        return ret;
}

int     mqueue_discovery(AGENT_REQUEST *request, AGENT_RESULT *result)
{
        char                    qname[MAX_STRING_LEN], qfile[MAX_STRING_LEN],
                                quid[MAX_STRING_LEN], qgid[MAX_STRING_LEN], qreadable[2];
        int                     ret = SYSINFO_RET_FAIL, qrok = 0;
        FILE                    *f_queue = NULL;
        DIR                     *dir;
        struct dirent           *entries;
        struct stat             sb;
        struct passwd           *pwd;
        struct group            *grp;
        struct zbx_json         j;

        if (NULL == (dir = opendir("/dev/mqueue")))
        {
                SET_MSG_RESULT(result, zbx_strdup(NULL, "Could not open queue directory"));
                return SYSINFO_RET_FAIL;
        }

        zbx_json_init(&j, ZBX_JSON_STAT_BUF_LEN);
        zbx_json_addarray(&j, ZBX_PROTO_TAG_DATA);

        while (NULL != (entries = readdir(dir)))
        {
                if (0 == strcmp(entries->d_name, ".") || 0 == strcmp(entries->d_name, ".."))
                {
                        continue;
                }

                zbx_snprintf(qname, sizeof(qname), "/%s", entries->d_name);
                zbx_snprintf(qfile, sizeof(qfile), "/dev/mqueue%s", qname);

                if (stat(qfile, &sb) == -1)
                {
                        SET_MSG_RESULT(result, zbx_dsprintf(NULL, "Could not stat queue: %s", qfile));
                        goto err;
                }

                zbx_snprintf(quid, sizeof(quid), "%ld", sb.st_uid);
                zbx_snprintf(qgid, sizeof(qgid), "%ld", sb.st_gid);

                if (0 == access(qfile, R_OK))
                {
                        qrok = 1;
                }

                zbx_snprintf(qreadable, sizeof(qreadable), "%d", qrok);


                zbx_json_addobject(&j, NULL);
                zbx_json_addstring(&j, "{#NAME}", qname, ZBX_JSON_TYPE_STRING);
                zbx_json_addstring(&j, "{#FILE}", qfile, ZBX_JSON_TYPE_STRING);
                zbx_json_addstring(&j, "{#UID}", quid, ZBX_JSON_TYPE_INT);
                zbx_json_addstring(&j, "{#GID}", qgid, ZBX_JSON_TYPE_INT);
                zbx_json_addstring(&j, "{#READABLE}", qreadable, ZBX_JSON_TYPE_INT);

                if ((pwd = getpwuid(sb.st_uid)) != NULL)
                {
                        zbx_json_addstring(&j, "{#USER}", pwd->pw_name, ZBX_JSON_TYPE_STRING);
                }

                if ((grp = getgrgid(sb.st_gid)) != NULL)
                {
                        zbx_json_addstring(&j, "{#GROUP}", grp->gr_name, ZBX_JSON_TYPE_STRING);
                }

                zbx_json_close(&j);
        }

        zbx_json_close(&j);
        SET_STR_RESULT(result, zbx_strdup(NULL, j.buffer));
        ret = SYSINFO_RET_OK;
err:
        zbx_json_free(&j);
        closedir(dir);
        return ret;
}

int     mqueue_sumattr(AGENT_REQUEST *request, AGENT_RESULT *result)
{
        char    qname[MAX_STRING_LEN], qfile[MAX_STRING_LEN], *qattr;
        mqd_t   mqd;
        struct  mq_attr attr;
        int     sum = 0, ret = SYSINFO_RET_FAIL;
        DIR     *dir;
        struct dirent   *entries;

        if (1 != request->nparam)
        {
                SET_MSG_RESULT(result, zbx_strdup(NULL, "Invalid number of parameters"));
                return SYSINFO_RET_FAIL;
        }

        qattr = get_rparam(request,0);

        if (NULL == qattr || '\0' == *qattr)
        {
                SET_MSG_RESULT(result, zbx_strdup(NULL, "First parameter missing"));
                return SYSINFO_RET_FAIL;
        }


        if (NULL == (dir = opendir("/dev/mqueue")))
        {
                SET_MSG_RESULT(result, zbx_strdup(NULL, "Could not open queue directory"));
                return SYSINFO_RET_FAIL;
        }
        while (NULL != (entries = readdir(dir)))
        {
                if (0 == strcmp(entries->d_name, ".") || 0 == strcmp(entries->d_name, ".."))
                {
                        continue;
                }

                zbx_snprintf(qname, sizeof(qname), "/%s", entries->d_name);
                zbx_snprintf(qfile, sizeof(qfile), "/dev/mqueue%s", qname);

                if (0 != access(qfile, R_OK))
                {
                        continue;
                }

                mqd = mq_open(qname, O_RDONLY);

                if (mqd == (mqd_t) -1)
                {
                        SET_MSG_RESULT(result, zbx_dsprintf(NULL, "Could not open queue:%s", qname));
                        return SYSINFO_RET_FAIL;
                }

                if (mq_getattr(mqd, &attr) == -1)
                {
                        SET_MSG_RESULT(result, zbx_dsprintf(NULL, "Could not get attributes of queue:%s", qname));
                        goto err;
                }

                if (0 == strcmp(qattr, "maxmsg"))
                {
                        sum += attr.mq_maxmsg;
                }
                else if (0 == strcmp(qattr, "msgsize"))
                {
                        sum += attr.mq_msgsize;
                }
                else if (0 == strcmp(qattr, "curmsgs"))
                {
                        sum += attr.mq_curmsgs;
                }
                else if (0 == strcmp(qattr, "malloc"))
                {
                        sum += attr.mq_maxmsg * attr.mq_msgsize;
                }
                else
                {
                        SET_MSG_RESULT(result, zbx_strdup(NULL, "Invalid first parameter"));
                        goto err;
                }
        }

        SET_UI64_RESULT(result, sum);
        ret = SYSINFO_RET_OK;
err:
        mq_close(mqd);
        closedir(dir);

        return ret;
}
