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