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
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#
# ==========================================================================
#
# 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.
#
use strict ;
use bytes ;
# ==========================================================================
#
# These are the elements you can edit to suit your installation
#
# ==========================================================================
use constant MIN_AGE = > 300 ; # Minimum age when we will delete anything
2006-10-18 23:09:08 +08:00
use constant MAX_AGED_DIRS = > 10 ; # Number of event dirs to check age on
2005-12-16 18:05:29 +08:00
use constant RECOVER_TAG = > "(r)" ; # Tag to append to event name when recovered
use constant RECOVER_TEXT = > "Recovered." ; # Text to append to event notes when recovered
2005-12-16 21:16:37 +08:00
use constant DBG_ID = > "zmaudit" ; # Tag that appears in debug to identify source
2008-01-14 02:17:42 +08:00
use constant DBG_LEVEL = > 0 ; # 0 is errors, warnings and info only, > 0 for debug
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 ;
use constant IMAGE_PATH = > ZM_PATH_WEB . '/' . ZM_DIR_IMAGES ;
use constant EVENT_PATH = > ZM_PATH_WEB . '/' . ZM_DIR_EVENTS ;
$| = 1 ;
$ ENV { PATH } = '/bin:/usr/bin' ;
$ 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 ;
2005-12-16 18:05:29 +08:00
sub usage
{
print ( "
2006-01-12 07:56:46 +08:00
Usage: zmaudit . pl [ - r , - report | - i , - interactive ]
2005-12-16 18:05:29 +08:00
Parameters are : -
- r , - - report - Just report don ' t actually do anything
2006-01-12 07:56:46 +08:00
- i , - - interactive - Ask before applying any changes
2006-01-12 23:38:24 +08:00
- c , - - continuous - Run continuously
2005-12-16 18:05:29 +08:00
" ) ;
exit ( - 1 ) ;
}
sub aud_print
{
my $ string = shift ;
2006-01-12 23:38:24 +08:00
if ( ! $ continuous )
2005-12-16 18:05:29 +08:00
{
2006-01-12 07:56:46 +08:00
print ( $ string ) ;
2005-12-16 18:05:29 +08:00
}
else
{
2006-01-12 07:56:46 +08:00
Info ( $ string ) ;
2005-12-16 18:05:29 +08:00
}
}
sub confirm
{
my $ prompt = shift || "delete" ;
my $ action = shift || "deleting" ;
2006-01-12 07:56:46 +08:00
my $ yesno = 0 ;
2005-12-16 18:05:29 +08:00
if ( $ report )
{
2006-01-12 07:56:46 +08:00
print ( "\n" ) ;
2005-12-16 18:05:29 +08:00
}
2006-01-12 07:56:46 +08:00
elsif ( $ interactive )
2005-12-16 18:05:29 +08:00
{
print ( ", $prompt y/n: " ) ;
my $ char = < > ;
chomp ( $ char ) ;
if ( $ char eq 'q' )
{
exit ( 0 ) ;
}
if ( ! $ char )
{
$ char = 'y' ;
}
$ yesno = ( $ char =~ /[yY]/ ) ;
}
2006-01-12 07:56:46 +08:00
else
{
2006-01-12 23:38:24 +08:00
if ( ! $ continuous )
2006-01-12 23:34:12 +08:00
{
print ( ", $action\n" ) ;
}
else
{
Info ( $ action ) ;
}
2006-01-12 07:56:46 +08:00
$ yesno = 1 ;
}
2005-12-16 18:05:29 +08:00
return ( $ yesno ) ;
}
2006-01-12 07:56:46 +08:00
zmDbgInit ( DBG_ID , level = > DBG_LEVEL ) ;
2006-07-04 18:34:21 +08:00
zmDbgSetSignal ( ) ;
2005-12-16 18:05:29 +08:00
2006-01-12 23:38:24 +08:00
if ( ! GetOptions ( 'report' = > \ $ report , 'interactive' = > \ $ interactive , 'continuous' = > \ $ continuous ) )
2005-12-16 18:05:29 +08:00
{
usage ( ) ;
}
2006-01-12 23:38:24 +08:00
if ( ( $ report + $ interactive + $ continuous ) > 1 )
2005-12-16 18:05:29 +08:00
{
2006-01-12 23:38:24 +08:00
print ( STDERR "Error, only option may be specified\n" ) ;
2005-12-16 18:05:29 +08:00
usage ( ) ;
}
2007-09-07 23:39:44 +08:00
my $ dbh = zmDbConnect ( ) ;
2005-12-16 18:05:29 +08:00
chdir ( EVENT_PATH ) ;
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
2005-12-16 18:05:29 +08:00
my $ image_path = IMAGE_PATH ;
2007-08-30 02:11:09 +08:00
my $ swap_image_path = ZM_PATH_SWAP ;
2005-12-16 18:05:29 +08:00
do
{
my $ db_monitors ;
2009-10-09 04:23:45 +08:00
my $ monitorSelectSql = "select Id from Monitors order by Id" ;
my $ monitorSelectSth = $ dbh - > prepare_cached ( $ monitorSelectSql ) or Fatal ( "Can't prepare '$monitorSelectSql': " . $ dbh - > errstr ( ) ) ;
my $ eventSelectSql = "select Id, (unix_timestamp() - unix_timestamp(StartTime)) as Age from Events where MonitorId = ? order by Id" ;
my $ eventSelectSth = $ dbh - > prepare_cached ( $ eventSelectSql ) or Fatal ( "Can't prepare '$eventSelectSql': " . $ dbh - > errstr ( ) ) ;
my $ res = $ monitorSelectSth - > execute ( ) or Fatal ( "Can't execute: " . $ monitorSelectSth - > errstr ( ) ) ;
while ( my $ monitor = $ monitorSelectSth - > fetchrow_hashref ( ) )
2005-12-16 18:05:29 +08:00
{
Debug ( "Found database monitor '$monitor->{Id}'" ) ;
my $ db_events = $ db_monitors - > { $ monitor - > { Id } } = { } ;
2009-10-09 04:23:45 +08:00
my $ res = $ eventSelectSth - > execute ( $ monitor - > { Id } ) or Fatal ( "Can't execute: " . $ eventSelectSth - > errstr ( ) ) ;
while ( my $ event = $ eventSelectSth - > fetchrow_hashref ( ) )
2005-12-16 18:05:29 +08:00
{
$ db_events - > { $ event - > { Id } } = $ event - > { Age } ;
}
Debug ( "Got " . int ( keys ( %$ db_events ) ) . " events\n" ) ;
2009-10-09 04:23:45 +08:00
$ eventSelectSth - > finish ( ) ;
2005-12-16 18:05:29 +08:00
}
2009-10-09 04:23:45 +08:00
$ monitorSelectSth - > finish ( ) ;
2005-12-16 18:05:29 +08:00
my $ fs_monitors ;
foreach my $ monitor ( <[0-9]*> )
{
Debug ( "Found filesystem monitor '$monitor'" ) ;
my $ fs_events = $ fs_monitors - > { $ monitor } = { } ;
( my $ monitor_dir ) = ( $ monitor =~ /^(.*)$/ ) ; # De-taint
2007-09-05 00:08:55 +08:00
if ( ZM_USE_DEEP_STORAGE )
{
foreach my $ day_dir ( <$monitor_dir/*/*/*> )
{
Debug ( "Checking $day_dir" ) ;
( $ day_dir ) = ( $ day_dir =~ /^(.*)$/ ) ; # De-taint
chdir ( $ day_dir ) ;
opendir ( DIR , "." ) or Fatal ( "Can't open directory '$day_dir': $!" ) ;
my @ event_links = sort { $ b <=> $ a } grep { - l $ _ } readdir ( DIR ) ;
closedir ( DIR ) ;
my $ count = 0 ;
foreach my $ event_link ( @ event_links )
{
Debug ( "Checking link $event_link" ) ;
( my $ event = $ event_link ) =~ s/^.*\.// ;
my $ event_path = readlink ( $ event_link ) ;
if ( $ count + + > MAX_AGED_DIRS )
{
$ fs_events - > { $ event } = - 1 ;
}
else
{
$ fs_events - > { $ event } = ( time ( ) - ( $^T - ( ( - M $ event_path ) * 24 * 60 * 60 ) ) ) ;
}
}
chdir ( EVENT_PATH ) ;
}
}
else
{
chdir ( $ monitor_dir ) ;
2007-09-05 22:13:13 +08:00
opendir ( DIR , "." ) or Fatal ( "Can't open directory '$monitor_dir': $!" ) ;
my @ temp_events = sort { $ b <=> $ a } grep { - d $ _ && $ _ =~ /^\d+$/ } readdir ( DIR ) ;
closedir ( DIR ) ;
2007-09-05 00:08:55 +08:00
my $ count = 0 ;
foreach my $ event ( @ temp_events )
{
if ( $ count + + > MAX_AGED_DIRS )
{
$ fs_events - > { $ event } = - 1 ;
}
else
{
$ fs_events - > { $ event } = ( time ( ) - ( $^T - ( ( - M $ event ) * 24 * 60 * 60 ) ) ) ;
}
}
2007-09-05 22:13:13 +08:00
chdir ( EVENT_PATH ) ;
2007-09-05 00:08:55 +08:00
}
2005-12-16 18:05:29 +08:00
Debug ( "Got " . int ( keys ( %$ fs_events ) ) . " events\n" ) ;
}
while ( my ( $ fs_monitor , $ fs_events ) = each ( %$ fs_monitors ) )
{
if ( my $ db_events = $ db_monitors - > { $ fs_monitor } )
{
if ( $ fs_events )
{
while ( my ( $ fs_event , $ age ) = each ( %$ fs_events ) )
{
if ( ! defined ( $ db_events - > { $ fs_event } ) && ( $ age < 0 || ( $ age > MIN_AGE ) ) )
{
aud_print ( "Filesystem event '$fs_monitor/$fs_event' does not exist in database" ) ;
if ( confirm ( ) )
{
2007-09-05 00:08:55 +08:00
deleteEventFiles ( $ fs_event , $ fs_monitor ) ;
2005-12-16 18:05:29 +08:00
}
}
}
}
}
else
{
aud_print ( "Filesystem monitor '$fs_monitor' does not exist in database" ) ;
if ( confirm ( ) )
{
2007-12-17 22:14:47 +08:00
my $ command = "rm -rf $fs_monitor" ;
2007-09-05 00:08:55 +08:00
executeShellCommand ( $ command ) ;
2005-12-16 18:05:29 +08:00
}
}
}
2009-10-09 04:23:45 +08:00
my $ deleteMonitorSql = "delete from Monitors where Id = ?" ;
my $ deleteMonitorSth = $ dbh - > prepare_cached ( $ deleteMonitorSql ) or Fatal ( "Can't prepare '$deleteMonitorSql': " . $ dbh - > errstr ( ) ) ;
my $ deleteEventSql = "delete from Events where Id = ?" ;
my $ deleteEventSth = $ dbh - > prepare_cached ( $ deleteEventSql ) or Fatal ( "Can't prepare '$deleteEventSql': " . $ dbh - > errstr ( ) ) ;
my $ deleteFramesSql = "delete from Frames where EventId = ?" ;
my $ deleteFramesSth = $ dbh - > prepare_cached ( $ deleteFramesSql ) or Fatal ( "Can't prepare '$deleteFramesSql': " . $ dbh - > errstr ( ) ) ;
my $ deleteStatsSql = "delete from Stats where EventId = ?" ;
my $ deleteStatsSth = $ dbh - > prepare_cached ( $ deleteStatsSql ) or Fatal ( "Can't prepare '$deleteStatsSql': " . $ dbh - > errstr ( ) ) ;
2005-12-16 18:05:29 +08:00
while ( my ( $ db_monitor , $ db_events ) = each ( %$ db_monitors ) )
{
if ( my $ fs_events = $ fs_monitors - > { $ db_monitor } )
{
if ( $ db_events )
{
while ( my ( $ db_event , $ age ) = each ( %$ db_events ) )
{
if ( ! defined ( $ fs_events - > { $ db_event } ) && ( $ age > MIN_AGE ) )
{
aud_print ( "Database event '$db_monitor/$db_event' does not exist in filesystem" ) ;
if ( confirm ( ) )
{
2009-10-09 04:23:45 +08:00
my $ res = $ deleteEventSth - > execute ( $ db_event ) or Fatal ( "Can't execute: " . $ deleteEventSth - > errstr ( ) ) ;
$ res = $ deleteFramesSth - > execute ( $ db_event ) or Fatal ( "Can't execute: " . $ deleteFramesSth - > errstr ( ) ) ;
$ res = $ deleteStatsSth - > execute ( $ db_event ) or Fatal ( "Can't execute: " . $ deleteStatsSth - > errstr ( ) ) ;
2005-12-16 18:05:29 +08:00
}
}
}
}
}
else
{
#aud_print( "Database monitor '$db_monitor' does not exist in filesystem" );
#if ( confirm() )
#{
# We don't actually do this in case it's new
2009-10-09 04:23:45 +08:00
#my $res = $deleteMonitorSth->execute( $db_monitor ) or Fatal( "Can't execute: ".$deleteMonitorSth->errstr() );
2005-12-16 18:05:29 +08:00
#}
}
}
2009-10-09 04:23:45 +08:00
# Remove orphaned events (with no frames)
my $ selectOrphanedEventsSql = "select * from Events as E left join Frames as F on (E.Id = F.EventId) where isnull(F.EventId) and now() - interval " . MIN_AGE . " second > E.StartTime" ;
my $ selectOrphanedEventsSth = $ dbh - > prepare_cached ( $ selectOrphanedEventsSql ) or Fatal ( "Can't prepare '$selectOrphanedEventsSql': " . $ dbh - > errstr ( ) ) ;
$ res = $ selectOrphanedEventsSth - > execute ( ) or Fatal ( "Can't execute: " . $ selectOrphanedEventsSth - > errstr ( ) ) ;
while ( my $ event = $ selectOrphanedEventsSth - > fetchrow_hashref ( ) )
{
aud_print ( "Found orphaned event with no frame records '$event->{Id}'" ) ;
if ( confirm ( ) )
{
$ res = $ deleteEventSth - > execute ( $ event - > { Id } ) or Fatal ( "Can't execute: " . $ deleteEventSth - > errstr ( ) ) ;
}
}
$ selectOrphanedEventsSth - > finish ( ) ;
# Remove orphaned frame records
my $ selectOrphanedFramesSql = "select distinct EventId from Frames where EventId not in (select Id from Events)" ;
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 ( ) )
2005-12-16 18:05:29 +08:00
{
aud_print ( "Found orphaned frame records for event '$frame->{EventId}'" ) ;
if ( confirm ( ) )
{
2009-10-09 04:23:45 +08:00
$ res = $ deleteFramesSth - > execute ( $ frame - > { EventId } ) or Fatal ( "Can't execute: " . $ deleteFramesSth - > errstr ( ) ) ;
2005-12-16 18:05:29 +08:00
}
}
2009-10-09 04:23:45 +08:00
$ selectOrphanedFramesSth - > finish ( ) ;
2005-12-16 18:05:29 +08:00
2009-10-09 04:23:45 +08:00
# Remove orphaned stats records
my $ selectOrphanedStatsSql = "select distinct EventId from Stats where EventId not in (select Id from Events)" ;
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 ( ) )
2005-12-16 18:05:29 +08:00
{
aud_print ( "Found orphaned statistic records for event '$stat->{EventId}'" ) ;
if ( confirm ( ) )
{
2009-10-09 04:23:45 +08:00
$ res = $ deleteStatsSth - > execute ( $ stat - > { EventId } ) or Fatal ( "Can't execute: " . $ deleteStatsSth - > errstr ( ) ) ;
2005-12-16 18:05:29 +08:00
}
}
2009-10-09 04:23:45 +08:00
$ selectOrphanedStatsSth - > finish ( ) ;
2005-12-16 18:05:29 +08:00
# New audit to close any events that were left open for longer than MIN_AGE seconds
2009-10-09 04:23:45 +08:00
my $ selectUnclosedEventsSql = "select E.Id, 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, M.EventPrefix as Prefix from Events as E left join Monitors as M on E.MonitorId = M.Id inner join Frames as F on E.Id = F.EventId where isnull(E.Frames) or isnull(E.EndTime) group by E.Id having EndTime < (now() - interval " . MIN_AGE . " second)" ;
my $ selectUnclosedEventsSth = $ dbh - > prepare_cached ( $ selectUnclosedEventsSql ) or Fatal ( "Can't prepare '$selectUnclosedEventsSql': " . $ dbh - > errstr ( ) ) ;
my $ updateUnclosedEventsSql = "update Events set Name = ?, EndTime = ?, Length = ?, Frames = ?, AlarmFrames = ?, TotScore = ?, AvgScore = ?, MaxScore = ?, Notes = concat_ws( ' ', Notes, ? ) where Id = ?" ;
my $ updateUnclosedEventsSql = $ 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 ( ) )
2005-12-16 18:05:29 +08:00
{
aud_print ( "Found open event '$event->{Id}'" ) ;
if ( confirm ( 'close' , 'closing' ) )
{
2009-10-09 04:23:45 +08:00
$ res = $ updateUnclosedEventsSql - > execute ( sprintf ( "%s%d%s" , $ event - > { Prefix } , $ event - > { Id } , RECOVER_TAG ) , $ event - > { EndTime } , $ event - > { Length } , $ event - > { Frames } , $ event - > { AlarmFrames } , $ event - > { TotScore } , $ event - > { AlarmFrames } ? int ( $ event - > { TotScore } / $ event - > { AlarmFrames } ) : 0 , $ event - > { MaxScore } , RECOVER_TEXT , $ event - > { Id } ) or Fatal ( "Can't execute: " . $ updateUnclosedEventsSql - > errstr ( ) ) ;
2005-12-16 18:05:29 +08:00
}
}
2009-10-09 04:23:45 +08:00
$ selectUnclosedEventsSth - > finish ( ) ;
2005-12-16 18:05:29 +08:00
# Now delete any old image files
if ( my @ old_files = grep { - M > $ max_image_age } <$image_path/*.{jpg,gif,wbmp}> )
{
aud_print ( "Deleting " . int ( @ old_files ) . " old images\n" ) ;
my $ untainted_old_files = join ( ";" , @ old_files ) ;
( $ untainted_old_files ) = ( $ untainted_old_files =~ /^(.*)$/ ) ;
unlink ( split ( ";" , $ untainted_old_files ) ) ;
}
2007-08-30 02:11:09 +08:00
# Now delete any old swap files
sub deleteSwapImage
{
my $ file = $ _ ;
if ( $ file !~ /^zmswap-/ )
{
return ;
}
# Ignore directories
if ( - d $ file )
{
return ;
}
if ( - M $ file > $ max_swap_age )
{
Debug ( "Deleting $file" ) ;
#unlink( $file );
}
}
( my $ swap_image_root ) = ( $ swap_image_path =~ /^(.*)$/ ) ; # De-taint
File::Find:: find ( { wanted = > \ & deleteSwapImage , untaint = > 1 } , $ swap_image_root ) ;
2006-01-12 23:38:24 +08:00
sleep ( ZM_AUDIT_CHECK_INTERVAL ) if ( $ continuous ) ;
} while ( $ continuous ) ;