1 /*
2 ** Zabbix
3 ** Copyright (C) 2001-2021 Zabbix SIA
4 **
5 ** This program is free software; you can redistribute it and/or modify
6 ** it under the terms of the GNU General Public License as published by
7 ** the Free Software Foundation; either version 2 of the License, or
8 ** (at your option) any later version.
9 **
10 ** This program is distributed in the hope that it will be useful,
11 ** but WITHOUT ANY WARRANTY; without even the implied warranty of
12 ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 ** GNU General Public License for more details.
14 **
15 ** You should have received a copy of the GNU General Public License
16 ** along with this program; if not, write to the Free Software
17 ** Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
18 **/
19 #include "common.h"
20 #include "sysinfo.h"
21 #include "zbxregexp.h"
22 
23 #ifdef KERNEL_2_4
24 #define DEVICE_DIR	"/proc/sys/dev/sensors"
25 #else
26 #define DEVICE_DIR	"/sys/class/hwmon"
27 static const char	*locations[] = {"", "/device", NULL};
28 #endif
29 
30 #define ATTR_MAX	128
31 
count_sensor(int do_task,const char * filename,double * aggr,int * cnt)32 static void	count_sensor(int do_task, const char *filename, double *aggr, int *cnt)
33 {
34 	FILE	*f;
35 	char	line[MAX_STRING_LEN];
36 	double	value;
37 
38 	if (NULL == (f = fopen(filename, "r")))
39 		return;
40 
41 	if (NULL == fgets(line, sizeof(line), f))
42 	{
43 		zbx_fclose(f);
44 		return;
45 	}
46 
47 	zbx_fclose(f);
48 
49 #ifdef KERNEL_2_4
50 	if (1 == sscanf(line, "%*f\t%*f\t%lf\n", &value))
51 	{
52 #else
53 	if (1 == sscanf(line, "%lf", &value))
54 	{
55 		if (NULL == strstr(filename, "fan"))
56 			value = value / 1000;
57 #endif
58 		(*cnt)++;
59 
60 		switch (do_task)
61 		{
62 			case ZBX_DO_ONE:
63 				*aggr = value;
64 				break;
65 			case ZBX_DO_AVG:
66 				*aggr += value;
67 				break;
68 			case ZBX_DO_MAX:
69 				*aggr = (1 == *cnt ? value : MAX(*aggr, value));
70 				break;
71 			case ZBX_DO_MIN:
72 				*aggr = (1 == *cnt ? value : MIN(*aggr, value));
73 				break;
74 		}
75 	}
76 }
77 
78 #ifndef KERNEL_2_4
79 /*********************************************************************************
80  *                                                                               *
81  * Function: sysfs_read_attr                                                     *
82  *                                                                               *
83  * Purpose: locate and read the name attribute of a sensor from sysfs            *
84  *                                                                               *
85  * Parameters:  device        - [IN] the path to sensor data in sysfs            *
86  *              attribute     - [OUT] the sensor name                            *
87  *                                                                               *
88  * Return value: Subfolder where the sensor name file was found or NULL          *
89  *                                                                               *
90  * Comments: attribute string must be freed by caller after it's been used.      *
91  *                                                                               *
92  *********************************************************************************/
93 static const char	*sysfs_read_attr(const char *device, char **attribute)
94 {
95 	const char	**location;
96 	char		path[MAX_STRING_LEN], buf[ATTR_MAX], *p;
97 	FILE		*f;
98 
99 	for (location = locations; NULL != *location; location++)
100 	{
101 		zbx_snprintf(path, MAX_STRING_LEN, "%s%s/name", device, *location);
102 
103 		if (NULL != (f = fopen(path, "r")))
104 		{
105 			p = fgets(buf, ATTR_MAX, f);
106 			zbx_fclose(f);
107 
108 			if (NULL == p)
109 				break;
110 
111 			/* Last byte is a '\n'; chop that off */
112 			buf[strlen(buf) - 1] = '\0';
113 
114 			if (NULL != attribute)
115 				*attribute = zbx_strdup(*attribute, buf);
116 
117 			return *location;
118 		}
119 	}
120 
121 	return NULL;
122 }
123 
124 static int	get_device_info(const char *dev_path, const char *dev_name, char *device_info, const char **name_subfolder)
125 {
126 	int		ret = FAIL;
127 	unsigned int	addr;
128 	ssize_t		sub_len;
129 	char		*subsys, *prefix = NULL, linkpath[MAX_STRING_LEN], subsys_path[MAX_STRING_LEN];
130 
131 	/* ignore any device without name attribute */
132 	if (NULL == (*name_subfolder = sysfs_read_attr(dev_path, &prefix)))
133 		goto out;
134 
135 	if (NULL == dev_name)
136 	{
137 		/* Virtual device */
138 		/* Assuming that virtual devices are unique */
139 		zbx_snprintf(device_info, MAX_STRING_LEN, "%s-virtual-0", prefix);
140 		ret = SUCCEED;
141 
142 		goto out;
143 	}
144 
145 	/* Find bus type */
146 	zbx_snprintf(linkpath, MAX_STRING_LEN, "%s/device/subsystem", dev_path);
147 
148 	sub_len = readlink(linkpath, subsys_path, MAX_STRING_LEN - 1);
149 
150 	if (0 > sub_len && ENOENT == errno)
151 	{
152 		/* Fallback to "bus" link for kernels <= 2.6.17 */
153 		zbx_snprintf(linkpath, MAX_STRING_LEN, "%s/device/bus", dev_path);
154 		sub_len = readlink(linkpath, subsys_path, MAX_STRING_LEN - 1);
155 	}
156 
157 	if (0 > sub_len)
158 	{
159 		/* Older kernels (<= 2.6.11) have neither the subsystem symlink nor the bus symlink */
160 		if (errno == ENOENT)
161 			subsys = NULL;
162 		else
163 			goto out;
164 	}
165 	else
166 	{
167 		subsys_path[sub_len] = '\0';
168 		subsys = strrchr(subsys_path, '/') + 1;
169 	}
170 
171 	if ((NULL == subsys || 0 == strcmp(subsys, "i2c")))
172 	{
173 		short int	bus_i2c;
174 
175 		if (2 != sscanf(dev_name, "%hd-%x", &bus_i2c, &addr))
176 			goto out;
177 
178 		/* find out if legacy ISA or not */
179 		if (9191 == bus_i2c)
180 		{
181 			zbx_snprintf(device_info, MAX_STRING_LEN, "%s-isa-%04x", prefix, addr);
182 		}
183 		else
184 		{
185 			const char	*bus_subfolder;
186 			char		*bus_attr = NULL, bus_path[MAX_STRING_LEN];
187 
188 			zbx_snprintf(bus_path, sizeof(bus_path), "/sys/class/i2c-adapter/i2c-%d", bus_i2c);
189 			bus_subfolder = sysfs_read_attr(bus_path, &bus_attr);
190 
191 			if (NULL != bus_subfolder && '\0' != *bus_subfolder)
192 			{
193 				if (0 != strncmp(bus_attr, "ISA ", 4))
194 				{
195 					zbx_free(bus_attr);
196 					goto out;
197 				}
198 
199 				zbx_snprintf(device_info, MAX_STRING_LEN, "%s-isa-%04x", prefix, addr);
200 			}
201 			else
202 				zbx_snprintf(device_info, MAX_STRING_LEN, "%s-i2c-%hd-%02x", prefix, bus_i2c, addr);
203 
204 			zbx_free(bus_attr);
205 		}
206 
207 		ret = SUCCEED;
208 	}
209 	else if (0 == strcmp(subsys, "spi"))
210 	{
211 		int		address;
212 		short int	bus_spi;
213 
214 		/* SPI */
215 		if (2 != sscanf(dev_name, "spi%hd.%d", &bus_spi, &address))
216 			goto out;
217 
218 		zbx_snprintf(device_info, MAX_STRING_LEN, "%s-spi-%hd-%x", prefix, bus_spi, (unsigned int)address);
219 
220 		ret = SUCCEED;
221 	}
222 	else if (0 == strcmp(subsys, "pci"))
223 	{
224 		unsigned int	domain, bus, slot, fn;
225 
226 		/* PCI */
227 		if (4 != sscanf(dev_name, "%x:%x:%x.%x", &domain, &bus, &slot, &fn))
228 			goto out;
229 
230 		addr = (domain << 16) + (bus << 8) + (slot << 3) + fn;
231 		zbx_snprintf(device_info, MAX_STRING_LEN, "%s-pci-%04x", prefix, addr);
232 
233 		ret = SUCCEED;
234 	}
235 	else if (0 == strcmp(subsys, "platform") || 0 == strcmp(subsys, "of_platform"))
236 	{
237 		int	address;
238 
239 		/* must be new ISA (platform driver) */
240 		if (1 != sscanf(dev_name, "%*[a-z0-9_].%d", &address))
241 			address = 0;
242 
243 		zbx_snprintf(device_info, MAX_STRING_LEN, "%s-isa-%04x", prefix, (unsigned int)address);
244 
245 		ret = SUCCEED;
246 	}
247 	else if (0 == strcmp(subsys, "acpi"))
248 	{
249 		/* Assuming that acpi devices are unique */
250 		zbx_snprintf(device_info, MAX_STRING_LEN, "%s-acpi-0", prefix);
251 
252 		ret = SUCCEED;
253 	}
254 	else if (0 == strcmp(subsys, "hid"))
255 	{
256 		unsigned int	bus, vendor, product;
257 
258 		/* As of kernel 2.6.32, the hid device names do not look good */
259 		if (4 != sscanf(dev_name, "%x:%x:%x.%x", &bus, &vendor, &product, &addr))
260 			goto out;
261 
262 		zbx_snprintf(device_info, MAX_STRING_LEN, "%s-hid-%hd-%x", prefix, (short int)bus, addr);
263 
264 		ret = SUCCEED;
265 	}
266 out:
267 	zbx_free(prefix);
268 
269 	return ret;
270 }
271 #endif
272 
273 static void	get_device_sensors(int do_task, const char *device, const char *name, double *aggr, int *cnt)
274 {
275 	char	sensorname[MAX_STRING_LEN];
276 
277 #ifdef KERNEL_2_4
278 	if (ZBX_DO_ONE == do_task)
279 	{
280 		zbx_snprintf(sensorname, sizeof(sensorname), "%s/%s/%s", DEVICE_DIR, device, name);
281 		count_sensor(do_task, sensorname, aggr, cnt);
282 	}
283 	else
284 	{
285 		DIR		*devicedir = NULL, *sensordir = NULL;
286 		struct dirent	*deviceent, *sensorent;
287 		char		devicename[MAX_STRING_LEN];
288 
289 		if (NULL == (devicedir = opendir(DEVICE_DIR)))
290 			return;
291 
292 		while (NULL != (deviceent = readdir(devicedir)))
293 		{
294 			if (0 == strcmp(deviceent->d_name, ".") || 0 == strcmp(deviceent->d_name, ".."))
295 				continue;
296 
297 			if (NULL == zbx_regexp_match(deviceent->d_name, device, NULL))
298 				continue;
299 
300 			zbx_snprintf(devicename, sizeof(devicename), "%s/%s", DEVICE_DIR, deviceent->d_name);
301 
302 			if (NULL == (sensordir = opendir(devicename)))
303 				continue;
304 
305 			while (NULL != (sensorent = readdir(sensordir)))
306 			{
307 				if (0 == strcmp(sensorent->d_name, ".") || 0 == strcmp(sensorent->d_name, ".."))
308 					continue;
309 
310 				if (NULL == zbx_regexp_match(sensorent->d_name, name, NULL))
311 					continue;
312 
313 				zbx_snprintf(sensorname, sizeof(sensorname), "%s/%s", devicename, sensorent->d_name);
314 				count_sensor(do_task, sensorname, aggr, cnt);
315 			}
316 			closedir(sensordir);
317 		}
318 		closedir(devicedir);
319 	}
320 #else
321 	DIR		*sensordir = NULL, *devicedir = NULL;
322 	struct dirent	*sensorent, *deviceent;
323 	char		hwmon_dir[MAX_STRING_LEN], devicepath[MAX_STRING_LEN], deviced[MAX_STRING_LEN],
324 			device_info[MAX_STRING_LEN], regex[MAX_STRING_LEN], *device_p;
325 	const char	*subfolder;
326 	int		err;
327 
328 	zbx_snprintf(hwmon_dir, sizeof(hwmon_dir), "%s", DEVICE_DIR);
329 
330 	if (NULL == (devicedir = opendir(hwmon_dir)))
331 		return;
332 
333 	while (NULL != (deviceent = readdir(devicedir)))
334 	{
335 		ssize_t	dev_len;
336 
337 		if (0 == strcmp(deviceent->d_name, ".") || 0 == strcmp(deviceent->d_name, ".."))
338 			continue;
339 
340 		zbx_snprintf(devicepath, sizeof(devicepath), "%s/%s/device", DEVICE_DIR, deviceent->d_name);
341 		dev_len = readlink(devicepath, deviced, MAX_STRING_LEN - 1);
342 		zbx_snprintf(devicepath, sizeof(devicepath), "%s/%s", DEVICE_DIR, deviceent->d_name);
343 
344 		if (0 > dev_len)
345 		{
346 			/* No device link? Treat device as virtual */
347 			err = get_device_info(devicepath, NULL, device_info, &subfolder);
348 		}
349 		else
350 		{
351 			deviced[dev_len] = '\0';
352 			device_p = strrchr(deviced, '/') + 1;
353 
354 			if (0 == strcmp(device, device_p))
355 			{
356 				zbx_snprintf(device_info, sizeof(device_info), "%s", device);
357 				err = (NULL != (subfolder = sysfs_read_attr(devicepath, NULL)) ? SUCCEED : FAIL);
358 			}
359 			else
360 				err = get_device_info(devicepath, device_p, device_info, &subfolder);
361 		}
362 
363 		if (SUCCEED == err && 0 == strcmp(device_info, device))
364 		{
365 			zbx_snprintf(devicepath, sizeof(devicepath), "%s/%s%s", DEVICE_DIR, deviceent->d_name,
366 					subfolder);
367 
368 			if (ZBX_DO_ONE == do_task)
369 			{
370 				zbx_snprintf(sensorname, sizeof(sensorname), "%s/%s_input", devicepath, name);
371 				count_sensor(do_task, sensorname, aggr, cnt);
372 			}
373 			else
374 			{
375 				zbx_snprintf(regex, sizeof(regex), "%s[0-9]*_input", name);
376 
377 				if (NULL == (sensordir = opendir(devicepath)))
378 					goto out;
379 
380 				while (NULL != (sensorent = readdir(sensordir)))
381 				{
382 					if (0 == strcmp(sensorent->d_name, ".") ||
383 							0 == strcmp(sensorent->d_name, ".."))
384 						continue;
385 
386 					if (NULL == zbx_regexp_match(sensorent->d_name, regex, NULL))
387 						continue;
388 
389 					zbx_snprintf(sensorname, sizeof(sensorname), "%s/%s", devicepath,
390 							sensorent->d_name);
391 					count_sensor(do_task, sensorname, aggr, cnt);
392 				}
393 				closedir(sensordir);
394 			}
395 		}
396 	}
397 out:
398 	closedir(devicedir);
399 #endif
400 }
401 
402 int	GET_SENSOR(AGENT_REQUEST *request, AGENT_RESULT *result)
403 {
404 	char	*device, *name, *function;
405 	int	do_task, cnt = 0;
406 	double	aggr = 0;
407 
408 	if (3 < request->nparam)
409 	{
410 		SET_MSG_RESULT(result, zbx_strdup(NULL, "Too many parameters."));
411 		return SYSINFO_RET_FAIL;
412 	}
413 
414 	device = get_rparam(request, 0);
415 	name = get_rparam(request, 1);
416 	function = get_rparam(request, 2);
417 
418 	if (NULL == device || '\0' == *device)
419 	{
420 		SET_MSG_RESULT(result, zbx_strdup(NULL, "Invalid first parameter."));
421 		return SYSINFO_RET_FAIL;
422 	}
423 
424 	if (NULL == name || '\0' == *name)
425 	{
426 		SET_MSG_RESULT(result, zbx_strdup(NULL, "Invalid second parameter."));
427 		return SYSINFO_RET_FAIL;
428 	}
429 
430 	if (NULL == function || '\0' == *function)
431 		do_task = ZBX_DO_ONE;
432 	else if (0 == strcmp(function, "avg"))
433 		do_task = ZBX_DO_AVG;
434 	else if (0 == strcmp(function, "max"))
435 		do_task = ZBX_DO_MAX;
436 	else if (0 == strcmp(function, "min"))
437 		do_task = ZBX_DO_MIN;
438 	else
439 	{
440 		SET_MSG_RESULT(result, zbx_strdup(NULL, "Invalid third parameter."));
441 		return SYSINFO_RET_FAIL;
442 	}
443 
444 	if (ZBX_DO_ONE != do_task && 0 != isdigit(name[strlen(name) - 1]))
445 		do_task = ZBX_DO_ONE;
446 
447 	if (ZBX_DO_ONE != do_task && 0 == isalpha(name[strlen(name) - 1]))
448 	{
449 		SET_MSG_RESULT(result, zbx_strdup(NULL, "Generic sensor name must be specified for selected mode."));
450 		return SYSINFO_RET_FAIL;
451 	}
452 
453 	get_device_sensors(do_task, device, name, &aggr, &cnt);
454 
455 	if (0 == cnt)
456 	{
457 		SET_MSG_RESULT(result, zbx_strdup(NULL, "Cannot obtain sensor information."));
458 		return SYSINFO_RET_FAIL;
459 	}
460 
461 	if (ZBX_DO_AVG == do_task)
462 		SET_DBL_RESULT(result, aggr / cnt);
463 	else
464 		SET_DBL_RESULT(result, aggr);
465 
466 	return SYSINFO_RET_OK;
467 }
468