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 
49 static struct lmodule *module;
50 
51 static const struct asn_oid oid_hast = OIDX_begemotHast;
52 
53 /* the Object Resource registration index */
54 static u_int hast_index = 0;
55 
56 /*
57  * Structure that describes single resource.
58  */
59 struct 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 
91 static TAILQ_HEAD(, hast_snmp_resource) resources =
92     TAILQ_HEAD_INITIALIZER(resources);
93 
94 /* Path to configuration file. */
95 static u_char *cfgpath;
96 /* Ticks of the last hast resources update. */
97 static uint64_t last_resources_update;
98 
99 static void free_resources(void);
100 static int hastctl(struct nv *nvin, struct nv **nvout);
101 static int hast_fini(void);
102 static int hast_init(struct lmodule *mod, int argc, char *argv[]);
103 static void hast_start(void);
104 static int set_role(const char *resource, int role);
105 static int str2role(const char *str);
106 static int str2replication(const char *str);
107 static int str2status(const char *str);
108 static int update_resources(void);
109 
110 const 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 
119 static int
120 hast_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 
138 static void
139 hast_start(void)
140 {
141 	hast_index = or_register(&oid_hast,
142 	    "The MIB module for BEGEMOT-HAST-MIB.", module);
143 }
144 
145 static int
146 hast_fini(void)
147 {
148 
149 	or_unregister(hast_index);
150 	free_resources();
151 	free(cfgpath);
152 	return (0);
153 }
154 
155 static void
156 free_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 
166 static int
167 str2role(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 
179 static int
180 str2replication(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 
192 static int
193 str2status(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 
203 static int
204 hastctl(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 
254 static int
255 set_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 
272 static int
273 update_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 
371 int
372 op_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 
404 int
405 op_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