code review and rewrite

This commit is contained in:
Marko Oldenburg 2021-04-23 12:34:27 +02:00
parent 622b6bcf4c
commit 06b109389d

View File

@ -1,12 +1,12 @@
################################################################ ################################################################
# Developed with Kate # Developed with Kate
# #
# (c) 2012-2020 Copyright: Martin Fischer (m_fischer at gmx dot de) # (c) 2012-2021 Copyright: Martin Fischer (m_fischer at gmx dot de)
# Rewrite and Maintained by Marko Oldenburg since 2019 # Rewrite and Maintained by Marko Oldenburg since 2019
# All rights reserved # All rights reserved
# #
# Contributors: # Contributors:
# - Marko Oldenburg (CoolTux) # - Marko Oldenburg (CoolTux - fhemdevelopment at cooltux dot net)
# #
# This script free software; you can redistribute it and/or modify # This script free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by # it under the terms of the GNU General Public License as published by
@ -37,7 +37,7 @@ use FHEM::Meta;
sub backup_Initialize { sub backup_Initialize {
my %hash = ( my %hash = (
Fn => 'FHEM::backup::CommandBackup', Fn => \&FHEM::backup::CommandBackup,
Hlp => ',create a backup of fhem configuration, state and modpath' Hlp => ',create a backup of fhem configuration, state and modpath'
); );
@ -54,30 +54,6 @@ use strict;
use warnings; use warnings;
use FHEM::Meta; use FHEM::Meta;
use GPUtils qw(GP_Import)
; # wird für den Import der FHEM Funktionen aus der fhem.pl benötigt
## Import der FHEM Funktionen
BEGIN {
GP_Import(
qw(AttrVal
InternalVal
gettimeofday
ResolveDateWildcards
readingsSingleUpdate
attr
Log
fhemForked
defs
configDBUsed
TimeNow
BC_searchTelnet
BC_telnetDevice
DoTrigger
devspec2array
configDB)
);
}
my @pathname; my @pathname;
@ -86,31 +62,38 @@ sub CommandBackup {
my $param = shift; my $param = shift;
my $byUpdate = ( $param && $param eq 'startedByUpdate' ); my $byUpdate = ( $param && $param eq 'startedByUpdate' );
my $modpath = AttrVal( 'global', 'modpath', '.' ); my $modpath = ::AttrVal( 'global', 'modpath', '.' );
my $configfile = AttrVal( 'global', 'configfile', $modpath . '/fhem.cfg' ); my $configfile = ::AttrVal( 'global', 'configfile', $modpath . '/fhem.cfg' );
my $statefile = AttrVal( 'global', 'statefile', $modpath . '/log/fhem.save' ); my $statefile = ::AttrVal( 'global', 'statefile', $modpath . '/log/fhem.save' );
my $dir = AttrVal( 'global', 'backupdir', $modpath . '/backup'); my $dir = ::AttrVal( 'global', 'backupdir', $modpath . '/backup');
my $now = gettimeofday(); my $now = ::gettimeofday();
my @t = localtime($now); my @t = localtime($now);
my $dateTime = dateTime(); my $dateTime = dateTime();
$statefile = ResolveDateWildcards( $statefile, @t ); $statefile = ::ResolveDateWildcards( $statefile, @t );
# prevent duplicate entries in backup list for default config, forum #54826 # prevent duplicate entries in backup list for default config, forum #54826
$configfile = '' if ( $configfile eq 'fhem.cfg' || configDBUsed() ); $configfile = '' if ( $configfile eq 'fhem.cfg' || ::configDBUsed() );
$statefile = '' if ( $statefile eq './log/fhem.save' ); $statefile = '' if ( $statefile eq './log/fhem.save' );
my $msg; my $msg;
my $ret; my $ret;
my ($err,$backupdir) = createBackupDir( $dir, $modpath ); my ($err,$backupdir) = createBackupDir( $dir, $modpath );
return Log( 1, 'ERROR: if create backup directory!' ) return ::Log(1, q(ERROR: if create backup directory!))
if ( defined($err) and $err ); if ( defined($err) && $err );
Log( 1, 'NOTE: make sure you have a database backup!' ) ::Log(1, q(NOTE: make sure you have a database backup!))
if ( configDBUsed() ); if ( ::configDBUsed() );
$ret = addConfDBFiles( $configfile, $statefile ); $ret = addConfDBFiles( $configfile, $statefile );
return ::Log(1, qq(Backup ERROR - addConfDBFiles: $ret))
if ( defined($ret)
&& $ret =~ m{\ACan\'t open.*}xms);
$ret = readModpath( $modpath, $backupdir ); $ret = readModpath( $modpath, $backupdir );
return ::Log(1, qq(Backup ERROR - readModpath: $ret))
if ( defined($ret)
&& $ret =~ m{\ACan\'t open.*}xms);
## add all logfile path to pathname array ## add all logfile path to pathname array
$ret = addLogPathToPathnameArray(); $ret = addLogPathToPathnameArray();
@ -124,13 +107,13 @@ sub CommandBackup {
$ret = createArchiv( $backupdir, $cl, $byUpdate, $dateTime ); $ret = createArchiv( $backupdir, $cl, $byUpdate, $dateTime );
### support for backupToStorage Modul ### support for backupToStorage Modul
readingsSingleUpdate($defs{join(' ', ::readingsSingleUpdate($::defs{join(' ',
devspec2array('TYPE=backupToStorage'))} ::devspec2array('TYPE=backupToStorage'))}
, 'fhemBackupFile' , 'fhemBackupFile'
, "$backupdir/FHEM-$dateTime.tar.gz" , "$backupdir/FHEM-$dateTime.tar.gz"
, 0 , 0
) )
if ( devspec2array('TYPE=backupToStorage') > 0 ); if ( ::devspec2array('TYPE=backupToStorage') > 0 );
@pathname = []; @pathname = [];
undef @pathname; undef @pathname;
@ -144,30 +127,30 @@ sub addConfDBFiles {
my $ret; my $ret;
if ( configDBUsed() ) { if ( ::configDBUsed() ) {
# add configDB configuration file # add ::configDB configuration file
push( @pathname, 'configDB.conf' ); push( @pathname, 'configDB.conf' );
Log( 2, 'backup include: \'configDB.conf\'' ); ::Log(2, q(backup include: 'configDB.conf'));
## check if sqlite db file outside of modpath ## check if sqlite db file outside of modpath
if ( $configDB{type} eq 'SQLITE' if ( $::configDB{type} eq 'SQLITE'
and defined($configDB{filename}) && defined($::configDB{filename})
and $configDB{filename} !~ m#^[a-zA-Z].*|^\.\/[a-zA-Z].*# ) && $::configDB{filename} !~ m{\A[a-zA-Z].*|^\.\/[a-zA-Z].*}xms )
{ {
## backup sqlite db file ## backup sqlite db file
Log( 2, 'backup include SQLite DB File: ' . $configDB{filename} ); ::Log(2, qq(backup include SQLite DB File: $::configDB{filename}));
push( @pathname, $configDB{filename} ); push( @pathname, $::configDB{filename} );
} }
} }
else { else {
# get pathnames to archiv # get pathnames to archiv
push( @pathname, $configfile ) if ($configfile); push( @pathname, $configfile ) if ($configfile);
Log( 2, 'backup include: ' . $configfile ) ::Log(2, qq(backup include: $configfile))
if ($configfile); if ($configfile);
$ret = parseConfig($configfile); $ret = parseConfig($configfile);
push( @pathname, $statefile ) if ($statefile); push( @pathname, $statefile ) if ($statefile);
Log( 2, 'backup include: ' . $statefile ) ::Log(2, qq(backup include: $statefile))
if ($statefile); if ($statefile);
} }
@ -180,11 +163,11 @@ sub createBackupDir {
my $msg; my $msg;
my $ret; my $ret;
my $backupdir = $dir =~ m#^\.(\/.*)$# ? $modpath.$1 : $dir =~ m#^\.\.\/# ? $modpath.'/'.$dir : $dir; my $backupdir = $dir =~ m{\A\.(\/.*)\z}xms ? $modpath.$1 : $dir =~ m{\A\.\.\/}xms ? $modpath.'/'.$dir : $dir;
# create backupdir if not exists # create backupdir if not exists
if ( !-d $backupdir ) { if ( !-d $backupdir ) {
Log( 4, 'backup create backupdir: ' . $backupdir ); ::Log(4, qq(backup create backupdir: $backupdir));
$ret = `(mkdir -p $backupdir) 2>&1`; $ret = `(mkdir -p $backupdir) 2>&1`;
if ($ret) { if ($ret) {
chomp($ret); chomp($ret);
@ -207,23 +190,20 @@ sub parseConfig {
if ( !open( $fh, $configfile ) ) { if ( !open( $fh, $configfile ) ) {
$msg = 'Can\'t open ' . $configfile . ': ' . $!; $msg = 'Can\'t open ' . $configfile . ': ' . $!;
Log( 1, 'backup ' . $msg ); ::Log(1, qq(backup $msg));
return $msg; return $msg;
} }
while ( my $l = <$fh> ) { while ( my $l = <$fh> ) {
$l =~ s/[\r\n]//g; $l =~ s/[\r\n]//g;
if ( $l =~ m/^\s*include\s+(\S+)\s*.*$/ ) { if ( $l =~ m{\A\s*include\s+(\S+)\s*.*\z}xms ) {
if ( -e $1 ) { if ( -e $1 ) {
push @pathname, $1; push @pathname, $1;
Log( 4, 'backup include: ' . $1 ); ::Log(4, qq(backup include: $1));
$ret = parseConfig($1); $ret = parseConfig($1);
} }
else { else {
Log( 1, ::Log(1, qq(backup configfile: $1 does not exists! File not included.));
'backup configfile: '
. $1
. ' does not exists! File not included.' );
} }
} }
} }
@ -241,17 +221,17 @@ sub readModpath {
if ( !opendir( DH, $modpath ) ) { if ( !opendir( DH, $modpath ) ) {
$msg = 'Can\'t open $modpath: ' . $!; $msg = 'Can\'t open $modpath: ' . $!;
Log( 1, 'backup ' . $msg ); ::Log(1, qq(backup $msg));
return $msg; return $msg;
} }
my @files = <$modpath/*>; my @files = <$modpath/*>;
foreach my $file (@files) { foreach my $file (@files) {
if ( $file eq $backupdir && ( -d $file || -l $file ) ) { if ( $file eq $backupdir && ( -d $file || -l $file ) ) {
Log( 4, 'backup exclude: ' . $file ); ::Log(4, qq(backup exclude: $file));
} }
else { else {
Log( 4, 'backup include: ' . $file ); ::Log(4, qq(backup include: $file));
push @pathname, $file; push @pathname, $file;
} }
} }
@ -260,7 +240,7 @@ sub readModpath {
} }
sub dateTime { sub dateTime {
my $dateTime = TimeNow(); my $dateTime = ::TimeNow();
$dateTime =~ s/ /_/g; $dateTime =~ s/ /_/g;
$dateTime =~ s/(:|-)//g; $dateTime =~ s/(:|-)//g;
@ -270,8 +250,8 @@ sub dateTime {
sub createArchiv { sub createArchiv {
my ($backupdir, $cl, $byUpdate, $dateTime) = @_; my ($backupdir, $cl, $byUpdate, $dateTime) = @_;
my $backupcmd = AttrVal('global','backupcmd',undef); my $backupcmd = ::AttrVal('global','backupcmd',undef);
my $symlink = AttrVal('global','backupsymlink','no'); my $symlink = ::AttrVal('global','backupsymlink','no');
my $tarOpts; my $tarOpts;
my $msg; my $msg;
my $ret; my $ret;
@ -295,12 +275,11 @@ sub createArchiv {
$cmd = $backupcmd . ' \"' . $pathlist . '\"'; $cmd = $backupcmd . ' \"' . $pathlist . '\"';
} }
Log( 2, 'Backup with command: ' . $cmd ); ::Log(2, qq(Backup with command: $cmd));
if ( !$fhemForked && !$byUpdate ) { if ( !$::fhemForked && !$byUpdate ) {
use Blocking; require Blocking;
our $BC_telnetDevice; ::BC_searchTelnet('backup');
BC_searchTelnet('backup'); my $tp = $::defs{$::BC_telnetDevice}{PORT};
my $tp = $defs{$BC_telnetDevice}{PORT};
system( "($cmd; echo Backup done;" system( "($cmd; echo Backup done;"
. "$^X $0 localhost:$tp 'trigger global backup done')2>&1 &" ); . "$^X $0 localhost:$tp 'trigger global backup done')2>&1 &" );
@ -313,14 +292,14 @@ sub createArchiv {
if ($ret) { if ($ret) {
chomp $ret; chomp $ret;
Log( 1, 'backup ' . $ret ); ::Log(1, qq(backup $ret));
} }
if ( !defined($backupcmd) && -e "$backupdir/FHEM-$dateTime.tar.gz" ) { if ( !defined($backupcmd) && -e "$backupdir/FHEM-$dateTime.tar.gz" ) {
my $size = -s "$backupdir/FHEM-$dateTime.tar.gz"; my $size = -s "$backupdir/FHEM-$dateTime.tar.gz";
$msg = "backup done: FHEM-$dateTime.tar.gz ($size Bytes)"; $msg = "backup done: FHEM-$dateTime.tar.gz ($size Bytes)";
DoTrigger( 'global', $msg ); ::DoTrigger( 'global', $msg );
Log( 1, $msg ); ::Log(1, $msg);
$ret .= "\n" . $msg; $ret .= "\n" . $msg;
} }
@ -332,18 +311,19 @@ sub addLogPathToPathnameArray {
my @logpathname; my @logpathname;
my $extlogpath; my $extlogpath;
Log( 4, 'addLogPathToPathnameArray' ); ::Log(4, q(addLogPathToPathnameArray));
foreach my $logFile (devspec2array('TYPE=FileLog')) { foreach my $logFile (::devspec2array('TYPE=FileLog')) {
Log( 5, 'found logFiles: ' . $logFile ); ::Log(5, qq(found logFiles: $logFile));
my $logpath = InternalVal($logFile,'currentlogfile',''); my $logpath = ::InternalVal($logFile,'currentlogfile','');
Log( 4, 'found logpath: ' . $logpath ); ::Log(4, qq(found logpath: $logpath));
if ( $logpath =~ m#^(.+?)\/[\_|\-|\w]+\.log$# ) { if ( $logpath =~ m{\A(.+?)\/[\_|\-|\w]+\.log\z}xms ) {
$extlogpath = $1; $extlogpath = $1;
Log( 4, 'found extlogpath: ' . $extlogpath ); ::Log(4, qq(found extlogpath: $extlogpath));
if ( $1 =~ /^\/[A-Za-z]/ ) {
if ( $1 =~ m{\A\/[A-Za-z]}xms ) {
push( @logpathname, $extlogpath ) ; push( @logpathname, $extlogpath ) ;
Log( 4, 'external logpath include: ' . $extlogpath ); ::Log(4, qq(external logpath include: $extlogpath));
} }
} }
} }
@ -370,17 +350,17 @@ sub addLogPathToPathnameArray {
pgm2 (if installed) and the config-file will be saved into a .tar.gz pgm2 (if installed) and the config-file will be saved into a .tar.gz
file by default. The file is stored with a timestamp in the file by default. The file is stored with a timestamp in the
<a href="#modpath">modpath</a>/backup directory or to a directory <a href="#modpath">modpath</a>/backup directory or to a directory
specified by the global attribute <a href="#backupdir">backupdir</a>.<br> specified by the global Attribute <a href="#backupdir">backupdir</a>.<br>
Note: tar and gzip must be installed to use this feature. Note: tar and gzip must be installed to use this feature.
<br> <br>
<br> <br>
If you need to call tar with support for symlinks, you could set the If you need to call tar with support for symlinks, you could set the
global attribute <a href="#backupsymlink">backupsymlink</a> to everything global Attribute <a href="#backupsymlink">backupsymlink</a> to everything
else as "no". else as "no".
<br> <br>
<br> <br>
You could pass the backup to your own command / script by using the You could pass the backup to your own command / script by using the
global attribute <a href="#backupcmd">backupcmd</a>. global ::attribute <a href="#backupcmd">backupcmd</a>.
<br> <br>
<br> <br>
</ul> </ul>