Files
zoneminder/src/zm_monitor_monitorlink.cpp
Isaac Connor 0e51fb6788 fix: call disconnect() in MonitorLink::connect() before reopening map_fd
MonitorLink::connect() opened a new map_fd on each invocation without
closing any previously-opened one. Token::score() in
zm_monitorlink_token.h calls connect() on every analysis cycle when the
linked monitor is unavailable, causing rapid file descriptor
accumulation and eventual "Too many open files" errors in zmc.

Call disconnect() first to release any prior map_fd, mmap, and shared
state before re-establishing the link.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 20:48:42 -04:00

251 lines
7.2 KiB
C++

//
// ZoneMinder Monitor Class Implementation, $Date$, $Revision$
// Copyright (C) 2001-2008 Philip Coombes
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// 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 General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
//
#include "zm_monitor.h"
#include <sys/stat.h>
#if ZM_MEM_MAPPED
#include <sys/mman.h>
#include <fcntl.h>
#include <unistd.h>
#else // ZM_MEM_MAPPED
#include <sys/ipc.h>
#include <sys/shm.h>
#endif // ZM_MEM_MAPPED
Monitor::MonitorLink::MonitorLink(std::shared_ptr<Monitor>p_monitor, unsigned int p_zone_id) :
monitor(p_monitor),
zone_id(p_zone_id),
zone(nullptr),
zone_index(-1),
shared_data(nullptr),
trigger_data(nullptr),
video_store_data(nullptr),
zone_scores(nullptr) {
name = monitor->Name();
if (zone_id) {
zones = Zone::Load(monitor);
int zone_i = 0;
for (const Zone &z : zones) {
if (z.Id() == zone_id) {
zone = &z;
zone_index = zone_i;
break;
}
++zone_i;
}
if (zone_index == -1) {
Error("Unable to find zone %u", zone_id);
zone_index = -1;
}
}
if (zone) name += " : " + zone->Name();
#if ZM_MEM_MAPPED
map_fd = -1;
mem_file = stringtf("%s/zm.mmap.%u", staticConfig.PATH_MAP.c_str(), monitor->Id());
#else // ZM_MEM_MAPPED
shm_id = 0;
#endif // ZM_MEM_MAPPED
mem_size = 0;
mem_ptr = nullptr;
last_event_id = 0;
last_state = IDLE;
last_connect_time = 0;
connected = false;
}
Monitor::MonitorLink::~MonitorLink() {
zones.clear();
disconnect();
}
bool Monitor::MonitorLink::connect() {
SystemTimePoint now = std::chrono::system_clock::now();
if (!last_connect_time || (now - std::chrono::system_clock::from_time_t(last_connect_time)) > Seconds(1)) {
last_connect_time = std::chrono::system_clock::to_time_t(now);
// Clean up any existing resources before reconnecting to avoid fd leaks
disconnect();
mem_size = sizeof(SharedData) + sizeof(TriggerData);
Debug(1, "link.mem.size=%jd", static_cast<intmax_t>(mem_size));
#if ZM_MEM_MAPPED
map_fd = open(mem_file.c_str(), O_RDWR, (mode_t)0600);
if (map_fd < 0) {
Debug(3, "Can't open linked memory map file %s: %s", mem_file.c_str(), strerror(errno));
disconnect();
return false;
}
while (map_fd <= 2) {
int new_map_fd = dup(map_fd);
Warning("Got one of the stdio fds for our mmap handle. map_fd was %d, new one is %d", map_fd, new_map_fd);
close(map_fd);
map_fd = new_map_fd;
}
struct stat map_stat;
if (fstat(map_fd, &map_stat) < 0) {
Error("Can't stat linked memory map file %s: %s", mem_file.c_str(), strerror(errno));
disconnect();
return false;
}
if (map_stat.st_size == 0) {
Error("Linked memory map file %s is empty: %s", mem_file.c_str(), strerror(errno));
disconnect();
return false;
} else if (map_stat.st_size < mem_size) {
Error("Got unexpected memory map file size %jd, expected %jd", static_cast<intmax_t>(map_stat.st_size), static_cast<intmax_t>(mem_size));
disconnect();
return false;
}
mem_ptr = (unsigned char *)mmap(nullptr, mem_size, PROT_READ|PROT_WRITE, MAP_SHARED, map_fd, 0);
if (mem_ptr == MAP_FAILED) {
Error("Can't map file %s (%jd bytes) to memory: %s", mem_file.c_str(), static_cast<intmax_t>(mem_size), strerror(errno));
disconnect();
return false;
}
#else // ZM_MEM_MAPPED
shm_id = shmget((config.shm_key&0xffff0000)|id, mem_size, 0700);
if (shm_id < 0) {
Debug(3, "Can't shmget link memory: %s", strerror(errno));
connected = false;
return false;
}
mem_ptr = (unsigned char *)shmat(shm_id, 0, 0);
if ((int)mem_ptr == -1) {
Debug(3, "Can't shmat link memory: %s", strerror(errno));
connected = false;
return false;
}
#endif // ZM_MEM_MAPPED
shared_data = (SharedData *)mem_ptr;
trigger_data = (TriggerData *)((char *)shared_data + sizeof(SharedData));
zone_scores = (int *)((unsigned long)trigger_data + sizeof(TriggerData));
if (!shared_data->valid) {
Debug(3, "Linked memory not initialised by capture daemon");
disconnect();
return false;
}
last_state = shared_data->state;
last_event_id = shared_data->last_event_id;
connected = true;
return true;
}
return connected;
} // end bool Monitor::MonitorLink::connect()
bool Monitor::MonitorLink::disconnect() {
connected = false;
#if ZM_MEM_MAPPED
if (mem_ptr != nullptr && mem_ptr != MAP_FAILED) {
msync(mem_ptr, mem_size, MS_ASYNC);
munmap(mem_ptr, mem_size);
}
if (map_fd >= 0)
close(map_fd);
map_fd = -1;
#else // ZM_MEM_MAPPED
if (mem_ptr != nullptr) {
struct shmid_ds shm_data;
if (shmctl(shm_id, IPC_STAT, &shm_data) < 0) {
Debug(3, "Can't shmctl: %s", strerror(errno));
return false;
}
if (shm_data.shm_nattch <= 1) {
if (shmctl(shm_id, IPC_RMID, 0) < 0) {
Debug(3, "Can't shmctl: %s", strerror(errno));
return false;
}
}
if (shmdt(mem_ptr) < 0) {
Debug(3, "Can't shmdt: %s", strerror(errno));
return false;
}
}
shm_id = 0;
#endif // ZM_MEM_MAPPED
mem_size = 0;
mem_ptr = nullptr;
shared_data = nullptr;
trigger_data = nullptr;
zone_scores = nullptr;
return true;
}
bool Monitor::MonitorLink::isAlarmed() {
if (!connected) {
return false;
}
return( shared_data->state == ALARM );
}
bool Monitor::MonitorLink::inAlarm() {
if (!connected) {
return false;
}
return( shared_data->state == ALARM || shared_data->state == ALERT );
}
int Monitor::MonitorLink::score() {
if (zone_id and (zone_index >= 0)) {
Debug(1, "Checking zone %u, zone_index is %d, score is %d", zone_id, zone_index, zone_scores[zone_index]);
return zone_scores[zone_index];
}
// Latch: detect if a new event started since we last checked.
// This catches alarms even if the linked monitor transitioned through
// ALARM -> ALERT -> IDLE between our analysis cycles.
if (shared_data->last_event_id != last_event_id) {
Debug(1, "New event %" PRIu64 " on linked monitor (was %" PRIu64 "), last_frame_score %d",
shared_data->last_event_id, last_event_id, shared_data->last_frame_score);
last_event_id = shared_data->last_event_id;
// Return last_frame_score if still available, otherwise minimum of 1
// to ensure the alarm is not silently missed
return shared_data->last_frame_score > 0 ? shared_data->last_frame_score : 1;
}
if (shared_data->state == ALARM) {
Debug(1, "Checking all zones score is %d", shared_data->last_frame_score);
return shared_data->last_frame_score;
}
Debug(1, "not alarmed. %d", shared_data->state);
return 0;
}
bool Monitor::MonitorLink::hasAlarmed() {
return this->score() > 0;
}