1 /*-
2  * Copyright (c) 2014 Luiz Otavio O Souza <loos@FreeBSD.org>
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  *
14  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
15  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
18  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24  * SUCH DAMAGE.
25  */
26 
27 #include <sys/param.h>
28 #include <sys/queue.h>
29 #include <sys/sysctl.h>
30 
31 #include <bsnmp/snmpmod.h>
32 
33 #include <stdio.h>
34 #include <stdlib.h>
35 #include <string.h>
36 #include <syslog.h>
37 
38 #include "lm75_oid.h"
39 #include "lm75_tree.h"
40 
41 #ifndef	LM75BUF
42 #define	LM75BUF		64
43 #endif
44 #define	TZ_ZEROC	2732
45 #define	UPDATE_INTERVAL	500	/* update interval in ticks */
46 
47 static struct lmodule *module;
48 
49 static const struct asn_oid oid_lm75 = OIDX_begemotLm75;
50 
51 /* the Object Resource registration index */
52 static u_int lm75_index = 0;
53 
54 /* Number of available sensors in the system. */
55 static int lm75_sensors;
56 
57 /*
58  * Structure that describes single sensor.
59  */
60 struct lm75_snmp_sensor {
61 	TAILQ_ENTRY(lm75_snmp_sensor) link;
62 	int32_t		index;
63 	int32_t		sysctlidx;
64 	int32_t		temp;
65 	char		desc[LM75BUF];
66 	char		location[LM75BUF];
67 	char		parent[LM75BUF];
68 	char		pnpinfo[LM75BUF];
69 };
70 
71 static TAILQ_HEAD(, lm75_snmp_sensor) sensors =
72     TAILQ_HEAD_INITIALIZER(sensors);
73 
74 /* Ticks of the last sensors reading. */
75 static uint64_t last_sensors_update;
76 
77 static void free_sensors(void);
78 static int lm75_fini(void);
79 static int lm75_init(struct lmodule *mod, int argc, char *argv[]);
80 static void lm75_start(void);
81 static int update_sensors(void);
82 
83 const struct snmp_module config = {
84     .comment   =
85 	"This module implements the BEGEMOT MIB for reading LM75 sensors data.",
86     .init      = lm75_init,
87     .start     = lm75_start,
88     .fini      = lm75_fini,
89     .tree      = lm75_ctree,
90     .tree_size = lm75_CTREE_SIZE,
91 };
92 
93 static int
94 lm75_init(struct lmodule *mod, int argc __unused, char *argv[] __unused)
95 {
96 
97 	module = mod;
98 
99 	lm75_sensors = 0;
100 
101 	return(0);
102 }
103 
104 static void
105 lm75_start(void)
106 {
107 
108 	lm75_index = or_register(&oid_lm75,
109 	    "The MIB module for reading lm75 sensors data.", module);
110 }
111 
112 static int
113 lm75_fini(void)
114 {
115 
116 	or_unregister(lm75_index);
117 	free_sensors();
118 	closelog();
119 
120 	return (0);
121 }
122 
123 static void
124 free_sensors(void)
125 {
126 	struct lm75_snmp_sensor *sensor;
127 
128 	while ((sensor = TAILQ_FIRST(&sensors)) != NULL) {
129 		TAILQ_REMOVE(&sensors, sensor, link);
130 		free(sensor);
131 	}
132 }
133 
134 static int
135 sysctlname(int *oid, int nlen, char *name, size_t len)
136 {
137 	int mib[12];
138 
139 	if (nlen > (int)(sizeof(mib) / sizeof(int) - 2))
140 		return (-1);
141 
142 	mib[0] = 0;
143 	mib[1] = 1;
144 	memcpy(mib + 2, oid, nlen * sizeof(int));
145 
146 	if (sysctl(mib, nlen + 2, name, &len, 0, 0) == -1)
147 		return (-1);
148 
149 	return (0);
150 }
151 
152 static int
153 sysctlgetnext(int *oid, int nlen, int *next, size_t *nextlen)
154 {
155 	int mib[12];
156 
157 	if (nlen > (int)(sizeof(mib) / sizeof(int) - 2))
158 		return (-1);
159 
160 	mib[0] = 0;
161 	mib[1] = 2;
162 	memcpy(mib + 2, oid, nlen * sizeof(int));
163 
164 	if (sysctl(mib, nlen + 2, next, nextlen, 0, 0) == -1)
165 		return (-1);
166 
167 	return (0);
168 }
169 
170 static int
171 update_sensor_sysctl(void *obuf, size_t *obuflen, int idx, const char *name)
172 {
173 	char buf[LM75BUF];
174 	int mib[5];
175 	size_t len;
176 
177 	/* Fill out the mib information. */
178 	snprintf(buf, sizeof(buf) - 1, "dev.lm75.%d.%s", idx, name);
179 	len = sizeof(mib) / sizeof(int);
180 	if (sysctlnametomib(buf, mib, &len) == -1)
181 		return (-1);
182 
183 	if (len != 4)
184 		return (-1);
185 
186 	/* Read the sysctl data. */
187 	if (sysctl(mib, len, obuf, obuflen, NULL, 0) == -1)
188 		return (-1);
189 
190 	return (0);
191 }
192 
193 static void
194 update_sensor(struct lm75_snmp_sensor *sensor, int idx)
195 {
196 	size_t len;
197 
198 	len = sizeof(sensor->desc);
199 	update_sensor_sysctl(sensor->desc, &len, idx, "%desc");
200 
201 	len = sizeof(sensor->location);
202 	update_sensor_sysctl(sensor->location, &len, idx, "%location");
203 
204 	len = sizeof(sensor->pnpinfo);
205 	update_sensor_sysctl(sensor->pnpinfo, &len, idx, "%pnpinfo");
206 
207 	len = sizeof(sensor->parent);
208 	update_sensor_sysctl(sensor->parent, &len, idx, "%parent");
209 }
210 
211 static int
212 add_sensor(char *buf)
213 {
214 	int idx, temp;
215 	size_t len;
216 	struct lm75_snmp_sensor *sensor;
217 
218 	if (sscanf(buf, "dev.lm75.%d.temperature", &idx) != 1)
219 		return (-1);
220 
221 	/* Read the sensor temperature. */
222 	len = sizeof(temp);
223 	if (update_sensor_sysctl(&temp, &len, idx, "temperature") != 0)
224 		return (-1);
225 
226 	/* Add the sensor data to the table. */
227 	sensor = calloc(1, sizeof(*sensor));
228 	if (sensor == NULL) {
229 		syslog(LOG_ERR, "Unable to allocate %zu bytes for resource",
230 		    sizeof(*sensor));
231 		return (-1);
232 	}
233 	sensor->index = ++lm75_sensors;
234 	sensor->sysctlidx = idx;
235 	sensor->temp = (temp - TZ_ZEROC) / 10;
236 	TAILQ_INSERT_TAIL(&sensors, sensor, link);
237 
238 	update_sensor(sensor, idx);
239 
240 	return (0);
241 }
242 
243 static int
244 update_sensors(void)
245 {
246 	char buf[LM75BUF];
247 	int i, root[5], *next, *oid;
248 	size_t len, nextlen, rootlen;
249 	static uint64_t now;
250 
251 	now = get_ticks();
252 	if (now - last_sensors_update < UPDATE_INTERVAL)
253 		return (0);
254 
255 	last_sensors_update = now;
256 
257 	/* Reset the sensor data. */
258 	free_sensors();
259 	lm75_sensors = 0;
260 
261 	/* Start from the lm75 default root node. */
262 	rootlen = 2;
263 	if (sysctlnametomib("dev.lm75", root, &rootlen) == -1)
264 		return (0);
265 
266 	oid = (int *)malloc(sizeof(int) * rootlen);
267 	if (oid == NULL) {
268 		perror("malloc");
269 		return (-1);
270 	}
271 	memcpy(oid, root, rootlen * sizeof(int));
272 	len = rootlen;
273 
274 	/* Traverse the sysctl(3) interface and find the active sensors. */
275 	for (;;) {
276 
277 		/* Find the size of the next mib. */
278 		nextlen = 0;
279 		if (sysctlgetnext(oid, len, NULL, &nextlen) == -1) {
280 			free(oid);
281 			return (0);
282 		}
283 		/* Allocate and read the next mib. */
284 		next = (int *)malloc(nextlen);
285 		if (next == NULL) {
286 			syslog(LOG_ERR,
287 			    "Unable to allocate %zu bytes for resource",
288 			    nextlen);
289 			free(oid);
290 			return (-1);
291 		}
292 		if (sysctlgetnext(oid, len, next, &nextlen) == -1) {
293 			free(oid);
294 			free(next);
295 			return (0);
296 		}
297 		free(oid);
298 		/* Check if we care about the next mib. */
299 		for (i = 0; i < (int)rootlen; i++)
300 			if (next[i] != root[i]) {
301 				free(next);
302 				return (0);
303 			}
304 		oid = (int *)malloc(nextlen);
305 		if (oid == NULL) {
306 			syslog(LOG_ERR,
307 			    "Unable to allocate %zu bytes for resource",
308 			    nextlen);
309 			free(next);
310 			return (-1);
311 		}
312 		memcpy(oid, next, nextlen);
313 		free(next);
314 		len = nextlen / sizeof(int);
315 
316 		/* Find the mib name. */
317 		if (sysctlname(oid, len, buf, sizeof(buf)) != 0)
318 			continue;
319 
320 		if (strstr(buf, "temperature"))
321 			if (add_sensor(buf) != 0) {
322 				free(oid);
323 				return (-1);
324 			}
325 	}
326 
327 	return (0);
328 }
329 
330 int
331 op_lm75Sensors(struct snmp_context *context __unused, struct snmp_value *value,
332     u_int sub, u_int iidx __unused, enum snmp_op op)
333 {
334 	asn_subid_t which;
335 
336 	if (update_sensors() == -1)
337 		return (SNMP_ERR_RES_UNAVAIL);
338 
339 	which = value->var.subs[sub - 1];
340 
341 	switch (op) {
342 	case SNMP_OP_GET:
343 		switch (which) {
344 		case LEAF_lm75Sensors:
345 			value->v.integer = lm75_sensors;
346 			break;
347 		default:
348 			return (SNMP_ERR_RES_UNAVAIL);
349 		}
350 		break;
351 	case SNMP_OP_SET:
352 		return (SNMP_ERR_NOT_WRITEABLE);
353 	case SNMP_OP_GETNEXT:
354 	case SNMP_OP_ROLLBACK:
355 	case SNMP_OP_COMMIT:
356 		return (SNMP_ERR_NOERROR);
357 	default:
358 		return (SNMP_ERR_RES_UNAVAIL);
359 	}
360 
361 	return (SNMP_ERR_NOERROR);
362 }
363 
364 int
365 op_lm75SensorTable(struct snmp_context *context __unused,
366     struct snmp_value *value, u_int sub, u_int iidx __unused, enum snmp_op op)
367 {
368 	struct lm75_snmp_sensor *sensor;
369 	asn_subid_t which;
370 	int ret;
371 
372 	if (update_sensors() == -1)
373 		return (SNMP_ERR_RES_UNAVAIL);
374 
375 	which = value->var.subs[sub - 1];
376 
377 	switch (op) {
378 	case SNMP_OP_GETNEXT:
379 		sensor = NEXT_OBJECT_INT(&sensors, &value->var, sub);
380 		if (sensor == NULL)
381 			return (SNMP_ERR_NOSUCHNAME);
382 		value->var.len = sub + 1;
383 		value->var.subs[sub] = sensor->index;
384 		break;
385 	case SNMP_OP_GET:
386 		if (value->var.len - sub != 1)
387 			return (SNMP_ERR_NOSUCHNAME);
388 		sensor = FIND_OBJECT_INT(&sensors, &value->var, sub);
389 		if (sensor == NULL)
390 			return (SNMP_ERR_NOSUCHNAME);
391 		break;
392 	case SNMP_OP_SET:
393 		return (SNMP_ERR_NOT_WRITEABLE);
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 	ret = SNMP_ERR_NOERROR;
402 
403 	switch (which) {
404 	case LEAF_lm75SensorIndex:
405 		value->v.integer = sensor->index;
406 		break;
407 	case LEAF_lm75SensorSysctlIndex:
408 		value->v.integer = sensor->sysctlidx;
409 		break;
410 	case LEAF_lm75SensorDesc:
411 		ret = string_get(value, sensor->desc, -1);
412 		break;
413 	case LEAF_lm75SensorLocation:
414 		ret = string_get(value, sensor->location, -1);
415 		break;
416 	case LEAF_lm75SensorPnpInfo:
417 		ret = string_get(value, sensor->pnpinfo, -1);
418 		break;
419 	case LEAF_lm75SensorParent:
420 		ret = string_get(value, sensor->parent, -1);
421 		break;
422 	case LEAF_lm75SensorTemperature:
423 		value->v.integer = sensor->temp;
424 		break;
425 	default:
426 		ret = SNMP_ERR_RES_UNAVAIL;
427 		break;
428 	}
429 
430 	return (ret);
431 }
432