root/usr.sbin/bsnmpd/modules/snmp_hast/hast_snmp.c
/*-
 * SPDX-License-Identifier: BSD-2-Clause
 *
 * Copyright (c) 2013 Mikolaj Golub <trociny@FreeBSD.org>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

#include <sys/param.h>
#include <sys/queue.h>

#include <bsnmp/snmpmod.h>

#include <string.h>

#include "hast.h"
#include "hast_oid.h"
#include "hast_proto.h"
#include "hast_tree.h"
#include "nv.h"
#include "pjdlog.h"
#include "proto.h"

#define UPDATE_INTERVAL 500     /* update interval in ticks */

static struct lmodule *module;

static const struct asn_oid oid_hast = OIDX_begemotHast;

/* the Object Resource registration index */
static u_int hast_index = 0;

/*
 * Structure that describes single resource.
 */
struct hast_snmp_resource {
        TAILQ_ENTRY(hast_snmp_resource) link;
        int32_t         index;
        char            name[NAME_MAX];
        int             error;
        int             role;
        char            provname[NAME_MAX];
        char            localpath[PATH_MAX];
        int32_t         extentsize;
        int32_t         keepdirty;
        char            remoteaddr[HAST_ADDRSIZE];
        char            sourceaddr[HAST_ADDRSIZE];
        int             replication;
        int             status;
        uint64_t        dirty;
        uint64_t        reads;
        uint64_t        writes;
        uint64_t        deletes;
        uint64_t        flushes;
        uint64_t        activemap_updates;
        uint64_t        read_errors;
        uint64_t        write_errors;
        uint64_t        delete_errors;
        uint64_t        flush_errors;
        pid_t           workerpid;
        uint32_t        local_queue;
        uint32_t        send_queue;
        uint32_t        recv_queue;
        uint32_t        done_queue;
        uint32_t        idle_queue;
};

static TAILQ_HEAD(, hast_snmp_resource) resources =
    TAILQ_HEAD_INITIALIZER(resources);

/* Path to configuration file. */
static u_char *cfgpath;
/* Ticks of the last hast resources update. */
static uint64_t last_resources_update;

static void free_resources(void);
static int hastctl(struct nv *nvin, struct nv **nvout);
static int hast_fini(void);
static int hast_init(struct lmodule *mod, int argc, char *argv[]);
static void hast_start(void);
static int set_role(const char *resource, int role);
static int str2role(const char *str);
static int str2replication(const char *str);
static int str2status(const char *str);
static int update_resources(void);

const struct snmp_module config = {
    .comment   = "This module implements the BEGEMOT MIB for HAST.",
    .init      = hast_init,
    .start     = hast_start,
    .fini      = hast_fini,
    .tree      = hast_ctree,
    .tree_size = hast_CTREE_SIZE,
};

static int
hast_init(struct lmodule *mod, int argc __unused, char *argv[] __unused)
{

        module = mod;

        pjdlog_init(PJDLOG_MODE_SYSLOG);
        pjdlog_debug_set(0);

        cfgpath = malloc(sizeof(HAST_CONFIG));
        if (cfgpath == NULL) {
                pjdlog_error("Unable to allocate %zu bytes for cfgpath",
                    sizeof(HAST_CONFIG));
                return (-1);
        }
        strcpy(cfgpath, HAST_CONFIG);
        return(0);
}

static void
hast_start(void)
{
        hast_index = or_register(&oid_hast,
            "The MIB module for BEGEMOT-HAST-MIB.", module);
}

static int
hast_fini(void)
{

        or_unregister(hast_index);
        free_resources();
        free(cfgpath);
        return (0);
}

static void
free_resources(void)
{
        struct hast_snmp_resource *res;

        while ((res = TAILQ_FIRST(&resources)) != NULL) {
                TAILQ_REMOVE(&resources, res, link);
                free(res);
        }
}

static int
str2role(const char *str)
{

        if (strcmp(str, "init") == 0)
                return (HAST_ROLE_INIT);
        if (strcmp(str, "primary") == 0)
                return (HAST_ROLE_PRIMARY);
        if (strcmp(str, "secondary") == 0)
                return (HAST_ROLE_SECONDARY);
        return (HAST_ROLE_UNDEF);
}

