Commit Graph

3084 Commits

Author SHA1 Message Date
Isaac Connor
19c5bcbbde docs: correct Event::delete lock-order comment to match triggers.sql
The previous comment block claimed event_update_trigger fires BEFORE UPDATE
and that the lock order was buckets -> Event_Summaries -> Events. Neither
matches the code: triggers.sql defines event_update_trigger as AFTER UPDATE,
and InnoDB X-locks the matched Events row during WHERE evaluation before
either BEFORE or AFTER trigger bodies fire — so the canonical chain is
  Events[Id] -> buckets[EventId] -> Event_Summaries[MonitorId]
which is what triggers.sql already documents. Comment-only change.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 09:39:08 -04:00
Isaac Connor
830a92542d perf: snapshot bucket aggregates instead of joining in Event_Summaries resync
The previous resync code in zmstats and zmaudit used multi-table
UPDATEs against Event_Summaries that joined the bucket tables:

  UPDATE Event_Summaries es
  LEFT JOIN (SELECT ... FROM Events_Hour ...) h ON ...
  LEFT JOIN (SELECT ... FROM Events_Day  ...) d ON ...
  ... SET es.HourEvents = h.c, ...

zmaudit additionally used scalar correlated subqueries against Events
for the Total/Archived columns and against Events for Storage.DiskSpace.

MariaDB takes S-locks on the joined and sub-queried rows for the
duration of any multi-table UPDATE statement, regardless of isolation
level. event_update_trigger and event_delete_trigger hold X-locks on
those same bucket rows while they walk the trigger body, so the resync
deadlocks against active event lifecycle traffic. Captured a textbook
example in SHOW ENGINE INNODB STATUS:

  TX(1) zma:    HOLDS X Events_Hour[42229643]
                WAITS X Event_Summaries[28]
  TX(2) zmstats: HOLDS X Event_Summaries[2,4,5,...,28,...,73]
                 WAITS S Events_Hour[42229643]

Replace the JOIN/subquery pattern in both scripts with a snapshot phase
followed by per-monitor UPDATEs:

  1. SELECT MonitorId, COUNT(*), SUM(DiskSpace) FROM each bucket
     (and the equivalent Total/Archived aggregate from Events).
     Plain SELECTs do consistent reads and take no row locks.
  2. SELECT MonitorId FROM Event_Summaries to widen the universe so
     monitors with empty buckets still get zeroed out.
  3. For each monitor, UPDATE Event_Summaries SET ... WHERE MonitorId=?.
     Each UPDATE only X-locks one ES row and reads no other table.

zmaudit's five separate UPDATEs collapse to one snapshot phase plus
one UPDATE per monitor. Storage DiskSpace gets the same treatment.

zmstats keeps the same outer transaction (BEGIN ... COMMIT, RC
isolation, retry on 1213) so the bucket DELETEs and the resync stay
atomic, but the resync no longer reads the bucket tables under lock.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 15:22:51 -04:00
Isaac Connor
fe85f1dedd fix: rollback and retry Event::delete under deadlock at READ COMMITTED
Three issues in the existing Stats/Event_Data/Frames/Events delete
sequence:

  - On any zmDbDo error inside the transaction the code called
    dbh->commit() instead of dbh->rollback(). The server-side
    transaction was already rolled back when InnoDB picked us as the
    deadlock victim, so the commit() was effectively against a fresh
    auto-started TX, but the bug pattern leaked through as confusing
    state and prevented any retry.

  - There was no retry. errno 1213 is expected under contention with
    zmstats and zma touching the same Event_Summaries[MonitorId] row,
    and the loser is supposed to re-run.

  - At REPEATABLE READ, two concurrent filter workers deleting events
    with adjacent EventIds take next-key/gap locks on each other's
    rows in the bucket tables.

