From 5f27c124c371f58ae09d12c2dc44219c05d57b37 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Tue, 2 Nov 2021 18:00:18 -0400 Subject: [PATCH 1/9] Ignore bootstrap-4.5.0.js --- .eslintignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.eslintignore b/.eslintignore index 4682851df..91b0fd196 100644 --- a/.eslintignore +++ b/.eslintignore @@ -4,7 +4,7 @@ web/api/lib web/includes/csrf/ web/js/videojs.zoomrotate.js -web/skins/classic/js/bootstrap.js +web/skins/classic/js/bootstrap-4.5.0.js web/skins/classic/js/chosen web/skins/classic/js/dateTimePicker web/skins/classic/js/jquery-*.js From b47e96d7cfde58a0c84553d88116736e5fffa423 Mon Sep 17 00:00:00 2001 From: maddios Date: Mon, 29 Nov 2021 00:20:25 -0500 Subject: [PATCH 2/9] Fix Copy/Move to Default Storage When moving from a 2nd storage to Default it fails with "New storage does not have an id. Moving will not happen" because the default ID is 0. --- scripts/ZoneMinder/lib/ZoneMinder/Event.pm | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Event.pm b/scripts/ZoneMinder/lib/ZoneMinder/Event.pm index c5e3994a7..026ff6cea 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Event.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Event.pm @@ -43,6 +43,7 @@ require Date::Parse; require POSIX; use Date::Format qw(time2str); use Time::HiRes qw(gettimeofday tv_interval stat); +use Scalar::Util qw(looks_like_number); #our @ISA = qw(ZoneMinder::Object); use parent qw(ZoneMinder::Object); @@ -601,7 +602,7 @@ sub CopyTo { # First determine if we can move it to the dest. # We do this before bothering to lock the event my ( $NewPath ) = ( $NewStorage->Path() =~ /^(.*)$/ ); # De-taint - if ( ! $$NewStorage{Id} ) { + if ( ! looks_like_number($$NewStorage{Id}) ) { return 'New storage does not have an id. Moving will not happen.'; } elsif ( $$NewStorage{Id} == $$self{StorageId} ) { return 'Event is already located at ' . $NewPath; From c927ef4b52b3ede8835e65f1e0c9aa7d62ca59df Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Mon, 29 Nov 2021 18:32:48 -0500 Subject: [PATCH 3/9] Aim to do db updates every 5 seconds instead of 1 second --- src/zm_event.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/zm_event.cpp b/src/zm_event.cpp index 9f1124e7b..2d95ec23a 100644 --- a/src/zm_event.cpp +++ b/src/zm_event.cpp @@ -546,7 +546,7 @@ void Event::AddFrame(Image *image, or (frame_type == BULK) or - (fps and (frame_data.size() > fps))) { + (fps and (frame_data.size() > 5*fps))) { Debug(1, "Adding %zu frames to DB because write_to_db:%d or frames > analysis fps %f or BULK(%d)", frame_data.size(), write_to_db, fps, (frame_type == BULK)); WriteDbFrames(); From 1cddac4efd45cd6ea965d8d7e90c3815060332d8 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Mon, 29 Nov 2021 18:34:06 -0500 Subject: [PATCH 4/9] remove remaining signal blocking cruft, add ignoring sigchld so that anything we spawn doesn't become a zombie. --- src/zmc.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/zmc.cpp b/src/zmc.cpp index 68c6227af..c2f14e254 100644 --- a/src/zmc.cpp +++ b/src/zmc.cpp @@ -220,12 +220,13 @@ int main(int argc, char *argv[]) { zmSetDefaultTermHandler(); zmSetDefaultDieHandler(); - sigset_t block_set; - sigemptyset(&block_set); - - sigaddset(&block_set, SIGHUP); - sigaddset(&block_set, SIGUSR1); - sigaddset(&block_set, SIGUSR2); + struct sigaction sa; + sa.sa_handler = SIG_IGN; //handle signal by ignoring + sigemptyset(&sa.sa_mask); + sa.sa_flags = 0; + if (sigaction(SIGCHLD, &sa, 0) == -1) { + Error("Unable to set SIGCHLD to ignore. There may be zombies."); + } int result = 0; int prime_capture_log_count = 0; @@ -286,7 +287,6 @@ int main(int argc, char *argv[]) { Microseconds sleep_time = Microseconds(0); while (!zm_terminate) { - //sigprocmask(SIG_BLOCK, &block_set, 0); for (size_t i = 0; i < monitors.size(); i++) { monitors[i]->CheckAction(); From 37069cb6fc4cc2650ebaa8c0e5ed547a66a8443d Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Mon, 29 Nov 2021 18:59:44 -0500 Subject: [PATCH 5/9] add cmake module to find libFmt --- cmake/Modules/FindFmt.cmake | 100 ++++++++++++++++++++++++++++++++++++ 1 file changed, 100 insertions(+) create mode 100644 cmake/Modules/FindFmt.cmake diff --git a/cmake/Modules/FindFmt.cmake b/cmake/Modules/FindFmt.cmake new file mode 100644 index 000000000..b426d8c77 --- /dev/null +++ b/cmake/Modules/FindFmt.cmake @@ -0,0 +1,100 @@ +# FindFmt +# ------- +# Finds the Fmt library +# +# This will define the following variables:: +# +# FMT_FOUND - system has Fmt +# FMT_INCLUDE_DIRS - the Fmt include directory +# FMT_LIBRARIES - the Fmt libraries +# +# and the following imported targets:: +# +# Fmt::Fmt - The Fmt library + +if(ENABLE_INTERNAL_FMT) + include(ExternalProject) + file(STRINGS ${CMAKE_SOURCE_DIR}/tools/depends/target/libfmt/Makefile VER REGEX "^[ ]*VERSION[ ]*=.+$") + string(REGEX REPLACE "^[ ]*VERSION[ ]*=[ ]*" "" FMT_VERSION "${VER}") + + # allow user to override the download URL with a local tarball + # needed for offline build envs + if(FMT_URL) + get_filename_component(FMT_URL "${FMT_URL}" ABSOLUTE) + else() + set(FMT_URL http://mirrors.kodi.tv/build-deps/sources/fmt-${FMT_VERSION}.tar.gz) + endif() + if(VERBOSE) + message(STATUS "FMT_URL: ${FMT_URL}") + endif() + + if(APPLE) + set(EXTRA_ARGS "-DCMAKE_OSX_ARCHITECTURES=${CMAKE_OSX_ARCHITECTURES}") + endif() + + set(FMT_LIBRARY ${CMAKE_BINARY_DIR}/${CORE_BUILD_DIR}/lib/libfmt.a) + set(FMT_INCLUDE_DIR ${CMAKE_BINARY_DIR}/${CORE_BUILD_DIR}/include) + externalproject_add(fmt + URL ${FMT_URL} + DOWNLOAD_DIR ${CMAKE_BINARY_DIR}/${CORE_BUILD_DIR}/download + PREFIX ${CORE_BUILD_DIR}/fmt + CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${CMAKE_BINARY_DIR}/${CORE_BUILD_DIR} + -DCMAKE_CXX_EXTENSIONS=${CMAKE_CXX_EXTENSIONS} + -DCMAKE_CXX_STANDARD=${CMAKE_CXX_STANDARD} + -DCMAKE_TOOLCHAIN_FILE=${CMAKE_TOOLCHAIN_FILE} + -DCMAKE_INSTALL_LIBDIR=lib + -DFMT_DOC=OFF + -DFMT_TEST=OFF + "${EXTRA_ARGS}" + BUILD_BYPRODUCTS ${FMT_LIBRARY}) + set_target_properties(fmt PROPERTIES FOLDER "External Projects") + + include(FindPackageHandleStandardArgs) + find_package_handle_standard_args(Fmt + REQUIRED_VARS FMT_LIBRARY FMT_INCLUDE_DIR + VERSION_VAR FMT_VERSION) + + set(FMT_LIBRARIES ${FMT_LIBRARY}) + set(FMT_INCLUDE_DIRS ${FMT_INCLUDE_DIR}) + +else() + +find_package(FMT 6.1.2 CONFIG REQUIRED QUIET) + +if(PKG_CONFIG_FOUND) + pkg_check_modules(PC_FMT libfmt QUIET) + if(PC_FMT_VERSION AND NOT FMT_VERSION) + set(FMT_VERSION ${PC_FMT_VERSION}) + endif() +endif() + +find_path(FMT_INCLUDE_DIR NAMES fmt/format.h + PATHS ${PC_FMT_INCLUDEDIR}) + +find_library(FMT_LIBRARY_RELEASE NAMES fmt + PATHS ${PC_FMT_LIBDIR}) +find_library(FMT_LIBRARY_DEBUG NAMES fmtd + PATHS ${PC_FMT_LIBDIR}) + +include(SelectLibraryConfigurations) +select_library_configurations(FMT) + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(Fmt + REQUIRED_VARS FMT_LIBRARY FMT_INCLUDE_DIR FMT_VERSION + VERSION_VAR FMT_VERSION) + +if(FMT_FOUND) + set(FMT_LIBRARIES ${FMT_LIBRARY}) + set(FMT_INCLUDE_DIRS ${FMT_INCLUDE_DIR}) + + if(NOT TARGET fmt) + add_library(fmt UNKNOWN IMPORTED) + set_target_properties(fmt PROPERTIES + IMPORTED_LOCATION "${FMT_LIBRARY}" + INTERFACE_INCLUDE_DIRECTORIES "${FMT_INCLUDE_DIR}") + endif() +endif() + +endif() +mark_as_advanced(FMT_INCLUDE_DIR FMT_LIBRARY) From 5647c224d1d6407b6a44288c91a8c9384c9f2f08 Mon Sep 17 00:00:00 2001 From: Petter Reinholdtsen Date: Thu, 2 Dec 2021 15:18:04 +0100 Subject: [PATCH 6/9] Make config file comment on unix socket option a bit clearer Initially I took the comment for granted, and the 'unix_socket' string as a magic string to tell zoneminder to use the mysql default socket. Alas, this do not work, as the setting really need to point to the path of the socket. Rewrite the comment to make this more clear, and less confusing for the future users. --- zm.conf.in | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/zm.conf.in b/zm.conf.in index 312c9aeae..d48dfce56 100644 --- a/zm.conf.in +++ b/zm.conf.in @@ -37,7 +37,8 @@ ZM_WEB_GROUP=@WEB_GROUP@ ZM_DB_TYPE=@ZM_DB_TYPE@ # ZoneMinder database hostname or ip address and optionally port or unix socket -# Acceptable formats include hostname[:port], ip_address[:port], or localhost:unix_socket +# Acceptable formats include hostname[:port], ip_address[:port], or +# localhost:/path/to/unix_socket ZM_DB_HOST=@ZM_DB_HOST@ # ZoneMinder database name From 1277e752783ed8d2dc061e1ae633a3670cc4b617 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Fri, 3 Dec 2021 12:03:23 -0500 Subject: [PATCH 7/9] Detect group hierarchy loops and break them. --- web/includes/Group.php | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/web/includes/Group.php b/web/includes/Group.php index 026d1f7f4..34db0e869 100644 --- a/web/includes/Group.php +++ b/web/includes/Group.php @@ -167,9 +167,15 @@ class Group extends ZM_Object { public function Parents() { $Parents = array(); $Parent = $this->Parent(); - while( $Parent ) { + $seen_parents = array(); + while ($Parent) { + $seen_parents[$Parent->Id()] = $Parent; array_unshift($Parents, $Parent); $Parent = $Parent->Parent(); + if ($Parent and isset($seen_parents[$Parent->Id()])) { + Warning("Detected hierarchy loop in group {$Parent->Name()}"); + break; + } } return $Parents; } @@ -189,6 +195,9 @@ class Group extends ZM_Object { public function canView($u=null) { global $user; if (!$u) $u = $user; + if (!count($this->Monitors()) and !count($this->Children())) { + return true; + } # Can view if we can view any of the monitors in it. foreach ($this->Monitors() as $monitor) { if ($monitor->canView($u)) return true; From eef172379c11c60a68236d3768495e962bef398f Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Fri, 3 Dec 2021 13:25:21 -0500 Subject: [PATCH 8/9] Move all the opening of events code into one function called openEvent. --- src/zm_monitor.cpp | 208 ++++++++++++++++----------------------------- 1 file changed, 75 insertions(+), 133 deletions(-) diff --git a/src/zm_monitor.cpp b/src/zm_monitor.cpp index d50e4e6eb..0410d39bc 100644 --- a/src/zm_monitor.cpp +++ b/src/zm_monitor.cpp @@ -1882,12 +1882,20 @@ bool Monitor::Analyse() { // Get new score. int motion_score = DetectMotion(*(snap->image), zoneSet); + // lets construct alarm cause. It will contain cause + names of zones alarmed + std::string alarm_cause; snap->zone_stats.reserve(zones.size()); for (const Zone &zone : zones) { const ZoneStats &stats = zone.GetStats(); stats.DumpToLog("After detect motion"); snap->zone_stats.push_back(stats); + if (zone.Alarmed()) { + if (!alarm_cause.empty()) alarm_cause += ","; + alarm_cause += std::string(zone.Label()); + } } + if (!alarm_cause.empty()) + cause = cause+" "+alarm_cause; Debug(3, "After motion detection, score:%d last_motion_score(%d), new motion score(%d)", score, last_motion_score, motion_score); @@ -1913,6 +1921,7 @@ bool Monitor::Analyse() { ); } // end if active and doing motion detection + if (function == RECORD or function == MOCORD) { // If doing record, check to see if we need to close the event or not. if (event) { @@ -1935,75 +1944,9 @@ bool Monitor::Analyse() { } // end if event if (!event) { - Debug(2, "Creating continuous event"); - if (!snap->keyframe and (videowriter == PASSTHROUGH)) { - // Must start on a keyframe so rewind. Only for passthrough though I guess. - // FIXME this iterator is not protected from invalidation - packetqueue_iterator *start_it = packetqueue.get_event_start_packet_it( - *analysis_it, 0 /* pre_event_count */ - ); + event = openEvent(snap, cause.empty() ? "Continuous" : cause, noteSetMap); - // This gets a lock on the starting packet - - ZMLockedPacket *starting_packet_lock = nullptr; - std::shared_ptr starting_packet = nullptr; - if (*start_it != *analysis_it) { - starting_packet_lock = packetqueue.get_packet(start_it); - if (!starting_packet_lock) { - Warning("Unable to get starting packet lock"); - delete packet_lock; - return false; - } - starting_packet = starting_packet_lock->packet_; - } else { - starting_packet = snap; - } - - event = new Event(this, starting_packet->timestamp, "Continuous", noteSetMap); - // Write out starting packets, do not modify packetqueue it will garbage collect itself - while (starting_packet and ((*start_it) != *analysis_it)) { - event->AddPacket(starting_packet); - // Have added the packet, don't want to unlock it until we have locked the next - - packetqueue.increment_it(start_it); - if ((*start_it) == *analysis_it) { - if (starting_packet_lock) delete starting_packet_lock; - break; - } - ZMLockedPacket *lp = packetqueue.get_packet(start_it); - delete starting_packet_lock; - if (!lp) return false; - starting_packet_lock = lp; - starting_packet = lp->packet_; - } - packetqueue.free_it(start_it); - delete start_it; - start_it = nullptr; - } else { - // Create event from current snap - event = new Event(this, timestamp, "Continuous", noteSetMap); - } - shared_data->last_event_id = event->Id(); - - // lets construct alarm cause. It will contain cause + names of zones alarmed - std::string alarm_cause; - for (const Zone &zone : zones) { - if (zone.Alarmed()) { - if (!alarm_cause.empty()) alarm_cause += ","; - alarm_cause += std::string(zone.Label()); - } - } - alarm_cause = cause+" Continuous "+alarm_cause; - strncpy(shared_data->alarm_cause, alarm_cause.c_str(), sizeof(shared_data->alarm_cause)-1); - SetVideoWriterStartTime(event->StartTime()); - if (!event_start_command.empty()) { - if (fork() == 0) { - execlp(event_start_command.c_str(), event_start_command.c_str(), std::to_string(event->Id()).c_str(), nullptr); - Error("Error execing %s", event_start_command.c_str()); - } - } - - Info("%s: %03d - Opened new event %" PRIu64 ", section start", + Info("%s: %03d - Opened new event %" PRIu64 ", continuous section start", name.c_str(), analysis_image_count, event->Id()); /* To prevent cancelling out an existing alert\prealarm\alarm state */ if (state == IDLE) { @@ -2035,70 +1978,12 @@ bool Monitor::Analyse() { (event_close_mode == CLOSE_ALARM)); } if ((!pre_event_count) || (Event::PreAlarmCount() >= alarm_frame_count-1)) { - // lets construct alarm cause. It will contain cause + names of zones alarmed - std::string alarm_cause = ""; - for (const Zone &zone : zones) { - if (zone.Alarmed()) { - alarm_cause = alarm_cause + "," + std::string(zone.Label()); - } - } - if (!alarm_cause.empty()) alarm_cause[0] = ' '; - alarm_cause = cause + alarm_cause; - strncpy(shared_data->alarm_cause, alarm_cause.c_str(), sizeof(shared_data->alarm_cause)-1); Info("%s: %03d - Gone into alarm state PreAlarmCount: %u > AlarmFrameCount:%u Cause:%s", - name.c_str(), image_count, Event::PreAlarmCount(), alarm_frame_count, shared_data->alarm_cause); + name.c_str(), image_count, Event::PreAlarmCount(), alarm_frame_count, cause.c_str()); if (!event) { - packetqueue_iterator *start_it = packetqueue.get_event_start_packet_it( - *analysis_it, - (pre_event_count > alarm_frame_count ? pre_event_count : alarm_frame_count) - ); - ZMLockedPacket *starting_packet_lock = nullptr; - std::shared_ptr starting_packet = nullptr; - if (*start_it != *analysis_it) { - starting_packet_lock = packetqueue.get_packet(start_it); - if (!starting_packet_lock) return false; - starting_packet = starting_packet_lock->packet_; - } else { - starting_packet = snap; - } - - event = new Event(this, starting_packet->timestamp, cause, noteSetMap); - shared_data->last_event_id = event->Id(); - snprintf(video_store_data->event_file, sizeof(video_store_data->event_file), "%s", event->getEventFile()); - SetVideoWriterStartTime(event->StartTime()); + event = openEvent(snap, cause, noteSetMap); shared_data->state = state = ALARM; - - // Write out starting packets, do not modify packetqueue it will garbage collect itself - while (*start_it != *analysis_it) { - event->AddPacket(starting_packet); - - packetqueue.increment_it(start_it); - if ( (*start_it) == (*analysis_it) ) { - if (starting_packet_lock) delete starting_packet_lock; - break; - } - ZMLockedPacket *lp = packetqueue.get_packet(start_it); - delete starting_packet_lock; - if (!lp) { - // Shutting down event will be closed by ~Monitor() - // Perhaps we shouldn't do this. - return false; - } - starting_packet_lock = lp; - starting_packet = lp->packet_; - } - packetqueue.free_it(start_it); - delete start_it; - start_it = nullptr; - - if (!event_start_command.empty()) { - if (fork() == 0) { - execlp(event_start_command.c_str(), event_start_command.c_str(), std::to_string(event->Id()).c_str(), nullptr); - Error("Error execing %s", event_start_command.c_str()); - } - } - Info("%s: %03d - Opening new event %" PRIu64 ", alarm start", name.c_str(), analysis_image_count, event->Id()); } else { shared_data->state = state = ALARM; @@ -2208,11 +2093,7 @@ bool Monitor::Analyse() { static_cast(std::chrono::duration_cast(timestamp - GetVideoWriterStartTime()).count()), static_cast(Seconds(section_length).count())); closeEvent(); - event = new Event(this, timestamp, cause, noteSetMap); - shared_data->last_event_id = event->Id(); - //set up video store data - snprintf(video_store_data->event_file, sizeof(video_store_data->event_file), "%s", event->getEventFile()); - SetVideoWriterStartTime(event->StartTime()); + event = openEvent(snap, cause, noteSetMap); } } else { Error("ALARM but no event"); @@ -2791,6 +2672,67 @@ void Monitor::TimestampImage(Image *ts_image, SystemTimePoint ts_time) const { Debug(2, "done annotating %s", label_text); } // end void Monitor::TimestampImage +Event * Monitor::openEvent( + const std::shared_ptr &snap, + const std::string &cause, + const Event::StringSetMap noteSetMap) { + + // FIXME this iterator is not protected from invalidation + packetqueue_iterator *start_it = packetqueue.get_event_start_packet_it( + *analysis_it, + (cause == "Continuous" ? 0 : (pre_event_count > alarm_frame_count ? pre_event_count : alarm_frame_count)) + ); + + // This gets a lock on the starting packet + + ZMLockedPacket *starting_packet_lock = nullptr; + std::shared_ptr starting_packet = nullptr; + if (*start_it != *analysis_it) { + starting_packet_lock = packetqueue.get_packet(start_it); + if (!starting_packet_lock) { + Warning("Unable to get starting packet lock"); + return nullptr; + } + starting_packet = starting_packet_lock->packet_; + } else { + starting_packet = snap; + } + + event = new Event(this, starting_packet->timestamp, cause, noteSetMap); + + shared_data->last_event_id = event->Id(); + strncpy(shared_data->alarm_cause, cause.c_str(), sizeof(shared_data->alarm_cause)-1); + + if (!event_start_command.empty()) { + if (fork() == 0) { + execlp(event_start_command.c_str(), event_start_command.c_str(), std::to_string(event->Id()).c_str(), nullptr); + Error("Error execing %s", event_start_command.c_str()); + } + } + + // Write out starting packets, do not modify packetqueue it will garbage collect itself + while (starting_packet and ((*start_it) != *analysis_it)) { + event->AddPacket(starting_packet); + // Have added the packet, don't want to unlock it until we have locked the next + + packetqueue.increment_it(start_it); + if ((*start_it) == *analysis_it) { + if (starting_packet_lock) delete starting_packet_lock; + break; + } + ZMLockedPacket *lp = packetqueue.get_packet(start_it); + delete starting_packet_lock; + if (!lp) return nullptr; // only on terminate FIXME + starting_packet_lock = lp; + starting_packet = lp->packet_; + } + packetqueue.free_it(start_it); + delete start_it; + start_it = nullptr; + + return event; +} + void Monitor::closeEvent() { if (!event) return; From 27e87fc21ff305a062416dd508d6d208146b0809 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Fri, 3 Dec 2021 13:25:36 -0500 Subject: [PATCH 9/9] Move all the opening of events code into one function called openEvent. --- src/zm_monitor.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/zm_monitor.h b/src/zm_monitor.h index d670911bb..8d6bbd95e 100644 --- a/src/zm_monitor.h +++ b/src/zm_monitor.h @@ -593,6 +593,10 @@ public: bool Decode(); void DumpImage( Image *dump_image ) const; void TimestampImage(Image *ts_image, SystemTimePoint ts_time) const; + Event *openEvent( + const std::shared_ptr &snap, + const std::string &cause, + const Event::StringSetMap noteSetMap); void closeEvent(); void Reload();