static int
str2replication(const char *str)
{

        if (strcmp(str, "fullsync") == 0)
                return (HAST_REPLICATION_FULLSYNC);
        if (strcmp(str, "memsync") == 0)
                return (HAST_REPLICATION_MEMSYNC);
        if (strcmp(str, "async") == 0)
                return (HAST_REPLICATION_ASYNC);
        return (-1);
}

static int
str2status(const char *str)
{

        if (strcmp(str, "complete") == 0)
                return (0);
        if (strcmp(str, "degraded") == 0)
                return (1);
        return (-1);
}

static int
hastctl(struct nv *nvin, struct nv **nvout)
{
        struct hastd_config *cfg;
        struct proto_conn *conn;
        struct nv *nv;
        int error;

        cfg = yy_config_parse(cfgpath, true);
        if (cfg == NULL)
                return (-1);

        /* Setup control connection... */
        if (proto_client(NULL, cfg->hc_controladdr, &conn) == -1) {
                pjdlog_error("Unable to setup control connection to %s",
                    cfg->hc_controladdr);
                return (-1);
        }
        /* ...and connect to hastd. */
        if (proto_connect(conn, HAST_TIMEOUT) == -1) {
                pjdlog_error("Unable to connect to hastd via %s",
                    cfg->hc_controladdr);
                proto_close(conn);
                return (-1);
        }
        /* Send the command to the server... */
        if (hast_proto_send(NULL, conn, nvin, NULL, 0) == -1) {
                pjdlog_error("Unable to send command to hastd via %s",
                    cfg->hc_controladdr);
                proto_close(conn);
                return (-1);
        }
        /* ...and receive reply. */
        if (hast_proto_recv_hdr(conn, &nv) == -1) {
                pjdlog_error("cannot receive reply from hastd via %s",
                    cfg->hc_controladdr);
                proto_close(conn);
                return (-1);
        }
        proto_close(conn);
        error = nv_get_int16(nv, "error");
        if (error != 0) {
                pjdlog_error("Error %d received from hastd.", error);
                nv_free(nv);
                return (-1);
        }
        nv_set_error(nv, 0);
        *nvout = nv;
        return (0);
}

static int
set_role(const char *resource, int role)
{
        struct nv *nvin, *nvout;
        int error;

        nvin = nv_alloc();
        nv_add_string(nvin, resource, "resource%d", 0);
        nv_add_uint8(nvin, HASTCTL_CMD_SETROLE, "cmd");
        nv_add_uint8(nvin, role, "role");
        error = hastctl(nvin, &nvout);
        nv_free(nvin);
        if (error != 0)
                return (-1);
        nv_free(nvout);
        return (SNMP_ERR_NOERROR);
}

