bug-gnu-emacs
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

bug#35497: [PATCH v2] Don't rewrite buffer contents after saving by rena


From: Jonathan Tomer
Subject: bug#35497: [PATCH v2] Don't rewrite buffer contents after saving by rename
Date: Tue, 30 Apr 2019 17:26:42 -0700

When `file-precious-flag' is non-nil, files are saved by renaming a
temporary file to the new name; this is an atomic operation on POSIX
so other programs will not see the file in an intermediate state.
Unfortunately, due to a paren-matching error introduced in change
574c05e219476912db3105fa164accd9ba12b35f, we would then write the
contents again in the usual way after this rename.  In addition to
being wasteful, this is a serious bug: the whole point of
`file-precious-flag' is to prevent race conditions with other programs
that might otherwise see an empty file, but with this bug the race is
actually much *more* likely to be visible: the rename will alert any
inotify watchers of a change, and then the subsequent write is very
likely to truncate the file just as those programs start to read it!
* lisp/files.el (basic-save-buffer-2): Don't rewrite file contents
  after saving-by-renaming.
* test/lisp/files-tests.el (files-tests-dont-rewrite-precious-files):
* test/lisp/net/tramp-tests.el (tramp-test46-file-precious-flag):
  Regression tests for this change.
---
 lisp/files.el                |  4 ++--
 test/lisp/files-tests.el     | 26 ++++++++++++++++++++++++++
 test/lisp/net/tramp-tests.el | 28 ++++++++++++++++++++++++++++
 3 files changed, 56 insertions(+), 2 deletions(-)

diff --git a/lisp/files.el b/lisp/files.el
index c05d70a00e..72518e8127 100644
--- a/lisp/files.el
+++ b/lisp/files.el
@@ -5256,7 +5256,7 @@ basic-save-buffer-2
                     (set-file-extended-attributes buffer-file-name
                                                   (nth 1 setmodes)))
                 (set-file-modes buffer-file-name
-                                (logior (car setmodes) 128))))))
+                                (logior (car setmodes) 128)))))
        (let (success)
          (unwind-protect
              (progn
@@ -5272,7 +5272,7 @@ basic-save-buffer-2
            (and setmodes (not success)
                 (progn
                   (rename-file (nth 2 setmodes) buffer-file-name t)
-                  (setq buffer-backed-up nil))))))
+                  (setq buffer-backed-up nil)))))))
     setmodes))
 
 (declare-function diff-no-select "diff"
diff --git a/test/lisp/files-tests.el b/test/lisp/files-tests.el
index ae8ea41a79..15f2a760c4 100644
--- a/test/lisp/files-tests.el
+++ b/test/lisp/files-tests.el
@@ -1244,5 +1244,31 @@ files-tests-file-attributes-equal
                     (executable-find (file-name-nondirectory tmpfile))))))
       (delete-file tmpfile))))
 
+(ert-deftest files-tests-dont-rewrite-precious-files ()
+  "Test that `file-precious-flag' forces files to be saved by
+renaming only, rather than modified in-place."
+  (files-tests--with-temp-file temp-file-name
+    (let* (temp-file-events
+           (watch (file-notify-add-watch
+                   temp-file-name '(change)
+                   (lambda (event)
+                     (push (cadr event) temp-file-events)))))
+      (unwind-protect
+          (with-current-buffer (find-file-noselect temp-file-name)
+            (setq-local file-precious-flag t)
+            (insert "foobar")
+            (should (null (save-buffer)))
+
+            ;; file-notify callbacks are triggered by input events,
+            ;; so we need to accept input before checking results.
+            (with-timeout (3.0 (ignore))
+              (while (read-event nil nil 0.01) (ignore)))
+
+            ;; When file-precious-flag is set, the visited file
+            ;; should never be modified, only renamed-to (which may
+            ;; appear as "renamed" and/or "created" to file-notify).
+            (should (not (memq 'changed temp-file-events))))
+        (file-notify-rm-watch watch)))))
+
 (provide 'files-tests)
 ;;; files-tests.el ends here
diff --git a/test/lisp/net/tramp-tests.el b/test/lisp/net/tramp-tests.el
index cba697da18..1ca98520b3 100644
--- a/test/lisp/net/tramp-tests.el
+++ b/test/lisp/net/tramp-tests.el
@@ -44,6 +44,7 @@
 (require 'dired)
 (require 'ert)
 (require 'ert-x)
+(require 'filenotify)
 (require 'tramp)
 (require 'vc)
 (require 'vc-bzr)
@@ -5741,6 +5742,33 @@ tramp--test-asynchronous-requests-timeout
          (ignore-errors (all-completions "tramp" (symbol-value x)))
          (ert-fail (format "Hook `%s' still contains Tramp function" x))))))
 
+(ert-deftest tramp-test46-file-precious-flag ()
+  "Check that file-precious-flag is respected with Tramp in use."
+  (let* ((temp-file (make-temp-file "emacs"))
+         (remote-file (concat "/mock:localhost:" temp-file))
+         temp-file-events
+         (watch
+          (file-notify-add-watch
+           temp-file '(change)
+           (lambda (event)
+             (push (cadr event) 'temp-file-events)))))
+    (unwind-protect
+        (with-current-buffer (find-file-noselect remote-file)
+          (setq-local file-precious-flag t)
+          (insert "foobar")
+          (should (null (save-buffer)))
+
+          ;; file-notify callbacks are triggered by input events, so
+          ;; we need to accept input before checking results.
+          (with-timeout (3.0 (ignore))
+            (while (read-event nil nil 0.01) (ignore)))
+
+          ;; When file-precious-flag is set, the visited file should
+          ;; never be modified, only renamed-to.
+          (should (not (memq 'changed temp-file-events)))))
+      (file-notify-rm-watch watch)
+      (delete-file temp-file)))
+
 (defun tramp-test-all (&optional interactive)
   "Run all tests for \\[tramp]."
   (interactive "p")
-- 
2.21.0.593.g511ec345e18-goog






reply via email to

[Prev in Thread] Current Thread [Next in Thread]