From 0918cc928f87e5eef5c6d5654e528128b960e5ac Mon Sep 17 00:00:00 2001 From: LeonGaultier Date: Sun, 25 Apr 2021 08:55:00 +0000 Subject: [PATCH] 98_backup: split code in to two files, add code for error handling git-svn-id: https://svn.fhem.de/fhem/trunk@24332 2b470e98-0d58-463d-a4d8-8e2adae1ed80 --- fhem/CHANGED | 2 + fhem/FHEM/98_backup.pm | 323 +------------------------ fhem/lib/FHEM/Core/Utils/FHEMbackup.pm | 315 ++++++++++++++++++++++++ 3 files changed, 327 insertions(+), 313 deletions(-) create mode 100644 fhem/lib/FHEM/Core/Utils/FHEMbackup.pm 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;