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