diff --git a/fhem/CHANGED b/fhem/CHANGED
index 8fd5386d4..a98a521ba 100644
--- a/fhem/CHANGED
+++ b/fhem/CHANGED
@@ -1,5 +1,6 @@
# 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: save writes a copy into restoreDirs (Forum #78769)
- feature: WMBus: preliminary support for SML payload
- feature: 98_DOIFtools: unattended save removed, Forum(78769)
DOIFtools will perform save if allowed by
diff --git a/fhem/FHEM/98_autocreate.pm b/fhem/FHEM/98_autocreate.pm
index 51176febd..117b872f2 100644
--- a/fhem/FHEM/98_autocreate.pm
+++ b/fhem/FHEM/98_autocreate.pm
@@ -379,8 +379,7 @@ autocreate_Notify($$)
}
CommandSave(undef, undef) if(!$ret && $nrcreated &&
- AttrVal($me,"autosave",
- AttrVal("global","autosave",1)));
+ AttrVal($me,"autosave", 1));
return $ret;
}
diff --git a/fhem/FHEM/98_update.pm b/fhem/FHEM/98_update.pm
index f1a878338..eb58ca3b5 100644
--- a/fhem/FHEM/98_update.pm
+++ b/fhem/FHEM/98_update.pm
@@ -11,8 +11,6 @@ use Blocking;
sub CommandUpdate($$);
sub upd_getUrl($);
sub upd_initRestoreDirs($);
-sub upd_mkDir($$$);
-sub upd_rmTree($);
sub upd_writeFile($$$$);
sub upd_mv($$);
sub upd_metainit($);
@@ -21,7 +19,6 @@ sub upd_saveConfig($$$);
my $updateInBackground;
my $updRet;
-my %updDirs;
my $updArg;
my $mainPgm = "/fhem.pl\$";
my %upd_connecthash;
@@ -274,7 +271,7 @@ doUpdate($$$$)
###########################
# read in & digest the local control file
my $root = $attr{global}{modpath};
- my $restoreDir = ($arg eq "check" ? "" : upd_initRestoreDirs($root));
+ my $restoreDir = ($arg eq "check" ? "" : restoreDir_init());
my @locList;
if(($arg eq "check" || $arg eq "all") &&
@@ -318,7 +315,7 @@ doUpdate($$$$)
uLog 1, "Suspicious line $r, aborting";
return 1;
}
- upd_mkDir($root, $r[2], 0);
+ restoreDir_mkDir($root, $r[2], 0);
my $mvret = upd_mv("$root/$r[1]", "$root/$r[2]");
uLog 4, "mv $root/$r[1] $root/$r[2]". ($mvret ? " FAILED:$mvret":"");
}
@@ -463,26 +460,6 @@ upd_mv($$)
return undef;
}
-sub
-upd_mkDir($$$)
-{
- my ($root, $dir, $isFile) = @_;
- if($isFile) { # Delete the file Component
- $dir =~ m,^(.*)/([^/]*)$,;
- $dir = $1;
- }
- return if($updDirs{$dir});
- $updDirs{$dir} = 1;
- my @p = split("/", $dir);
- for(my $i = 0; $i < int(@p); $i++) {
- my $path = "$root/".join("/", @p[0..$i]);
- if(!-d $path) {
- mkdir $path;
- uLog 4, "MKDIR $root/".join("/", @p[0..$i]);
- }
- }
-}
-
sub
upd_getChanges($$)
{
@@ -538,7 +515,7 @@ upd_saveConfig($$$)
my($root, $restoreDir, $fName) = @_;
return if(!$fName || !$restoreDir || configDBUsed() || !-r "$root/$fName");
- upd_mkDir($root, "$restoreDir/$fName", 1);
+ restoreDir_mkDir($root, "$restoreDir/$fName", 1);
Log 1, "saving $fName";
if(!copy("$root/$fName", "$root/$restoreDir/$fName")) {
uLog 1, "copy $root/$fName $root/$restoreDir/$fName failed:$!, ".
@@ -553,8 +530,8 @@ upd_writeFile($$$$)
my($root, $restoreDir, $fName, $content) = @_;
# copy the old file and save the new
- upd_mkDir($root, $fName, 1);
- upd_mkDir($root, "$restoreDir/$fName", 1) if($restoreDir);
+ restoreDir_mkDir($root, $fName, 1);
+ restoreDir_mkDir($root, "$restoreDir/$fName", 1) if($restoreDir);
if($restoreDir && -f "$root/$fName" &&
! copy("$root/$fName", "$root/$restoreDir/$fName")) {
uLog 1, "copy $root/$fName $root/$restoreDir/$fName failed:$!, ".
@@ -587,69 +564,6 @@ upd_writeFile($$$$)
return 1;
}
-sub
-upd_rmTree($)
-{
- my ($dir) = @_;
-
- my $dh;
- if(!opendir($dh, $dir)) {
- uLog 1, "opendir $dir: $!";
- return;
- }
- my @files = grep { $_ ne "." && $_ ne ".." } readdir($dh);
- closedir($dh);
-
- foreach my $f (@files) {
- if(-d "$dir/$f") {
- upd_rmTree("$dir/$f");
- } else {
- uLog 4, "rm $dir/$f";
- if(!unlink("$dir/$f")) {
- uLog 1, "rm $dir/$f failed: $!";
- }
- }
- }
- uLog 4, "rmdir $dir";
- if(!rmdir($dir)) {
- uLog 1, "rmdir $dir failed: $!";
- }
-}
-
-sub
-upd_initRestoreDirs($)
-{
- my ($root) = @_;
-
- my $nDirs = AttrVal("global","restoreDirs", 3);
- if($nDirs !~ m/^\d+$/ || $nDirs < 0) {
- uLog 1, "invalid restoreDirs value $nDirs, setting it to 3";
- $nDirs = 3;
- }
- return "" if($nDirs == 0);
-
- my $rdName = "restoreDir";
- my @t = localtime;
- my $restoreDir = sprintf("$rdName/%04d-%02d-%02d",
- $t[5]+1900, $t[4]+1, $t[3]);
- Log 1, "MKDIR $restoreDir" if(! -d "$root/restoreDir");
- upd_mkDir($root, $restoreDir, 0);
-
- if(!opendir(DH, "$root/$rdName")) {
- uLog 1, "opendir $root/$rdName: $!";
- return "";
- }
- my @oldDirs = sort grep { $_ !~ m/^\./ && $_ ne $restoreDir } readdir(DH);
- closedir(DH);
- while(int(@oldDirs) > $nDirs) {
- my $dir = "$root/$rdName/". shift(@oldDirs);
- next if($dir =~ m/$restoreDir/); # Just in case
- uLog 1, "RMDIR: $dir";
- upd_rmTree($dir);
- }
-
- return $restoreDir;
-}
1;
=pod
@@ -741,17 +655,7 @@ upd_initRestoreDirs($)
updating it from another source, specify fhem.de.*:FILE.pm
-
-
restoreDirs
- update saves each file before overwriting it with the new version from
- the Web. For this purpose update creates a directory restoreDir in the
- global modpath directory, then a subdirectory with the current date,
- where the old version of the currently replaced file is stored.
- The default value of this attribute is 3, meaning that 3 old versions
- (i.e. date-directories) are kept, and the older ones are deleted. If
- the attribute is set to 0, the feature is deactivated.
-
-
+ restoreDirs
@@ -854,16 +758,7 @@ upd_initRestoreDirs($)
- restoreDirs
- update sichert jede Datei vor dem Überschreiben mit der neuen
- Version aus dem Web. Für diesen Zweck wird zuerst ein restoreDir
- Verzeichnis in der global modpath Verzeichnis angelegt, und danach
- ein Unterverzeichnis mit dem aktuellen Datum. In diesem Verzeichnis
- werden vor dem Überschreiben die alten Versionen der Dateien
- gerettet. Die Voreinstellung ist 3, d.h. die letzten 3
- Datums-Verzeichnisse werden aufgehoben, und die älteren entfernt.
- Falls man den Wert auf 0 setzt, dann ist dieses Feature deaktiviert.
-
+ restoreDirs
diff --git a/fhem/docs/commandref_frame.html b/fhem/docs/commandref_frame.html
index 3ef955358..fb7e2399b 100644
--- a/fhem/docs/commandref_frame.html
+++ b/fhem/docs/commandref_frame.html
@@ -1082,6 +1082,9 @@ The following local attributes are used by a wider range of devices:
structures, but it won't work correctly if some of these files are not
writeable.
+ before overwriting the files, the old version will be saved, see the restoreDirs global attribute for details.
+
@@ -1539,6 +1542,20 @@ The following local attributes are used by a wider range of devices:
regexp for hosts to exclude when using a proxy
+
+ restoreDirs
+ update saves each file before overwriting it with the new version from
+ the Web. For this purpose update creates a directory restoreDir in the
+ global modpath directory, then a subdirectory with the current date,
+ where the old version of the currently replaced file is stored.
+ The default value of this attribute is 3, meaning that 3 old versions
+ (i.e. date-directories) are kept, and the older ones are deleted.
+ fhem.cfg and fhem.state will be also copied with this method before
+ executing save. To restore the files, you can use the restore FHEM
+ command.
+
If the attribute is set to 0, the feature is deactivated.
+
+
sendStatistics
diff --git a/fhem/docs/commandref_frame_DE.html b/fhem/docs/commandref_frame_DE.html
index 13c2a247d..4d922f284 100644
--- a/fhem/docs/commandref_frame_DE.html
+++ b/fhem/docs/commandref_frame_DE.html
@@ -1148,6 +1148,8 @@ Die folgenden lokalen Attribute werden von mehreren Geräten verwendet:
nicht korrekt wenn FHEM für diese Dateien keine Schreibrechte
besitzt.
+ Vor dem Überschreiben der Dateien wird die alte Version gesichert,
+ siehe restoreDirs für Einzelheiten.
@@ -1644,6 +1646,21 @@ Die folgenden lokalen Attribute werden von mehreren Geräten verwendet:
Regexp, um bestimmte Hosts nicht via proxy zu kontaktieren.
+
+ restoreDirs
+ update sichert jede Datei vor dem Überschreiben mit der neuen
+ Version aus dem Web. Für diesen Zweck wird zuerst ein restoreDir
+ Verzeichnis in der global modpath Verzeichnis angelegt, und danach
+ ein Unterverzeichnis mit dem aktuellen Datum. In diesem Verzeichnis
+ werden vor dem Überschreiben die alten Versionen der Dateien
+ gerettet. Die Voreinstellung ist 3, d.h. die letzten 3
+ Datums-Verzeichnisse werden aufgehoben, und die älteren entfernt.
+
+ Auch fhem.cfg und fhem.state wird auf diese Weise vor dem ausfüren
+ von save gesichert. Zum restaurieren der alten Dateien kann man das
+ restore Befehl verwenden.
+ Falls man den Wert auf 0 setzt, dann ist dieses Feature deaktiviert.
+
sendStatistics
diff --git a/fhem/fhem.pl b/fhem/fhem.pl
index 6e9585958..fa182af54 100755
--- a/fhem/fhem.pl
+++ b/fhem/fhem.pl
@@ -38,6 +38,7 @@ use IO::Socket::INET;
use Time::HiRes qw(gettimeofday);
use Scalar::Util qw(looks_like_number);
use POSIX;
+use File::Copy qw(copy);
##################################################
# Forward declarations
@@ -134,6 +135,10 @@ sub readingsEndUpdate($$);
sub readingsSingleUpdate($$$$);
sub redirectStdinStdErr();
sub rejectDuplicate($$$);
+sub restoreDir_init();
+sub restoreDir_rmTree($);
+sub restoreDir_saveFile($$);
+sub restoreDir_mkDir($$$);
sub setGlobalAttrBeforeFork($);
sub setReadingsVal($$$$);
sub toJSON($);
@@ -551,7 +556,8 @@ if(configDBUsed()) {
}
if($cfgRet) {
- $attr{global}{motd} = "$cfgErrMsg\n$cfgRet";
+ $attr{global}{autosave} = 0;
+ $attr{global}{motd} = "$cfgErrMsg\n$cfgRet\nAutosave deactivated";
Log 1, $cfgRet;
} elsif($attr{global}{motd} && $attr{global}{motd} =~ m/^$cfgErrMsg/) {
@@ -1566,10 +1572,19 @@ CommandSave($$)
return "Last 10 structural changes:\n ".join("\n ", @structChangeHist);
}
+ if(!$cl && !AttrVal("global", "autosave", 1)) { # Forum #78769
+ Log 4, "Skipping save, as autosave is disabled";
+ return;
+ }
+ my $restoreDir;
+ $restoreDir = restoreDir_init() if(!configDBUsed());
+
@structChangeHist = ();
DoTrigger("global", "SAVE", 1);
+ restoreDir_saveFile($restoreDir, $attr{global}{statefile});
my $ret = WriteStatefile();
+
return $ret if($ret);
$ret = ""; # cfgDB_SaveState may return undef
@@ -1580,6 +1595,7 @@ CommandSave($$)
$param = $attr{global}{configfile} if(!$param);
return "No configfile attribute set and no argument specified" if(!$param);
+ restoreDir_saveFile($restoreDir, $param);
if(!open(SFH, ">$param")) {
return "Cannot open $param: $!";
}
@@ -1606,6 +1622,7 @@ CommandSave($$)
my $cfgfile = $h->{CFGFN} ? $h->{CFGFN} : "configfile";
my $fh = $fh{$cfgfile};
if(!$fh) {
+ restoreDir_saveFile($restoreDir, $cfgfile);
if(!open($fh, ">$cfgfile")) {
$ret .= "Cannot open $cfgfile: $!, ignoring its content\n";
$fh{$cfgfile} = 1;
@@ -5238,4 +5255,104 @@ computeAlignTime($$@)
return (undef, $ttime);
}
+############################
+my %restoreDir_dirs;
+sub
+restoreDir_mkDir($$$)
+{
+ my ($root, $dir, $isFile) = @_;
+ if($isFile) { # Delete the file Component
+ $dir =~ m,^(.*)/([^/]*)$,;
+ $dir = $1;
+ }
+ return if($restoreDir_dirs{$dir});
+ $restoreDir_dirs{$dir} = 1;
+ my @p = split("/", $dir);
+ for(my $i = 0; $i < int(@p); $i++) {
+ my $path = "$root/".join("/", @p[0..$i]);
+ if(!-d $path) {
+ mkdir $path;
+ Log 4, "MKDIR $root/".join("/", @p[0..$i]);
+ }
+ }
+}
+
+sub
+restoreDir_rmTree($)
+{
+ my ($dir) = @_;
+
+ my $dh;
+ if(!opendir($dh, $dir)) {
+ Log 1, "opendir $dir: $!";
+ return;
+ }
+ my @files = grep { $_ ne "." && $_ ne ".." } readdir($dh);
+ closedir($dh);
+
+ foreach my $f (@files) {
+ if(-d "$dir/$f") {
+ restoreDir_rmTree("$dir/$f");
+ } else {
+ Log 4, "rm $dir/$f";
+ if(!unlink("$dir/$f")) {
+ Log 1, "rm $dir/$f failed: $!";
+ }
+ }
+ }
+ Log 4, "rmdir $dir";
+ if(!rmdir($dir)) {
+ Log 1, "rmdir $dir failed: $!";
+ }
+}
+
+sub
+restoreDir_init()
+{
+ my $root = $attr{global}{modpath};
+
+ my $nDirs = AttrVal("global","restoreDirs", 3);
+ if($nDirs !~ m/^\d+$/ || $nDirs < 0) {
+ Log 1, "invalid restoreDirs value $nDirs, setting it to 3";
+ $nDirs = 3;
+ }
+ return "" if($nDirs == 0);
+
+ my $rdName = "restoreDir";
+ my @t = localtime;
+ my $restoreDir = sprintf("$rdName/%04d-%02d-%02d",
+ $t[5]+1900, $t[4]+1, $t[3]);
+ Log 1, "MKDIR $restoreDir" if(! -d "$root/restoreDir");
+ restoreDir_mkDir($root, $restoreDir, 0);
+
+ if(!opendir(DH, "$root/$rdName")) {
+ Log 1, "opendir $root/$rdName: $!";
+ return "";
+ }
+ my @oldDirs = sort grep { $_ !~ m/^\./ && $_ ne $restoreDir } readdir(DH);
+ closedir(DH);
+ while(int(@oldDirs) > $nDirs) {
+ my $dir = "$root/$rdName/". shift(@oldDirs);
+ next if($dir =~ m/$restoreDir/); # Just in case
+ Log 1, "RMDIR: $dir";
+ restoreDir_rmTree($dir);
+ }
+
+ return $restoreDir;
+}
+
+sub
+restoreDir_saveFile($$)
+{
+ my($restoreDir, $fName) = @_;
+
+ return if(!$restoreDir || !$fName);
+
+ my $root = $attr{global}{modpath};
+ restoreDir_mkDir($root, "$restoreDir/$fName", 1);
+ if(!copy($fName, "$root/$restoreDir/$fName")) {
+ log 1, "copy $fName $root/$restoreDir/$fName failed:$!";
+ }
+}
+
1;