changeset 31830:354ed032ba50

load-save.cc: Save to temporary file first to prevent data loss (bug #63803) load-save.cc: To prevent any crashes with saving individual variables from corrupting a file with already-saved data, save first to an in-progress temp file, then after that is successful rename it to the desired filename. If there is a failure in the writing, the old file is not affected.
author Arun Giridhar <arungiridhar@gmail.com>
date Wed, 15 Feb 2023 17:41:53 -0500
parents 2ea6344450e3
children 45ac4c5272e9
files libinterp/corefcn/load-save.cc
diffstat 1 files changed, 23 insertions(+), 1 deletions(-) [+]
line wrap: on
line diff
--- a/libinterp/corefcn/load-save.cc	Wed Feb 15 16:41:58 2023 +0100
+++ b/libinterp/corefcn/load-save.cc	Wed Feb 15 17:41:53 2023 -0500
@@ -29,6 +29,7 @@
 
 #include <cstring>
 
+#include <filesystem>
 #include <fstream>
 #include <iomanip>
 #include <iostream>
@@ -1465,7 +1466,14 @@
     print_usage ();
   else
     {
-      std::string fname = sys::file_ops::tilde_expand (argv[i]);
+      // We make a new temporary filename, write to that instead of the
+      // file specified, then try renaming it at the end.
+      // That way, if something goes wrong during the save like OOM,
+      // we won't overwrite already-saved data in a file.
+      // See bug #63803 for context.
+
+      std::string desiredname = sys::file_ops::tilde_expand (argv[i]);
+      std::string fname = desiredname + ".saving_in_progress";
 
       i++;
 
@@ -1542,6 +1550,20 @@
               file.close ();
             }
         }
+
+        // If we are all the way here without Octave crashing or running out of
+        // memory etc, then we can say that writing to the temporary file
+        // was successful. So now we try to rename it to the actual file
+        // that was specified.
+        try
+          {
+            std::filesystem::rename (fname, desiredname);
+          }
+        catch (std::filesystem::filesystem_error& e)
+          {
+            error ("save: unable to save to %s  %s",
+                   desiredname.c_str (), e.what ());
+          }
     }
 
   return retval;