/* ** Copyright (C) 2001-2024 Zabbix SIA ** ** This program is free software: you can redistribute it and/or modify it under the terms of ** the GNU Affero General Public License as published by the Free Software Foundation, version 3. ** ** This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; ** without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. ** See the GNU Affero General Public License for more details. ** ** You should have received a copy of the GNU Affero General Public License along with this program. ** If not, see <https://www.gnu.org/licenses/>. **/ #include "../sysinfo.h" #include "zbxregexp.h" #ifdef KERNEL_2_4 #define DEVICE_DIR "/proc/sys/dev/sensors" #else #define DEVICE_DIR "/sys/class/hwmon" static const char *locations[] = {"", "/device", NULL}; #endif static void count_sensor(int do_task, const char *filename, double *aggr, int *cnt) { FILE *f; char line[MAX_STRING_LEN]; double value; if (NULL == (f = fopen(filename, "r"))) return; if (NULL == fgets(line, sizeof(line), f)) { zbx_fclose(f); return; } zbx_fclose(f); #ifdef KERNEL_2_4 if (1 == sscanf(line, "%*f\t%*f\t%lf\n", &value)) { #else if (1 == sscanf(line, "%lf", &value)) { if (NULL == strstr(filename, "fan")) value = value / 1000; #endif (*cnt)++; switch (do_task) { case ZBX_DO_ONE: *aggr = value; break; case ZBX_DO_AVG: *aggr += value; break; case ZBX_DO_MAX: *aggr = (1 == *cnt ? value : MAX(*aggr, value)); break; case ZBX_DO_MIN: *aggr = (1 == *cnt ? value : MIN(*aggr, value)); break; } } } #ifndef KERNEL_2_4 /********************************************************************************* * * * Purpose: locates and reads name attribute of sensor from sysfs * * * * Parameters: device - [IN] path to sensor data in sysfs * * attribute - [OUT] sensor name * * * * Return value: Subfolder where the sensor name file was found or NULL. * * * * Comments: Attribute string must be freed by caller after it's been used. * * * *********************************************************************************/ static const char *sysfs_read_attr(const char *device, char **attribute) { #define ATTR_MAX 128 const char **location; char path[MAX_STRING_LEN], buf[ATTR_MAX], *p; FILE *f; size_t l; for (location = locations; NULL != *location; location++) { zbx_snprintf(path, MAX_STRING_LEN, "%s%s/name", device, *location); if (NULL != (f = fopen(path, "r"))) { p = fgets(buf, ATTR_MAX, f); zbx_fclose(f); if (NULL == p) break; /* Last byte is a '\n'; chop that off */ l = strlen(buf); buf[l - 1] = '\0'; if (NULL != attribute) *attribute = zbx_strdup(*attribute, buf); return *location; } } return NULL; #undef ATTR_MAX } static int get_device_info(const char *dev_path, const char *dev_name, char *device_info, const char **name_subfolder) { int ret = FAIL; unsigned int addr; ssize_t sub_len; char *subsys, *prefix = NULL, linkpath[MAX_STRING_LEN], subsys_path[MAX_STRING_LEN]; /* ignore any device without name attribute */ if (NULL == (*name_subfolder = sysfs_read_attr(dev_path, &prefix))) goto out; if (NULL == dev_name) { /* Virtual device */ /* Assuming that virtual devices are unique */ zbx_snprintf(device_info, MAX_STRING_LEN, "%s-virtual-0", prefix); ret = SUCCEED; goto out; } /* Find bus type */ zbx_snprintf(linkpath, MAX_STRING_LEN, "%s/device/subsystem", dev_path); sub_len = readlink(linkpath, subsys_path, MAX_STRING_LEN - 1); if (0 > sub_len && ENOENT == errno) { /* Fallback to "bus" link for kernels <= 2.6.17 */ zbx_snprintf(linkpath, MAX_STRING_LEN, "%s/device/bus", dev_path); sub_len = readlink(linkpath, subsys_path, MAX_STRING_LEN - 1); } if (0 > sub_len) { /* Older kernels (<= 2.6.11) have neither the subsystem symlink nor the bus symlink */ if (errno == ENOENT) subsys = NULL; else goto out; } else { subsys_path[sub_len] = '\0'; subsys = strrchr(subsys_path, '/') + 1; } if ((NULL == subsys || 0 == strcmp(subsys, "i2c"))) { short int bus_i2c; if (2 != sscanf(dev_name, "%hd-%x", &bus_i2c, &addr)) goto out; /* find out if legacy ISA or not */ if (9191 == bus_i2c) { zbx_snprintf(device_info, MAX_STRING_LEN, "%s-isa-%04x", prefix, addr); } else { const char *bus_subfolder; char *bus_attr = NULL, bus_path[MAX_STRING_LEN]; zbx_snprintf(bus_path, sizeof(bus_path), "/sys/class/i2c-adapter/i2c-%d", bus_i2c); bus_subfolder = sysfs_read_attr(bus_path, &bus_attr); if (NULL != bus_subfolder && '\0' != *bus_subfolder) { if (0 != strncmp(bus_attr, "ISA ", 4)) { zbx_free(bus_attr); goto out; } zbx_snprintf(device_info, MAX_STRING_LEN, "%s-isa-%04x", prefix, addr); } else zbx_snprintf(device_info, MAX_STRING_LEN, "%s-i2c-%hd-%02x", prefix, bus_i2c, addr); zbx_free(bus_attr); } ret = SUCCEED; } else if (0 == strcmp(subsys, "spi")) { int address; short int bus_spi; /* SPI */ if (2 != sscanf(dev_name, "spi%hd.%d", &bus_spi, &address)) goto out; zbx_snprintf(device_info, MAX_STRING_LEN, "%s-spi-%hd-%x", prefix, bus_spi, (unsigned int)address); ret = SUCCEED; } else if (0 == strcmp(subsys, "pci")) { unsigned int domain, bus, slot, fn; /* PCI */ if (4 != sscanf(dev_name, "%x:%x:%x.%x", &domain, &bus, &slot, &fn)) goto out; addr = (domain << 16) + (bus << 8) + (slot << 3) + fn; zbx_snprintf(device_info, MAX_STRING_LEN, "%s-pci-%04x", prefix, addr); ret = SUCCEED; } else if (0 == strcmp(subsys, "platform") || 0 == strcmp(subsys, "of_platform")) { int address; /* must be new ISA (platform driver) */ if (1 != sscanf(dev_name, "%*[a-z0-9_].%d", &address)) address = 0; zbx_snprintf(device_info, MAX_STRING_LEN, "%s-isa-%04x", prefix, (unsigned int)address); ret = SUCCEED; } else if (0 == strcmp(subsys, "acpi")) { /* Assuming that acpi devices are unique */ zbx_snprintf(device_info, MAX_STRING_LEN, "%s-acpi-0", prefix); ret = SUCCEED; } else if (0 == strcmp(subsys, "hid")) { unsigned int bus, vendor, product; /* As of kernel 2.6.32, the hid device names do not look good */ if (4 != sscanf(dev_name, "%x:%x:%x.%x", &bus, &vendor, &product, &addr)) goto out; zbx_snprintf(device_info, MAX_STRING_LEN, "%s-hid-%hd-%x", prefix, (short int)bus, addr); ret = SUCCEED; } out: zbx_free(prefix); return ret; } #endif static void get_device_sensors(int do_task, const char *device, const char *name, double *aggr, int *cnt) { char sensorname[MAX_STRING_LEN]; #ifdef KERNEL_2_4 if (ZBX_DO_ONE == do_task) { zbx_snprintf(sensorname, sizeof(sensorname), "%s/%s/%s", DEVICE_DIR, device, name); count_sensor(do_task, sensorname, aggr, cnt); } else { DIR *devicedir = NULL, *sensordir = NULL; struct dirent *deviceent, *sensorent; char devicename[MAX_STRING_LEN]; if (NULL == (devicedir = opendir(DEVICE_DIR))) return; while (NULL != (deviceent = readdir(devicedir))) { if (0 == strcmp(deviceent->d_name, ".") || 0 == strcmp(deviceent->d_name, "..")) continue; if (NULL == zbx_regexp_match(deviceent->d_name, device, NULL)) continue; zbx_snprintf(devicename, sizeof(devicename), "%s/%s", DEVICE_DIR, deviceent->d_name); if (NULL == (sensordir = opendir(devicename))) continue; while (NULL != (sensorent = readdir(sensordir))) { if (0 == strcmp(sensorent->d_name, ".") || 0 == strcmp(sensorent->d_name, "..")) continue; if (NULL == zbx_regexp_match(sensorent->d_name, name, NULL)) continue; zbx_snprintf(sensorname, sizeof(sensorname), "%s/%s", devicename, sensorent->d_name); count_sensor(do_task, sensorname, aggr, cnt); } closedir(sensordir); } closedir(devicedir); } #else DIR *sensordir = NULL, *devicedir = NULL; struct dirent *sensorent, *deviceent; char hwmon_dir[MAX_STRING_LEN], devicepath[MAX_STRING_LEN], deviced[MAX_STRING_LEN], device_info[MAX_STRING_LEN], regex[MAX_STRING_LEN], *device_p; const char *subfolder; int err; zbx_snprintf(hwmon_dir, sizeof(hwmon_dir), "%s", DEVICE_DIR); if (NULL == (devicedir = opendir(hwmon_dir))) return; while (NULL != (deviceent = readdir(devicedir))) { ssize_t dev_len; if (0 == strcmp(deviceent->d_name, ".") || 0 == strcmp(deviceent->d_name, "..")) continue; zbx_snprintf(devicepath, sizeof(devicepath), "%s/%s/device", DEVICE_DIR, deviceent->d_name); dev_len = readlink(devicepath, deviced, MAX_STRING_LEN - 1); zbx_snprintf(devicepath, sizeof(devicepath), "%s/%s", DEVICE_DIR, deviceent->d_name); if (0 > dev_len) { /* No device link? Treat device as virtual */ err = get_device_info(devicepath, NULL, device_info, &subfolder); } else { deviced[dev_len] = '\0'; device_p = strrchr(deviced, '/') + 1; if (0 == strcmp(device, device_p)) { zbx_snprintf(device_info, sizeof(device_info), "%s", device); err = (NULL != (subfolder = sysfs_read_attr(devicepath, NULL)) ? SUCCEED : FAIL); } else err = get_device_info(devicepath, device_p, device_info, &subfolder); } if (SUCCEED == err && 0 == strcmp(device_info, device)) { zbx_snprintf(devicepath, sizeof(devicepath), "%s/%s%s", DEVICE_DIR, deviceent->d_name, subfolder); if (ZBX_DO_ONE == do_task) { zbx_snprintf(sensorname, sizeof(sensorname), "%s/%s_input", devicepath, name); count_sensor(do_task, sensorname, aggr, cnt); } else { zbx_snprintf(regex, sizeof(regex), "%s[0-9]*_input", name); if (NULL == (sensordir = opendir(devicepath))) goto out; while (NULL != (sensorent = readdir(sensordir))) { if (0 == strcmp(sensorent->d_name, ".") || 0 == strcmp(sensorent->d_name, "..")) continue; if (NULL == zbx_regexp_match(sensorent->d_name, regex, NULL)) continue; zbx_snprintf(sensorname, sizeof(sensorname), "%s/%s", devicepath, sensorent->d_name); count_sensor(do_task, sensorname, aggr, cnt); } closedir(sensordir); } } } out: closedir(devicedir); #endif } int get_sensor(AGENT_REQUEST *request, AGENT_RESULT *result) { char *device, *name, *function; int do_task, cnt = 0; double aggr = 0; if (3 < request->nparam) { SET_MSG_RESULT(result, zbx_strdup(NULL, "Too many parameters.")); return SYSINFO_RET_FAIL; } device = get_rparam(request, 0); name = get_rparam(request, 1); function = get_rparam(request, 2); if (NULL == device || '\0' == *device) { SET_MSG_RESULT(result, zbx_strdup(NULL, "Invalid first parameter.")); return SYSINFO_RET_FAIL; } if (NULL == name || '\0' == *name) { SET_MSG_RESULT(result, zbx_strdup(NULL, "Invalid second parameter.")); return SYSINFO_RET_FAIL; } if (NULL == function || '\0' == *function) do_task = ZBX_DO_ONE; else if (0 == strcmp(function, "avg")) do_task = ZBX_DO_AVG; else if (0 == strcmp(function, "max")) do_task = ZBX_DO_MAX; else if (0 == strcmp(function, "min")) do_task = ZBX_DO_MIN; else { SET_MSG_RESULT(result, zbx_strdup(NULL, "Invalid third parameter.")); return SYSINFO_RET_FAIL; } if (ZBX_DO_ONE != do_task && 0 != isdigit(name[strlen(name) - 1])) do_task = ZBX_DO_ONE; if (ZBX_DO_ONE != do_task && 0 == isalpha(name[strlen(name) - 1])) { SET_MSG_RESULT(result, zbx_strdup(NULL, "Generic sensor name must be specified for selected mode.")); return SYSINFO_RET_FAIL; } get_device_sensors(do_task, device, name, &aggr, &cnt); if (0 == cnt) { SET_MSG_RESULT(result, zbx_strdup(NULL, "Cannot obtain sensor information.")); return SYSINFO_RET_FAIL; } if (ZBX_DO_AVG == do_task) SET_DBL_RESULT(result, aggr / cnt); else SET_DBL_RESULT(result, aggr); return SYSINFO_RET_OK; }