static int
update_resources(void)
{
        struct hast_snmp_resource *res;
        struct nv *nvin, *nvout;
        static uint64_t now;
        unsigned int i;
        const char *str;
        int error;

        now = get_ticks();
        if (now - last_resources_update < UPDATE_INTERVAL)
                return (0);

        last_resources_update = now;

        free_resources();

        nvin = nv_alloc();
        nv_add_uint8(nvin, HASTCTL_CMD_STATUS, "cmd");
        nv_add_string(nvin, "all", "resource%d", 0);
        error = hastctl(nvin, &nvout);
        nv_free(nvin);
        if (error != 0)
                return (-1);

        for (i = 0; ; i++) {
                str = nv_get_string(nvout, "resource%u", i);
                if (str == NULL)
                        break;
                res = calloc(1, sizeof(*res));
                if (res == NULL) {
                        pjdlog_error("Unable to allocate %zu bytes for "
                            "resource", sizeof(*res));
                        return (-1);
                }
                res->index = i + 1;
                strncpy(res->name, str, sizeof(res->name) - 1);
                error = nv_get_int16(nvout, "error%u", i);
                if (error != 0)
                        continue;
                str = nv_get_string(nvout, "role%u", i);
                res->role = str != NULL ? str2role(str) : HAST_ROLE_UNDEF;
                str = nv_get_string(nvout, "provname%u", i);
                if (str != NULL)
                        strncpy(res->provname, str, sizeof(res->provname) - 1);
                str = nv_get_string(nvout, "localpath%u", i);
                if (str != NULL) {
                        strncpy(res->localpath, str,
                            sizeof(res->localpath) - 1);
                }
                res->extentsize = nv_get_uint32(nvout, "extentsize%u", i);
                res->keepdirty = nv_get_uint32(nvout, "keepdirty%u", i);
                str = nv_get_string(nvout, "remoteaddr%u", i);
                if (str != NULL) {
                        strncpy(res->remoteaddr, str,
                            sizeof(res->remoteaddr) - 1);
                }
                str = nv_get_string(nvout, "sourceaddr%u", i);
                if (str != NULL) {
                        strncpy(res->sourceaddr, str,
                            sizeof(res->sourceaddr) - 1);
                }
                str = nv_get_string(nvout, "replication%u", i);
                res->replication = str != NULL ? str2replication(str) : -1;
                str = nv_get_string(nvout, "status%u", i);
                res->status = str != NULL ? str2status(str) : -1;
                res->dirty = nv_get_uint64(nvout, "dirty%u", i);
                res->reads = nv_get_uint64(nvout, "stat_read%u", i);
                res->writes = nv_get_uint64(nvout, "stat_write%u", i);
                res->deletes = nv_get_uint64(nvout, "stat_delete%u", i);
                res->flushes = nv_get_uint64(nvout, "stat_flush%u", i);
                res->activemap_updates =
                    nv_get_uint64(nvout, "stat_activemap_update%u", i);
                res->read_errors =
                    nv_get_uint64(nvout, "stat_read_error%u", i);
                res->write_errors =
                    nv_get_uint64(nvout, "stat_write_error%u", i);
                res->delete_errors =
                    nv_get_uint64(nvout, "stat_delete_error%u", i);
                res->flush_errors =
                    nv_get_uint64(nvout, "stat_flush_error%u", i);
                res->workerpid = nv_get_int32(nvout, "workerpid%u", i);
                res->local_queue =
                    nv_get_uint64(nvout, "local_queue_size%u", i);
                res->send_queue =
                    nv_get_uint64(nvout, "send_queue_size%u", i);
                res->recv_queue =
                    nv_get_uint64(nvout, "recv_queue_size%u", i);
                res->done_queue =
                    nv_get_uint64(nvout, "done_queue_size%u", i);
                res->idle_queue =
                    nv_get_uint64(nvout, "idle_queue_size%u", i);
                TAILQ_INSERT_TAIL(&resources, res, link);
        }
        nv_free(nvout);
        return (0);
}

int
op_hastConfig(struct snmp_context *context, struct snmp_value *value,
    u_int sub, u_int iidx __unused, enum snmp_op op)
{
        asn_subid_t which;

        which = value->var.subs[sub - 1];

        switch (op) {
        case SNMP_OP_GET:
                switch (which) {
                case LEAF_hastConfigFile:
                        return (string_get(value, cfgpath, -1));
                default:
                        return (SNMP_ERR_RES_UNAVAIL);
                }
        case SNMP_OP_SET:
                switch (which) {
                case LEAF_hastConfigFile:
                        return (string_save(value, context, -1,
                            (u_char **)&cfgpath));
                default:
                        return (SNMP_ERR_RES_UNAVAIL);
                }
        case SNMP_OP_GETNEXT:
        case SNMP_OP_ROLLBACK:
        case SNMP_OP_COMMIT:
                return (SNMP_ERR_NOERROR);
        default:
                return (SNMP_ERR_RES_UNAVAIL);
        }
}

int
op_hastResourceTable(struct snmp_context *context __unused,
    struct snmp_value *value, u_int sub, u_int iidx __unused, enum snmp_op op)
{
        struct hast_snmp_resource *res;
        asn_subid_t which;
        int ret;

        if (update_resources() == -1)
                return (SNMP_ERR_RES_UNAVAIL);

        which = value->var.subs[sub - 1];