Rewrite the delete block as a retry loop: SET TRANSACTION ISOLATION
LEVEL READ COMMITTED, begin_work, run the four DELETEs, commit on
success. On any error rollback (was: commit). On errno 1213 retry up
to 5 times with backoff. Skip both the isolation switch and the
rollback-then-retry when the caller is managing their own transaction
(in_transaction); they would be the wrong scope to act in.

Falls through to the storage DiskSpace adjustment only on commit, so
a deadlocked delete leaves the event for the next filter pass instead
of orphaning the row with stale storage accounting.

Note: do NOT pre-lock Event_Summaries[MonitorId] FOR UPDATE here, even
though the trigger touches it last. Pre-locking puts ES before
buckets[Id] in the lock acquisition order, which inverts against zma's
event_update_trigger path (Events[A] -> buckets[A] -> ES[N]) and
re-introduces the cycle the rest of this work is removing.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 15:13:37 -04:00
Isaac Connor
73126938c4 feat: auto-retry zmDbDo on InnoDB deadlock errno 1213
Deadlock detection (errno 1213) is part of normal InnoDB operation
under contention; the engine rolls back the loser and expects the
caller to re-run the statement on a fresh transaction. Most callers
into zmDbDo go through autocommit, where there's no caller-managed
transaction state for a retry to disturb.

When AutoCommit is on, retry the statement up to 5 times with
exponential backoff (~100ms -> ~1.6s, jittered). When AutoCommit is
off, the caller owns the transaction and a unilateral retry would
silently succeed against a TX that no longer reflects the work the
caller staged before this statement; preserve the existing behavior
of logging and returning undef so the caller can rebuild the TX
itself.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 15:13:37 -04:00
copilot-swe-agent[bot]
b799962e2a fix: ensure trigger_state written last in zmTriggerEventOn to fix 1/3 event trigger rate
Agent-Logs-Url: https://github.com/ZoneMinder/zoneminder/sessions/68794b2b-7137-4888-b66e-20c629112a57

Co-authored-by: connortechnology <925519+connortechnology@users.noreply.github.com>
2026-05-04 13:22:13 +00:00
Isaac Connor
f09d77ae89 fix: log bind params correctly when SQL contains literal % characters
zmDbDo built log messages by s/\?/'%s'/g on the SQL and then passing
the result to sprintf with the bind values. Any literal % in the SQL
(LIKE '%foo%' patterns, or the disk-percent substitution used by
dynamic filters) was interpreted as a sprintf format spec, producing
garbage output or an uncaught sprintf error.

Replace the two-step approach with a single regex that substitutes
bind values directly, so literal % in the SQL is preserved verbatim.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 15:34:44 -04:00
Isaac Connor
6ea40a52e4 Merge pull request #4726 from f1rmb/fix_zmstats_top_usage_on_freebsd
Fix "top" command parsing on FreeBSD (tested in FreeBSD 13.5 jail, running 1.38.1).
2026-04-10 16:39:57 -04:00
Daniel Caujolle-Bert
b8183e2642 Use qx() lc() and chomp() for command execution and result.
Use eq in the if() statement, instead of '=='.
2026-04-10 16:17:03 +02:00
Daniel Caujolle-Bert
48fd036d63 Change ::Config{uname} to ::Config{ZM_PATH_UNAME} 2026-04-10 14:36:36 +02:00
Daniel Caujolle-Bert
d3e82fde62 Use ZoneMinder::Config. 2026-04-09 15:04:08 +02:00
Daniel Caujolle-Bert
7a39b2d750 Fix "top" command parsing on FreeBSD (tested in FreeBSD 13.5 jail, running 1.38.1). 2026-04-08 15:28:41 +02:00
Isaac Connor
bb9e74a2f9 fix: only validate Device path for Local monitors
Follow-up to 419846c87 (GHSA-g66m-77fq-79v9). The Device path check was
applied to all monitor Types in three places, but the Device column is
only passed to a shell for Type='Local'. Non-Local monitors (Ffmpeg,
Remote, Libvlc, cURL, VNC) may legitimately hold legacy values such as
an RTSP URL in that column and should not be rejected or warned about.

