2005-12-16 18:05:29 +08:00
#!/usr/bin/perl -wT
#
# ==========================================================================
#
# ZoneMinder Audit Script, $Date$, $Revision$
2008-07-25 17:48:16 +08:00
# Copyright (C) 2001-2008 Philip Coombes
2005-12-16 18:05:29 +08:00
#
# 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
2016-12-26 23:23:16 +08:00
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
2005-12-16 18:05:29 +08:00
#
# ==========================================================================
2015-04-10 17:27:45 +08:00
2005-12-16 18:05:29 +08:00
use strict ;
use bytes ;
# ==========================================================================
#
# These are the elements you can edit to suit your installation
#
# ==========================================================================
2016-09-08 23:06:02 +08:00
use constant RECOVER_TAG = > '(r)' ; # Tag to append to event name when recovered
2017-05-31 21:33:51 +08:00
use constant RECOVER_TEXT = > 'Recovered.' ; # Text to append to event notes when recovered
2005-12-16 18:05:29 +08:00
# ==========================================================================
#
# You shouldn't need to change anything from here downwards
#
# ==========================================================================
2009-06-08 17:11:56 +08:00
@ EXTRA_PERL_LIB @
2005-12-16 18:05:29 +08:00
use ZoneMinder ;
use DBI ;
use POSIX ;
2007-08-30 02:11:09 +08:00
use File::Find ;
2005-12-16 18:05:29 +08:00
use Time::HiRes qw/gettimeofday/ ;
use Getopt::Long ;
2015-04-10 17:27:45 +08:00
use autouse 'Pod::Usage' = > qw( pod2usage ) ;
2005-12-16 18:05:29 +08:00
2015-04-10 18:20:05 +08:00
use constant EVENT_PATH = > ( $ Config { ZM_DIR_EVENTS } =~ m | / | )
? $ Config { ZM_DIR_EVENTS }
: ( $ Config { ZM_PATH_WEB } . '/' . $ Config { ZM_DIR_EVENTS } )
;
2017-11-22 14:18:07 +08:00
use constant ZM_AUDIT_PID = > '@ZM_RUNDIR@/zmaudit.pid' ;
2005-12-16 18:05:29 +08:00
2016-03-12 05:28:16 +08:00
$ ENV { PATH } = '/bin:/usr/bin:/usr/local/bin' ;
2005-12-16 18:05:29 +08:00
$ ENV { SHELL } = '/bin/sh' if exists $ ENV { SHELL } ;
delete @ ENV { qw( IFS CDPATH ENV BASH_ENV ) } ;
my $ report = 0 ;
2006-01-12 07:56:46 +08:00
my $ interactive = 0 ;
2006-01-12 23:38:24 +08:00
my $ continuous = 0 ;
2018-04-19 23:18:23 +08:00
my $ level = 1 ;
2017-03-24 07:54:16 +08:00
my $ monitor_id = 0 ;
2015-01-07 10:08:41 +08:00
my $ version ;
2018-01-20 00:40:07 +08:00
my $ force = 0 ;
2018-09-10 06:32:00 +08:00
my $ server_id = undef ;
2018-01-22 06:14:32 +08:00
my $ storage_id = undef ;
2005-12-16 18:05:29 +08:00
2011-06-21 17:19:10 +08:00
logInit ( ) ;
2005-12-16 18:05:29 +08:00
2015-04-10 17:27:45 +08:00
GetOptions (
2018-01-23 00:10:31 +08:00
continuous = > \ $ continuous ,
force = > \ $ force ,
interactive = > \ $ interactive ,
2018-04-19 23:18:23 +08:00
level = > \ $ level ,
2018-01-23 00:10:31 +08:00
'monitor_id=i' = > \ $ monitor_id ,
report = > \ $ report ,
2018-09-10 06:32:00 +08:00
'server_id=i' = > \ $ server_id ,
2018-01-23 00:10:31 +08:00
'storage_id=i' = > \ $ storage_id ,
version = > \ $ version
2017-05-17 23:52:25 +08:00
) or pod2usage ( - exitstatus = > - 1 ) ;
2005-12-16 18:05:29 +08:00
2015-01-07 10:08:41 +08:00
if ( $ version ) {
2017-05-17 23:52:25 +08:00
print ( ZoneMinder::Base:: ZM_VERSION . "\n" ) ;
exit ( 0 ) ;
2015-01-07 10:08:41 +08:00
}
2016-09-08 23:06:02 +08:00
if ( ( $ report + $ interactive + $ continuous ) > 1 ) {
2017-05-17 23:52:25 +08:00
print ( STDERR "Error, only one option may be specified\n" ) ;
pod2usage ( - exitstatus = > - 1 ) ;
2005-12-16 18:05:29 +08:00
}
2017-06-03 00:35:01 +08:00
if ( ! exists $ Config { ZM_AUDIT_MIN_AGE } ) {
Fatal ( 'ZM_AUDIT_MIN_AGE is not set in config.' ) ;
}
2017-11-21 00:55:42 +08:00
if ( - e ZM_AUDIT_PID ) {
2018-01-20 00:40:07 +08:00
local $/ = undef ;
open FILE , ZM_AUDIT_PID or die "Couldn't open file: $!" ;
binmode FILE ;
my $ pid = <FILE> ;
close FILE ;
if ( $ force ) {
Error ( "zmaudit.pl appears to already be running at pid $pid. Continuing." ) ;
2017-11-21 00:55:42 +08:00
} else {
2018-01-20 00:40:07 +08:00
Fatal ( "zmaudit.pl appears to already be running at pid $pid. If not, please delete " .
ZM_AUDIT_PID . " or use the --force command line option." ) ;
2017-11-21 00:55:42 +08:00
}
2018-01-20 00:40:07 +08:00
} # end if ZM_AUDIT_PID exists
if ( open ( my $ PID , '>' , ZM_AUDIT_PID ) ) {
print ( $ PID $$ ) ;
close ( $ PID ) ;
} else {
Error ( "Can't open pid file at " . ZM_PID ) ;
}
sub HupHandler {
Info ( "Received HUP, reloading" ) ;
& ZoneMinder::Logger:: logHupHandler ( ) ;
}
sub TermHandler {
Info ( "Received TERM, exiting" ) ;
2018-01-23 04:26:18 +08:00
Term ( ) ;
}
sub Term {
2018-01-20 00:40:07 +08:00
unlink ZM_AUDIT_PID ;
exit ( 0 ) ;
2017-11-21 00:55:42 +08:00
}
2018-01-20 00:40:07 +08:00
$ SIG { HUP } = \ & HupHandler ;
$ SIG { TERM } = \ & TermHandler ;
2018-01-20 01:14:36 +08:00
$ SIG { INT } = \ & TermHandler ;
2017-11-21 00:55:42 +08:00
2007-09-07 23:39:44 +08:00
my $ dbh = zmDbConnect ( ) ;
2005-12-16 18:05:29 +08:00
2018-01-23 04:26:18 +08:00
$| = 1 ;
2016-01-05 04:13:01 +08:00
require ZoneMinder::Monitor ;
2015-12-23 00:50:29 +08:00
require ZoneMinder::Storage ;
2015-12-23 01:35:25 +08:00
require ZoneMinder::Event ;
2006-01-12 07:56:46 +08:00
2006-01-18 05:29:44 +08:00
my $ max_image_age = 6 / 24 ; # 6 hours
2007-08-30 02:11:09 +08:00
my $ max_swap_age = 24 / 24 ; # 24 hours
2018-12-29 22:52:58 +08:00
# images now live under the event path
my $ image_path = EVENT_PATH ;
2011-04-19 21:01:45 +08:00
my $ loop = 1 ;
my $ cleaned = 0 ;
2014-10-15 21:43:00 +08:00
MAIN: while ( $ loop ) {
2015-05-14 00:36:35 +08:00
2017-04-20 00:46:44 +08:00
if ( $ continuous ) {
# if we are running continuously, then just skip to the next
# interval, otherwise we are a one off run, so wait a second and
# retry until someone kills us.
sleep ( $ Config { ZM_AUDIT_CHECK_INTERVAL } ) ;
} else {
sleep 1 ;
} # end if
# After a long sleep, we may need to reconnect to the db
2016-06-03 23:57:38 +08:00
while ( ! ( $ dbh and $ dbh - > ping ( ) ) ) {
$ dbh = zmDbConnect ( ) ;
2017-07-27 21:56:07 +08:00
if ( ! $ dbh ) {
2018-02-12 03:18:10 +08:00
Error ( 'Unable to connect to database' ) ;
2017-07-27 21:56:07 +08:00
if ( $ continuous ) {
# if we are running continuously, then just skip to the next
# interval, otherwise we are a one off run, so wait a second and
# retry until someone kills us.
sleep ( $ Config { ZM_AUDIT_CHECK_INTERVAL } ) ;
} else {
2018-02-12 03:18:10 +08:00
Term ( ) ;
2017-07-27 21:56:07 +08:00
} # end if
2016-06-03 23:57:38 +08:00
} # end if
} # end while can't connect to the db
2018-01-23 00:10:31 +08:00
my @ Storage_Areas ;
2018-11-27 04:02:34 +08:00
my @ all_Storage_Areas = ZoneMinder::Storage - > find ( ) ;
2018-01-23 00:10:31 +08:00
if ( defined $ storage_id ) {
2018-11-27 04:02:34 +08:00
@ Storage_Areas = map { $$ _ { Id } == $ storage_id ? $ _ : ( ) } @ all_Storage_Areas ;
2018-01-23 00:10:31 +08:00
if ( ! @ Storage_Areas ) {
2018-02-12 03:18:10 +08:00
Error ( "No Storage Area found with Id $storage_id" ) ;
Term ( ) ;
2018-01-23 00:10:31 +08:00
}
Info ( "Auditing Storage Area $Storage_Areas[0]{Id} $Storage_Areas[0]{Name} at $Storage_Areas[0]{Path}" ) ;
2018-09-10 06:32:00 +08:00
} elsif ( $ server_id ) {
@ Storage_Areas = ZoneMinder::Storage - > find ( ServerId = > $ server_id ) ;
2018-01-27 04:54:54 +08:00
if ( ! @ Storage_Areas ) {
2018-09-10 06:32:00 +08:00
Error ( "No Storage Area found with ServerId =" . $ server_id ) ;
2018-02-12 03:18:10 +08:00
Term ( ) ;
2018-01-27 04:54:54 +08:00
}
2018-09-10 06:32:00 +08:00
foreach my $ Storage ( @ Storage_Areas ) {
Info ( 'Auditing ' . $ Storage - > Name ( ) . ' at ' . $ Storage - > Path ( ) . ' on ' . $ Storage - > Server ( ) - > Name ( ) ) ;
}
2018-01-23 00:10:31 +08:00
} else {
@ Storage_Areas = ZoneMinder::Storage - > find ( ) ;
Info ( "Auditing All Storage Areas" ) ;
}
2016-06-03 23:57:38 +08:00
my % Monitors ;
my $ db_monitors ;
2017-06-05 09:42:56 +08:00
my $ monitorSelectSql = $ monitor_id ? 'SELECT * FROM Monitors WHERE Id=?' : 'SELECT * FROM Monitors ORDER BY Id' ;
2016-06-03 23:57:38 +08:00
my $ monitorSelectSth = $ dbh - > prepare_cached ( $ monitorSelectSql )
or Fatal ( "Can't prepare '$monitorSelectSql': " . $ dbh - > errstr ( ) ) ;
2017-06-05 09:42:56 +08:00
my $ eventSelectSql = ' SELECT Id , ( unix_timestamp ( ) - unix_timestamp ( StartTime ) ) AS Age
2018-02-03 01:08:54 +08:00
FROM Events WHERE MonitorId = ? '.(@Storage_Areas ? ' AND StorageId IN ( '.join(' , ',map { ' ? '} @Storage_Areas).' ) ' : ' ' ). ' ORDER BY Id ' ;
2016-06-03 23:57:38 +08:00
my $ eventSelectSth = $ dbh - > prepare_cached ( $ eventSelectSql )
or Fatal ( "Can't prepare '$eventSelectSql': " . $ dbh - > errstr ( ) ) ;
$ cleaned = 0 ;
2017-03-24 07:54:16 +08:00
my $ res = $ monitorSelectSth - > execute ( $ monitor_id ? $ monitor_id : ( ) )
2017-04-20 00:46:44 +08:00
or Fatal ( "Can't execute: $monitorSelectSql " . $ monitorSelectSth - > errstr ( ) ) ;
2016-06-03 23:57:38 +08:00
while ( my $ monitor = $ monitorSelectSth - > fetchrow_hashref ( ) ) {
$ Monitors { $$ monitor { Id } } = $ monitor ;
my $ db_events = $ db_monitors - > { $ monitor - > { Id } } = { } ;
2018-02-03 01:08:54 +08:00
my $ res = $ eventSelectSth - > execute ( $ monitor - > { Id } , map { $$ _ { Id } } @ Storage_Areas )
2016-06-03 23:57:38 +08:00
or Fatal ( "Can't execute: " . $ eventSelectSth - > errstr ( ) ) ;
while ( my $ event = $ eventSelectSth - > fetchrow_hashref ( ) ) {
$ db_events - > { $ event - > { Id } } = $ event - > { Age } ;
2011-06-21 17:19:10 +08:00
}
2018-01-23 00:10:31 +08:00
Debug ( 'Got ' . int ( keys ( %$ db_events ) ) . " events for monitor $monitor->{Id}" ) ;
2018-01-26 23:32:37 +08:00
} # end while monitors
2011-06-21 17:19:10 +08:00
2016-06-03 23:57:38 +08:00
my $ fs_monitors ;
2018-01-22 06:14:32 +08:00
foreach my $ Storage ( @ Storage_Areas ) {
2016-09-08 23:06:02 +08:00
Debug ( 'Checking events in ' . $ Storage - > Path ( ) ) ;
if ( ! chdir ( $ Storage - > Path ( ) ) ) {
Error ( 'Unable to change dir to ' . $ Storage - > Path ( ) ) ;
next ;
} # end if
2016-06-03 23:57:38 +08:00
# Please note that this glob will take all files beginning with a digit.
2016-09-08 23:06:02 +08:00
foreach my $ monitor ( glob ( '[0-9]*' ) ) {
2018-01-26 23:32:37 +08:00
if ( $ monitor =~ /\D/ ) {
Debug ( "Weird non digit characters in $monitor" ) ;
next ;
}
2018-01-27 04:53:47 +08:00
if ( $ monitor_id and ( $ monitor_id != $ monitor ) ) {
Debug ( "Skipping monitor $monitor because we are only interested in monitor $monitor_id" ) ;
next ;
}
2016-06-03 23:57:38 +08:00
Debug ( "Found filesystem monitor '$monitor'" ) ;
2016-07-12 23:27:25 +08:00
$ fs_monitors - > { $ monitor } = { } if ! $ fs_monitors - > { $ monitor } ;
my $ fs_events = $ fs_monitors - > { $ monitor } ;
2016-06-03 23:57:38 +08:00
# De-taint
( my $ monitor_dir ) = ( $ monitor =~ /^(.*)$/ ) ;
2018-01-26 23:32:37 +08:00
{
my @ day_dirs = glob ( "$monitor_dir/[0-9][0-9]/[0-9][0-9]/[0-9][0-9]" ) ;
2018-04-04 05:41:32 +08:00
Debug ( qq`Checking for Deep Events under $$Storage{Path} using glob("$monitor_dir/[0-9][0-9]/[0-9][0-9]/[0-9][0-9]") returned ` . scalar @ day_dirs . ' events' ) ;
2018-01-26 23:32:37 +08:00
foreach my $ day_dir ( @ day_dirs ) {
2016-07-13 02:16:20 +08:00
Debug ( "Checking day dir $day_dir" ) ;
2016-06-03 23:57:38 +08:00
( $ day_dir ) = ( $ day_dir =~ /^(.*)$/ ) ; # De-taint
2018-06-26 01:41:19 +08:00
if ( ! chdir ( $ day_dir ) ) {
Error ( "Can't chdir to '$$Storage{Path}/$day_dir': $!" ) ;
2016-06-08 09:37:33 +08:00
next ;
}
2018-06-26 01:41:19 +08:00
if ( ! opendir ( DIR , '.' ) ) {
Error ( "Can't open directory '$$Storage{Path}/$day_dir': $!" ) ;
2016-06-08 09:37:33 +08:00
next ;
}
2018-09-21 00:31:19 +08:00
my % event_ids_by_path ;
2016-06-03 23:57:38 +08:00
my @ event_links = sort { $ b <=> $ a } grep { - l $ _ } readdir ( DIR ) ;
2018-06-26 01:41:19 +08:00
Debug ( "Have " . @ event_links . ' event links' ) ;
closedir ( DIR ) ;
2018-09-21 22:31:54 +08:00
2016-06-03 23:57:38 +08:00
my $ count = 0 ;
foreach my $ event_link ( @ event_links ) {
2018-09-21 00:31:19 +08:00
# Event links start with a period and consist of the digits of the event id. Anything else is not an event link
2018-09-21 22:31:54 +08:00
my ( $ event_id ) = $ event_link =~ /^\.(\d+)$/ ;
2018-09-21 00:31:19 +08:00
if ( ! $ event_id ) {
2016-06-03 23:57:38 +08:00
Warning ( "Non-event link found $event_link in $day_dir, skipping" ) ;
next ;
2007-09-05 00:08:55 +08:00
}
2018-06-26 01:41:19 +08:00
Debug ( "Checking link $event_link" ) ;
2016-08-05 00:22:33 +08:00
#Event path is hour/minute/sec
2018-06-26 01:41:19 +08:00
my $ event_path = readlink ( $ event_link ) ;
2016-07-12 23:27:25 +08:00
2018-01-30 10:16:41 +08:00
if ( ! ( $ event_path and - e $ event_path ) ) {
2018-09-21 22:31:54 +08:00
aud_print ( "Event link $day_dir/$event_link does not point to valid target at $event_path" ) ;
2016-07-12 23:27:25 +08:00
if ( confirm ( ) ) {
( $ event_link ) = ( $ event_link =~ /^(.*)$/ ) ; # De-taint
2018-06-26 01:41:19 +08:00
unlink ( $ event_link ) ;
2016-07-12 23:27:25 +08:00
$ cleaned = 1 ;
}
2016-06-03 23:57:38 +08:00
} else {
2018-09-21 00:31:19 +08:00
$ event_ids_by_path { $ event_path } = $ event_id ;
2018-06-26 01:41:19 +08:00
Debug ( "Checking link $event_link points to $event_path " ) ;
2018-09-21 00:31:19 +08:00
my $ Event = $ fs_events - > { $ event_id } = new ZoneMinder:: Event ( ) ;
$$ Event { Id } = $ event_id ;
$$ Event { Path } = join ( '/' , $ Storage - > Path ( ) , $ day_dir , $ event_path ) ;
$$ Event { RelativePath } = join ( '/' , $ day_dir , $ event_path ) ;
2018-01-23 00:10:31 +08:00
$$ Event { Scheme } = 'Deep' ;
2016-07-12 23:27:25 +08:00
$ Event - > MonitorId ( $ monitor_dir ) ;
$ Event - > StorageId ( $ Storage - > Id ( ) ) ;
2017-10-27 23:03:09 +08:00
$ Event - > DiskSpace ( undef ) ;
2016-07-12 23:27:25 +08:00
} # event path exists
2016-06-03 23:57:38 +08:00
} # end foreach event_link
2018-06-26 01:41:19 +08:00
# Now check for events that have lost their link
my @ time_dirs = glob ( '[0-9][0-9]/[0-9][0-9]/[0-9][0-9]' ) ;
foreach my $ event_dir ( @ time_dirs ) {
Debug ( "Checking time dir $event_dir" ) ;
( $ event_dir ) = ( $ event_dir =~ /^(.*)$/ ) ; # De-taint
my $ event_id = undef ;
2018-09-21 00:31:19 +08:00
if ( ! opendir ( DIR , $ event_dir ) ) {
Error ( "Can't open directory '$$Storage{Path}/$day_dir': $!" ) ;
next ;
}
my @ contents = readdir ( DIR ) ;
Debug ( "Have " . @ contents . " files in $day_dir/$event_dir" ) ;
closedir ( DIR ) ;
my @ mp4_files = grep ( /^\d+\-video.mp4$/ , @ contents ) ;
2018-06-26 01:41:19 +08:00
foreach my $ mp4_file ( @ mp4_files ) {
my ( $ id ) = $ mp4_file =~ /^([0-9]+)\-video\.mp4$/ ;
if ( $ id ) {
$ event_id = $ id ;
2018-09-21 00:31:19 +08:00
Debug ( "Got event id from mp4 file $mp4_file => $event_id" ) ;
2018-06-26 01:41:19 +08:00
last ;
}
}
2018-09-21 00:31:19 +08:00
if ( ! $ event_id ) {
# Look for .id file
my @ hidden_files = grep ( /^\.\d+$/ , @ contents ) ;
Debug ( "Have " . @ hidden_files . ' hidden files' ) ;
if ( @ hidden_files ) {
( $ event_id ) = $ hidden_files [ 0 ] =~ /^.(\d+)$/ ;
}
}
2018-09-21 22:31:54 +08:00
if ( $ event_id and ! $ fs_events - > { $ event_id } ) {
2018-06-26 01:41:19 +08:00
my $ Event = $ fs_events - > { $ event_id } = new ZoneMinder:: Event ( ) ;
$$ Event { Id } = $ event_id ;
$$ Event { Path } = join ( '/' , $ Storage - > Path ( ) , $ day_dir , $ event_dir ) ;
$$ Event { RelativePath } = join ( '/' , $ day_dir , $ event_dir ) ;
$$ Event { Scheme } = 'Deep' ;
$ Event - > MonitorId ( $ monitor_dir ) ;
$ Event - > StorageId ( $ Storage - > Id ( ) ) ;
$ Event - > DiskSpace ( undef ) ;
2018-09-21 00:31:19 +08:00
if ( ! $ event_ids_by_path { $ event_dir } ) {
Warning ( "No event link found at " . $ Event - > LinkPath ( ) . " for " . $ Event - > to_string ( ) ) ;
}
2018-06-26 01:41:19 +08:00
} else {
2018-09-21 22:31:54 +08:00
if ( $ event_ids_by_path { $ event_dir } ) {
Debug ( "Have an event link, leaving dir alone." ) ;
next ;
}
my ( undef , $ year , $ month , $ day ) = split ( '/' , $ day_dir ) ;
$ year += 2000 ;
my ( $ hour , $ minute , $ second ) = split ( '/' , $ event_dir ) ;
my $ StartTime = sprintf ( '%.4d-%.2d-%.2d %.2d:%.2d:%.2d' , $ year , $ month , $ day , $ hour , $ minute , $ second ) ;
my $ Event = ZoneMinder::Event - > find_one (
MonitorId = > $ monitor_dir ,
StartTime = > $ StartTime ,
) ;
if ( $ Event ) {
Debug ( "Found event matching starttime on monitor $monitor_dir at $StartTime: " . $ Event - > to_string ( ) ) ;
next ;
}
aud_print ( "Deleting event directories with no event id information at $day_dir/$event_dir" ) ;
if ( confirm ( ) ) {
my $ command = "rm -rf $event_dir" ;
executeShellCommand ( $ command ) ;
$ cleaned = 1 ;
2018-06-26 01:41:19 +08:00
}
} # end if able to find id
} # end foreach event_dir without link
2016-06-03 23:57:38 +08:00
chdir ( $ Storage - > Path ( ) ) ;
} # end foreach day dir
2018-01-26 23:32:37 +08:00
}
2018-01-23 00:10:31 +08:00
2018-04-04 05:41:32 +08:00
Debug ( "Checking for Medium Scheme Events under $$Storage{Path}/$monitor_dir" ) ;
2018-01-26 23:32:37 +08:00
{
my @ event_dirs = glob ( "$monitor_dir/[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]/*" ) ;
2018-11-14 03:13:41 +08:00
Debug ( qq`glob("$monitor_dir/[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]/*") returned ` . scalar @ event_dirs . ' entries.' ) ;
2018-01-26 23:32:37 +08:00
foreach my $ event_dir ( @ event_dirs ) {
if ( ! - d $ event_dir ) {
Debug ( "$event_dir is not a dir. Skipping" ) ;
next ;
}
2018-01-11 04:36:36 +08:00
my ( $ date , $ event_id ) = $ event_dir =~ /^$monitor_dir\/(\d{4}\-\d{2}\-\d{2})\/(\d+)$/ ;
2017-12-30 04:29:57 +08:00
if ( ! $ event_id ) {
Debug ( "Unable to parse date/event_id from $event_dir" ) ;
next ;
}
my $ Event = $ fs_events - > { $ event_id } = new ZoneMinder:: Event ( ) ;
$$ Event { Id } = $ event_id ;
2018-01-20 00:40:07 +08:00
$$ Event { Path } = join ( '/' , $ Storage - > Path ( ) , $ event_dir ) ;
2018-01-26 23:32:37 +08:00
Debug ( "Have event $$Event{Id} at $$Event{Path}" ) ;
2018-01-23 00:10:31 +08:00
$$ Event { Scheme } = 'Medium' ;
2018-01-20 00:40:07 +08:00
$$ Event { RelativePath } = $ event_dir ;
2017-12-19 01:52:26 +08:00
$ Event - > MonitorId ( $ monitor_dir ) ;
$ Event - > StorageId ( $ Storage - > Id ( ) ) ;
2018-11-27 04:02:34 +08:00
$ Event - > StartTime ( POSIX:: strftime ( '%Y-%m-%d %H:%M:%S' , gmtime ( time_of_youngest_file ( $ Event - > Path ( ) ) ) ) ) ;
2017-12-19 01:52:26 +08:00
} # end foreach event
2018-01-26 23:32:37 +08:00
}
2018-01-23 00:10:31 +08:00
if ( ! $$ Storage { Scheme } ) {
2018-09-20 00:03:51 +08:00
Error ( "Storage Scheme not set on $$Storage{Name}" ) ;
2018-11-24 01:16:11 +08:00
if ( ! chdir ( $ monitor_dir ) ) {
Error ( "Can't chdir directory '$$Storage{Path}/$monitor_dir': $!" ) ;
2016-06-08 09:43:47 +08:00
next ;
}
2018-11-24 01:16:11 +08:00
if ( ! opendir ( DIR , '.' ) ) {
Error ( "Can't open directory '$$Storage{Path}/$monitor_dir': $!" ) ;
2016-06-08 09:43:47 +08:00
next ;
}
2018-11-24 01:16:11 +08:00
my @ temp_events = sort { $ b <=> $ a } grep { - d $ _ && $ _ =~ /^\d+$/ } readdir ( DIR ) ;
closedir ( DIR ) ;
2016-06-03 23:57:38 +08:00
my $ count = 0 ;
foreach my $ event ( @ temp_events ) {
2016-07-12 23:27:25 +08:00
my $ Event = $ fs_events - > { $ event } = new ZoneMinder:: Event ( ) ;
$$ Event { Id } = $ event ;
#$$Event{Path} = $event_path;
2018-11-24 01:16:11 +08:00
$$ Event { Scheme } = 'Shallow' ;
2016-07-12 23:27:25 +08:00
$ Event - > MonitorId ( $ monitor_dir ) ;
$ Event - > StorageId ( $ Storage - > Id ( ) ) ;
2016-06-03 23:57:38 +08:00
} # end foreach event
chdir ( $ Storage - > Path ( ) ) ;
} # if USE_DEEP_STORAGE
2018-11-14 03:13:41 +08:00
Debug ( 'Got ' . int ( keys ( %$ fs_events ) ) . " filesystem events for monitor $monitor_dir" ) ;
2016-06-04 00:01:31 +08:00
2018-09-20 08:00:26 +08:00
delete_empty_subdirs ( $$ Storage { Path } . '/' . $ monitor_dir ) ;
2016-06-03 23:57:38 +08:00
} # end foreach monitor
2018-01-26 23:32:37 +08:00
if ( $ cleaned ) {
Debug ( "First stage cleaning done. Restarting." ) ;
redo MAIN ;
}
2005-12-16 18:05:29 +08:00
2011-04-19 21:01:45 +08:00
$ cleaned = 0 ;
2016-06-06 22:30:52 +08:00
while ( my ( $ monitor_id , $ fs_events ) = each ( %$ fs_monitors ) ) {
if ( my $ db_events = $ db_monitors - > { $ monitor_id } ) {
2018-01-26 23:32:37 +08:00
if ( ! $ fs_events ) {
Debug ( "No fs_events for database monitor $monitor_id" ) ;
next ;
}
my @ event_ids = keys %$ fs_events ;
2018-11-14 03:13:41 +08:00
Debug ( 'Have ' . scalar @ event_ids . " events for monitor $monitor_id" ) ;
2016-06-03 23:57:38 +08:00
2016-07-12 23:27:25 +08:00
foreach my $ fs_event_id ( sort { $ a <=> $ b } keys %$ fs_events ) {
my $ Event = $ fs_events - > { $ fs_event_id } ;
2016-08-05 00:22:33 +08:00
if ( ! defined ( $ db_events - > { $ fs_event_id } ) ) {
2018-11-27 04:16:10 +08:00
# Long running zmaudits can find events that were created after we loaded all db events.
# So do a secondary lookup
if ( ZoneMinder::Event - > find_one ( Id = > $ fs_event_id ) ) {
Debug ( "$$Event{Id} found in secondary lookup." ) ;
next ;
}
2016-08-05 00:22:33 +08:00
my $ age = $ Event - > age ( ) ;
if ( $ age > $ Config { ZM_AUDIT_MIN_AGE } ) {
2018-01-20 00:40:07 +08:00
aud_print ( "Filesystem event $fs_event_id at $$Event{Path} does not exist in database and is $age seconds old" ) ;
2016-08-05 00:22:33 +08:00
if ( confirm ( ) ) {
$ Event - > delete_files ( ) ;
$ cleaned = 1 ;
delete $ fs_events - > { $ fs_event_id } ;
} # end if confirm
} # end if old enough
} # end if ! in db events
2016-06-06 22:30:52 +08:00
} # end foreach fs event
2016-06-03 23:57:38 +08:00
} else {
2016-06-06 22:30:52 +08:00
aud_print ( "Filesystem monitor '$monitor_id' in $$Storage{Path} does not exist in database" ) ;
2018-01-27 05:05:43 +08:00
2016-06-03 23:57:38 +08:00
if ( confirm ( ) ) {
2016-06-06 22:30:52 +08:00
my $ command = "rm -rf $monitor_id" ;
2016-06-03 23:57:38 +08:00
executeShellCommand ( $ command ) ;
$ cleaned = 1 ;
2011-06-21 17:19:10 +08:00
}
2016-06-03 23:57:38 +08:00
}
} # end foreach monitor/filesystem events
2005-12-16 18:05:29 +08:00
2011-06-21 17:19:10 +08:00
my $ monitor_links ;
2016-09-08 23:06:02 +08:00
foreach my $ link ( glob ( '*' ) ) {
2016-06-03 23:57:38 +08:00
next if ( ! - l $ link ) ;
next if ( - e $ link ) ;
aud_print ( "Filesystem monitor link '$link' does not point to valid monitor directory" ) ;
if ( confirm ( ) ) {
( $ link ) = ( $ link =~ /^(.*)$/ ) ; # De-taint
2016-12-21 00:29:31 +08:00
my $ command = qq`rm "$link"` ;
2016-06-03 23:57:38 +08:00
executeShellCommand ( $ command ) ;
$ cleaned = 1 ;
}
2011-06-21 17:19:10 +08:00
}
2016-06-03 23:57:38 +08:00
} # end foreach Storage Area
2018-11-14 03:13:41 +08:00
if ( $ cleaned ) {
Debug ( "Events were deleted, starting again." ) ;
redo MAIN ;
}
2016-06-03 23:57:38 +08:00
$ cleaned = 0 ;
2018-01-20 01:14:36 +08:00
my $ deleteMonitorSql = 'DELETE LOW_PRIORITY FROM Monitors WHERE Id = ?' ;
2016-06-03 23:57:38 +08:00
my $ deleteMonitorSth = $ dbh - > prepare_cached ( $ deleteMonitorSql )
or Fatal ( "Can't prepare '$deleteMonitorSql': " . $ dbh - > errstr ( ) ) ;
2018-01-20 01:14:36 +08:00
my $ deleteEventSql = 'DELETE LOW_PRIORITY FROM Events WHERE Id = ?' ;
2016-06-03 23:57:38 +08:00
my $ deleteEventSth = $ dbh - > prepare_cached ( $ deleteEventSql )
or Fatal ( "Can't prepare '$deleteEventSql': " . $ dbh - > errstr ( ) ) ;
2018-01-20 01:14:36 +08:00
my $ deleteFramesSql = 'DELETE LOW_PRIORITY FROM Frames WHERE EventId = ?' ;
2016-06-03 23:57:38 +08:00
my $ deleteFramesSth = $ dbh - > prepare_cached ( $ deleteFramesSql )
or Fatal ( "Can't prepare '$deleteFramesSql': " . $ dbh - > errstr ( ) ) ;
2018-01-20 01:14:36 +08:00
my $ deleteStatsSql = 'DELETE LOW_PRIORITY FROM Stats WHERE EventId = ?' ;
2016-06-03 23:57:38 +08:00
my $ deleteStatsSth = $ dbh - > prepare_cached ( $ deleteStatsSql )
or Fatal ( "Can't prepare '$deleteStatsSql': " . $ dbh - > errstr ( ) ) ;
2017-03-29 21:59:58 +08:00
# Foreach database monitor and it's list of events.
2016-06-03 23:57:38 +08:00
while ( my ( $ db_monitor , $ db_events ) = each ( %$ db_monitors ) ) {
2018-11-09 23:39:57 +08:00
Debug ( "Checking db events for monitor $db_monitor" ) ;
2018-11-11 23:17:32 +08:00
if ( ! $ db_events ) {
Debug ( "Skipping db events for $db_monitor because there are none" ) ;
next ;
}
2017-03-29 21:59:58 +08:00
# If we found the monitor in the file system
2018-11-09 23:39:57 +08:00
my $ fs_events = $ fs_monitors - > { $ db_monitor } ;
2018-11-27 04:02:34 +08:00
EVENT: while ( my ( $ db_event , $ age ) = each ( %$ db_events ) ) {
2018-11-09 23:39:57 +08:00
if ( ! ( $ fs_events and defined ( $ fs_events - > { $ db_event } ) ) ) {
Debug ( "Don't have an fs event for $db_event" ) ;
my $ Event = ZoneMinder::Event - > find_one ( Id = > $ db_event ) ;
if ( ! $ Event ) {
Debug ( "Event $db_event is no longer in db. Filter probably deleted it while we were auditing." ) ;
next ;
}
Debug ( "Event $db_event is not in fs. Should have been at " . $ Event - > Path ( ) ) ;
2018-11-23 23:10:50 +08:00
# Check for existence in other Storage Areas
2018-11-27 04:02:34 +08:00
foreach my $ Storage ( @ all_Storage_Areas ) {
next if $$ Storage { Id } == $$ Event { StorageId } ;
2018-11-23 23:10:50 +08:00
my $ path = $ Storage - > Path ( ) . '/' . $ Event - > RelativePath ( ) ;
if ( - e $ path ) {
Info ( "Event $$Event{Id} found at $path instead of $$Event{Path}" ) ;
2018-11-24 04:13:47 +08:00
if ( confirm ( 'update' , 'updating' ) ) {
2018-11-23 23:10:50 +08:00
$ Event - > save ( { StorageId = > $$ Storage { Id } } ) ;
2018-11-27 04:02:34 +08:00
next EVENT ;
2018-11-23 23:10:50 +08:00
}
} else {
Debug ( "$$Event{Id} Not found at $path" ) ;
}
}
2018-11-09 23:39:57 +08:00
if ( $ Event - > Archived ( ) ) {
Warning ( "Event $$Event{Id} is Archived. Taking no further action on it." ) ;
next ;
}
if ( ! $ Event - > StartTime ( ) ) {
2018-11-14 03:13:41 +08:00
Info ( "Event $$Event{Id} has no start time." ) ;
2018-11-09 23:39:57 +08:00
if ( confirm ( ) ) {
$ Event - > delete ( ) ;
$ cleaned = 1 ;
2017-08-16 01:06:59 +08:00
}
2018-11-09 23:39:57 +08:00
next ;
}
if ( ! $ Event - > EndTime ( ) ) {
if ( $ age > $ Config { ZM_AUDIT_MIN_AGE } ) {
Info ( "Event $$Event{Id} has no end time and is $age seconds old. deleting it." ) ;
if ( confirm ( ) ) {
$ Event - > delete ( ) ;
$ cleaned = 1 ;
}
2018-09-20 00:03:51 +08:00
next ;
}
2018-11-09 23:39:57 +08:00
}
if ( $ Event - > check_for_in_filesystem ( ) ) {
Debug ( "Database event $$Event{Id} apparently exists at " . $ Event - > Path ( ) ) ;
} else {
if ( $ age > $ Config { ZM_AUDIT_MIN_AGE } ) {
2018-11-24 01:16:11 +08:00
aud_print ( "Database event '$db_monitor/$db_event' does not exist at " . $ Event - > Path ( ) . ' in filesystem, deleting' ) ;
2016-07-13 02:16:20 +08:00
if ( confirm ( ) ) {
$ Event - > delete ( ) ;
$ cleaned = 1 ;
}
2018-11-09 23:39:57 +08:00
} else {
aud_print ( "Database event '" . $ Event - > Path ( ) . " monitor:$db_monitor event:$db_event' does not exist in filesystem but too young to delete age: $age > MIN $Config{ZM_AUDIT_MIN_AGE}.\n" ) ;
}
} # end if exists in filesystem
} else {
2018-11-24 01:16:11 +08:00
Debug ( "Found fs event for id $db_event, $age seconds old at " . $$ fs_events { $ db_event } - > Path ( ) ) ;
2018-11-29 23:09:49 +08:00
my $ Event = ZoneMinder::Event - > find_one ( Id = > $ db_event ) ;
if ( $ Event and ! $ Event - > check_for_in_filesystem ( ) ) {
2018-11-11 23:17:32 +08:00
Warning ( "Not found at " . $ Event - > Path ( ) . ' was found at ' . $$ fs_events { $ db_event } - > Path ( ) ) ;
Warning ( $ Event - > to_string ( ) ) ;
Warning ( $$ fs_events { $ db_event } - > to_string ( ) ) ;
2018-11-28 06:01:49 +08:00
$$ Event { Scheme } = '' if ! defined $$ Event { Scheme } ;
2018-11-09 23:39:57 +08:00
if ( $$ fs_events { $ db_event } - > Scheme ( ) ne $ Event - > Scheme ( ) ) {
Info ( "Updating scheme on event $$Event{Id} from $$Event{Scheme} to $$fs_events{$db_event}{Scheme}" ) ;
$ Event - > Scheme ( $$ fs_events { $ db_event } - > Scheme ( ) ) ;
2016-07-13 02:16:20 +08:00
}
2018-11-09 23:39:57 +08:00
if ( $$ fs_events { $ db_event } - > StorageId ( ) != $ Event - > StorageId ( ) ) {
Info ( "Updating storage area on event $$Event{Id} from $$Event{StorageId} to $$fs_events{$db_event}{StorageId}" ) ;
$ Event - > StorageId ( $$ fs_events { $ db_event } - > StorageId ( ) ) ;
2018-01-20 01:14:36 +08:00
}
2018-11-11 23:17:32 +08:00
if ( $$ fs_events { $ db_event } - > StartTime ( ) ne $ Event - > StartTime ( ) ) {
2018-11-14 03:13:41 +08:00
Info ( "Updating StartTime on event $$Event{Id} from $$Event{StartTime} to $$fs_events{$db_event}{StartTime}" ) ;
2018-11-11 23:17:32 +08:00
if ( $$ Event { Scheme } eq 'Deep' ) {
$ Event - > StartTime ( $$ fs_events { $ db_event } - > StartTime ( ) ) ;
} else {
2018-11-14 03:13:41 +08:00
$ Event - > StartTime ( $$ fs_events { $ db_event } - > StartTime ( ) ) ;
2018-11-11 23:17:32 +08:00
}
$ Event - > save ( ) ;
}
2018-11-09 23:39:57 +08:00
$ Event - > save ( ) ;
}
} # end if ! in fs_events
} # foreach db_event
2017-05-17 23:52:25 +08:00
} # end foreach db_monitor
2018-02-13 02:25:11 +08:00
if ( $ cleaned ) {
Debug ( "Have done some cleaning, restarting." ) ;
redo MAIN ;
}
2016-06-03 23:57:38 +08:00
2018-04-19 23:18:23 +08:00
if ( $ level > 1 ) {
2016-06-03 23:57:38 +08:00
# Remove orphaned events (with no monitor)
2018-04-19 23:18:23 +08:00
# Shouldn't be possible anymore with FOREIGN KEYS in place
2016-06-03 23:57:38 +08:00
$ cleaned = 0 ;
2018-02-13 02:25:11 +08:00
Debug ( "Checking for Orphaned Events" ) ;
2016-09-08 23:06:02 +08:00
my $ selectOrphanedEventsSql = ' SELECT Events . Id , Events . Name
2016-06-03 23:57:38 +08:00
FROM Events LEFT JOIN Monitors ON ( Events . MonitorId = Monitors . Id )
2016-09-08 23:06:02 +08:00
WHERE isnull ( Monitors . Id ) ' ;
2016-06-03 23:57:38 +08:00
my $ selectOrphanedEventsSth = $ dbh - > prepare_cached ( $ selectOrphanedEventsSql )
2018-02-12 03:18:10 +08:00
or Error ( "Can't prepare '$selectOrphanedEventsSql': " . $ dbh - > errstr ( ) ) ;
2016-06-03 23:57:38 +08:00
$ res = $ selectOrphanedEventsSth - > execute ( )
2018-02-12 03:18:10 +08:00
or Error ( "Can't execute: " . $ selectOrphanedEventsSth - > errstr ( ) ) ;
2016-06-03 23:57:38 +08:00
while ( my $ event = $ selectOrphanedEventsSth - > fetchrow_hashref ( ) ) {
aud_print ( "Found orphaned event with no monitor '$event->{Id}'" ) ;
if ( confirm ( ) ) {
2018-02-12 03:18:10 +08:00
if ( $ res = $ deleteEventSth - > execute ( $ event - > { Id } ) ) {
$ cleaned = 1 ;
} else {
Error ( "Can't execute: " . $ deleteEventSth - > errstr ( ) ) ;
}
2011-06-21 17:19:10 +08:00
}
2016-06-03 23:57:38 +08:00
}
2018-02-12 03:18:10 +08:00
redo MAIN if $ cleaned ;
2018-04-19 23:18:23 +08:00
} # end if level > 1
2016-06-03 23:57:38 +08:00
# Remove empty events (with no frames)
$ cleaned = 0 ;
2018-02-13 02:25:11 +08:00
Debug ( "Checking for Events with no Frames" ) ;
2016-09-08 23:06:02 +08:00
my $ selectEmptyEventsSql = ' SELECT E . Id AS Id , E . StartTime , F . EventId FROM Events as E LEFT JOIN Frames as F ON ( E . Id = F . EventId )
WHERE isnull ( F . EventId ) AND now ( ) - interval '.$Config{ZM_AUDIT_MIN_AGE}.' second > E . StartTime ' ;
2018-02-12 03:18:10 +08:00
if ( my $ selectEmptyEventsSth = $ dbh - > prepare_cached ( $ selectEmptyEventsSql ) ) {
if ( $ res = $ selectEmptyEventsSth - > execute ( ) ) {
while ( my $ event = $ selectEmptyEventsSth - > fetchrow_hashref ( ) ) {
aud_print ( "Found empty event with no frame records '$event->{Id}' at $$event{StartTime}" ) ;
if ( confirm ( ) ) {
if ( $ res = $ deleteEventSth - > execute ( $ event - > { Id } ) ) {
$ cleaned = 1 ;
} else {
Error ( "Can't execute: " . $ deleteEventSth - > errstr ( ) ) ;
}
}
} # end foreach row
} else {
Error ( "Can't execute: " . $ selectEmptyEventsSth - > errstr ( ) ) ;
2011-06-21 17:19:10 +08:00
}
2018-02-12 03:18:10 +08:00
} else {
Error ( "Can't prepare '$selectEmptyEventsSql': " . $ dbh - > errstr ( ) ) ;
2016-06-03 23:57:38 +08:00
}
2018-02-12 03:18:10 +08:00
redo MAIN if $ cleaned ;
2016-06-03 23:57:38 +08:00
# Remove orphaned frame records
$ cleaned = 0 ;
2018-02-13 02:25:11 +08:00
Debug ( "Checking for Orphaned Frames" ) ;
2016-09-08 23:06:02 +08:00
my $ selectOrphanedFramesSql = ' SELECT DISTINCT EventId FROM Frames
2018-04-17 23:47:11 +08:00
WHERE ( SELECT COUNT ( * ) FROM Events WHERE Events . Id = EventId ) = 0 ' ;
2016-06-03 23:57:38 +08:00
my $ selectOrphanedFramesSth = $ dbh - > prepare_cached ( $ selectOrphanedFramesSql )
or Fatal ( "Can't prepare '$selectOrphanedFramesSql': " . $ dbh - > errstr ( ) ) ;
$ res = $ selectOrphanedFramesSth - > execute ( )
or Fatal ( "Can't execute: " . $ selectOrphanedFramesSth - > errstr ( ) ) ;
while ( my $ frame = $ selectOrphanedFramesSth - > fetchrow_hashref ( ) ) {
aud_print ( "Found orphaned frame records for event '$frame->{EventId}'" ) ;
if ( confirm ( ) ) {
$ res = $ deleteFramesSth - > execute ( $ frame - > { EventId } )
or Fatal ( "Can't execute: " . $ deleteFramesSth - > errstr ( ) ) ;
$ cleaned = 1 ;
2011-06-21 17:19:10 +08:00
}
2016-06-03 23:57:38 +08:00
}
2018-02-12 03:18:10 +08:00
redo MAIN if $ cleaned ;
2016-06-03 23:57:38 +08:00
2018-04-19 23:18:23 +08:00
if ( $ level > 1 ) {
2016-06-03 23:57:38 +08:00
# Remove orphaned stats records
$ cleaned = 0 ;
2018-02-13 02:25:11 +08:00
Debug ( "Checking for Orphaned Stats" ) ;
2016-09-08 23:06:02 +08:00
my $ selectOrphanedStatsSql = ' SELECT DISTINCT EventId FROM Stats
WHERE EventId NOT IN ( SELECT Id FROM Events ) ' ;
2016-06-03 23:57:38 +08:00
my $ selectOrphanedStatsSth = $ dbh - > prepare_cached ( $ selectOrphanedStatsSql )
or Fatal ( "Can't prepare '$selectOrphanedStatsSql': " . $ dbh - > errstr ( ) ) ;
$ res = $ selectOrphanedStatsSth - > execute ( )
or Fatal ( "Can't execute: " . $ selectOrphanedStatsSth - > errstr ( ) ) ;
while ( my $ stat = $ selectOrphanedStatsSth - > fetchrow_hashref ( ) ) {
aud_print ( "Found orphaned statistic records for event '$stat->{EventId}'" ) ;
if ( confirm ( ) ) {
$ res = $ deleteStatsSth - > execute ( $ stat - > { EventId } )
or Fatal ( "Can't execute: " . $ deleteStatsSth - > errstr ( ) ) ;
$ cleaned = 1 ;
2011-06-21 17:19:10 +08:00
}
2016-06-03 23:57:38 +08:00
}
redo MAIN if ( $ cleaned ) ;
2018-04-19 23:18:23 +08:00
}
2011-06-21 17:19:10 +08:00
2016-06-03 23:57:38 +08:00
# New audit to close any events that were left open for longer than MIN_AGE seconds
my $ selectUnclosedEventsSql =
#"SELECT E.Id, ANY_VALUE(E.MonitorId),
#
#max(F.TimeStamp) as EndTime,
#unix_timestamp(max(F.TimeStamp)) - unix_timestamp(E.StartTime) as Length,
#max(F.FrameId) as Frames,
#count(if(F.Score>0,1,NULL)) as AlarmFrames,
#sum(F.Score) as TotScore,
#max(F.Score) as MaxScore
#FROM Events as E
#INNER JOIN Frames as F on E.Id = F.EventId
#WHERE isnull(E.Frames) or isnull(E.EndTime)
2016-09-08 23:06:02 +08:00
#GROUP BY E.Id HAVING EndTime < (now() - interval ".$Config{ZM_AUDIT_MIN_AGE}.' second)'
2016-06-03 23:57:38 +08:00
#;
2016-09-08 23:06:02 +08:00
'SELECT *, unix_timestamp(StartTime) AS TimeStamp FROM Events WHERE EndTime IS NULL AND StartTime < (now() - interval ' . $ Config { ZM_AUDIT_MIN_AGE } . ' second)' ;
2016-06-03 23:57:38 +08:00
2018-02-13 02:25:11 +08:00
my $ selectFrameDataSql = '
SELECT
max ( TimeStamp ) as EndTime ,
unix_timestamp ( max ( TimeStamp ) ) AS EndTimeStamp ,
max ( FrameId ) as Frames ,
count ( if ( Score > 0 , 1 , NULL ) ) as AlarmFrames ,
sum ( Score ) as TotScore ,
max ( Score ) as MaxScore
FROM Frames WHERE EventId = ? ' ;
2016-06-03 23:57:38 +08:00
my $ selectFrameDataSth = $ dbh - > prepare_cached ( $ selectFrameDataSql )
or Fatal ( "Can't prepare '$selectFrameDataSql': " . $ dbh - > errstr ( ) ) ;
my $ selectUnclosedEventsSth = $ dbh - > prepare_cached ( $ selectUnclosedEventsSql )
or Fatal ( "Can't prepare '$selectUnclosedEventsSql': " . $ dbh - > errstr ( ) ) ;
my $ updateUnclosedEventsSql =
" UPDATE low_priority Events
SET Name = ? ,
EndTime = ? ,
Length = ? ,
Frames = ? ,
AlarmFrames = ? ,
TotScore = ? ,
AvgScore = ? ,
MaxScore = ? ,
Notes = concat_ws ( ' ' , Notes , ? )
WHERE Id = ? "
;
my $ updateUnclosedEventsSth = $ dbh - > prepare_cached ( $ updateUnclosedEventsSql )
or Fatal ( "Can't prepare '$updateUnclosedEventsSql': " . $ dbh - > errstr ( ) ) ;
$ res = $ selectUnclosedEventsSth - > execute ( )
or Fatal ( "Can't execute: " . $ selectUnclosedEventsSth - > errstr ( ) ) ;
while ( my $ event = $ selectUnclosedEventsSth - > fetchrow_hashref ( ) ) {
2018-02-14 00:27:20 +08:00
aud_print ( "Found open event '$event->{Id}' at $$event{StartTime}" ) ;
2016-06-03 23:57:38 +08:00
if ( confirm ( 'close' , 'closing' ) ) {
2018-02-14 00:27:20 +08:00
if ( ! ( $ res = $ selectFrameDataSth - > execute ( $ event - > { Id } ) ) ) {
Error ( "Can't execute: $selectFrameDataSql:" . $ selectFrameDataSth - > errstr ( ) ) ;
next ;
}
2016-06-03 23:57:38 +08:00
my $ frame = $ selectFrameDataSth - > fetchrow_hashref ( ) ;
if ( $ frame ) {
$ res = $ updateUnclosedEventsSth - > execute (
2016-09-08 23:06:02 +08:00
sprintf ( '%s%d%s' ,
2016-06-03 23:57:38 +08:00
$ Monitors { $ event - > { MonitorId } } - > { EventPrefix } ,
$ event - > { Id } ,
RECOVER_TAG
) ,
$ frame - > { EndTime } ,
$ frame - > { EndTimeStamp } - $ event - > { TimeStamp } ,
$ frame - > { Frames } ,
$ frame - > { AlarmFrames } ,
$ frame - > { TotScore } ,
$ frame - > { AlarmFrames }
? int ( $ frame - > { TotScore } / $ frame - > { AlarmFrames } )
: 0
,
$ frame - > { MaxScore } ,
RECOVER_TEXT ,
$ event - > { Id }
2018-02-14 00:27:20 +08:00
) or Error ( "Can't execute: " . $ updateUnclosedEventsSth - > errstr ( ) ) ;
2016-06-03 23:57:38 +08:00
} else {
2016-09-08 23:06:02 +08:00
Error ( 'SHOULD DELETE' ) ;
2016-06-03 23:57:38 +08:00
} # end if has frame data
2011-06-21 17:19:10 +08:00
}
2016-06-03 23:57:38 +08:00
} # end while unclosed event
2018-02-14 00:27:20 +08:00
Debug ( "Done closing open events." ) ;
2016-06-03 23:57:38 +08:00
# Now delete any old image files
if ( my @ old_files = grep { - M > $ max_image_age } <$image_path/*.{jpg,gif,wbmp}> ) {
2016-09-08 23:06:02 +08:00
aud_print ( 'Deleting ' . int ( @ old_files ) . " old images\n" ) ;
my $ untainted_old_files = join ( ';' , @ old_files ) ;
2016-06-03 23:57:38 +08:00
( $ untainted_old_files ) = ( $ untainted_old_files =~ /^(.*)$/ ) ;
unlink ( split ( /;/ , $ untainted_old_files ) ) ;
}
2011-06-21 17:19:10 +08:00
2016-06-03 23:57:38 +08:00
# Now delete any old swap files
( my $ swap_image_root ) = ( $ Config { ZM_PATH_SWAP } =~ /^(.*)$/ ) ; # De-taint
File::Find:: find ( { wanted = > \ & deleteSwapImage , untaint = > 1 } , $ swap_image_root ) ;
# Prune the Logs table if required
if ( $ Config { ZM_LOG_DATABASE_LIMIT } ) {
if ( $ Config { ZM_LOG_DATABASE_LIMIT } =~ /^\d+$/ ) {
# Number of rows
2017-05-31 21:33:51 +08:00
my $ selectLogRowCountSql = 'SELECT count(*) AS Rows FROM Logs' ;
2016-06-03 23:57:38 +08:00
my $ selectLogRowCountSth = $ dbh - > prepare_cached ( $ selectLogRowCountSql )
2018-02-04 03:50:19 +08:00
or Fatal ( "Can't prepare '$selectLogRowCountSql': " . $ dbh - > errstr ( ) ) ;
2016-06-03 23:57:38 +08:00
$ res = $ selectLogRowCountSth - > execute ( )
2018-02-04 03:50:19 +08:00
or Fatal ( "Can't execute: " . $ selectLogRowCountSth - > errstr ( ) ) ;
2016-06-03 23:57:38 +08:00
my $ row = $ selectLogRowCountSth - > fetchrow_hashref ( ) ;
my $ logRows = $ row - > { Rows } ;
if ( $ logRows > $ Config { ZM_LOG_DATABASE_LIMIT } ) {
2016-09-08 23:06:02 +08:00
my $ deleteLogByRowsSql = 'DELETE low_priority FROM Logs ORDER BY TimeKey ASC LIMIT ?' ;
2016-06-03 23:57:38 +08:00
my $ deleteLogByRowsSth = $ dbh - > prepare_cached ( $ deleteLogByRowsSql )
2018-02-04 03:50:19 +08:00
or Fatal ( "Can't prepare '$deleteLogByRowsSql': " . $ dbh - > errstr ( ) ) ;
2016-06-03 23:57:38 +08:00
$ res = $ deleteLogByRowsSth - > execute ( $ logRows - $ Config { ZM_LOG_DATABASE_LIMIT } )
2018-02-04 03:50:19 +08:00
or Fatal ( "Can't execute: " . $ deleteLogByRowsSth - > errstr ( ) ) ;
2016-06-03 23:57:38 +08:00
if ( $ deleteLogByRowsSth - > rows ( ) ) {
2017-05-31 21:33:51 +08:00
aud_print ( 'Deleted ' . $ deleteLogByRowsSth - > rows ( ) . " log table entries by count\n" ) ;
2011-06-21 17:19:10 +08:00
}
2016-06-03 23:57:38 +08:00
}
} else {
# Time of record
2018-02-12 03:18:10 +08:00
# 7 days is invalid. We need to remove the s
if ( $ Config { ZM_LOG_DATABASE_LIMIT } =~ /^(.*)s$/ ) {
$ Config { ZM_LOG_DATABASE_LIMIT } = $ 1 ;
}
2018-06-26 01:41:19 +08:00
my $ deleted_rows ;
do {
my $ deleteLogByTimeSql =
' DELETE FROM Logs
WHERE TimeKey < unix_timestamp ( now ( ) - interval '.$Config{ZM_LOG_DATABASE_LIMIT}.' ) LIMIT 10 ' ;
my $ deleteLogByTimeSth = $ dbh - > prepare_cached ( $ deleteLogByTimeSql )
or Fatal ( "Can't prepare '$deleteLogByTimeSql': " . $ dbh - > errstr ( ) ) ;
$ res = $ deleteLogByTimeSth - > execute ( )
or Fatal ( "Can't execute: " . $ deleteLogByTimeSth - > errstr ( ) ) ;
$ deleted_rows = $ deleteLogByTimeSth - > rows ( ) ;
aud_print ( "Deleted $deleted_rows log table entries by time\n" ) ;
} while ( $ deleted_rows ) ;
2011-06-21 17:19:10 +08:00
}
2016-06-03 23:57:38 +08:00
} # end if ZM_LOG_DATABASE_LIMIT
$ loop = $ continuous ;
2007-08-30 02:11:09 +08:00
2018-01-05 04:04:13 +08:00
my $ eventcounts_sql = q `
2018-02-04 03:50:19 +08:00
UPDATE Monitors SET
2018-01-05 04:04:13 +08:00
TotalEvents = ( SELECT COUNT ( Id ) FROM Events WHERE MonitorId = Monitors . Id ) ,
TotalEventDiskSpace = ( SELECT SUM ( DiskSpace ) FROM Events WHERE MonitorId = Monitors . Id AND DiskSpace IS NOT NULL ) ,
ArchivedEvents = ( SELECT COUNT ( Id ) FROM Events WHERE MonitorId = Monitors . Id AND Archived = 1 ) ,
ArchivedEventDiskSpace = ( SELECT SUM ( DiskSpace ) FROM Events WHERE MonitorId = Monitors . Id AND Archived = 1 AND DiskSpace IS NOT NULL )
2018-02-04 03:50:19 +08:00
` ;
my $ eventcounts_sth = $ dbh - > prepare_cached ( $ eventcounts_sql ) ;
$ eventcounts_sth - > execute ( ) ;
$ eventcounts_sth - > finish ( ) ;
my $ eventcounts_hour_sql = q `
UPDATE Monitors INNER JOIN (
SELECT MonitorId , COUNT ( * ) AS HourEvents , SUM ( COALESCE ( DiskSpace , 0 ) ) AS HourEventDiskSpace
FROM Events_Hour GROUP BY MonitorId
) AS E ON E . MonitorId = Monitors . Id SET
Monitors . HourEvents = E . HourEvents ,
Monitors . HourEventDiskSpace = E . HourEventDiskSpace
` ;
my $ eventcounts_day_sql = q `
UPDATE Monitors INNER JOIN (
SELECT MonitorId , COUNT ( * ) AS DayEvents , SUM ( COALESCE ( DiskSpace , 0 ) ) AS DayEventDiskSpace
FROM Events_Day GROUP BY MonitorId
) AS E ON E . MonitorId = Monitors . Id SET
Monitors . DayEvents = E . DayEvents ,
Monitors . DayEventDiskSpace = E . DayEventDiskSpace
` ;
my $ eventcounts_week_sql = q `
UPDATE Monitors INNER JOIN (
SELECT MonitorId , COUNT ( * ) AS WeekEvents , SUM ( COALESCE ( DiskSpace , 0 ) ) AS WeekEventDiskSpace
FROM Events_Week GROUP BY MonitorId
) AS E ON E . MonitorId = Monitors . Id SET
Monitors . WeekEvents = E . WeekEvents ,
Monitors . WeekEventDiskSpace = E . WeekEventDiskSpace
` ;
my $ eventcounts_month_sql = q `
UPDATE Monitors INNER JOIN (
SELECT MonitorId , COUNT ( * ) AS MonthEvents , SUM ( COALESCE ( DiskSpace , 0 ) ) AS MonthEventDiskSpace
FROM Events_Month GROUP BY MonitorId
) AS E ON E . MonitorId = Monitors . Id SET
Monitors . MonthEvents = E . MonthEvents ,
Monitors . MonthEventDiskSpace = E . MonthEventDiskSpace
` ;
my $ eventcounts_hour_sth = $ dbh - > prepare_cached ( $ eventcounts_hour_sql ) ;
my $ eventcounts_day_sth = $ dbh - > prepare_cached ( $ eventcounts_day_sql ) ;
my $ eventcounts_week_sth = $ dbh - > prepare_cached ( $ eventcounts_week_sql ) ;
my $ eventcounts_month_sth = $ dbh - > prepare_cached ( $ eventcounts_month_sql ) ;
$ eventcounts_hour_sth - > execute ( ) or Error ( "Can't execute: " . $ eventcounts_sth - > errstr ( ) ) ;
$ eventcounts_day_sth - > execute ( ) or Error ( "Can't execute: " . $ eventcounts_sth - > errstr ( ) ) ;
$ eventcounts_week_sth - > execute ( ) or Error ( "Can't execute: " . $ eventcounts_sth - > errstr ( ) ) ;
$ eventcounts_month_sth - > execute ( ) or Error ( "Can't execute: " . $ eventcounts_sth - > errstr ( ) ) ;
2016-06-03 23:57:38 +08:00
sleep ( $ Config { ZM_AUDIT_CHECK_INTERVAL } ) if $ continuous ;
2011-04-19 21:01:45 +08:00
} ;
2007-08-30 02:11:09 +08:00
2018-01-23 04:26:18 +08:00
Term ( ) ;
2011-04-19 21:01:45 +08:00
2016-06-03 23:57:38 +08:00
sub aud_print {
my $ string = shift ;
2017-05-17 23:52:25 +08:00
if ( ! $ continuous ) {
2016-06-03 23:57:38 +08:00
print ( $ string ) ;
} else {
Info ( $ string ) ;
}
2011-04-19 21:01:45 +08:00
}
2016-06-03 23:57:38 +08:00
sub confirm {
2016-09-08 23:06:02 +08:00
my $ prompt = shift || 'delete' ;
my $ action = shift || 'deleting' ;
2016-06-03 23:57:38 +08:00
my $ yesno = 0 ;
if ( $ report ) {
print ( "\n" ) ;
} elsif ( $ interactive ) {
2018-01-27 05:05:43 +08:00
print ( ", $prompt Y/n/q: " ) ;
2016-06-03 23:57:38 +08:00
my $ char = < > ;
chomp ( $ char ) ;
if ( $ char eq 'q' ) {
exit ( 0 ) ;
2011-06-21 17:19:10 +08:00
}
2016-06-03 23:57:38 +08:00
if ( ! $ char ) {
$ char = 'y' ;
2011-06-21 17:19:10 +08:00
}
2016-06-03 23:57:38 +08:00
$ yesno = ( $ char =~ /[yY]/ ) ;
} else {
if ( ! $ continuous ) {
print ( ", $action\n" ) ;
} else {
Info ( $ action ) ;
2011-06-21 17:19:10 +08:00
}
2016-06-03 23:57:38 +08:00
$ yesno = 1 ;
}
return ( $ yesno ) ;
2011-04-19 21:01:45 +08:00
}
2016-06-03 23:57:38 +08:00
sub deleteSwapImage {
my $ file = $ _ ;
2011-04-19 21:01:45 +08:00
2016-07-13 02:16:20 +08:00
return if $ file =~ /^./ ;
2016-06-03 23:57:38 +08:00
if ( $ file !~ /^zmswap-/ ) {
2016-07-13 02:16:20 +08:00
Error ( "Trying to delete SwapImage that isnt a swap image $file" ) ;
2016-06-03 23:57:38 +08:00
return ;
}
2007-08-30 02:11:09 +08:00
2016-06-03 23:57:38 +08:00
# Ignore directories
if ( - d $ file ) {
2016-07-13 02:16:20 +08:00
Error ( "Trying to delete a directory instead of a swap image $file" ) ;
2016-06-03 23:57:38 +08:00
return ;
}
2011-04-19 21:01:45 +08:00
2016-06-03 23:57:38 +08:00
if ( - M $ file > $ max_swap_age ) {
2016-07-13 02:16:20 +08:00
( $ file ) = ( $ file =~ /^(.*)$/ ) ;
2016-06-03 23:57:38 +08:00
Debug ( "Deleting $file" ) ;
2016-07-13 02:16:20 +08:00
unlink ( $ file ) ;
2016-06-03 23:57:38 +08:00
}
2011-04-19 21:01:45 +08:00
}
2016-06-03 23:35:28 +08:00
2018-09-20 08:00:26 +08:00
# Deletes empty sub directories of the given path.
# Does not delete the path if empty. Is not meant to be recursive.
2018-12-04 00:08:24 +08:00
# Assumes absolute path
2018-09-20 08:00:26 +08:00
sub delete_empty_subdirs {
my $ DIR ;
if ( ! opendir ( $ DIR , $ _ [ 0 ] ) ) {
2018-12-04 00:08:24 +08:00
Error ( "delete_empty_subdirs: Can't open directory '/$_[0]': $!" ) ;
2018-09-20 08:00:26 +08:00
return ;
}
my @ contents = map { ( $ _ eq '.' or $ _ eq '..' ) ? ( ) : $ _ } readdir ( $ DIR ) ;
Debug ( "delete_empty_subdirectories $_[0] has " . @ contents . ' entries:' . ( @ contents < 2 ? join ( ',' , @ contents ) : '' ) ) ;
my @ dirs = map { - d $ _ [ 0 ] . '/' . $ _ ? $ _ : ( ) } @ contents ;
Debug ( "Have " . @ dirs . " dirs" ) ;
foreach ( @ dirs ) {
delete_empty_directories ( $ _ [ 0 ] . '/' . $ _ ) ;
}
closedir ( $ DIR ) ;
}
2018-12-04 00:08:24 +08:00
# Assumes absolute path
2016-06-03 23:35:28 +08:00
sub delete_empty_directories {
2016-06-08 10:03:46 +08:00
my $ DIR ;
2018-09-10 01:28:24 +08:00
if ( ! opendir ( $ DIR , $ _ [ 0 ] ) ) {
2018-12-04 00:08:24 +08:00
Error ( "delete_empty_directories: Can't open directory '/$_[0]': $!" ) ;
2016-06-03 23:35:28 +08:00
return ;
}
2019-03-19 05:13:19 +08:00
my @ contents = map { ( $ _ eq '.' or $ _ eq '..' ) ? ( ) : $ _ } readdir ( $ DIR ) ;
2018-11-11 23:17:32 +08:00
#Debug("delete_empty_directories $_[0] has " . @contents .' entries:' . ( @contents <= 2 ? join(',',@contents) : '' ));
2016-06-08 09:43:47 +08:00
my @ dirs = map { - d $ _ [ 0 ] . '/' . $ _ ? $ _ : ( ) } @ contents ;
2016-06-03 23:35:28 +08:00
if ( @ dirs ) {
2019-03-19 05:13:19 +08:00
Debug ( 'Have ' . @ dirs . " dirs in $_[0]" ) ;
2016-06-03 23:35:28 +08:00
foreach ( @ dirs ) {
2019-03-19 05:13:19 +08:00
delete_empty_directories ( $ _ [ 0 ] . '/' . $ _ ) ;
2016-06-03 23:35:28 +08:00
}
#Reload, since we may now be empty
2016-06-08 10:03:46 +08:00
rewinddir $ DIR ;
2019-03-19 05:13:19 +08:00
@ contents = map { ( $ _ eq '.' or $ _ eq '..' ) ? ( ) : $ _ } readdir ( $ DIR ) ;
2016-06-03 23:35:28 +08:00
}
2018-09-10 01:28:24 +08:00
closedir ( $ DIR ) ;
2016-06-03 23:35:28 +08:00
if ( ! @ contents ) {
2016-06-08 10:03:46 +08:00
( my $ dir ) = ( $ _ [ 0 ] =~ /^(.*)$/ ) ;
2018-09-10 01:28:24 +08:00
Debug ( "Unlinking $dir because it's empty" ) ;
if ( ! rmdir $ dir ) {
Error ( "Unable to unlink $dir: $!" ) ;
}
2016-06-03 23:35:28 +08:00
}
} # end sub delete_empty_directories
2018-11-14 03:13:41 +08:00
sub time_of_youngest_file {
my $ dir = shift ;
if ( ! opendir ( DIR , $ dir ) ) {
Error ( "Can't open directory '$dir': $!" ) ;
return ;
}
my $ youngest = ( stat ( $ dir ) ) [ 9 ] ;
Debug ( "stat of $dir is $youngest" ) ;
foreach my $ file ( readdir ( DIR ) ) {
next if $ file =~ /^\./ ;
$ _ = ( stat ( $ dir ) ) [ 9 ] ;
$ youngest = $ _ if $ _ and ( $ _ < $ youngest ) ;
#Debug("stat of $dir is $_ < $youngest");
}
Debug ( "stat of $dir is $youngest" ) ;
return $ youngest ;
} # end sub time_of_youngest_file
2017-05-17 23:52:25 +08:00
1 ;
__END__
2017-09-12 10:19:49 +08:00
= head1 NAME
zmaudit . pl - ZoneMinder event file system and database consistency checker
= head1 SYNOPSIS
zmaudit . pl [ - r , - report | - i , - interactive ]
= head1 DESCRIPTION
This script checks for consistency between the event filesystem and
the database . If events are found in one and not the other they are
deleted ( optionally ) . Additionally any monitor event directories that
do not correspond to a database monitor are similarly disposed of .
However monitors in the database that don ' t have a directory are left
alone as this is valid if they are newly created and have no events
yet .
= head1 OPTIONS
- c , - - continuous - Run continuously
2018-01-20 00:40:07 +08:00
- f , - - force - Run even if pid file exists
2017-09-12 10:19:49 +08:00
- i , - - interactive - Ask before applying any changes
2018-01-27 04:53:47 +08:00
- m , - - monitor_id - Only consider the given monitor
2017-09-12 10:19:49 +08:00
- r , - - report - Just report don ' t actually do anything
2018-01-23 00:10:31 +08:00
- s , - - storage_id - Specify a storage area to audit instead of all
2017-09-12 10:19:49 +08:00
- v , - - version - Print the installed version of ZoneMinder
= cut