1/*-
2 * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
3 *
4 * Copyright (c) 2013 Mikolaj Golub <trociny@FreeBSD.org>
5 * All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 * 1. Redistributions of source code must retain the above copyright
11 *    notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 *    notice, this list of conditions and the following disclaimer in the
14 *    documentation and/or other materials provided with the distribution.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
17 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
20 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
22 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
23 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
24 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
25 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
26 * SUCH DAMAGE.
27 */
28
29#include <sys/cdefs.h>
30__FBSDID("$FreeBSD$");
31
32#include <sys/param.h>
33#include <sys/queue.h>
34
35#include <bsnmp/snmpmod.h>
36
37#include <string.h>
38
39#include "hast.h"
40#include "hast_oid.h"
41#include "hast_proto.h"
42#include "hast_tree.h"
43#include "nv.h"
44#include "pjdlog.h"
45#include "proto.h"
46
47#define UPDATE_INTERVAL	500	/* update interval in ticks */
48
49static struct lmodule *module;
50
51static const struct asn_oid oid_hast = OIDX_begemotHast;
52
53/* the Object Resource registration index */
54static u_int hast_index = 0;
55
56/*
57 * Structure that describes single resource.
58 */
59struct hast_snmp_resource {
60	TAILQ_ENTRY(hast_snmp_resource) link;
61	int32_t		index;
62	char		name[NAME_MAX];
63	int		error;
64	int		role;
65	char		provname[NAME_MAX];
66	char		localpath[PATH_MAX];
67	int32_t		extentsize;
68	int32_t		keepdirty;
69	char		remoteaddr[HAST_ADDRSIZE];
70	char		sourceaddr[HAST_ADDRSIZE];
71	int		replication;
72	int		status;
73	uint64_t	dirty;
74	uint64_t	reads;
75	uint64_t	writes;
76	uint64_t	deletes;
77	uint64_t	flushes;
78	uint64_t	activemap_updates;
79	uint64_t	read_errors;
80	uint64_t	write_errors;
81	uint64_t	delete_errors;
82	uint64_t	flush_errors;
83	pid_t		workerpid;
84	uint32_t	local_queue;
85	uint32_t	send_queue;
86	uint32_t	recv_queue;
87	uint32_t	done_queue;
88	uint32_t	idle_queue;
89};
90
91static TAILQ_HEAD(, hast_snmp_resource) resources =
92    TAILQ_HEAD_INITIALIZER(resources);
93
94/* Path to configuration file. */
95static u_char *cfgpath;
96/* Ticks of the last hast resources update. */
97static uint64_t last_resources_update;
98
99static void free_resources(void);
100static int hastctl(struct nv *nvin, struct nv **nvout);
101static int hast_fini(void);
102static int hast_init(struct lmodule *mod, int argc, char *argv[]);
103static void hast_start(void);
104static int set_role(const char *resource, int role);
105static int str2role(const char *str);
106static int str2replication(const char *str);
107static int str2status(const char *str);
108static int update_resources(void);
109
110const struct snmp_module config = {
111    .comment   = "This module implements the BEGEMOT MIB for HAST.",
112    .init      = hast_init,
113    .start     = hast_start,
114    .fini      = hast_fini,
115    .tree      = hast_ctree,
116    .tree_size = hast_CTREE_SIZE,
117};
118
119static int
120hast_init(struct lmodule *mod, int argc __unused, char *argv[] __unused)
121{
122
123	module = mod;
124
125	pjdlog_init(PJDLOG_MODE_SYSLOG);
126	pjdlog_debug_set(0);
127
128	cfgpath = malloc(sizeof(HAST_CONFIG));
129	if (cfgpath == NULL) {
130		pjdlog_error("Unable to allocate %zu bytes for cfgpath",
131		    sizeof(HAST_CONFIG));
132		return (-1);
133	}
134	strcpy(cfgpath, HAST_CONFIG);
135	return(0);
136}
137
138static void
139hast_start(void)
140{
141	hast_index = or_register(&oid_hast,
142	    "The MIB module for BEGEMOT-HAST-MIB.", module);
143}
144
145static int
146hast_fini(void)
147{
148
149	or_unregister(hast_index);
150	free_resources();
151	free(cfgpath);
152	return (0);
153}
154
155static void
156free_resources(void)
157{
158	struct hast_snmp_resource *res;
159
160	while ((res = TAILQ_FIRST(&resources)) != NULL) {
161		TAILQ_REMOVE(&resources, res, link);
162		free(res);
163	}
164}
165
166static int
167str2role(const char *str)
168{
169
170	if (strcmp(str, "init") == 0)
171		return (HAST_ROLE_INIT);
172	if (strcmp(str, "primary") == 0)
173		return (HAST_ROLE_PRIMARY);
174	if (strcmp(str, "secondary") == 0)
175		return (HAST_ROLE_SECONDARY);
176	return (HAST_ROLE_UNDEF);
177}
178
179static int
180str2replication(const char *str)
181{
182
183	if (strcmp(str, "fullsync") == 0)
184		return (HAST_REPLICATION_FULLSYNC);
185	if (strcmp(str, "memsync") == 0)
186		return (HAST_REPLICATION_MEMSYNC);
187	if (strcmp(str, "async") == 0)
188		return (HAST_REPLICATION_ASYNC);
189	return (-1);
190}
191
192static int
193str2status(const char *str)
194{
195
196	if (strcmp(str, "complete") == 0)
197		return (0);
198	if (strcmp(str, "degraded") == 0)
199		return (1);
200	return (-1);
201}
202
203static int
204hastctl(struct nv *nvin, struct nv **nvout)
205{
206	struct hastd_config *cfg;
207	struct proto_conn *conn;
208	struct nv *nv;
209	int error;
210
211	cfg = yy_config_parse(cfgpath, true);
212	if (cfg == NULL)
213		return (-1);
214
215	/* Setup control connection... */
216	if (proto_client(NULL, cfg->hc_controladdr, &conn) == -1) {
217		pjdlog_error("Unable to setup control connection to %s",
218		    cfg->hc_controladdr);
219		return (-1);
220	}
221	/* ...and connect to hastd. */
222	if (proto_connect(conn, HAST_TIMEOUT) == -1) {
223		pjdlog_error("Unable to connect to hastd via %s",
224		    cfg->hc_controladdr);
225		proto_close(conn);
226		return (-1);
227	}
228	/* Send the command to the server... */
229	if (hast_proto_send(NULL, conn, nvin, NULL, 0) == -1) {
230		pjdlog_error("Unable to send command to hastd via %s",
231		    cfg->hc_controladdr);
232		proto_close(conn);
233		return (-1);
234	}
235	/* ...and receive reply. */
236	if (hast_proto_recv_hdr(conn, &nv) == -1) {
237		pjdlog_error("cannot receive reply from hastd via %s",
238		    cfg->hc_controladdr);
239		proto_close(conn);
240		return (-1);
241	}
242	proto_close(conn);
243	error = nv_get_int16(nv, "error");
244	if (error != 0) {
245		pjdlog_error("Error %d received from hastd.", error);
246		nv_free(nv);
247		return (-1);
248	}
249	nv_set_error(nv, 0);
250	*nvout = nv;
251	return (0);
252}
253
254static int
255set_role(const char *resource, int role)
256{
257	struct nv *nvin, *nvout;
258	int error;
259
260	nvin = nv_alloc();
261	nv_add_string(nvin, resource, "resource%d", 0);
262	nv_add_uint8(nvin, HASTCTL_CMD_SETROLE, "cmd");
263	nv_add_uint8(nvin, role, "role");
264	error = hastctl(nvin, &nvout);
265	nv_free(nvin);
266	if (error != 0)
267		return (-1);
268	nv_free(nvout);
269	return (SNMP_ERR_NOERROR);
270}
271
272static int
273update_resources(void)
274{
275	struct hast_snmp_resource *res;
276	struct nv *nvin, *nvout;
277	static uint64_t now;
278	unsigned int i;
279	const char *str;
280	int error;
281
282	now = get_ticks();
283	if (now - last_resources_update < UPDATE_INTERVAL)
284		return (0);
285
286	last_resources_update = now;
287
288	free_resources();
289
290	nvin = nv_alloc();
291	nv_add_uint8(nvin, HASTCTL_CMD_STATUS, "cmd");
292	nv_add_string(nvin, "all", "resource%d", 0);
293	error = hastctl(nvin, &nvout);
294	nv_free(nvin);
295	if (error != 0)
296		return (-1);
297
298	for (i = 0; ; i++) {
299		str = nv_get_string(nvout, "resource%u", i);
300		if (str == NULL)
301			break;
302		res = calloc(1, sizeof(*res));
303		if (res == NULL) {
304			pjdlog_error("Unable to allocate %zu bytes for "
305			    "resource", sizeof(*res));
306			return (-1);
307		}
308		res->index = i + 1;
309		strncpy(res->name, str, sizeof(res->name) - 1);
310		error = nv_get_int16(nvout, "error%u", i);
311		if (error != 0)
312			continue;
313		str = nv_get_string(nvout, "role%u", i);
314		res->role = str != NULL ? str2role(str) : HAST_ROLE_UNDEF;
315		str = nv_get_string(nvout, "provname%u", i);
316		if (str != NULL)
317			strncpy(res->provname, str, sizeof(res->provname) - 1);
318		str = nv_get_string(nvout, "localpath%u", i);
319		if (str != NULL) {
320			strncpy(res->localpath, str,
321			    sizeof(res->localpath) - 1);
322		}
323		res->extentsize = nv_get_uint32(nvout, "extentsize%u", i);
324		res->keepdirty = nv_get_uint32(nvout, "keepdirty%u", i);
325		str = nv_get_string(nvout, "remoteaddr%u", i);
326		if (str != NULL) {
327			strncpy(res->remoteaddr, str,
328			    sizeof(res->remoteaddr) - 1);
329		}
330		str = nv_get_string(nvout, "sourceaddr%u", i);
331		if (str != NULL) {
332			strncpy(res->sourceaddr, str,
333			    sizeof(res->sourceaddr) - 1);
334		}
335		str = nv_get_string(nvout, "replication%u", i);
336		res->replication = str != NULL ? str2replication(str) : -1;
337		str = nv_get_string(nvout, "status%u", i);
338		res->status = str != NULL ? str2status(str) : -1;
339		res->dirty = nv_get_uint64(nvout, "dirty%u", i);
340		res->reads = nv_get_uint64(nvout, "stat_read%u", i);
341		res->writes = nv_get_uint64(nvout, "stat_write%u", i);
342		res->deletes = nv_get_uint64(nvout, "stat_delete%u", i);
343		res->flushes = nv_get_uint64(nvout, "stat_flush%u", i);
344		res->activemap_updates =
345		    nv_get_uint64(nvout, "stat_activemap_update%u", i);
346		res->read_errors =
347		    nv_get_uint64(nvout, "stat_read_error%u", i);
348		res->write_errors =
349		    nv_get_uint64(nvout, "stat_write_error%u", i);
350		res->delete_errors =
351		    nv_get_uint64(nvout, "stat_delete_error%u", i);
352		res->flush_errors =
353		    nv_get_uint64(nvout, "stat_flush_error%u", i);
354		res->workerpid = nv_get_int32(nvout, "workerpid%u", i);
355		res->local_queue =
356		    nv_get_uint64(nvout, "local_queue_size%u", i);
357		res->send_queue =
358		    nv_get_uint64(nvout, "send_queue_size%u", i);
359		res->recv_queue =
360		    nv_get_uint64(nvout, "recv_queue_size%u", i);
361		res->done_queue =
362		    nv_get_uint64(nvout, "done_queue_size%u", i);
363		res->idle_queue =
364		    nv_get_uint64(nvout, "idle_queue_size%u", i);
365		TAILQ_INSERT_TAIL(&resources, res, link);
366	}
367	nv_free(nvout);
368	return (0);
369}
370
371int
372op_hastConfig(struct snmp_context *context, struct snmp_value *value,
373    u_int sub, u_int iidx __unused, enum snmp_op op)
374{
375	asn_subid_t which;
376
377	which = value->var.subs[sub - 1];
378
379	switch (op) {
380	case SNMP_OP_GET:
381		switch (which) {
382		case LEAF_hastConfigFile:
383			return (string_get(value, cfgpath, -1));
384		default:
385			return (SNMP_ERR_RES_UNAVAIL);
386		}
387	case SNMP_OP_SET:
388		switch (which) {
389		case LEAF_hastConfigFile:
390			return (string_save(value, context, -1,
391			    (u_char **)&cfgpath));
392		default:
393			return (SNMP_ERR_RES_UNAVAIL);
394		}
395	case SNMP_OP_GETNEXT:
396	case SNMP_OP_ROLLBACK:
397	case SNMP_OP_COMMIT:
398		return (SNMP_ERR_NOERROR);
399	default:
400		return (SNMP_ERR_RES_UNAVAIL);
401	}
402}
403
404int
405op_hastResourceTable(struct snmp_context *context __unused,
406    struct snmp_value *value, u_int sub, u_int iidx __unused, enum snmp_op op)
407{
408	struct hast_snmp_resource *res;
409	asn_subid_t which;
410	int ret;
411
412	if (update_resources() == -1)
413		return (SNMP_ERR_RES_UNAVAIL);
414
415	which = value->var.subs[sub - 1];
416
417	switch (op) {
418	case SNMP_OP_GETNEXT:
419		res = NEXT_OBJECT_INT(&resources, &value->var, sub);
420		if (res == NULL)
421			return (SNMP_ERR_NOSUCHNAME);
422		value->var.len = sub + 1;
423		value->var.subs[sub] = res->index;
424		break;
425	case SNMP_OP_GET:
426		if (value->var.len - sub != 1)
427			return (SNMP_ERR_NOSUCHNAME);
428		res = FIND_OBJECT_INT(&resources, &value->var, sub);
429		if (res == NULL)
430			return (SNMP_ERR_NOSUCHNAME);
431		break;
432	case SNMP_OP_SET:
433		res = FIND_OBJECT_INT(&resources, &value->var, sub);
434		if (res == NULL)
435			return (SNMP_ERR_NOSUCHNAME);
436		switch (which) {
437		case LEAF_hastResourceRole:
438			ret = set_role(res->name, value->v.integer);
439			/* force update on next run */
440			last_resources_update = 0;
441			break;
442		default:
443			ret = SNMP_ERR_NOT_WRITEABLE;
444			break;
445		}
446		return ret;
447	case SNMP_OP_ROLLBACK:
448	case SNMP_OP_COMMIT:
449		return (SNMP_ERR_NOERROR);
450	default:
451		return (SNMP_ERR_RES_UNAVAIL);
452	}
453
454	ret = SNMP_ERR_NOERROR;
455
456	switch (which) {
457	case LEAF_hastResourceIndex:
458		value->v.integer = res->index;
459		break;
460	case LEAF_hastResourceName:
461		ret = string_get(value, res->name, -1);
462		break;
463	case LEAF_hastResourceRole:
464		value->v.integer = res->role;
465		break;
466	case LEAF_hastResourceProvName:
467		ret = string_get(value, res->provname, -1);
468		break;
469	case LEAF_hastResourceLocalPath:
470		ret = string_get(value, res->localpath, -1);
471		break;
472	case LEAF_hastResourceExtentSize:
473		value->v.integer = res->extentsize;
474		break;
475	case LEAF_hastResourceKeepDirty:
476		value->v.integer = res->keepdirty;
477		break;
478	case LEAF_hastResourceRemoteAddr:
479		ret = string_get(value, res->remoteaddr, -1);
480		break;
481	case LEAF_hastResourceSourceAddr:
482		ret = string_get(value, res->sourceaddr, -1);
483		break;
484	case LEAF_hastResourceReplication:
485		value->v.integer = res->replication;
486		break;
487	case LEAF_hastResourceStatus:
488		value->v.integer = res->status;
489		break;
490	case LEAF_hastResourceDirty:
491		value->v.counter64 = res->dirty;
492		break;
493	case LEAF_hastResourceReads:
494		value->v.counter64 = res->reads;
495		break;
496	case LEAF_hastResourceWrites:
497		value->v.counter64 = res->writes;
498		break;
499	case LEAF_hastResourceDeletes:
500		value->v.counter64 = res->deletes;
501		break;
502	case LEAF_hastResourceFlushes:
503		value->v.counter64 = res->flushes;
504		break;
505	case LEAF_hastResourceActivemapUpdates:
506		value->v.counter64 = res->activemap_updates;
507		break;
508	case LEAF_hastResourceReadErrors:
509		value->v.counter64 = res->read_errors;
510		break;
511	case LEAF_hastResourceWriteErrors:
512		value->v.counter64 = res->write_errors;
513		break;
514	case LEAF_hastResourceDeleteErrors:
515		value->v.counter64 = res->delete_errors;
516		break;
517	case LEAF_hastResourceFlushErrors:
518		value->v.counter64 = res->flush_errors;
519		break;
520	case LEAF_hastResourceWorkerPid:
521		value->v.integer = res->workerpid;
522		break;
523	case LEAF_hastResourceLocalQueue:
524		value->v.uint32 = res->local_queue;
525		break;
526	case LEAF_hastResourceSendQueue:
527		value->v.uint32 = res->send_queue;
528		break;
529	case LEAF_hastResourceRecvQueue:
530		value->v.uint32 = res->recv_queue;
531		break;
532	case LEAF_hastResourceDoneQueue:
533		value->v.uint32 = res->done_queue;
534		break;
535	case LEAF_hastResourceIdleQueue:
536		value->v.uint32 = res->idle_queue;
537		break;
538	default:
539		ret = SNMP_ERR_RES_UNAVAIL;
540		break;
541	}
542	return (ret);
543}
544