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