/* ** Copyright (C) 2001-2025 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 "hardware.h" #include "../common/zbxsysinfo_common.h" #include "../sysinfo.h" #include "zbxalgo.h" #include "zbxregexp.h" #include "zbxnum.h" #include <sys/mman.h> #include <setjmp.h> #include <signal.h> static ZBX_THREAD_LOCAL volatile char sigbus_handler_set; static ZBX_THREAD_LOCAL sigjmp_buf sigbus_jmp_buf; static void sigbus_handler(int signal) { siglongjmp(sigbus_jmp_buf, signal); } static void install_sigbus_handler(void) { struct sigaction act; if (0 == sigbus_handler_set) { sigbus_handler_set = 1; act.sa_handler = &sigbus_handler; act.sa_flags = SA_NODEFER; sigemptyset(&act.sa_mask); sigaction(SIGBUS, &act, NULL); } } static void remove_sigbus_handler(void) { struct sigaction act; if (0 != sigbus_handler_set) { act.sa_handler = SIG_DFL; act.sa_flags = SA_NODEFER; sigemptyset(&act.sa_mask); sigaction(SIGBUS, &act, NULL); } sigbus_handler_set = 0; } /****************************************************************************** * * * Comments: reads string #num from dmi data into buffer * * * ******************************************************************************/ static size_t get_dmi_string(char *buf, int bufsize, unsigned char *data, int num) { char *c = (char *)data; if (0 == num) return 0; c += data[1]; /* skip to string data */ while (1 < num) { c += strlen(c); c++; num--; } return zbx_snprintf(buf, bufsize, " %s", c); } static size_t get_chassis_type(char *buf, int bufsize, int type) { /* from System Management BIOS (SMBIOS) Reference Specification v2.7.1 */ static const char *chassis_types[] = { "", /* 0x00 */ "Other", "Unknown", "Desktop", "Low Profile Desktop", "Pizza Box", "Mini Tower", "Tower", "Portable", "LapTop", "Notebook", "Hand Held", "Docking Station", "All in One", "Sub Notebook", "Space-saving", "Lunch Box", "Main Server Chassis", "Expansion Chassis", "SubChassis", "Bus Expansion Chassis", "Peripheral Chassis", "RAID Chassis", "Rack Mount Chassis", "Sealed-case PC", "Multi-system chassis", "Compact PCI", "Advanced TCA", "Blade", "Blade Enclosure", "Tablet", "Convertible", "Detachable", "IoT Gateway", "Embedded PC", "Mini PC", "Stick PC" /* 0x24 (MAX_CHASSIS_TYPE) */ }; type = CHASSIS_TYPE_BITS & type; if (1 > type || MAX_CHASSIS_TYPE < type) return 0; return zbx_snprintf(buf, bufsize, " %s", chassis_types[type]); } static int get_dmi_info(char *buf, int bufsize, volatile int flags) { volatile int ret = SYSINFO_RET_FAIL, fd, offset = 0; unsigned char *volatile smbuf = NULL, *data; void *volatile mmp = NULL; volatile size_t len, page, page_offset; static size_t pagesize = 0; static int smbios_status = SMBIOS_STATUS_UNKNOWN; static size_t smbios_len, smbios; /* length and address of SMBIOS table (if found) */ if (-1 != (fd = open(SYS_TABLE_FILE, O_RDONLY))) { ssize_t nbytes; zbx_stat_t file_buf; if (-1 == fstat(fd, &file_buf)) goto close; smbuf = (unsigned char *)zbx_malloc(NULL, file_buf.st_size); smbios_len = 0; while (0 != (nbytes = read(fd, smbuf + smbios_len, file_buf.st_size - smbios_len))) { if (-1 == nbytes) goto clean; smbios_len += (size_t)nbytes; } } else if (-1 != (fd = open(DEV_MEM, O_RDONLY))) { if (SMBIOS_STATUS_UNKNOWN == smbios_status && /* look for SMBIOS table only once */ (size_t)-1 != (pagesize = sysconf(_SC_PAGESIZE))) { /* on some platforms mmap() result does not indicate that address is not available, */ /* but then SIGBUS is raised accessing the memory */ install_sigbus_handler(); /* find smbios entry point - located between 0xF0000 and 0xFFFFF (according to the specs) */ for(page = 0xf0000; page < 0xfffff; page += pagesize) { /* mmp needs to be a multiple of pagesize for munmap */ if (MAP_FAILED == (mmp = mmap(0, pagesize, PROT_READ, MAP_SHARED, fd, page))) goto close; if (0 != sigsetjmp(sigbus_jmp_buf, 0)) /* we get here if memory address is not valid */ { munmap(mmp, pagesize); goto close; } for(page_offset = 0; page_offset < pagesize; page_offset += 16) { data = (unsigned char *)mmp + page_offset; if (0 == strncmp((char *)data, "_DMI_", 5)) /* entry point found */ { smbios_len = data[7] << 8 | data[6]; smbios = (size_t)data[11] << 24 | (size_t)data[10] << 16 | (size_t)data[9] << 8 | data[8]; if (0 == smbios || 0 == smbios_len) smbios_status = SMBIOS_STATUS_ERROR; else smbios_status = SMBIOS_STATUS_OK; break; } } munmap(mmp, pagesize); if (SMBIOS_STATUS_UNKNOWN != smbios_status) break; } } if (SMBIOS_STATUS_OK != smbios_status) { smbios_status = SMBIOS_STATUS_ERROR; goto close; } smbuf = (unsigned char *)zbx_malloc(smbuf, smbios_len); len = smbios % pagesize; /* mmp needs to be a multiple of pagesize for munmap */ if (MAP_FAILED == (mmp = mmap(0, len + smbios_len, PROT_READ, MAP_SHARED, fd, smbios - len))) goto clean; if (0 == sigsetjmp(sigbus_jmp_buf, 0)) memcpy(smbuf, (char *)mmp + len, smbios_len); munmap(mmp, len + smbios_len); } else return ret; data = smbuf; while (data + DMI_HEADER_SIZE <= smbuf + smbios_len) { if (1 == data[0]) /* system information */ { if (0 != (flags & DMI_GET_VENDOR)) { offset += get_dmi_string(buf + offset, bufsize - offset, data, data[4]); flags &= ~DMI_GET_VENDOR; } if (0 != (flags & DMI_GET_MODEL)) { offset += get_dmi_string(buf + offset, bufsize - offset, data, data[5]); flags &= ~DMI_GET_MODEL; } if (0 != (flags & DMI_GET_SERIAL)) { offset += get_dmi_string(buf + offset, bufsize - offset, data, data[7]); flags &= ~DMI_GET_SERIAL; } } else if (3 == data[0] && 0 != (flags & DMI_GET_TYPE)) /* chassis */ { offset += get_chassis_type(buf + offset, bufsize - offset, data[5]); flags &= ~DMI_GET_TYPE; } if (0 == flags) break; data += data[1]; /* skip the main data */ while (0 != data[0] || 0 != data[1]) /* string data ends with two nulls */ { data++; } data += 2; } if (0 < offset) ret = SYSINFO_RET_OK; clean: zbx_free(smbuf); close: close(fd); remove_sigbus_handler(); return ret; } int system_hw_chassis(AGENT_REQUEST *request, AGENT_RESULT *result) { char *mode, buf[MAX_STRING_LEN]; int ret = SYSINFO_RET_FAIL; if (1 < request->nparam) { SET_MSG_RESULT(result, zbx_strdup(NULL, "Too many parameters.")); return SYSINFO_RET_FAIL; } mode = get_rparam(request, 0); if (NULL == mode || '\0' == *mode || 0 == strcmp(mode, "full")) /* show full info by default */ ret = get_dmi_info(buf, sizeof(buf), DMI_GET_TYPE | DMI_GET_VENDOR | DMI_GET_MODEL | DMI_GET_SERIAL); else if (0 == strcmp(mode, "type")) ret = get_dmi_info(buf, sizeof(buf), DMI_GET_TYPE); else if (0 == strcmp(mode, "vendor")) ret = get_dmi_info(buf, sizeof(buf), DMI_GET_VENDOR); else if (0 == strcmp(mode, "model")) ret = get_dmi_info(buf, sizeof(buf), DMI_GET_MODEL); else if (0 == strcmp(mode, "serial")) ret = get_dmi_info(buf, sizeof(buf), DMI_GET_SERIAL); else { SET_MSG_RESULT(result, zbx_strdup(NULL, "Invalid first parameter.")); return SYSINFO_RET_FAIL; } if (SYSINFO_RET_FAIL == ret) { SET_MSG_RESULT(result, zbx_strdup(NULL, "Cannot obtain hardware information.")); return SYSINFO_RET_FAIL; } SET_STR_RESULT(result, zbx_strdup(NULL, buf + 1)); /* buf has a leading space */ return ret; } static zbx_uint64_t get_cpu_max_freq(int cpu_num, int *status) { zbx_uint64_t freq = ZBX_MAX_UINT64; char filename[MAX_STRING_LEN]; FILE *f; zbx_snprintf(filename, sizeof(filename), CPU_MAX_FREQ_FILE, cpu_num); f = fopen(filename, "r"); if (NULL != f) { if (1 != fscanf(f, ZBX_FS_UI64, &freq)) *status = FAIL; else *status = SUCCEED; fclose(f); } else *status = FAIL; return freq; } static size_t print_freq(char *buffer, size_t size, int filter, int cpu, zbx_uint64_t maxfreq, zbx_uint64_t curfreq) { size_t offset = 0; if (HW_CPU_SHOW_MAXFREQ == filter && ZBX_MAX_UINT64 != maxfreq) { if (HW_CPU_ALL_CPUS == cpu) offset += zbx_snprintf(buffer + offset, size - offset, " " ZBX_FS_UI64 "MHz", maxfreq / 1000); else offset += zbx_snprintf(buffer + offset, size - offset, " " ZBX_FS_UI64, maxfreq * 1000); } else if (HW_CPU_SHOW_CURFREQ == filter && ZBX_MAX_UINT64 != curfreq) { if (HW_CPU_ALL_CPUS == cpu) offset += zbx_snprintf(buffer + offset, size - offset, " " ZBX_FS_UI64 "MHz", curfreq); else offset += zbx_snprintf(buffer + offset, size - offset, " " ZBX_FS_UI64, curfreq * 1000000); } else if (HW_CPU_SHOW_ALL == filter) { if (ZBX_MAX_UINT64 != curfreq) { offset += zbx_snprintf(buffer + offset, size - offset, " working at " ZBX_FS_UI64 "MHz", curfreq); } if (ZBX_MAX_UINT64 != maxfreq) { offset += zbx_snprintf(buffer + offset, size - offset, " (maximum " ZBX_FS_UI64 "MHz)", maxfreq / 1000); } } return offset; } int system_hw_cpu(AGENT_REQUEST *request, AGENT_RESULT *result) { int ret = SYSINFO_RET_FAIL, filter, cpu, cur_cpu = -1, offset = 0; zbx_uint64_t maxfreq = ZBX_MAX_UINT64, curfreq = ZBX_MAX_UINT64; char line[MAX_STRING_LEN], name[MAX_STRING_LEN], tmp[MAX_STRING_LEN], buffer[MAX_BUFFER_LEN], *param; FILE *f; if (2 < request->nparam) { SET_MSG_RESULT(result, zbx_strdup(NULL, "Too many parameters.")); return SYSINFO_RET_FAIL; } param = get_rparam(request, 0); if (NULL == param || '\0' == *param || 0 == strcmp(param, "all")) cpu = HW_CPU_ALL_CPUS; /* show all CPUs by default */ else if (FAIL == zbx_is_uint31(param, &cpu)) { SET_MSG_RESULT(result, zbx_strdup(NULL, "Invalid first parameter.")); return SYSINFO_RET_FAIL; } param = get_rparam(request, 1); if (NULL == param || '\0' == *param || 0 == strcmp(param, "full")) filter = HW_CPU_SHOW_ALL; /* show full info by default */ else if (0 == strcmp(param, "maxfreq")) filter = HW_CPU_SHOW_MAXFREQ; else if (0 == strcmp(param, "vendor")) filter = HW_CPU_SHOW_VENDOR; else if (0 == strcmp(param, "model")) filter = HW_CPU_SHOW_MODEL; else if (0 == strcmp(param, "curfreq")) filter = HW_CPU_SHOW_CURFREQ; else { SET_MSG_RESULT(result, zbx_strdup(NULL, "Invalid second parameter.")); return SYSINFO_RET_FAIL; } if (NULL == (f = fopen(HW_CPU_INFO_FILE, "r"))) { SET_MSG_RESULT(result, zbx_dsprintf(NULL, "Cannot open " HW_CPU_INFO_FILE ": %s", zbx_strerror(errno))); return SYSINFO_RET_FAIL; } *buffer = '\0'; while (NULL != fgets(line, sizeof(line), f)) { if (2 != sscanf(line, "%[^:]: %[^\n]", name, tmp)) continue; if (0 == strncmp(name, "processor", 9)) { /* print info about the previous cpu */ if (-1 != cur_cpu && (HW_CPU_ALL_CPUS == cpu || cpu == cur_cpu)) { offset += print_freq(buffer + offset, sizeof(buffer) - offset, filter, cpu, maxfreq, curfreq); } curfreq = ZBX_MAX_UINT64; cur_cpu = atoi(tmp); if (HW_CPU_ALL_CPUS != cpu && cpu != cur_cpu) continue; if (HW_CPU_ALL_CPUS == cpu || HW_CPU_SHOW_ALL == filter) { offset += zbx_snprintf(buffer + offset, sizeof(buffer) - offset, "\nprocessor %d:", cur_cpu); } if (HW_CPU_SHOW_ALL == filter || HW_CPU_SHOW_MAXFREQ == filter) { int max_freq_status; maxfreq = get_cpu_max_freq(cur_cpu, &max_freq_status); if (SUCCEED == max_freq_status) ret = SYSINFO_RET_OK; } } if (HW_CPU_ALL_CPUS != cpu && cpu != cur_cpu) continue; if (0 == strncmp(name, "vendor_id", 9) && (HW_CPU_SHOW_ALL == filter || HW_CPU_SHOW_VENDOR == filter)) { ret = SYSINFO_RET_OK; offset += zbx_snprintf(buffer + offset, sizeof(buffer) - offset, " %s", tmp); } else if (0 == strncmp(name, "model name", 10) && (HW_CPU_SHOW_ALL == filter || HW_CPU_SHOW_MODEL == filter)) { ret = SYSINFO_RET_OK; offset += zbx_snprintf(buffer + offset, sizeof(buffer) - offset, " %s", tmp); } else if (0 == strncmp(name, "cpu MHz", 7) && (HW_CPU_SHOW_ALL == filter || HW_CPU_SHOW_CURFREQ == filter)) { ret = SYSINFO_RET_OK; if (1 != sscanf(tmp, ZBX_FS_UI64, &curfreq)) { zbx_fclose(f); SET_MSG_RESULT(result, zbx_strdup(NULL, "Cannot obtain CPU frequency.")); return SYSINFO_RET_FAIL; } } } zbx_fclose(f); if (SYSINFO_RET_FAIL == ret) { SET_MSG_RESULT(result, zbx_strdup(NULL, "Cannot obtain CPU information.")); return SYSINFO_RET_FAIL; } if (-1 != cur_cpu && (HW_CPU_ALL_CPUS == cpu || cpu == cur_cpu)) /* print info about the last cpu */ print_freq(buffer + offset, sizeof(buffer) - offset, filter, cpu, maxfreq, curfreq); SET_TEXT_RESULT(result, zbx_strdup(NULL, buffer + 1)); /* buf has a leading space or '\n' */ return ret; } int system_hw_devices(AGENT_REQUEST *request, AGENT_RESULT *result) { char *type; if (1 < request->nparam) { SET_MSG_RESULT(result, zbx_strdup(NULL, "Too many parameters.")); return SYSINFO_RET_FAIL; } type = get_rparam(request, 0); if (NULL == type || '\0' == *type || 0 == strcmp(type, "pci")) return execute_str("lspci", result, request->timeout); /* list PCI devices by default */ else if (0 == strcmp(type, "usb")) return execute_str("lsusb", result, request->timeout); else { SET_MSG_RESULT(result, zbx_strdup(NULL, "Invalid first parameter.")); return SYSINFO_RET_FAIL; } } int system_hw_macaddr(AGENT_REQUEST *request, AGENT_RESULT *result) { size_t offset; int s, show_names; char *format, *p, *regex, address[MAX_STRING_LEN], buffer[MAX_STRING_LEN]; struct ifreq *ifr; struct ifconf ifc; zbx_vector_str_t addresses; if (2 < request->nparam) { SET_MSG_RESULT(result, zbx_strdup(NULL, "Too many parameters.")); return SYSINFO_RET_FAIL; } regex = get_rparam(request, 0); format = get_rparam(request, 1); if (NULL == format || '\0' == *format || 0 == strcmp(format, "full")) show_names = 1; /* show interface names */ else if (0 == strcmp(format, "short")) show_names = 0; else { SET_MSG_RESULT(result, zbx_strdup(NULL, "Invalid second parameter.")); return SYSINFO_RET_FAIL; } if (-1 == (s = socket(AF_INET, SOCK_DGRAM, 0))) { SET_MSG_RESULT(result, zbx_dsprintf(NULL, "Cannot create socket: %s", zbx_strerror(errno))); return SYSINFO_RET_FAIL; } /* get the interface list */ ifc.ifc_len = sizeof(buffer); ifc.ifc_buf = buffer; if (-1 == ioctl(s, SIOCGIFCONF, &ifc)) { close(s); SET_MSG_RESULT(result, zbx_dsprintf(NULL, "Cannot set socket parameters: %s", zbx_strerror(errno))); return SYSINFO_RET_FAIL; } ifr = ifc.ifc_req; zbx_vector_str_create(&addresses); zbx_vector_str_reserve(&addresses, 8); /* go through the list */ for (int i = ifc.ifc_len / sizeof(struct ifreq); 0 < i--; ifr++) { if (NULL != regex && '\0' != *regex && NULL == zbx_regexp_match(ifr->ifr_name, regex, NULL)) continue; if (-1 != ioctl(s, SIOCGIFFLAGS, ifr) && /* get the interface */ 0 == (ifr->ifr_flags & IFF_LOOPBACK) && /* skip loopback interface */ -1 != ioctl(s, SIOCGIFHWADDR, ifr)) /* get the MAC address */ { offset = 0; if (1 == show_names) { offset += zbx_snprintf(address + offset, sizeof(address) - offset, "[%s ", ifr->ifr_name); } zbx_snprintf(address + offset, sizeof(address) - offset, "%.2hx:%.2hx:%.2hx:%.2hx:%.2hx:%.2hx", (unsigned short int)(unsigned char)ifr->ifr_hwaddr.sa_data[0], (unsigned short int)(unsigned char)ifr->ifr_hwaddr.sa_data[1], (unsigned short int)(unsigned char)ifr->ifr_hwaddr.sa_data[2], (unsigned short int)(unsigned char)ifr->ifr_hwaddr.sa_data[3], (unsigned short int)(unsigned char)ifr->ifr_hwaddr.sa_data[4], (unsigned short int)(unsigned char)ifr->ifr_hwaddr.sa_data[5]); if (0 == show_names && FAIL != zbx_vector_str_search(&addresses, address, ZBX_DEFAULT_STR_COMPARE_FUNC)) { continue; } zbx_vector_str_append(&addresses, zbx_strdup(NULL, address)); } } offset = 0; if (0 != addresses.values_num) { zbx_vector_str_sort(&addresses, ZBX_DEFAULT_STR_COMPARE_FUNC); for (int i = 0; i < addresses.values_num; i++) { if (1 == show_names && NULL != (p = strchr(addresses.values[i], ' '))) *p = ']'; offset += zbx_snprintf(buffer + offset, sizeof(buffer) - offset, "%s, ", addresses.values[i]); zbx_free(addresses.values[i]); } offset -= 2; } buffer[offset] = '\0'; SET_STR_RESULT(result, zbx_strdup(NULL, buffer)); zbx_vector_str_destroy(&addresses); close(s); return SYSINFO_RET_OK; }