xref: /openbsd-src/usr.sbin/sensorsd/sensorsd.c (revision b725ae7711052a2233e31a66fefb8a752c388d7a)
1 /*	$OpenBSD: sensorsd.c,v 1.10 2004/04/11 20:02:00 otto Exp $ */
2 
3 /*
4  * Copyright (c) 2003 Henning Brauer <henning@openbsd.org>
5  *
6  * Permission to use, copy, modify, and distribute this software for any
7  * purpose with or without fee is hereby granted, provided that the above
8  * copyright notice and this permission notice appear in all copies.
9  *
10  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17  */
18 
19 #include <sys/param.h>
20 #include <sys/sysctl.h>
21 #include <sys/sensors.h>
22 
23 #include <err.h>
24 #include <errno.h>
25 #include <signal.h>
26 #include <stdio.h>
27 #include <stdlib.h>
28 #include <string.h>
29 #include <syslog.h>
30 #include <time.h>
31 #include <unistd.h>
32 
33 #define	RFBUFSIZ	28	/* buffer size for print_sensor */
34 #define	RFBUFCNT	4	/* ring buffers */
35 #define REPORT_PERIOD	60	/* report every n seconds */
36 #define CHECK_PERIOD	60	/* check every n seconds */
37 
38 int		 main(int, char *[]);
39 void		 check_sensors(void);
40 void		 report(time_t);
41 static char	*print_sensor(enum sensor_type, u_int64_t);
42 int		 parse_config(char *);
43 int64_t		 get_val(char *, int, enum sensor_type);
44 void		 reparse_cfg(int);
45 
46 enum sensor_status {
47 	STATUS_OK,
48 	STATUS_FAIL
49 };
50 
51 struct limits_t {
52 	TAILQ_ENTRY(limits_t)	entries;
53 	u_int8_t		watch;
54 	int			num;			/* sensor number */
55 	enum sensor_type	type;			/* sensor type */
56 	int64_t			lower;			/* lower limit */
57 	int64_t			upper;			/* upper limit */
58 	enum sensor_status	status;			/* last status */
59 	time_t			status_changed;
60 	int64_t			last_val;
61 };
62 
63 TAILQ_HEAD(limits, limits_t) limits = TAILQ_HEAD_INITIALIZER(limits);
64 
65 char			 *configfile;
66 volatile sig_atomic_t	  reload = 0;
67 
68 int
69 main(int argc, char *argv[])
70 {
71 	struct sensor	 sensor;
72 	struct limits_t	*limit;
73 	size_t		 len;
74 	time_t		 next_report, last_report = 0;
75 	time_t		 next_check;
76 	int		 mib[3];
77 	int		 i, sleeptime, watch_cnt;
78 
79 	mib[0] = CTL_HW;
80 	mib[1] = HW_SENSORS;
81 	len = sizeof(sensor);
82 
83 	for (i = 0; i < 256; i++) {
84 		mib[2] = i;
85 		if (sysctl(mib, 3, &sensor, &len, NULL, 0) == -1) {
86 			if (errno == ENXIO)
87 				break;
88 			else
89 				err(1, "sysctl");
90 		}
91 		if ((limit = calloc(1, sizeof(struct limits_t))) == NULL)
92 			err(1, "calloc");
93 		limit->num = i;
94 		limit->type = sensor.type;
95 		TAILQ_INSERT_TAIL(&limits, limit, entries);
96 	}
97 
98 	if (i == 0)
99 		errx(1, "no sensors found");
100 
101 	openlog("sensorsd", LOG_PID | LOG_NDELAY, LOG_DAEMON);
102 
103 	if (configfile == NULL)
104 		if (asprintf(&configfile, "/etc/sensorsd.conf") == -1)
105 			err(1, "out of memory");
106 	if ((watch_cnt = parse_config(configfile)) == -1)
107 		errx(1, "error in config file");
108 
109 	if (watch_cnt == 0)
110 		errx(1, "no watches defined");
111 
112 	if (daemon(0, 0) == -1)
113 		err(1, "unable to fork");
114 
115 	signal(SIGHUP, reparse_cfg);
116 
117 	syslog(LOG_INFO, "startup, %d watches for %d sensors", watch_cnt, i);
118 
119 	next_check = next_report = time(NULL);
120 
121 	for (;;) {
122 		if (reload) {
123 			if ((watch_cnt = parse_config(configfile)) == -1)
124 				syslog(LOG_CRIT, "error in config file %s",
125 				    configfile);
126 			else
127 				syslog(LOG_INFO,
128 				    "configuration reloaded, %d watches",
129 				    watch_cnt);
130 			reload = 0;
131 		}
132 		if (next_check <= time(NULL)) {
133 			check_sensors();
134 			next_check = time(NULL) + CHECK_PERIOD;
135 		}
136 		if (next_report <= time(NULL)) {
137 			report(last_report);
138 			last_report = next_report;
139 			next_report = time(NULL) + REPORT_PERIOD;
140 		}
141 		if (next_report < next_check)
142 			sleeptime = next_report - time(NULL);
143 		else
144 			sleeptime = next_check - time(NULL);
145 		if (sleeptime > 0)
146 			sleep(sleeptime);
147 	}
148 }
149 
150 void
151 check_sensors(void)
152 {
153 	struct sensor	 sensor;
154 	struct limits_t	*limit;
155 	size_t		 len;
156 	int		 mib[3];
157 	int		 newstatus;
158 
159 	mib[0] = CTL_HW;
160 	mib[1] = HW_SENSORS;
161 	len = sizeof(sensor);
162 
163 	TAILQ_FOREACH(limit, &limits, entries)
164 		if (limit->watch) {
165 			mib[2] = limit->num;
166 			if (sysctl(mib, 3, &sensor, &len, NULL, 0) == -1)
167 				err(1, "sysctl");
168 
169 			limit->last_val = sensor.value;
170 			if (sensor.value > limit->upper ||
171 			    sensor.value < limit->lower)
172 				newstatus = STATUS_FAIL;
173 			else
174 				newstatus = STATUS_OK;
175 
176 			if (limit->status != newstatus) {
177 				limit->status = newstatus;
178 				limit->status_changed = time(NULL);
179 			}
180 		}
181 }
182 
183 void
184 report(time_t last_report)
185 {
186 	struct limits_t	*limit = NULL;
187 
188 	TAILQ_FOREACH(limit, &limits, entries)
189 		if (limit->status_changed > last_report) {
190 			if (limit->status == STATUS_FAIL)
191 				syslog(LOG_ALERT,
192 				    "failure for hw.sensors.%d: "
193 				    "%s not within limits",
194 				    limit->num,
195 				    print_sensor(limit->type, limit->last_val));
196 			else
197 				syslog(LOG_ALERT,
198 				    "hw.sensors.%d within limits again, "
199 				    "current value %s",
200 				    limit->num,
201 				    print_sensor(limit->type, limit->last_val));
202 		}
203 }
204 
205 static char *
206 print_sensor(enum sensor_type type, u_int64_t value)
207 {
208 	static char	 rfbuf[RFBUFCNT][RFBUFSIZ];	/* ring buffer */
209 	static int	 idx;
210 	char		*fbuf;
211 
212 	fbuf = rfbuf[idx++];
213 	if (idx == RFBUFCNT)
214 		idx = 0;
215 
216 	switch (type) {
217 	case SENSOR_TEMP:
218 		snprintf(fbuf, RFBUFSIZ, "%.2fC/%.2fF",
219 		    (value - 273150000) / 1000000.0,
220 		    (value - 273150000) / 1000000.0 * 9 / 5 + 32);
221 		break;
222 	case SENSOR_FANRPM:
223 		snprintf(fbuf, RFBUFSIZ, "%lld RPM", value);
224 		break;
225 	case SENSOR_VOLTS_DC:
226 		snprintf(fbuf, RFBUFSIZ, "%.2fV", value / 1000.0 / 1000.0);
227 		break;
228 	default:
229 		snprintf(fbuf, RFBUFSIZ, "%lld ???", value);
230 	}
231 
232 	return (fbuf);
233 }
234 
235 int
236 parse_config(char *cf)
237 {
238 	struct limits_t	 *p, *next;
239 	char		 *buf = NULL, *ebuf = NULL;
240 	char		  node[24];
241 	char		**cfa;
242 	int		  watch_cnt = 0;
243 
244 	if ((cfa = calloc(2, sizeof(char *))) == NULL)
245 		err(1, "calloc");
246 	cfa[0] = cf;
247 	cfa[1] = NULL;
248 
249 	for (p = TAILQ_FIRST(&limits); p != NULL; p = next) {
250 		next = TAILQ_NEXT(p, entries);
251 		snprintf(node, sizeof(node), "hw.sensors.%d", p->num);
252 		if (cgetent(&buf, cfa, node) != 0)
253 			p->watch = 0;
254 		else {
255 			p->watch = 1;
256 			watch_cnt++;
257 			if (cgetstr(buf, "low", &ebuf) < 0)
258 				ebuf = NULL;
259 			p->lower = get_val(ebuf, 0, p->type);
260 			if (cgetstr(buf, "high", &ebuf) < 0)
261 				ebuf = NULL;
262 			p->upper = get_val(ebuf, 1, p->type);
263 			free(buf);
264 			buf = NULL;
265 		}
266 	}
267 	free(cfa);
268 	return (watch_cnt);
269 }
270 
271 int64_t
272 get_val(char *buf, int upper, enum sensor_type type)
273 {
274 	double	 val;
275 	int64_t	 rval = 0;
276 	char	*p;
277 
278 	if (buf == NULL) {
279 		if (upper)
280 			return (LLONG_MAX);
281 		else
282 			return (LLONG_MIN);
283 	}
284 
285 	val = strtod(buf, &p);
286 	if (buf == p)
287 		err(1, "incorrect value: %s", buf);
288 
289 	switch(type) {
290 	case SENSOR_TEMP:
291 		switch(*p) {
292 		case 'C':
293 			printf ("C");
294 			rval = (val + 273.16) * 1000 * 1000;
295 			break;
296 		case 'F':
297 			printf("F");
298 			rval = ((val - 32.0) / 9 * 5 + 273.16) * 1000 * 1000;
299 			break;
300 		default:
301 			errx(1, "unknown unit %s for temp sensor", p);
302 		}
303 		break;
304 	case SENSOR_FANRPM:
305 		rval = val;
306 		break;
307 	case SENSOR_VOLTS_DC:
308 		if (*p != 'V')
309 			errx(1, "unknown unit %s for voltage sensor", p);
310 		rval = val * 1000 * 1000;
311 		break;
312 	default:
313 		errx(1, "unsupported sensor type");
314 		/* not reached */
315 	}
316 	free(buf);
317 	return (rval);
318 }
319 
320 void
321 reparse_cfg(int signum)
322 {
323 	reload = 1;
324 }
325