diff --git a/fhem/CHANGED b/fhem/CHANGED
index 44cc408d7..be95ac140 100644
--- a/fhem/CHANGED
+++ b/fhem/CHANGED
@@ -1,5 +1,7 @@
# Add changes at the top of the list. Keep it in ASCII, and 80-char wide.
# Do not insert empty lines here, update check depends on it.
+ - feature: 98_backup: split modul code in to two files. add code for
+ error handling
- bugfix: lib/FHEM/Core/Authentication/Passwords.pm:
fix rename bug, new method to create object
You musst pass a instance TYPE to the function if you create a objekt
diff --git a/fhem/FHEM/98_backup.pm b/fhem/FHEM/98_backup.pm
index 7fc2417ff..a312ed1f9 100644
--- a/fhem/FHEM/98_backup.pm
+++ b/fhem/FHEM/98_backup.pm
@@ -1,12 +1,12 @@
################################################################
# 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
# All rights reserved
#
# Contributors:
-# - Marko Oldenburg (CoolTux)
+# - Marko Oldenburg (CoolTux - fhemdevelopment at cooltux dot net)
#
# This script free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -33,11 +33,13 @@ use strict;
use warnings;
use FHEM::Meta;
+require FHEM::Core::Utils::FHEMbackup;
+
#####################################
sub backup_Initialize {
my %hash = (
- Fn => 'FHEM::backup::CommandBackup',
+ Fn => \&FHEM::Core::Utils::FHEMbackup::CommandBackup,
Hlp => ',create a backup of fhem configuration, state and modpath'
);
@@ -46,312 +48,6 @@ sub backup_Initialize {
return FHEM::Meta::InitMod( __FILE__, \%hash );
}
-######################################
-## unserer packagename
-package FHEM::backup;
-
-use strict;
-use warnings;
-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;
-
-sub CommandBackup {
- my $cl = shift;
- my $param = shift;
-
- my $byUpdate = ( $param && $param eq 'startedByUpdate' );
- my $modpath = AttrVal( 'global', 'modpath', '.' );
- my $configfile = AttrVal( 'global', 'configfile', $modpath . '/fhem.cfg' );
- my $statefile = AttrVal( 'global', 'statefile', $modpath . '/log/fhem.save' );
- my $dir = AttrVal( 'global', 'backupdir', $modpath . '/backup');
- my $now = gettimeofday();
- my @t = localtime($now);
- my $dateTime = dateTime();
-
- $statefile = ResolveDateWildcards( $statefile, @t );
-
- # prevent duplicate entries in backup list for default config, forum #54826
- $configfile = '' if ( $configfile eq 'fhem.cfg' || configDBUsed() );
- $statefile = '' if ( $statefile eq './log/fhem.save' );
- my $msg;
- my $ret;
-
- my ($err,$backupdir) = createBackupDir( $dir, $modpath );
-
- return Log( 1, 'ERROR: if create backup directory!' )
- if ( defined($err) and $err );
-
- Log( 1, 'NOTE: make sure you have a database backup!' )
- if ( configDBUsed() );
- $ret = addConfDBFiles( $configfile, $statefile );
- $ret = readModpath( $modpath, $backupdir );
-
- ## add all logfile path to pathname array
- $ret = addLogPathToPathnameArray();
-
- ### remove double entries from pathname array
- my %all=();
- @all{@pathname}=1;
- @pathname = keys %all;
-
- ### create archiv
- $ret = createArchiv( $backupdir, $cl, $byUpdate, $dateTime );
-
- ### support for backupToStorage Modul
- readingsSingleUpdate($defs{join(' ',
- devspec2array('TYPE=backupToStorage'))}
- , 'fhemBackupFile'
- , "$backupdir/FHEM-$dateTime.tar.gz"
- , 0
- )
- if ( devspec2array('TYPE=backupToStorage') > 0 );
-
- @pathname = [];
- undef @pathname;
-
- return $ret;
-}
-
-sub addConfDBFiles {
- my $configfile = shift;
- my $statefile = shift;
-
- my $ret;
-
- if ( configDBUsed() ) {
- # add configDB configuration file
- push( @pathname, 'configDB.conf' );
- Log( 2, 'backup include: \'configDB.conf\'' );
-
- ## check if sqlite db file outside of modpath
- if ( $configDB{type} eq 'SQLITE'
- and defined($configDB{filename})
- and $configDB{filename} !~ m#^[a-zA-Z].*|^\.\/[a-zA-Z].*# )
- {
- ## backup sqlite db file
- Log( 2, 'backup include SQLite DB File: ' . $configDB{filename} );
- push( @pathname, $configDB{filename} );
- }
- }
- else {
- # get pathnames to archiv
- push( @pathname, $configfile ) if ($configfile);
- Log( 2, 'backup include: ' . $configfile )
- if ($configfile);
-
- $ret = parseConfig($configfile);
- push( @pathname, $statefile ) if ($statefile);
- Log( 2, 'backup include: ' . $statefile )
- if ($statefile);
- }
-
- return $ret;
-}
-
-sub createBackupDir {
- my $dir = shift;
- my $modpath = shift;
-
- my $msg;
- my $ret;
- my $backupdir = $dir =~ m#^\.(\/.*)$# ? $modpath.$1 : $dir =~ m#^\.\.\/# ? $modpath.'/'.$dir : $dir;
-
- # create backupdir if not exists
- if ( !-d $backupdir ) {
- Log( 4, 'backup create backupdir: ' . $backupdir );
- $ret = `(mkdir -p $backupdir) 2>&1`;
- if ($ret) {
- chomp($ret);
- $msg = 'backup: ' . $ret;
- return ($msg,undef);
- }
- }
-
- return (undef,$backupdir);
-}
-
-sub parseConfig {
- my $configfile = shift;
-
- # we need default value to read included files
- $configfile = $configfile ? $configfile : 'fhem.cfg';
- my $fh;
- my $msg;
- my $ret;
-
- if ( !open( $fh, $configfile ) ) {
- $msg = 'Can\'t open ' . $configfile . ': ' . $!;
- Log( 1, 'backup ' . $msg );
- return $msg;
- }
-
- while ( my $l = <$fh> ) {
- $l =~ s/[\r\n]//g;
- if ( $l =~ m/^\s*include\s+(\S+)\s*.*$/ ) {
- if ( -e $1 ) {
- push @pathname, $1;
- Log( 4, 'backup include: ' . $1 );
- $ret = parseConfig($1);
- }
- else {
- Log( 1,
- 'backup configfile: '
- . $1
- . ' does not exists! File not included.' );
- }
- }
- }
-
- close $fh;
- return $ret;
-}
-
-sub readModpath {
- my $modpath = shift;
- my $backupdir = shift;
-
- my $msg;
- my $ret;
-
- if ( !opendir( DH, $modpath ) ) {
- $msg = 'Can\'t open $modpath: ' . $!;
- Log( 1, 'backup ' . $msg );
- return $msg;
- }
-
- my @files = <$modpath/*>;
- foreach my $file (@files) {
- if ( $file eq $backupdir && ( -d $file || -l $file ) ) {
- Log( 4, 'backup exclude: ' . $file );
- }
- else {
- Log( 4, 'backup include: ' . $file );
- push @pathname, $file;
- }
- }
-
- return $ret;
-}
-
-sub dateTime {
- my $dateTime = TimeNow();
- $dateTime =~ s/ /_/g;
- $dateTime =~ s/(:|-)//g;
-
- return $dateTime;
-}
-
-sub createArchiv {
- my ($backupdir, $cl, $byUpdate, $dateTime) = @_;
-
- my $backupcmd = AttrVal('global','backupcmd',undef);
- my $symlink = AttrVal('global','backupsymlink','no');
- my $tarOpts;
- my $msg;
- my $ret;
-
- my $pathlist = join( '" "', @pathname );
-
- my $cmd = '';
- if ( !defined($backupcmd) ) {
- if ( lc($symlink) eq 'no' ) {
- $tarOpts = 'czf';
- }
- else {
- $tarOpts = 'chzf';
- }
-
-# prevents tar's output of "Removing leading /" and return total bytes of
-# archive
- $cmd = "tar $tarOpts $backupdir/FHEM-$dateTime.tar.gz \"$pathlist\"";
- }
- else {
- $cmd = $backupcmd . ' \"' . $pathlist . '\"';
- }
-
- Log( 2, 'Backup with command: ' . $cmd );
- if ( !$fhemForked && !$byUpdate ) {
- use Blocking;
- our $BC_telnetDevice;
- BC_searchTelnet('backup');
- my $tp = $defs{$BC_telnetDevice}{PORT};
-
- system( "($cmd; echo Backup done;"
- . "$^X $0 localhost:$tp 'trigger global backup done')2>&1 &" );
-
- return
- "Started the backup in the background, watch the log for details";
- }
-
- $ret = `($cmd) 2>&1`;
-
- if ($ret) {
- chomp $ret;
- Log( 1, 'backup ' . $ret );
- }
-
- if ( !defined($backupcmd) && -e "$backupdir/FHEM-$dateTime.tar.gz" ) {
- my $size = -s "$backupdir/FHEM-$dateTime.tar.gz";
- $msg = "backup done: FHEM-$dateTime.tar.gz ($size Bytes)";
- DoTrigger( 'global', $msg );
- Log( 1, $msg );
- $ret .= "\n" . $msg;
- }
-
- return $ret;
-}
-
-sub addLogPathToPathnameArray {
- my $ret;
- my @logpathname;
- my $extlogpath;
-
- Log( 4, 'addLogPathToPathnameArray' );
-
- foreach my $logFile (devspec2array('TYPE=FileLog')) {
- Log( 5, 'found logFiles: ' . $logFile );
- my $logpath = InternalVal($logFile,'currentlogfile','');
- Log( 4, 'found logpath: ' . $logpath );
- if ( $logpath =~ m#^(.+?)\/[\_|\-|\w]+\.log$# ) {
- $extlogpath = $1;
- Log( 4, 'found extlogpath: ' . $extlogpath );
- if ( $1 =~ /^\/[A-Za-z]/ ) {
- push( @logpathname, $extlogpath ) ;
- Log( 4, 'external logpath include: ' . $extlogpath );
- }
- }
- }
-
- push( @pathname, @logpathname);
-
- return $ret;
-}
1;
@@ -370,17 +66,17 @@ sub addLogPathToPathnameArray {
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
modpath/backup directory or to a directory
- specified by the global attribute backupdir.
+ specified by the global Attribute backupdir.
Note: tar and gzip must be installed to use this feature.
If you need to call tar with support for symlinks, you could set the
- global attribute backupsymlink to everything
+ global Attribute backupsymlink to everything
else as "no".
You could pass the backup to your own command / script by using the
- global attribute backupcmd.
+ global ::attribute backupcmd.
@@ -403,8 +99,9 @@ sub addLogPathToPathnameArray {
],
"release_status": "stable",
"license": "GPL_2",
+ "version": "v2.0.1",
"author": [
- "Marko Oldenburg "
+ "Marko Oldenburg "
],
"x_fhem_maintainer": [
"CoolTux"
diff --git a/fhem/lib/FHEM/Core/Utils/FHEMbackup.pm b/fhem/lib/FHEM/Core/Utils/FHEMbackup.pm
new file mode 100644
index 000000000..11b8747df
--- /dev/null
+++ b/fhem/lib/FHEM/Core/Utils/FHEMbackup.pm
@@ -0,0 +1,315 @@
+################################################################
+# Developed with Kate
+#
+# (c) 2012-2021 Copyright: Martin Fischer (m_fischer at gmx dot de)
+# Rewrite and Maintained by Marko Oldenburg since 2019
+# All rights reserved
+#
+# Contributors:
+# - Marko Oldenburg (CoolTux - fhemdevelopment at cooltux dot net)
+#
+# This script 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.
+#
+# The GNU General Public License can be found at
+# http://www.gnu.org/copyleft/gpl.html.
+# A copy is found in the textfile GPL.txt and important notices to the license
+# from the author is found in LICENSE.txt distributed with these scripts.
+#
+# This script 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.
+#
+#
+# $Id$
+#
+################################################################
+
+package FHEM::Core::Utils::FHEMbackup;
+
+use strict;
+use warnings;
+
+
+my @pathname;
+
+sub CommandBackup {
+ my $cl = shift;
+ my $param = shift;
+
+ my $byUpdate = ( $param && $param eq 'startedByUpdate' );
+ my $modpath = ::AttrVal( 'global', 'modpath', '.' );
+ my $configfile = ::AttrVal( 'global', 'configfile', $modpath . '/fhem.cfg' );
+ my $statefile = ::AttrVal( 'global', 'statefile', $modpath . '/log/fhem.save' );
+ my $dir = ::AttrVal( 'global', 'backupdir', $modpath . '/backup');
+ my $now = ::gettimeofday();
+ my @t = localtime($now);
+ my $dateTime = dateTime();
+
+ $statefile = ::ResolveDateWildcards( $statefile, @t );
+
+ # prevent duplicate entries in backup list for default config, forum #54826
+ $configfile = '' if ( $configfile eq 'fhem.cfg' || ::configDBUsed() );
+ $statefile = '' if ( $statefile eq './log/fhem.save' );
+ my $msg;
+ my $ret;
+
+ my ($err,$backupdir) = createBackupDir( $dir, $modpath );
+
+ return ::Log(1, q(ERROR: if create backup directory!))
+ if ( defined($err) && $err );
+
+ ::Log(1, q(NOTE: make sure you have a database backup!))
+ if ( ::configDBUsed() );
+ $ret = addConfDBFiles( $configfile, $statefile );
+ return ::Log(1, qq(Backup ERROR - addConfDBFiles: $ret))
+ if ( defined($ret)
+ && $ret =~ m{\ACan\'t\sopen.*:\s.*}xms);
+
+ $ret = readModpath( $modpath, $backupdir );
+ return ::Log(1, qq(Backup ERROR - readModpath: $ret))
+ if ( defined($ret)
+ && $ret =~ m{\ACan\'t\sopen\s\$modpath:\s.*}xms);
+
+ ## add all logfile path to pathname array
+ $ret = addLogPathToPathnameArray();
+
+ ### remove double entries from pathname array
+ my %all=();
+ @all{@pathname}=1;
+ @pathname = keys %all;
+
+ ### create archiv
+ $ret = createArchiv( $backupdir, $cl, $byUpdate, $dateTime );
+
+ ### support for backupToStorage Modul
+ ::readingsSingleUpdate($::defs{join(' ',
+ ::devspec2array('TYPE=backupToStorage'))}
+ , 'fhemBackupFile'
+ , "$backupdir/FHEM-$dateTime.tar.gz"
+ , 0
+ )
+ if ( ::devspec2array('TYPE=backupToStorage') > 0 );
+
+ @pathname = [];
+ undef @pathname;
+
+ return $ret;
+}
+
+sub addConfDBFiles {
+ my $configfile = shift;
+ my $statefile = shift;
+
+ my $ret;
+
+ if ( ::configDBUsed() ) {
+ # add ::configDB configuration file
+ push( @pathname, 'configDB.conf' );
+ ::Log(2, q(backup include: 'configDB.conf'));
+
+ ## check if sqlite db file outside of modpath
+ if ( $::configDB{type} eq 'SQLITE'
+ && defined($::configDB{filename})
+ && $::configDB{filename} !~ m{\A[a-zA-Z].*|^\.\/[a-zA-Z].*}xms )
+ {
+ ## backup sqlite db file
+ ::Log(2, qq(backup include SQLite DB File: $::configDB{filename}));
+ push( @pathname, $::configDB{filename} );
+ }
+ }
+ else {
+ # get pathnames to archiv
+ push( @pathname, $configfile ) if ($configfile);
+ ::Log(2, qq(backup include: $configfile))
+ if ($configfile);
+
+ $ret = parseConfig($configfile);
+ push( @pathname, $statefile ) if ($statefile);
+ ::Log(2, qq(backup include: $statefile))
+ if ($statefile);
+ }
+
+ return $ret;
+}
+
+sub createBackupDir {
+ my $dir = shift;
+ my $modpath = shift;
+
+ my $msg;
+ my $ret;
+ my $backupdir = $dir =~ m{\A\.(\/.*)\z}xms ? $modpath.$1 : $dir =~ m{\A\.\.\/}xms ? $modpath.'/'.$dir : $dir;
+
+ # create backupdir if not exists
+ if ( !-d $backupdir ) {
+ ::Log(4, qq(backup create backupdir: $backupdir));
+ $ret = `(mkdir -p $backupdir) 2>&1`;
+ if ($ret) {
+ chomp($ret);
+ $msg = 'backup: ' . $ret;
+ return ($msg,undef);
+ }
+ }
+
+ return (undef,$backupdir);
+}
+
+sub parseConfig {
+ my $configfile = shift;
+
+ # we need default value to read included files
+ $configfile = $configfile ? $configfile : 'fhem.cfg';
+ my $fh;
+ my $msg;
+ my $ret;
+
+ if ( !open( $fh, $configfile ) ) {
+ $msg = 'Can\'t open ' . $configfile . ': ' . $!;
+ ::Log(1, qq(backup $msg));
+ return $msg;
+ }
+
+ while ( my $l = <$fh> ) {
+ $l =~ s/[\r\n]//g;
+ if ( $l =~ m{\A\s*include\s+(\S+)\s*.*\z}xms ) {
+ if ( -e $1 ) {
+ push @pathname, $1;
+ ::Log(4, qq(backup include: $1));
+ $ret = parseConfig($1);
+ }
+ else {
+ ::Log(1, qq(backup configfile: $1 does not exists! File not included.));
+ }
+ }
+ }
+
+ close $fh;
+ return $ret;
+}
+
+sub readModpath {
+ my $modpath = shift;
+ my $backupdir = shift;
+
+ my $msg;
+ my $ret;
+
+ if ( !opendir( DH, $modpath ) ) {
+ $msg = qq(Can't open \$modpath: $!);
+ ::Log(1, qq(backup $msg));
+ return $msg;
+ }
+
+ my @files = <$modpath/*>;
+ foreach my $file (@files) {
+ if ( $file eq $backupdir && ( -d $file || -l $file ) ) {
+ ::Log(4, qq(backup exclude: $file));
+ }
+ else {
+ ::Log(4, qq(backup include: $file));
+ push @pathname, $file;
+ }
+ }
+
+ return $ret;
+}
+
+sub dateTime {
+ my $dateTime = ::TimeNow();
+ $dateTime =~ s/ /_/g;
+ $dateTime =~ s/(:|-)//g;
+
+ return $dateTime;
+}
+
+sub createArchiv {
+ my ($backupdir, $cl, $byUpdate, $dateTime) = @_;
+
+ my $backupcmd = ::AttrVal('global','backupcmd',undef);
+ my $symlink = ::AttrVal('global','backupsymlink','no');
+ my $tarOpts;
+ my $msg;
+ my $ret;
+
+ my $pathlist = join( '" "', @pathname );
+
+ my $cmd = '';
+ if ( !defined($backupcmd) ) {
+ if ( lc($symlink) eq 'no' ) {
+ $tarOpts = 'czf';
+ }
+ else {
+ $tarOpts = 'chzf';
+ }
+
+# prevents tar's output of "Removing leading /" and return total bytes of
+# archive
+ $cmd = "tar $tarOpts $backupdir/FHEM-$dateTime.tar.gz \"$pathlist\"";
+ }
+ else {
+ $cmd = $backupcmd . ' \"' . $pathlist . '\"';
+ }
+
+ ::Log(2, qq(Backup with command: $cmd));
+ if ( !$::fhemForked && !$byUpdate ) {
+ require Blocking;
+ ::BC_searchTelnet('backup');
+ my $tp = $::defs{$::BC_telnetDevice}{PORT};
+
+ system( "($cmd; echo Backup done;"
+ . "$^X $0 localhost:$tp 'trigger global backup done')2>&1 &" );
+
+ return
+ "Started the backup in the background, watch the log for details";
+ }
+
+ $ret = `($cmd) 2>&1`;
+
+ if ($ret) {
+ chomp $ret;
+ ::Log(1, qq(backup $ret));
+ }
+
+ if ( !defined($backupcmd) && -e "$backupdir/FHEM-$dateTime.tar.gz" ) {
+ my $size = -s "$backupdir/FHEM-$dateTime.tar.gz";
+ $msg = "backup done: FHEM-$dateTime.tar.gz ($size Bytes)";
+ ::DoTrigger( 'global', $msg );
+ ::Log(1, $msg);
+ $ret .= "\n" . $msg;
+ }
+
+ return $ret;
+}
+
+sub addLogPathToPathnameArray {
+ my $ret;
+ my @logpathname;
+ my $extlogpath;
+
+ ::Log(4, q(addLogPathToPathnameArray));
+
+ foreach my $logFile (::devspec2array('TYPE=FileLog')) {
+ ::Log(5, qq(found logFiles: $logFile));
+ my $logpath = ::InternalVal($logFile,'currentlogfile','');
+ ::Log(4, qq(found logpath: $logpath));
+ if ( $logpath =~ m{\A(.+?)\/[\_|\-|\w]+\.log\z}xms ) {
+ $extlogpath = $1;
+ ::Log(4, qq(found extlogpath: $extlogpath));
+
+ if ( $1 =~ m{\A\/[A-Za-z]}xms ) {
+ push( @logpathname, $extlogpath ) ;
+ ::Log(4, qq(external logpath include: $extlogpath));
+ }
+ }
+ }
+
+ push( @pathname, @logpathname);
+
+ return $ret;
+}
+
+1;