        switch (op) {
        case SNMP_OP_GETNEXT:
                res = NEXT_OBJECT_INT(&resources, &value->var, sub);
                if (res == NULL)
                        return (SNMP_ERR_NOSUCHNAME);
                value->var.len = sub + 1;
                value->var.subs[sub] = res->index;
                break;
        case SNMP_OP_GET:
                if (value->var.len - sub != 1)
                        return (SNMP_ERR_NOSUCHNAME);
                res = FIND_OBJECT_INT(&resources, &value->var, sub);
                if (res == NULL)
                        return (SNMP_ERR_NOSUCHNAME);
                break;
        case SNMP_OP_SET:
                res = FIND_OBJECT_INT(&resources, &value->var, sub);
                if (res == NULL)
                        return (SNMP_ERR_NOSUCHNAME);
                switch (which) {
                case LEAF_hastResourceRole:
                        ret = set_role(res->name, value->v.integer);
                        /* force update on next run */
                        last_resources_update = 0;
                        break;
                default:
                        ret = SNMP_ERR_NOT_WRITEABLE;
                        break;
                }
                return ret;
        case SNMP_OP_ROLLBACK:
        case SNMP_OP_COMMIT:
                return (SNMP_ERR_NOERROR);
        default:
                return (SNMP_ERR_RES_UNAVAIL);
        }

        ret = SNMP_ERR_NOERROR;

        switch (which) {
        case LEAF_hastResourceIndex:
                value->v.integer = res->index;
                break;
        case LEAF_hastResourceName:
                ret = string_get(value, res->name, -1);
                break;
        case LEAF_hastResourceRole:
                value->v.integer = res->role;
                break;
        case LEAF_hastResourceProvName:
                ret = string_get(value, res->provname, -1);
                break;
        case LEAF_hastResourceLocalPath:
                ret = string_get(value, res->localpath, -1);
                break;
        case LEAF_hastResourceExtentSize:
                value->v.integer = res->extentsize;
                break;
        case LEAF_hastResourceKeepDirty:
                value->v.integer = res->keepdirty;
                break;
        case LEAF_hastResourceRemoteAddr:
                ret = string_get(value, res->remoteaddr, -1);
                break;
        case LEAF_hastResourceSourceAddr:
                ret = string_get(value, res->sourceaddr, -1);
                break;
        case LEAF_hastResourceReplication:
                value->v.integer = res->replication;
                break;
        case LEAF_hastResourceStatus:
                value->v.integer = res->status;
                break;
        case LEAF_hastResourceDirty:
                value->v.counter64 = res->dirty;
                break;
        case LEAF_hastResourceReads:
                value->v.counter64 = res->reads;
                break;
        case LEAF_hastResourceWrites:
                value->v.counter64 = res->writes;
                break;
        case LEAF_hastResourceDeletes:
                value->v.counter64 = res->deletes;
                break;
        case LEAF_hastResourceFlushes:
                value->v.counter64 = res->flushes;
                break;
        case LEAF_hastResourceActivemapUpdates:
                value->v.counter64 = res->activemap_updates;
                break;
        case LEAF_hastResourceReadErrors:
                value->v.counter64 = res->read_errors;
                break;
        case LEAF_hastResourceWriteErrors:
                value->v.counter64 = res->write_errors;
                break;
        case LEAF_hastResourceDeleteErrors:
                value->v.counter64 = res->delete_errors;
                break;
        case LEAF_hastResourceFlushErrors:
                value->v.counter64 = res->flush_errors;
                break;
        case LEAF_hastResourceWorkerPid:
                value->v.integer = res->workerpid;
                break;
        case LEAF_hastResourceLocalQueue:
                value->v.uint32 = res->local_queue;
                break;
        case LEAF_hastResourceSendQueue:
                value->v.uint32 = res->send_queue;
                break;
        case LEAF_hastResourceRecvQueue:
                value->v.uint32 = res->recv_queue;
                break;
        case LEAF_hastResourceDoneQueue:
                value->v.uint32 = res->done_queue;
                break;
        case LEAF_hastResourceIdleQueue:
                value->v.uint32 = res->idle_queue;
                break;
        default:
                ret = SNMP_ERR_RES_UNAVAIL;
                break;
        }
        return (ret);
}