Subject: Re: bug#34093: auto-save prevents quitting emacs on read-only filesystem

From: Stefan Kangas <[email-masked]>
Date: Tue, 18 Aug 2020 17:52:24 +0000

tags 34093 + patch
thanks

Leonard Lausen <[email-masked]> writes:

> Recently my filesystem had to be emergency remounted as read-only. I
> observed that I could not kill-emacs anymore. The following showed up in
> my log upon trying to quit emacs:
>
> ```
> company-statistics--save: Opening output file: Read-only file system, /home/leonard/.emacs.d/.cache/company-statistics-cache.el
> Error running timer ‘savehist-autosave’: (file-error "Opening output file" "Read-only file system" "/home/leonard/.emacs.d/.cache/savehist")
> ```
>
> This prevented quitting emacs.
>
> I am not sure if `company-statistics--save` prevented the shutdown, or
> just warned about the read-only file system. Possibly the behavior of
> `company-statistics--save` is sane and only `savehist-autosave` is at
> fault. Maybe both are at fault?
>
> The expected behavior is that emacs may warn about or ignore that it
> cannot write the savehist file. Calling kill-emacs should still quit
> emacs.

I can reproduce this under "emacs -Q" by evaluating:

(progn (savehist-mode)
       (setq savehist-file "/usr/foo")
       (confirm-exit-emacs))

I have attached a patch which fixes this bug by showing a warning if
this file is not readable.

Best regards,
Stefan Kangas
From d780864fd38768801c88778ee694a1a535e41810 Mon Sep 17 00:00:00 2001
From: Stefan Kangas <[email-masked]>
Date: Tue, 18 Aug 2020 19:29:19 +0200
Subject: [PATCH] Fix exiting Emacs when savehist-file not writable

* lisp/savehist.el (savehist-save): Show warning when 'savehist-file'
is not writable.  (Bug#34093)
(savehist--has-given-file-warning): New variable.
---
 lisp/savehist.el | 41 ++++++++++++++++++++++++-----------------
 1 file changed, 24 insertions(+), 17 deletions(-)

diff --git a/lisp/savehist.el b/lisp/savehist.el
index 4e52efe7f1..5a64b9327a 100644
--- a/lisp/savehist.el
+++ b/lisp/savehist.el
@@ -215,6 +215,7 @@ savehist-uninstall
     (cancel-timer savehist-timer)
     (setq savehist-timer nil)))
 
+(defvar savehist--has-given-file-warning nil)
 (defun savehist-save (&optional auto-save)
   "Save the values of minibuffer history variables.
 Unbound symbols referenced in `savehist-additional-variables' are ignored.
@@ -288,23 +289,29 @@ savehist-save
     ;; If autosaving, avoid writing if nothing has changed since the
     ;; last write.
     (let ((checksum (md5 (current-buffer) nil nil savehist-coding-system)))
-      (unless (and auto-save (equal checksum savehist-last-checksum))
-	;; Set file-precious-flag when saving the buffer because we
-	;; don't want a half-finished write ruining the entire
-	;; history.  Remember that this is run from a timer and from
-	;; kill-emacs-hook, and also that multiple Emacs instances
-	;; could write to this file at once.
-	(let ((file-precious-flag t)
-	      (coding-system-for-write savehist-coding-system)
-              (dir (file-name-directory savehist-file)))
-          ;; Ensure that the directory exists before saving.
-          (unless (file-exists-p dir)
-            (make-directory dir t))
-	  (write-region (point-min) (point-max) savehist-file nil
-			(unless (called-interactively-p 'interactive) 'quiet)))
-	(when savehist-file-modes
-	  (set-file-modes savehist-file savehist-file-modes))
-	(setq savehist-last-checksum checksum)))))
+      (condition-case err
+        (unless (and auto-save (equal checksum savehist-last-checksum))
+	  ;; Set file-precious-flag when saving the buffer because we
+	  ;; don't want a half-finished write ruining the entire
+	  ;; history.  Remember that this is run from a timer and from
+	  ;; kill-emacs-hook, and also that multiple Emacs instances
+	  ;; could write to this file at once.
+	  (let ((file-precious-flag t)
+	        (coding-system-for-write savehist-coding-system)
+                (dir (file-name-directory savehist-file)))
+            ;; Ensure that the directory exists before saving.
+            (unless (file-exists-p dir)
+              (make-directory dir t))
+	    (write-region (point-min) (point-max) savehist-file nil
+			  (unless (called-interactively-p 'interactive) 'quiet)))
+	  (when savehist-file-modes
+	    (set-file-modes savehist-file savehist-file-modes))
+	  (setq savehist-last-checksum checksum))
+        (file-error
+         (unless savehist--has-given-file-warning
+          (lwarn '(savehist-file) :warning "Error writing `%s': %s"
+                 savehist-file (caddr err))
+          (setq savehist--has-given-file-warning t)))))))
 
 (defun savehist-autosave ()
   "Save the minibuffer history if it has been modified since the last save.
-- 
2.28.0