Merge pull request #795 from onlyjob/PBP

zmaudit.pl, zmcamtool.pl: pod2usage, PBP/5 + readability
This commit is contained in:
Isaac Connor 2015-04-10 13:02:19 -04:00
commit 2db94d35f6
2 changed files with 478 additions and 336 deletions

View File

@ -20,15 +20,33 @@
# 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.
#
=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
-r, --report - Just report don't actually do anything
-i, --interactive - Ask before applying any changes
-c, --continuous - Run continuously
-v, --version - Print the installed version of ZoneMinder
=cut
use strict;
use bytes;
@ -56,9 +74,13 @@ use POSIX;
use File::Find;
use Time::HiRes qw/gettimeofday/;
use Getopt::Long;
use autouse 'Pod::Usage'=>qw(pod2usage);
use constant IMAGE_PATH => $Config{ZM_PATH_WEB}.'/'.$Config{ZM_DIR_IMAGES};
use constant EVENT_PATH => ($Config{ZM_DIR_EVENTS}=~m|/|)?$Config{ZM_DIR_EVENTS}:($Config{ZM_PATH_WEB}.'/'.$Config{ZM_DIR_EVENTS});
use constant EVENT_PATH => ($Config{ZM_DIR_EVENTS}=~m|/|)
? $Config{ZM_DIR_EVENTS}
: ($Config{ZM_PATH_WEB}.'/'.$Config{ZM_DIR_EVENTS})
;
$| = 1;
@ -71,39 +93,24 @@ my $interactive = 0;
my $continuous = 0;
my $version;
sub usage
{
print( "
Usage: zmaudit.pl [-r,-report|-i,-interactive]
Parameters are :-
-r, --report - Just report don't actually do anything
-i, --interactive - Ask before applying any changes
-c, --continuous - Run continuously
-v, --version - Print the installed version of ZoneMinder
");
exit( -1 );
}
sub aud_print( $ );
sub confirm( ;$$ );
sub deleteSwapImage();
logInit();
logSetSignal();
if ( !GetOptions( report=>\$report, interactive=>\$interactive, continuous=>\$continuous, version=>\$version ) )
{
usage();
}
GetOptions(
'report' =>\$report,
'interactive' =>\$interactive,
'continuous' =>\$continuous,
'version' =>\$version
) or pod2usage(-exitstatus => -1);
if ( $version ) {
print( ZoneMinder::Base::ZM_VERSION . "\n");
exit(0);
print( ZoneMinder::Base::ZM_VERSION . "\n");
exit(0);
}
if ( ($report + $interactive + $continuous) > 1 )
{
print( STDERR "Error, only one option may be specified\n" );
usage();
pod2usage(-exitstatus => -1);
}
my $dbh = zmDbConnect();
@ -118,31 +125,38 @@ my $swap_image_path = $Config{ZM_PATH_SWAP};
my $loop = 1;
my $cleaned = 0;
MAIN: while( $loop ) {
while ( ! ( $dbh and $dbh->ping() ) ) {
$dbh = zmDbConnect();
while ( ! ( $dbh and $dbh->ping() ) ) {
$dbh = zmDbConnect();
last if $dbh;
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
} # end while can't connect to the db
last if $dbh;
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
} # end while can't connect to the db
my $db_monitors;
my $db_monitors;
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 $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() );
$cleaned = 0;
my $res = $monitorSelectSth->execute() or Fatal( "Can't execute: ".$monitorSelectSth->errstr() );
my $res = $monitorSelectSth->execute()
or Fatal( "Can't execute: ".$monitorSelectSth->errstr() );
while( my $monitor = $monitorSelectSth->fetchrow_hashref() )
{
Debug( "Found database monitor '$monitor->{Id}'" );
my $db_events = $db_monitors->{$monitor->{Id}} = {};
my $res = $eventSelectSth->execute( $monitor->{Id} ) or Fatal( "Can't execute: ".$eventSelectSth->errstr() );
my $res = $eventSelectSth->execute( $monitor->{Id} )
or Fatal( "Can't execute: ".$eventSelectSth->errstr() );
while ( my $event = $eventSelectSth->fetchrow_hashref() )
{
$db_events->{$event->{Id}} = $event->{Age};
@ -151,7 +165,7 @@ MAIN: while( $loop ) {
}
my $fs_monitors;
foreach my $monitor ( <[0-9]*> )
foreach my $monitor ( glob("[0-9]*") )
{
Debug( "Found filesystem monitor '$monitor'" );
my $fs_events = $fs_monitors->{$monitor} = {};
@ -159,12 +173,13 @@ MAIN: while( $loop ) {
if ( $Config{ZM_USE_DEEP_STORAGE} )
{
foreach my $day_dir ( <$monitor_dir/*/*/*> )
foreach my $day_dir ( glob("$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': $!" );
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;
@ -256,7 +271,7 @@ MAIN: while( $loop ) {
}
my $monitor_links;
foreach my $link ( <*> )
foreach my $link ( glob("*") )
{
next if ( !-l $link );
next if ( -e $link );
@ -274,13 +289,17 @@ MAIN: while( $loop ) {
$cleaned = 0;
my $deleteMonitorSql = "delete low_priority from Monitors where Id = ?";
my $deleteMonitorSth = $dbh->prepare_cached( $deleteMonitorSql ) or Fatal( "Can't prepare '$deleteMonitorSql': ".$dbh->errstr() );
my $deleteMonitorSth = $dbh->prepare_cached( $deleteMonitorSql )
or Fatal( "Can't prepare '$deleteMonitorSql': ".$dbh->errstr() );
my $deleteEventSql = "delete low_priority from Events where Id = ?";
my $deleteEventSth = $dbh->prepare_cached( $deleteEventSql ) or Fatal( "Can't prepare '$deleteEventSql': ".$dbh->errstr() );
my $deleteEventSth = $dbh->prepare_cached( $deleteEventSql )
or Fatal( "Can't prepare '$deleteEventSql': ".$dbh->errstr() );
my $deleteFramesSql = "delete low_priority from Frames where EventId = ?";
my $deleteFramesSth = $dbh->prepare_cached( $deleteFramesSql ) or Fatal( "Can't prepare '$deleteFramesSql': ".$dbh->errstr() );
my $deleteFramesSth = $dbh->prepare_cached( $deleteFramesSql )
or Fatal( "Can't prepare '$deleteFramesSql': ".$dbh->errstr() );
my $deleteStatsSql = "delete low_priority from Stats where EventId = ?";
my $deleteStatsSth = $dbh->prepare_cached( $deleteStatsSql ) or Fatal( "Can't prepare '$deleteStatsSql': ".$dbh->errstr() );
my $deleteStatsSth = $dbh->prepare_cached( $deleteStatsSql )
or Fatal( "Can't prepare '$deleteStatsSql': ".$dbh->errstr() );
while ( my ( $db_monitor, $db_events ) = each(%$db_monitors) )
{
if ( my $fs_events = $fs_monitors->{$db_monitor} )
@ -294,9 +313,12 @@ MAIN: while( $loop ) {
aud_print( "Database event '$db_monitor/$db_event' does not exist in filesystem" );
if ( confirm() )
{
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() );
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() );
$cleaned = 1;
}
}
@ -309,7 +331,8 @@ MAIN: while( $loop ) {
#if ( confirm() )
#{
# We don't actually do this in case it's new
#my $res = $deleteMonitorSth->execute( $db_monitor ) or Fatal( "Can't execute: ".$deleteMonitorSth->errstr() );
#my $res = $deleteMonitorSth->execute( $db_monitor )
# or Fatal( "Can't execute: ".$deleteMonitorSth->errstr() );
#$cleaned = 1;
#}
}
@ -318,15 +341,20 @@ MAIN: while( $loop ) {
# Remove orphaned events (with no monitor)
$cleaned = 0;
my $selectOrphanedEventsSql = "select Events.Id, Events.Name from Events left join Monitors on (Events.MonitorId = Monitors.Id) where isnull(Monitors.Id)";
my $selectOrphanedEventsSth = $dbh->prepare_cached( $selectOrphanedEventsSql ) or Fatal( "Can't prepare '$selectOrphanedEventsSql': ".$dbh->errstr() );
$res = $selectOrphanedEventsSth->execute() or Fatal( "Can't execute: ".$selectOrphanedEventsSth->errstr() );
my $selectOrphanedEventsSql = "SELECT Events.Id, Events.Name
FROM Events LEFT JOIN Monitors ON (Events.MonitorId = Monitors.Id)
WHERE isnull(Monitors.Id)";
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 monitor '$event->{Id}'" );
if ( confirm() )
{
$res = $deleteEventSth->execute( $event->{Id} ) or Fatal( "Can't execute: ".$deleteEventSth->errstr() );
$res = $deleteEventSth->execute( $event->{Id} )
or Fatal( "Can't execute: ".$deleteEventSth->errstr() );
$cleaned = 1;
}
}
@ -334,15 +362,19 @@ MAIN: while( $loop ) {
# Remove empty events (with no frames)
$cleaned = 0;
my $selectEmptyEventsSql = "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 $selectEmptyEventsSth = $dbh->prepare_cached( $selectEmptyEventsSql ) or Fatal( "Can't prepare '$selectEmptyEventsSql': ".$dbh->errstr() );
$res = $selectEmptyEventsSth->execute() or Fatal( "Can't execute: ".$selectEmptyEventsSth->errstr() );
my $selectEmptyEventsSql = "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 $selectEmptyEventsSth = $dbh->prepare_cached( $selectEmptyEventsSql )
or Fatal( "Can't prepare '$selectEmptyEventsSql': ".$dbh->errstr() );
$res = $selectEmptyEventsSth->execute()
or Fatal( "Can't execute: ".$selectEmptyEventsSth->errstr() );
while( my $event = $selectEmptyEventsSth->fetchrow_hashref() )
{
aud_print( "Found empty event with no frame records '$event->{Id}'" );
if ( confirm() )
{
$res = $deleteEventSth->execute( $event->{Id} ) or Fatal( "Can't execute: ".$deleteEventSth->errstr() );
$res = $deleteEventSth->execute( $event->{Id} )
or Fatal( "Can't execute: ".$deleteEventSth->errstr() );
$cleaned = 1;
}
}
@ -350,15 +382,19 @@ MAIN: while( $loop ) {
# Remove orphaned frame records
$cleaned = 0;
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() );
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() )
{
aud_print( "Found orphaned frame records for event '$frame->{EventId}'" );
if ( confirm() )
{
$res = $deleteFramesSth->execute( $frame->{EventId} ) or Fatal( "Can't execute: ".$deleteFramesSth->errstr() );
$res = $deleteFramesSth->execute( $frame->{EventId} )
or Fatal( "Can't execute: ".$deleteFramesSth->errstr() );
$cleaned = 1;
}
}
@ -366,32 +402,84 @@ MAIN: while( $loop ) {
# Remove orphaned stats records
$cleaned = 0;
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() );
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() )
{
aud_print( "Found orphaned statistic records for event '$stat->{EventId}'" );
if ( confirm() )
{
$res = $deleteStatsSth->execute( $stat->{EventId} ) or Fatal( "Can't execute: ".$deleteStatsSth->errstr() );
$res = $deleteStatsSth->execute( $stat->{EventId} )
or Fatal( "Can't execute: ".$deleteStatsSth->errstr() );
$cleaned = 1;
}
}
redo MAIN if ( $cleaned );
# New audit to close any events that were left open for longer than MIN_AGE seconds
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 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() );
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 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() )
{
aud_print( "Found open event '$event->{Id}'" );
if ( confirm( 'close', 'closing' ) )
{
$res = $updateUnclosedEventsSth->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: ".$updateUnclosedEventsSth->errstr() );
$res = $updateUnclosedEventsSth->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: ".$updateUnclosedEventsSth->errstr() );
}
}
@ -414,26 +502,43 @@ MAIN: while( $loop ) {
if ( $Config{ZM_LOG_DATABASE_LIMIT} =~ /^\d+$/ )
{
# Number of rows
my $selectLogRowCountSql = "select count(*) as Rows from Logs";
my $selectLogRowCountSth = $dbh->prepare_cached( $selectLogRowCountSql ) or Fatal( "Can't prepare '$selectLogRowCountSql': ".$dbh->errstr() );
$res = $selectLogRowCountSth->execute() or Fatal( "Can't execute: ".$selectLogRowCountSth->errstr() );
my $selectLogRowCountSql = "SELECT count(*) as Rows from Logs";
my $selectLogRowCountSth = $dbh->prepare_cached( $selectLogRowCountSql )
or Fatal( "Can't prepare '$selectLogRowCountSql': ".$dbh->errstr() );
$res = $selectLogRowCountSth->execute()
or Fatal( "Can't execute: ".$selectLogRowCountSth->errstr() );
my $row = $selectLogRowCountSth->fetchrow_hashref();
my $logRows = $row->{Rows};
if ( $logRows > $Config{ZM_LOG_DATABASE_LIMIT} )
{
my $deleteLogByRowsSql = "delete low_priority from Logs order by TimeKey asc limit ?";
my $deleteLogByRowsSth = $dbh->prepare_cached( $deleteLogByRowsSql ) or Fatal( "Can't prepare '$deleteLogByRowsSql': ".$dbh->errstr() );
$res = $deleteLogByRowsSth->execute( $logRows - $Config{ZM_LOG_DATABASE_LIMIT} ) or Fatal( "Can't execute: ".$deleteLogByRowsSth->errstr() );
aud_print( "Deleted ".$deleteLogByRowsSth->rows()." log table entries by count\n" ) if ( $deleteLogByRowsSth->rows() );
my $deleteLogByRowsSql = "DELETE low_priority FROM Logs ORDER BY TimeKey ASC LIMIT ?";
my $deleteLogByRowsSth = $dbh->prepare_cached( $deleteLogByRowsSql )
or Fatal( "Can't prepare '$deleteLogByRowsSql': ".$dbh->errstr() );
$res = $deleteLogByRowsSth->execute( $logRows - $Config{ZM_LOG_DATABASE_LIMIT} )
or Fatal( "Can't execute: ".$deleteLogByRowsSth->errstr() );
if ( $deleteLogByRowsSth->rows() )
{
aud_print( "Deleted ".$deleteLogByRowsSth->rows()
." log table entries by count\n" )
;
}
}
}
else
{
# Time of record
my $deleteLogByTimeSql = "delete low_priority from Logs where TimeKey < unix_timestamp(now() - interval ".$Config{ZM_LOG_DATABASE_LIMIT}.")";
my $deleteLogByTimeSth = $dbh->prepare_cached( $deleteLogByTimeSql ) or Fatal( "Can't prepare '$deleteLogByTimeSql': ".$dbh->errstr() );
$res = $deleteLogByTimeSth->execute() or Fatal( "Can't execute: ".$deleteLogByTimeSth->errstr() );
aud_print( "Deleted ".$deleteLogByTimeSth->rows()." log table entries by time\n" ) if ( $deleteLogByTimeSth->rows() );
my $deleteLogByTimeSql =
"DELETE low_priority FROM Logs
WHERE TimeKey < unix_timestamp(now() - interval ".$Config{ZM_LOG_DATABASE_LIMIT}.")";
my $deleteLogByTimeSth = $dbh->prepare_cached( $deleteLogByTimeSql )
or Fatal( "Can't prepare '$deleteLogByTimeSql': ".$dbh->errstr() );
$res = $deleteLogByTimeSth->execute()
or Fatal( "Can't execute: ".$deleteLogByTimeSth->errstr() );
if ( $deleteLogByTimeSth->rows() ){
aud_print( "Deleted ".$deleteLogByTimeSth->rows()
." log table entries by time\n" )
;
}
}
}
$loop = $continuous;
@ -443,7 +548,7 @@ MAIN: while( $loop ) {
exit( 0 );
sub aud_print( $ )
sub aud_print
{
my $string = shift;
if ( !$continuous )
@ -456,7 +561,7 @@ sub aud_print( $ )
}
}
sub confirm( ;$$ )
sub confirm
{
my $prompt = shift || "delete";
my $action = shift || "deleting";
@ -496,7 +601,7 @@ sub confirm( ;$$ )
return( $yesno );
}
sub deleteSwapImage()
sub deleteSwapImage
{
my $file = $_;

View File

@ -20,12 +20,43 @@
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#
# ==========================================================================
#
# This script provides a way to import new ptz camera controls & camera presets
# into existing zoneminder systems. This script also provides a way to export
# ptz camera controls & camera presets from an existing zoneminder system into
# a sql file, which can then be easily imported to another zoneminder system.
#
=head1 NAME
zmcamtool.pl - ZoneMinder tool to import camera controls and presets
=head1 SYNOPSIS
zmcamtool.pl [--user=<dbuser> --pass=<dbpass>]
[--import [file.sql] [--overwrite]]
[--export [name]]
[--topreset id [--noregex]]
=head1 DESCRIPTION
This script provides a way to import new ptz camera controls & camera presets
into existing zoneminder systems. This script also provides a way to export
ptz camera controls & camera presets from an existing zoneminder system into
a sql file, which can then be easily imported to another zoneminder system.
=head1 OPTIONS
--export - Export all camera controls and presets to STDOUT.
Optionally specify a control or preset name.
--import [file.sql] - Import new camera controls and presets found in
zm_create.sql into the ZoneMinder dB.
Optionally specify an alternate sql file to read from.
--overwrite - Overwrite any existing controls or presets.
with the same name as the new controls or presets.
--topreset id - Copy a monitor to a Camera Preset given the monitor id.
--noregex - Do not try to find and replace fields such as usernames,
passwords, IP addresses, etc with generic placeholders
when converting a monitor to a preset.
--help - Print usage information.
--user=<dbuser> - Alternate dB user with privileges to alter dB.
--pass=<dbpass> - Password of alternate dB user with privileges to alter dB.
=cut
use strict;
use bytes;
@ -35,6 +66,7 @@ use ZoneMinder::Logger qw(:all);
use ZoneMinder::Database qw(:all);
use DBI;
use Getopt::Long;
use autouse 'Pod::Usage'=>qw(pod2usage);
$ENV{PATH} = '/bin:/usr/bin:/usr/local/bin';
$ENV{SHELL} = '/bin/sh' if exists $ENV{SHELL};
@ -57,256 +89,258 @@ my $dbUser = $Config{ZM_DB_USER};
my $dbPass = $Config{ZM_DB_PASS};
my $version = 0;
# Process commandline parameters with getopt long
if ( !GetOptions( 'export'=>\$export, 'import'=>\$import, 'overwrite'=>\$overwrite, 'help'=>\$help, 'topreset'=>\$topreset, 'noregex'=>\$noregex, 'user:s'=>\$dbUser, 'pass:s'=>\$dbPass, 'version'=>\$version ) ) {
Usage();
}
GetOptions(
'export' =>\$export,
'import' =>\$import,
'overwrite' =>\$overwrite,
'help' =>\$help,
'topreset' =>\$topreset,
'noregex' =>\$noregex,
'user:s' =>\$dbUser,
'pass:s' =>\$dbPass,
'version' =>\$version
) or pod2usage(-exitstatus => -1);
$Config{ZM_DB_USER} = $dbUser;
$Config{ZM_DB_PASS} = $dbPass;
if ( $version ) {
print( ZoneMinder::Base::ZM_VERSION . "\n");
exit(0);
print( ZoneMinder::Base::ZM_VERSION . "\n");
exit(0);
}
# Check to make sure commandline params make sense
if ( ((!$help) && ($import + $export + $topreset) != 1 )) {
print( STDERR qq/Please give only one of the following: "import", "export", or "topreset".\n/ );
Usage();
print( STDERR qq/Please give only one of the following: "import", "export", or "topreset".\n/ );
pod2usage(-exitstatus => -1);
}
if ( ($export)&&($overwrite) ) {
print( "Warning: Overwrite parameter ignored during an export.\n");
print( "Warning: Overwrite parameter ignored during an export.\n");
}
if ( ($noregex)&&(!$topreset) ) {
print( qq/Warning: Noregex parameter only applies when "topreset" parameter is also set. Ignoring.\n/);
print( qq/Warning: Noregex parameter only applies when "topreset" parameter is also set. Ignoring.\n/);
}
if ( ($topreset)&&($ARGV[0] !~ /\d\d*/) ) {
print( STDERR qq/Parameter "topreset" requires a valid monitor ID.\n/ );
Usage();
print( STDERR qq/Parameter "topreset" requires a valid monitor ID.\n/ );
pod2usage(-exitstatus => -1);
}
# Call the appropriate subroutine based on the params given on the commandline
if ($help) {
Usage();
pod2usage(-exitstatus => -1);
}
if ($export) {
exportsql();
exportsql();
}
if ($import) {
importsql();
importsql();
}
if ($topreset) {
toPreset();
toPreset();
}
###############
# SUBROUTINES #
###############
# Usage subroutine help text
sub Usage
{
die("
USAGE:
zmcamtool.pl [--user=<dbuser> --pass=<dbpass>]
[--import [file.sql] [--overwrite]]
[--export [name]]
[--topreset id [--noregex]]
PARAMETERS:
--export - Export all camera controls and presets to STDOUT.
Optionally specify a control or preset name.
--import [file.sql] - Import new camera controls and presets found in
zm_create.sql into the ZoneMinder dB.
Optionally specify an alternate sql file to read from.
--overwrite - Overwrite any existing controls or presets.
with the same name as the new controls or presets.
--topreset id - Copy a monitor to a Camera Preset given the monitor id.
--noregex - Do not try to find and replace fields such as usernames,
passwords, ip addresses, etc with generic placeholders
when converting a monitor to a preset.
--help - Print usage information.
--user=<dbuser> - Alternate dB user with privileges to alter dB.
--pass=<dbpass> - Password of alternate dB user with privileges to alter dB.
\n");
}
# Execute a pre-built sql select query
sub selectQuery
{
my $dbh = shift;
my $sql = shift;
my $monitorid = shift;
my $dbh = shift;
my $sql = shift;
my $monitorid = shift;
my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() );
my $res = $sth->execute($monitorid) or die( "Can't execute: ".$sth->errstr() );
my $sth = $dbh->prepare_cached( $sql )
or die( "Can't prepare '$sql': ".$dbh->errstr() );
my $res = $sth->execute($monitorid)
or die( "Can't execute: ".$sth->errstr() );
my @data = $sth->fetchrow_array();
$sth->finish();
my @data = $sth->fetchrow_array();
$sth->finish();
return @data;
return @data;
}
# Exectute a pre-built sql query
sub runQuery
{
my $dbh = shift;
my $sql = shift;
my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() );
my $res = $sth->execute() or die( "Can't execute: ".$sth->errstr() );
$sth->finish();
my $dbh = shift;
my $sql = shift;
my $sth = $dbh->prepare_cached( $sql )
or die( "Can't prepare '$sql': ".$dbh->errstr() );
my $res = $sth->execute()
or die( "Can't execute: ".$sth->errstr() );
$sth->finish();
return $res;
return $res;
}
# Build and execute a sql insert query
sub insertQuery
{
my $dbh = shift;
my $tablename = shift;
my @data = @_;
my $dbh = shift;
my $tablename = shift;
my @data = @_;
my $sql = "insert into $tablename values (NULL,".(join ", ", ("?") x @data).")"; # Add "?" for each array element
my $sql = "INSERT INTO $tablename VALUES (NULL,"
.(join ", ", ("?") x @data).")"; # Add "?" for each array element
my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() );
my $res = $sth->execute(@data) or die( "Can't execute: ".$sth->errstr() );
$sth->finish();
my $sth = $dbh->prepare_cached( $sql )
or die( "Can't prepare '$sql': ".$dbh->errstr() );
my $res = $sth->execute(@data)
or die( "Can't execute: ".$sth->errstr() );
$sth->finish();
return $res;
return $res;
}
# Build and execute a sql delete query
sub deleteQuery
{
my $dbh = shift;
my $sqltable = shift;
my $sqlname = shift;
my $dbh = shift;
my $sqltable = shift;
my $sqlname = shift;
my $sql = "delete from $sqltable where Name = ?";
my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() );
my $res = $sth->execute($sqlname) or die( "Can't execute: ".$sth->errstr() );
$sth->finish();
my $sql = "DELETE FROM $sqltable WHERE Name = ?";
my $sth = $dbh->prepare_cached( $sql )
or die( "Can't prepare '$sql': ".$dbh->errstr() );
my $res = $sth->execute($sqlname)
or die( "Can't execute: ".$sth->errstr() );
$sth->finish();
return $res;
return $res;
}
# Build and execute a sql select count query
sub checkExists
{
my $dbh = shift;
my $sqltable = shift;
my $sqlname = shift;
my $result = 0;
my $dbh = shift;
my $sqltable = shift;
my $sqlname = shift;
my $result = 0;
my $sql = "select count(*) from $sqltable where Name = ?";
my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() );
my $res = $sth->execute($sqlname) or die( "Can't execute: ".$sth->errstr() );
my $sql = "SELECT count(*) FROM $sqltable WHERE Name = ?";
my $sth = $dbh->prepare_cached( $sql )
or die( "Can't prepare '$sql': ".$dbh->errstr() );
my $res = $sth->execute($sqlname)
or die( "Can't execute: ".$sth->errstr() );
my $rows = $sth->fetchrow_arrayref();
$sth->finish();
my $rows = $sth->fetchrow_arrayref();
$sth->finish();
if ($rows->[0] > 0) {
$result = 1;
}
if ($rows->[0] > 0) {
$result = 1;
}
return $result;
return $result;
}
# Import camera control & presets into the zoneminder dB
sub importsql
{
my @newcontrols;
my @overwritecontrols;
my @skippedcontrols;
my @newpresets;
my @overwritepresets;
my @skippedpresets;
my %controls;
my %monitorpresets;
my @newcontrols;
my @overwritecontrols;
my @skippedcontrols;
my @newpresets;
my @overwritepresets;
my @skippedpresets;
my %controls;
my %monitorpresets;
if ($ARGV[0]) {
$sqlfile = $ARGV[0];
} else {
$sqlfile = $Config{ZM_PATH_DATA}.'/db/zm_create.sql';
}
if ($ARGV[0]) {
$sqlfile = $ARGV[0];
} else {
$sqlfile = $Config{ZM_PATH_DATA}.'/db/zm_create.sql';
}
open(my $SQLFILE,"<",$sqlfile) or die( "Can't Open file: $!\n" );
open(my $SQLFILE,"<",$sqlfile)
or die( "Can't Open file: $!\n" );
# Find and extract ptz control and monitor preset records
while (<$SQLFILE>) {
# Our regex replaces the primary key with NULL
if (s/^(INSERT INTO .*?Controls.*? VALUES \().*?(,')(.*?)(',.*)/$1NULL$2$3$4/i) {
$controls{$3} = $_;
} elsif (s/^(INSERT INTO .*?MonitorPresets.*? VALUES \().*?(,')(.*?)(',.*)/$1NULL$2$3$4/i) {
$monitorpresets{$3} = $_;
}
}
close $SQLFILE;
# Find and extract ptz control and monitor preset records
while (<$SQLFILE>) {
# Our regex replaces the primary key with NULL
if (s/^(INSERT INTO .*?Controls.*? VALUES \().*?(,')(.*?)(',.*)/$1NULL$2$3$4/i) {
$controls{$3} = $_;
} elsif (s/^(INSERT INTO .*?MonitorPresets.*? VALUES \().*?(,')(.*?)(',.*)/$1NULL$2$3$4/i) {
$monitorpresets{$3} = $_;
}
}
close $SQLFILE;
if ( ! (%controls || %monitorpresets) ) {
die( "Error: No relevant data found in $sqlfile.\n" );
}
if ( ! (%controls || %monitorpresets) ) {
die( "Error: No relevant data found in $sqlfile.\n" );
}
# Now that we've got what we were looking for, compare to what is already in the dB
# Now that we've got what we were looking for,
# compare to what is already in the dB
my $dbh = zmDbConnect();
foreach (keys %controls) {
if (!checkExists($dbh,"Controls",$_)) {
# No existing Control was found. Add new control to dB.
runQuery($dbh,$controls{$_});
push @newcontrols, $_;
} elsif ($overwrite) {
# An existing Control was found and the overwrite flag is set. Overwrite the control.
deleteQuery($dbh,"Controls",$_);
runQuery($dbh,$controls{$_});
push @overwritecontrols, $_;
} else {
# An existing Control was found and the overwrite flag was not set. Do nothing.
push @skippedcontrols, $_;
}
}
my $dbh = zmDbConnect();
foreach (keys %controls) {
if (!checkExists($dbh,"Controls",$_)) {
# No existing Control was found. Add new control to dB.
runQuery($dbh,$controls{$_});
push @newcontrols, $_;
} elsif ($overwrite) {
# An existing Control was found and the overwrite flag is set.
# Overwrite the control.
deleteQuery($dbh,"Controls",$_);
runQuery($dbh,$controls{$_});
push @overwritecontrols, $_;
} else {
# An existing Control was found and the overwrite flag was not set.
# Do nothing.
push @skippedcontrols, $_;
}
}
foreach (keys %monitorpresets) {
if (!checkExists($dbh,"MonitorPresets",$_)) {
# No existing MonitorPreset was found. Add new MonitorPreset to dB.
runQuery($dbh,$monitorpresets{$_});
push @newpresets, $_;
} elsif ($overwrite) {
# An existing MonitorPreset was found and the overwrite flag is set. Overwrite the MonitorPreset.
deleteQuery($dbh,"MonitorPresets",$_);
runQuery($dbh,$monitorpresets{$_});
push @overwritepresets, $_;
} else {
# An existing MonitorPreset was found and the overwrite flag was not set. Do nothing.
push @skippedpresets, $_;
}
}
foreach (keys %monitorpresets) {
if (!checkExists($dbh,"MonitorPresets",$_)) {
# No existing MonitorPreset was found. Add new MonitorPreset to dB.
runQuery($dbh,$monitorpresets{$_});
push @newpresets, $_;
} elsif ($overwrite) {
# An existing MonitorPreset was found and the overwrite flag is set.
# Overwrite the MonitorPreset.
deleteQuery($dbh,"MonitorPresets",$_);
runQuery($dbh,$monitorpresets{$_});
push @overwritepresets, $_;
} else {
# An existing MonitorPreset was found and the overwrite flag was
# not set. Do nothing.
push @skippedpresets, $_;
}
}
if (@newcontrols) {
print "Number of ptz camera controls added: ".scalar(@newcontrols)."\n";
}
if (@overwritecontrols) {
print "Number of existing ptz camera controls overwritten: ".scalar(@overwritecontrols)."\n";
}
if (@skippedcontrols) {
print "Number of existing ptz camera controls skipped: ".scalar(@skippedcontrols)."\n";
}
if (@newcontrols) {
print "Number of ptz camera controls added: "
.scalar(@newcontrols)."\n";
}
if (@overwritecontrols) {
print "Number of existing ptz camera controls overwritten: "
.scalar(@overwritecontrols)."\n";
}
if (@skippedcontrols) {
print "Number of existing ptz camera controls skipped: "
.scalar(@skippedcontrols)."\n";
}
if (@newpresets) {
print "Number of monitor presets added: ".scalar(@newpresets)."\n";
}
if (@overwritepresets) {
print "Number of existing monitor presets overwritten: ".scalar(@overwritepresets)."\n";
}
if (@skippedpresets) {
print "Number of existing presets skipped: ".scalar(@skippedpresets)."\n";
}
if (@newpresets) {
print "Number of monitor presets added: "
.scalar(@newpresets)."\n";
}
if (@overwritepresets) {
print "Number of existing monitor presets overwritten: "
.scalar(@overwritepresets)."\n";
}
if (@skippedpresets) {
print "Number of existing presets skipped: "
.scalar(@skippedpresets)."\n";
}
}
# Export camera controls & presets from the zoneminder dB to STDOUT
@ -317,14 +351,14 @@ my ( $host, $port ) = ( $Config{ZM_DB_HOST} =~ /^([^:]+)(?::(.+))?$/ );
my $command = "mysqldump -t --skip-opt --compact -h".$host;
$command .= " -P".$port if defined($port);
if ( $dbUser ) {
$command .= " -u".$dbUser;
if ( $dbPass ) {
$command .= " -p".$dbPass;
}
}
$command .= " -u".$dbUser;
if ( $dbPass ) {
$command .= " -p".$dbPass;
}
}
if ($ARGV[0]) {
$command .= qq( --where="Name = '$ARGV[0]'");
$command .= qq( --where="Name = '$ARGV[0]'");
}
$command .= " zm Controls MonitorPresets";
@ -332,78 +366,81 @@ $command .= " zm Controls MonitorPresets";
my $output = qx($command);
my $status = $? >> 8;
if ( $status || logDebugging() ) {
chomp( $output );
print( "Output: $output\n" );
chomp( $output );
print( "Output: $output\n" );
}
if ( $status ) {
die( "Command '$command' exited with status: $status\n" );
die( "Command '$command' exited with status: $status\n" );
} else {
# NULLify the primary keys before printing the output to STDOUT
$output =~ s/VALUES \((.*?),'/VALUES \(NULL,'/ig;
print $output;
}
# NULLify the primary keys before printing the output to STDOUT
$output =~ s/VALUES \((.*?),'/VALUES \(NULL,'/ig;
print $output;
}
}
sub toPreset
{
my $dbh = zmDbConnect();
my $monitorid = $ARGV[0];
my $dbh = zmDbConnect();
my $monitorid = $ARGV[0];
# Grap the following fields from the Monitors table
my $sql = "select
Name,
Type,
Device,
Channel,
Format,
Protocol,
Method,
Host,
Port,
Path,
SubPath,
Width,
Height,
Palette,
MaxFPS,
Controllable,
ControlId,
ControlDevice,
ControlAddress,
DefaultRate,
DefaultScale
from Monitors where Id = ?";
my @data = selectQuery($dbh,$sql,$monitorid);
# Grap the following fields from the Monitors table
my $sql = "SELECT
Name,
Type,
Device,
Channel,
Format,
Protocol,
Method,
Host,
Port,
Path,
SubPath,
Width,
Height,
Palette,
MaxFPS,
Controllable,
ControlId,
ControlDevice,
ControlAddress,
DefaultRate,
DefaultScale
FROM Monitors WHERE Id = ?";
my @data = selectQuery($dbh,$sql,$monitorid);
if (!@data) {
die( "Error: Monitor Id $monitorid does not appear to exist in the database.\n" );
}
if (!@data) {
die( "Error: Monitor Id $monitorid does not appear to exist in the database.\n" );
}
# Attempt to search for and replace system specific values such as ip addresses, ports, usernames, etc. with generic placeholders
if (!$noregex) {
foreach (@data) {
s/\b(?:\d{1,3}\.){3}\d{1,3}\b/<ip-address>/; # ip address
s/<ip-address>:(6553[0-5]|655[0-2]\d|65[0-4]\d\d|6[0-4]\d{3}|[1-5]\d{4}|[1-9]\d{0,3}|0)$/<ip-address>:<port>/; # tcpip port
s/\/\/.*:.*@/\/\/<username>:<pwd>@/; # user & pwd preceeding an ip address
s/(&|\?)(user|username)=\w\w*(&|\?)/$1$2=<username>$3/i; # username embeded in url
s/(&|\?)(pwd|password)=\w\w*(&|\?)/$1$2=<pwd>$3/i; # password embeded in url
s/\w\w*:\w\w*/<username>:<pwd>/; # user & pwd in their own field
s/\/dev\/video\d\d*/\/dev\/video<?>/; # local video devices
}
}
# Attempt to search for and replace system specific values such as
# ip addresses, ports, usernames, etc. with generic placeholders
if (!$noregex) {
foreach (@data) {
s/\b(?:\d{1,3}\.){3}\d{1,3}\b/<ip-address>/; # ip address
s/<ip-address>:(6553[0-5]|655[0-2]\d|65[0-4]\d\d|6[0-4]\d{3}|[1-5]\d{4}|[1-9]\d{0,3}|0)$/<ip-address>:<port>/; # tcpip port
s/\/\/.*:.*@/\/\/<username>:<pwd>@/; # user & pwd preceeding an ip address
s/(&|\?)(user|username)=\w\w*(&|\?)/$1$2=<username>$3/i; # username embeded in url
s/(&|\?)(pwd|password)=\w\w*(&|\?)/$1$2=<pwd>$3/i; # password embeded in url
s/\w\w*:\w\w*/<username>:<pwd>/; # user & pwd in their own field
s/\/dev\/video\d\d*/\/dev\/video<?>/; # local video devices
}
}
if (!checkExists($dbh,"MonitorPresets",$data[0])) {
# No existing Preset was found. Add new Preset to dB.
print "Adding new preset: $data[0]\n";
insertQuery($dbh,"MonitorPresets",@data);
} elsif ($overwrite) {
# An existing Control was found and the overwrite flag is set. Overwrite the control.
print "Existing preset $data[0] detected.\nOverwriting...\n";
deleteQuery($dbh,"MonitorPresets",$data[0]);
insertQuery($dbh,"MonitorPresets",@data);
} else {
# An existing Control was found and the overwrite flag was not set. Do nothing.
print "Existing preset $data[0] detected and overwrite flag not set.\nSkipping...\n";
}
if (!checkExists($dbh,"MonitorPresets",$data[0])) {
# No existing Preset was found. Add new Preset to dB.
print "Adding new preset: $data[0]\n";
insertQuery($dbh,"MonitorPresets",@data);
} elsif ($overwrite) {
# An existing Control was found and the overwrite flag is set.
# Overwrite the control.
print "Existing preset $data[0] detected.\nOverwriting...\n";
deleteQuery($dbh,"MonitorPresets",$data[0]);
insertQuery($dbh,"MonitorPresets",@data);
} else {
# An existing Control was found and the overwrite flag was not set.
# Do nothing.
print "Existing preset $data[0] detected and overwrite flag not set.\nSkipping...\n";
}
}