- scripts/ZoneMinder/lib/ZoneMinder/Monitor.pm: control() dropped the
  spurious Warning for non-Local monitors that was flooding zmwatch
  logs. The Error/early-return path is preserved for Local.
- web/includes/actions/monitor.php: save action only runs
  validDevicePath() when Type=='Local'.
- web/api/app/Model/Monitor.php: replaced the unconditional regex rule
  with a validDevicePath() method that checks Type before enforcing
  the /dev/ pattern.

Also add client-side validation matching the server rule, so Local
monitors get immediate feedback instead of a round-trip error:

- web/skins/classic/views/monitor.php: HTML5 pattern attribute on the
  Device input. Escaped for the v-flag regex engine used by pattern=.
- web/skins/classic/views/js/monitor.js.php: validateForm() now also
  rejects Device values that don't match the /dev/ pattern.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 08:44:07 -04:00
Isaac Connor
df29fb7c70 Merge branch 'master' of github.com:ZoneMinder/zoneminder 2026-04-06 09:40:04 -04:00
Isaac Connor
252ac03b77 Move the delay calculation code to after we run the filter so that the time taken running the filter is included 2026-04-06 09:39:01 -04:00
Isaac Connor
d8f30811cf Rename last_action to last_reload as that is what it actually is 2026-04-06 09:38:52 -04:00
Isaac Connor
f112ead9dd Include ExecuteInterval in warning 2026-04-06 09:38:44 -04:00
Isaac Connor
4df52e6a41 Don't use Fatal when there is an error preparing the sql. handle PostSQLConditions being an empty array 2026-04-03 17:48:35 -04:00
Isaac Connor
c06dab727e Include ExecuteInterval in warning 2026-04-03 13:11:20 -04:00
Isaac Connor
82261c047e Use now instead of 0 when no last_ran so that elapsesd=0 on initial run, and we don't log a warning. 2026-04-03 11:54:03 -04:00
Isaac Connor
fcd925900b fix: redirect stdin/stdout/stderr to /dev/null instead of closing them
Bug 376 (2006) closed all FDs starting from 0 when daemonizing. This
caused FD reuse problems: libx264 writing to a reused stderr FD led to
memory corruption (fixed in child spawn by 66f11435b, but not in the
parent's run()). It also meant children inherited closed FDs 0-2, so
any Perl die/warn output was silently lost, making daemon crashes
impossible to diagnose.

Redirect 0-2 to /dev/null (standard daemon practice) and close only
FDs 3+ for inherited sockets/DB connections. Children now inherit valid
FDs that won't crash or corrupt on write.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 11:45:32 -04:00
Isaac Connor
b141c9e18a Warn() -> Warning(). Fixes #4724 2026-04-03 11:00:01 -04:00
Isaac Connor
34949769b0 fix: add Warn() as exported alias for Warning() in Logger
Warn() is a natural shorthand that's easy to reach for. Its absence
caused a silent crash in zmfilter when Warn() resolved to Perl's
built-in warn(), which wrote to a closed stderr under zmdc and killed
the process.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 10:59:51 -04:00
Isaac Connor
05cc6fcca7 chore: remove obsolete ZM_FILTER_EXECUTE_INTERVAL config option
Each filter now has its own ExecuteInterval column, making the global
ZM_FILTER_EXECUTE_INTERVAL unused. The DB row will be cleaned up
automatically by zmupdate.pl on the next upgrade.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 16:31:33 -04:00
Isaac Connor
04d28258db fix: use minimum per-filter delay instead of last filter's delay in zmfilter
The sleep delay was overwritten by each filter in the loop, so only the
last filter's timing controlled the sleep. Now tracks the minimum delay
across all filters so no filter oversleeps its ExecuteInterval.

Also removes the unused ZM_FILTER_EXECUTE_INTERVAL reference since each
filter has its own ExecuteInterval, and fixes the overdue warning to
check the unclamped filter_delay and include the filter name.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 16:17:35 -04:00
Isaac Connor
6fabbb6d75 fix: use prepare instead of prepare_cached in Filter::Execute
The SQL changes every cycle due to zmDiskPercent/zmDiskBlocks/zmSystemLoad
string substitution with live values, so prepare_cached never actually
returns a cached handle. Instead it leaks one cache entry per distinct
substituted value over the process lifetime.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 15:48:15 -04:00
Isaac Connor
62ef4014ad fix: clear accumulated state in Filter::Sql() before rebuilding
PostSQLConditions, HasDiskPercent, HasDiskBlocks, and HasSystemLoad were
set during Sql() term processing but never cleared between rebuilds.
Each Execute() cycle pushed duplicate ExistsInFileSystem terms onto the
PostSQLConditions array, causing it to grow unboundedly over the process
lifetime.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 15:47:01 -04:00
Isaac Connor
2d34db3e4c fix: handle HTTP-to-HTTPS redirect in Reolink_HTTP login
Reolink cameras may return a 302 redirect from HTTP to HTTPS.
LWP::UserAgent does not follow redirects for POST requests, so the
login would fail. Detect the redirect, update protocol/host/port from
the Location header, disable SSL verification for self-signed camera
certs, and retry the POST to the HTTPS endpoint. Subsequent API calls
use the updated protocol.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 14:01:14 -04:00
Isaac Connor
7ec5edc2e2 feat: add ZM_WEB_SHOW_SERVER_STATS config to toggle navbar stats
Add a boolean web config option to control whether server statistics
(load, CPU, DB connections, storage, RAM) are rendered in the navbar.
When disabled, the HTML is not output at all, saving the polling overhead.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-13 16:19:27 -04:00
Isaac Connor
dca0f76468 feat: add ZM_WEB_SHOW_NAV_BUTTONS config to hide Back/Refresh buttons
Add a boolean web config option to control visibility of the Back and
Refresh navigation buttons shown at the top of most views. Uses a body
class and CSS rule so no individual view files need changes.

Also remove the ZM_WEB_BUTTON_STYLE INSERT from the migration SQL since
zmupdate.pl handles Config table inserts from ConfigData.pm.in.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 18:56:14 -04:00
Isaac Connor
217c415551 feat: add ZM_WEB_BUTTON_STYLE config for icons+text, icons, or text buttons
Add a web config option to control toolbar button display:
- icons+text (default): show both icon and label
- icons: show only the icon, hide text labels
- text: show only the label, hide icons on buttons that have labels

Body class (btn-icons-only / btn-text-only) is set in getBodyTopHTML() and
CSS rules in skin.css toggle visibility of .text spans and icon elements.
Add title tooltips to console.php buttons so they remain usable in icon-only
mode. Migration appended to zm_update-1.39.4.sql.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 18:04:14 -04:00
Isaac Connor
8044c80f9d feat: make Remember Me a tri-state option (None/Yes/No)
Change ZM_OPT_USE_REMEMBER_ME from a boolean to a tri-state string:
- None: checkbox hidden, sessions persist for ZM_COOKIE_LIFETIME (old disabled)
- Yes: checkbox shown and pre-checked by default
- No: checkbox shown and unchecked by default (old enabled behavior)

Update ConfigData.pm.in with new type definition, login.php to honor the
checked state, and session/action handlers to recognize the new values.
Migration in zm_update-1.39.4.sql maps old '1' to 'No' and '0' to 'None'.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 16:35:41 -04:00
Isaac Connor
fc3eed8fab Move user self edit to auth category 2026-03-11 15:55:24 -04:00
Isaac Connor
2d3e0df07d feat: add ZM_WEB_FILTER_SETTINGS_POSITION config to control filter panel placement
Add a new config option (requires ZM_WEB_NAVBAR_TYPE=left) that controls
whether monitor filter settings are embedded in the left sidebar extruder
(default) or displayed inline at the top of the page. When set to inline,
the sidebar CSS no longer hides the filter elements, insertControlModuleMenu()
skips moving them, and the toggle icons are omitted. A `filter-inline` body
class drives the CSS scoping via :not(.filter-inline) selectors in sidebar.css.

Affects Console, Watch, Montage, and Montage Review views.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 12:26:50 -04:00
Isaac Connor
419846c875 fix: sanitize monitor Device path to prevent command injection (GHSA-g66m-77fq-79v9)
The Device field from the Monitors table was interpolated directly into
shell commands (qx(), backticks, exec()) without sanitization, allowing
authenticated users with monitor-edit permissions to execute arbitrary
commands as www-data via the Device Path field.

Defense in depth:
- Input validation: reject Device values not matching /^\/dev\/[\w\/.\-]+$/
  at save time in both web UI and REST API
- Output sanitization: use escapeshellarg() in PHP and quote validated
  values in Perl at every shell execution point

Affected locations:
- scripts/ZoneMinder/lib/ZoneMinder/Monitor.pm (control, zmcControl)
- scripts/zmpkg.pl.in (system startup)
- web/includes/Monitor.php (zmcControl)
- web/includes/functions.php (zmcStatus, zmcCheck, validDevicePath)
- web/includes/actions/monitor.php (save action)
- web/api/app/Model/Monitor.php (daemonControl, validation rules)
- web/api/app/Controller/MonitorsController.php (daemonStatus)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 13:19:03 -04:00
Isaac Connor
e1bd60116a fix: add credential fallback chain in ONVIF control module
When ControlAddress does not contain authentication info, fall back
to Monitor->ONVIF_Username/ONVIF_Password, then Monitor->User/Pass.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 12:32:28 -05:00
Isaac Connor
3432e39c45 feat: add Remember Me checkbox to login page
Add ZM_OPT_USE_REMEMBER_ME config option (auth section, requires
ZM_OPT_USE_AUTH) that controls whether a Remember Me checkbox appears
on the login form. When enabled and unchecked, the session cookie
lifetime is set to 0 so the browser discards it on close, logging the
user out. When checked, the session persists for ZM_COOKIE_LIFETIME.
When the option is disabled, behavior is unchanged.

- ConfigData.pm.in: new ZM_OPT_USE_REMEMBER_ME boolean option
- login.php: checkbox between password field and reCAPTCHA/submit
- session.php: use lifetime=0 when remember me is off
- actions/login.php: set/clear ZM_REMEMBER_ME cookie on login, also
  update $_COOKIE so zm_session_start sees it in the same request
- auth.php: clear ZM_REMEMBER_ME cookie on logout
- en_gb.php: add RememberMe translation string

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 15:41:25 -05:00
Isaac Connor
3de87e224a Merge pull request #4617 from nabbi/remove-makemaker-perl-install
build: replace Perl ExtUtils::MakeMaker with native CMake install
2026-03-04 20:34:46 -05:00
Isaac Connor
48a556fac6 Merge branch 'master' into feature/custom-model-training 2026-03-04 16:45:54 -05:00
Isaac Connor
e72423c7f0 fix: add SSL certificate verification fallback to base Control class
When cameras use HTTPS with self-signed or invalid certificates, LWP's
default SSL verification causes connection failures. Add automatic
fallback to all three HTTP methods (get, put, post) in the base Control
class: if a request fails with an SSL/certificate error, disable
verification and retry. Once disabled, the relaxed setting persists for
the rest of the control session via the ssl_verify_disabled flag.

This follows the pattern already used in Dahua.pm and
TapoC520WS_ONVIF.pm but applies it universally so all control modules
benefit without needing individual SSL handling.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 12:44:19 -05:00
Pliable Pixels
1189011060 fix: use content directory for training data fallback path
Change default training data directory from @ZM_CACHEDIR@/training to
@ZM_CONTENTDIR@/training so it lives alongside events storage instead
of inside the web cache folder. Update PHP fallback to use
dirname(ZM_DIR_EVENTS) accordingly.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 12:24:44 -05:00
Pliable Pixels
62678b24f8 fix: address Copilot review — path traversal, temp leak, double undo, help text
- Fix path traversal via directory prefix matching: append
  DIRECTORY_SEPARATOR to base path before strpos containment check
  (2 locations in training.php)
- Fix temp file leak: rename tempnam() base file to .jpg instead of
  creating a second file, leaving the original orphaned
- Remove raw_output from detect response (information disclosure)
- Fix double _pushUndo on Delete key (keydown handler + deleteAnnotation)
- Fix help text: "alongside events storage" → "inside the ZoneMinder
  cache directory" to match actual ZM_DIR_CACHE default (2 locations)
- Add missing .frame-total span element in event.php

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 11:25:07 -05:00
Pliable Pixels
772587b353 fix: improve TRAINING_DATA_DIR help text for empty path behavior
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 10:35:53 -05:00
Pliable Pixels
a2ff391eb1 feat: add Detect button to run object detection on current frame
Add ZM_TRAINING_DETECT_SCRIPT config option for specifying the path
to an external detection script (e.g. zm_detect.py). When configured,
a Detect button appears in the annotation editor that:
- Runs the script with -f <image> -m <monitor_id>
- Parses the --SPLIT-- JSON output for labels, boxes, confidences
- Displays results as orange (pending) bounding boxes
- Users can accept (checkmark) or reject (x) each detection
- Only accepted annotations are saved to the training set

Also adds accept/reject UI in the sidebar object list, with orange
color for pending detections and normal colors for accepted ones.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-28 22:55:00 -05:00
Pliable Pixels
a8c9c180b6 feat: add custom model training annotation UI
Add canvas-based bounding box annotation editor to the event view for
correcting object detection results and building YOLO training datasets.

- Two new config options: ZM_OPT_TRAINING (toggle) and ZM_TRAINING_DATA_DIR
- AJAX backend (training.php) with load/save/delete/status actions
- Canvas annotation editor (training.js) with draw/resize/relabel/undo
- Frame navigation (alarm/snapshot/objdetect/numbered frames)
- Roboflow-compatible YOLO output (images/all/, labels/all/, data.yaml)
- Training data statistics with per-class image counts and guidance
- Full i18n support via en_gb.php SLANG/OLANG entries
- Label validation, YOLO coordinate clamping, audit logging
- DB migration for existing installs (zm_update-1.39.2.sql)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-28 21:35:55 -05:00
Isaac Connor
796329a98d fix: zmDbDo error logging now substitutes all SQL placeholders with values
The error path was missing the /g flag on the regex replacing ? with %s,
so only the first placeholder was replaced. It also dumped bind values as
a space-separated string instead of using sprintf to substitute them into
the query. Now matches the existing debug path pattern.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 23:05:12 -05:00
Isaac Connor
e6ace6fcf4 feat: add AUDIT logging level for tracking administrative changes
Add a new AUDIT logging level (-5) between PANIC (-4) and NOLOG (shifted
to -6) across C++, PHP, and Perl loggers. AUDIT entries use code 'AUD'
and syslog priority LOG_NOTICE. They record who changed what, from where,
for monitors, filters, users, config, roles, groups, zones, states,
servers, storage, events, snapshots, control caps, and login/logout.

AUDIT entries have their own retention period (ZM_LOG_AUDIT_DATABASE_LIMIT,
default 1 year) separate from regular log pruning. The log pruning in
zmstats.pl and zmaudit.pl now excludes AUDIT rows from regular pruning
and prunes them independently.

Critical safety: the C++ termination logic is changed from
'if (level <= FATAL)' to 'if (level == FATAL || level == PANIC)' to
prevent AUDIT-level log calls from killing the process.

Includes db migration zm_update-1.39.1.sql to shift any stored NOLOG
config values from -5 to -6.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 18:19:20 -05:00
Isaac Connor
a2bf848ce0 feat: expand ONVIF config_types with full device/media/imaging/PTZ queries
Add config type entries for Hostname, DNS, NTP, NetworkInterfaces,
Capabilities, Scopes, Services, VideoSources, VideoSourceConfigurations,
AudioSources, AudioSourceConfigurations, AudioEncoderConfigurations,
StreamUri, SnapshotUri, ImagingOptions, PTZConfigurations, PTZNodes,
PTZPresets, and PTZStatus.

- Support __PROFILE_TOKEN__ substitution in request bodies
- Add writable flag to config types and enforce it in set_config
- Add NTP set handler for SetNTP ONVIF command
- Organize config_types by ONVIF service (device, media, imaging, PTZ)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 18:16:55 -05:00
Isaac Connor
a5fd6e063e feat: add --protocol mode to zmcontrol.pl for direct module testing
Add --protocol, --address, and --device CLI options that bypass the
database and socket machinery to load a control module directly and
execute a single command. This enables standalone testing of control
modules (e.g. ONVIF.pm get_config) without a running ZoneMinder
instance. Also update POD with full documentation and examples.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 18:07:49 -05:00
Nic Boet
600f5a12e8 build: add selective man3 generation for audited Perl modules
Generate man3 pages only for the 7 ZoneMinder Perl modules whose POD
documentation was audited and confirmed accurate:

  Verified accurate API docs:
  - ZoneMinder::Memory (shared memory API, 18 methods)
  - ZoneMinder::Logger (logging API, all levels/methods/options)
  - ZoneMinder::Control::Dahua (full PTZ method reference)

  Useful prose documentation:
  - ZoneMinder::Control::Reolink_HTTP (auth flow, config, features)
  - ZoneMinder::Control::Amcrest_HTTP (security warning, timing docs)
  - ZoneMinder::Control::Trendnet (setup guide, capability settings)
  - ZoneMinder::Control::Instar720p (CGI parameter reference)

The remaining ~3,100 modules (66 boilerplate skeletons, 3,005 WSDL
stubs, 14 with no POD) are excluded.

Build-time filenames use "/" separators to avoid GNU make's "::"
double-colon rule syntax conflict. Installed filenames use the
standard Perl "::" convention (e.g., ZoneMinder::Memory.3pm.gz).

Gated behind BUILD_MAN (default ON, same as section 8 script pages).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-15 14:47:42 -06:00
Nic Boet
c23869b6ac build: replace Perl ExtUtils::MakeMaker with pure CMake install
MakeMaker was only used to copy .pm files — no XS compilation, no binary
linking, no dependency resolution. Its hardcoded "Makefile" output name
conflicts with cmake's generated Makefile for in-source builds, and using
FIRST_MAKEFILE=MakefilePerl causes thousands of "uninitialized value"
warnings because MM.pm stats the wrong file.

Replace with native CMake install(DIRECTORY ... FILES_MATCHING PATTERN
"*.pm") directives. Perl module install path is auto-detected at configure
time via `perl -MConfig` (vendorlib on Linux, sitelib on FreeBSD),
overridable with -DZM_PERL_INSTALL_PATH=<path>.

What's removed:
- ExtUtils::MakeMaker as build dependency
- Three perl+make subprocesses at build time (zmperlmodules,
  zmonvifmodules, zmonvifproxy build targets)
- ~6000 auto-generated man3 pages from WSDL stubs
- MakeMaker scaffolding: Makefile.PL, MANIFEST, META.yml, Changes,
  README, and t/ZoneMinder.t test stub

What's preserved:
- configure_file() for .pm.in templates (same behavior)
- ZM_PERL_SEARCH_PATH (independent mechanism, unchanged)
- Section 8 man pages for .pl scripts (Pod2Man.cmake, unaffected)
- DESTDIR support (CMake install() handles natively)
- Installed file paths (perl -MConfig returns same paths MakeMaker used)

Verified: 3102 .pm files installed, 0 .pm.in files, 0 .3pm man pages,
no @VERSION@ markers in generated files, DESTDIR and user override work.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-15 14:47:35 -06:00