From: Matt Armstrong <matt@rfc20.org>
To: Eli Zaretskii <eliz@gnu.org>
Cc: 46397@debbugs.gnu.org, eggert@cs.ucla.edu, craven@gmx.net
Subject: bug#46397: 27.1; Cannot delete buffer pointing to a file in a path that includes a file
Date: Sun, 21 Mar 2021 18:43:45 -0700 [thread overview]
Message-ID: <874kh4gdym.fsf@rfc20.org> (raw)
In-Reply-To: <83k0q2cdgp.fsf@gnu.org>
[-- Attachment #1: Type: text/plain, Size: 2432 bytes --]
Eli Zaretskii <eliz@gnu.org> writes:
>> From: Matt Armstrong <matt@rfc20.org>
>> Cc: eggert@cs.ucla.edu, 46397@debbugs.gnu.org, craven@gmx.net
>> Date: Wed, 17 Mar 2021 16:51:20 -0700
>>
>> Thank you for the review Eli. I've incorporated your feedback in the
>> attached patches.
>
> Thanks, we are close.
You are welcome. I've quoted your latest feedback below for reference,
and attached the resulting patches after incorporating the changes. I
also re-ran the new tests.
>> I made the warning message even more terse than you suggested because
>> before doing so the warnings looked like this:
>>
>> Warning (unlock-file): Error unlocking file Unlocking file:
>> Permission denied, /tmp/inaccessible/foo, ignored [Disable showing]
>> [Disable logging]
>>
>> ...which says "unlock file" too many times. With the current patch it
>> is a little bit better:
>>
>> Warning (unlock-file): Unlocking file: Permission denied,
>> /tmp/inaccessible/foo, ignored [Disable showing] [Disable logging]
>
> Fine with me.
>
>> * src/filelock.c (unlock_file_body): New function, do what
>> unlock_file() used to.
>
> Please don't use "foo()" to refer to a function 'foo': that looks like
> a call of 'foo' with no arguments, which is not what you want. Our
> convention is to use 'foo' instead, quoted 'like this'.
>
>> --- a/doc/lispref/files.texi
>> +++ b/doc/lispref/files.texi
>> @@ -764,6 +764,8 @@ File Locks
>> if the buffer is modified. If the buffer is not modified, then
>> the file should not be locked, so this function does nothing. It also
>> does nothing if the current buffer is not visiting a file, or is not locked.
>> +Handles file system errors by calling @code{display-warning} and continuing
> ^^^^^^^
> "This function handles ..."
>
>> +** 'unlock-buffer' displays warnings instead of signaling.
>> +Instead of signaling 'file-error' conditions for file system level
>> +errors, the function now calls 'display-warning' and continues as if
>> +the error did not occur. ^^^^^^^^^^^^^^^^^^^
> ^^^^^^^^^^^^^^^^^^^^^^^
> I'd rephrase "... and otherwise ignores the error".
>
>> +(defun userlock--handle-unlock-error (err)
>> + "Report an error ERR that occurred while unlocking a file."
>
> This will sound better if you rename the argument:
>
> (defun userlock--handle-unlock-error (error)
> "Report an ERROR that occurred while unlocking a file."
[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #2: v4-0001-Add-test-coverage-for-src-filelock.c-Bug-46397.patch --]
[-- Type: text/x-diff, Size: 7671 bytes --]
From f3e82c23aa0290dec8d31f465d4a62704c98d0c1 Mon Sep 17 00:00:00 2001
From: Matt Armstrong <matt@rfc20.org>
Date: Mon, 15 Feb 2021 12:59:08 -0800
Subject: [PATCH v4 1/2] Add test coverage for src/filelock.c (Bug#46397)
* test/src/filelock-tests.el: New file.
---
test/src/filelock-tests.el | 173 +++++++++++++++++++++++++++++++++++++
1 file changed, 173 insertions(+)
create mode 100644 test/src/filelock-tests.el
diff --git a/test/src/filelock-tests.el b/test/src/filelock-tests.el
new file mode 100644
index 0000000000..c6f55efd49
--- /dev/null
+++ b/test/src/filelock-tests.el
@@ -0,0 +1,173 @@
+;;; filelock-tests.el --- test file locking -*- lexical-binding: t; -*-
+
+;; Copyright (C) 2021 Free Software Foundation, Inc.
+
+;; This file is part of GNU Emacs.
+
+;; GNU Emacs is 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 3 of the License, or
+;; (at your option) any later version.
+
+;; GNU Emacs 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.
+
+;; You should have received a copy of the GNU General Public License
+;; along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; This file tests code in src/filelock.c and, to some extent, the
+;; related code in src/fileio.c.
+;;
+;; See also (info "(emacs)Interlocking") and (info "(elisp)File Locks")
+
+;;; Code:
+
+(require 'cl-macs)
+(require 'ert)
+(require 'seq)
+
+(defun filelock-tests--fixture (test-function)
+ "Call TEST-FUNCTION under a test fixture.
+Create a test directory and a buffer whose `buffer-file-name' and
+`buffer-file-truename' are a file within it, then call
+TEST-FUNCTION. Finally, delete the buffer and the test
+directory."
+ (let* ((temp-dir (make-temp-file "filelock-tests" t))
+ (name (concat (file-name-as-directory temp-dir)
+ "userfile"))
+ (create-lockfiles t))
+ (unwind-protect
+ (with-temp-buffer
+ (setq buffer-file-name name
+ buffer-file-truename name)
+ (unwind-protect
+ (save-current-buffer
+ (funcall test-function))
+ ;; Set `buffer-file-truename' nil to prevent unlocking,
+ ;; which might prompt the user and/or signal errors.
+ (setq buffer-file-name nil
+ buffer-file-truename nil)))
+ (delete-directory temp-dir t nil))))
+
+(defun filelock-tests--make-lock-name (file-name)
+ "Return the lock file name for FILE-NAME.
+Equivalent logic in Emacs proper is implemented in C and
+unavailable to Lisp."
+ (concat (file-name-directory (expand-file-name file-name))
+ ".#"
+ (file-name-nondirectory file-name)))
+
+(defun filelock-tests--spoil-lock-file (file-name)
+ "Spoil the lock file for FILE-NAME.
+Cause Emacs to report errors for various file locking operations
+on FILE-NAME going forward. Create a file that is incompatible
+with Emacs' file locking protocol, but uses the same name as
+FILE-NAME's lock file. A directory file is used, which is
+portable in practice."
+ (make-directory (filelock-tests--make-lock-name file-name)))
+
+(defun filelock-tests--unspoil-lock-file (file-name)
+ "Remove the lock file spoiler for FILE-NAME.
+See `filelock-tests--spoil-lock-file'."
+ (delete-directory (filelock-tests--make-lock-name file-name) t))
+
+(defun filelock-tests--should-be-locked ()
+ "Abort the current test if the current buffer is not locked.
+Exception: on systems without lock file support, aborts the
+current test if the current file is locked (which should never
+the case)."
+ (if (eq system-type 'ms-dos)
+ (should-not (file-locked-p buffer-file-truename))
+ (should (file-locked-p buffer-file-truename))))
+
+(ert-deftest filelock-tests-lock-unlock-no-errors ()
+ "Check that locking and unlocking works without error."
+ (filelock-tests--fixture
+ (lambda ()
+ (should-not (file-locked-p (buffer-file-name)))
+
+ ;; inserting text should lock the buffer's file.
+ (insert "this locks the buffer's file")
+ (filelock-tests--should-be-locked)
+ (unlock-buffer)
+ (set-buffer-modified-p nil)
+ (should-not (file-locked-p (buffer-file-name)))
+
+ ;; `set-buffer-modified-p' should lock the buffer's file.
+ (set-buffer-modified-p t)
+ (filelock-tests--should-be-locked)
+ (unlock-buffer)
+ (should-not (file-locked-p (buffer-file-name)))
+
+ (should-not (file-locked-p (buffer-file-name))))))
+
+(ert-deftest filelock-tests-lock-spoiled ()
+ "Check `lock-buffer' ."
+ (skip-unless (not (eq system-type 'ms-dos))) ; no filelock support
+ (filelock-tests--fixture
+ (lambda ()
+ (filelock-tests--spoil-lock-file buffer-file-truename)
+ ;; FIXME: errors when locking a file are ignored; should they be?
+ (set-buffer-modified-p t)
+ (filelock-tests--unspoil-lock-file buffer-file-truename)
+ (should-not (file-locked-p buffer-file-truename)))))
+
+(ert-deftest filelock-tests-file-locked-p-spoiled ()
+ "Check that `file-locked-p' fails if the lockfile is \"spoiled\"."
+ (skip-unless (not (eq system-type 'ms-dos))) ; no filelock support
+ (filelock-tests--fixture
+ (lambda ()
+ (filelock-tests--spoil-lock-file buffer-file-truename)
+ (let ((err (should-error (file-locked-p (buffer-file-name)))))
+ (should (equal (seq-subseq err 0 2)
+ '(file-error "Testing file lock")))))))
+
+(ert-deftest filelock-tests-unlock-spoiled ()
+ "Check that `unlock-buffer' fails if the lockfile is \"spoiled\"."
+ (skip-unless (not (eq system-type 'ms-dos))) ; no filelock support
+ (filelock-tests--fixture
+ (lambda ()
+ ;; Set the buffer modified with file locking temporarily
+ ;; disabled.
+ (let ((create-lockfiles nil))
+ (set-buffer-modified-p t))
+ (should-not (file-locked-p buffer-file-truename))
+ (filelock-tests--spoil-lock-file buffer-file-truename)
+
+ ;; FIXME: Unlocking buffers should not signal errors related to
+ ;; their lock files (bug#46397).
+ (let ((err (should-error (unlock-buffer))))
+ (should (equal (cl-subseq err 0 2)
+ '(file-error "Unlocking file")))))))
+
+(ert-deftest filelock-tests-kill-buffer-spoiled ()
+ "Check that `kill-buffer' fails if a lockfile is \"spoiled\"."
+ (skip-unless (not (eq system-type 'ms-dos))) ; no filelock support
+ (filelock-tests--fixture
+ (lambda ()
+ ;; Set the buffer modified with file locking temporarily
+ ;; disabled.
+ (let ((create-lockfiles nil))
+ (set-buffer-modified-p t))
+ (should-not (file-locked-p buffer-file-truename))
+ (filelock-tests--spoil-lock-file buffer-file-truename)
+
+ ;; Kill the current buffer. Because the buffer is modified Emacs
+ ;; will attempt to unlock it. Temporarily bind `yes-or-no-p' to
+ ;; a function that fakes a "yes" answer for the "Buffer modified;
+ ;; kill anyway?" prompt.
+ ;;
+ ;; FIXME: Killing buffers should not signal errors related to
+ ;; their lock files (bug#46397).
+ (let* ((err (cl-letf (((symbol-function 'yes-or-no-p)
+ (lambda (&rest _) t)))
+ (should-error (kill-buffer)))))
+ (should (equal (seq-subseq err 0 2)
+ '(file-error "Unlocking file")))))))
+
+(provide 'filelock-tests)
+;;; filelock-tests.el ends here
--
2.30.2
[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #3: v4-0002-File-unlock-errors-now-issue-warnings-Bug-46397.patch --]
[-- Type: text/x-diff, Size: 6567 bytes --]
From 08516cc2555a9bd49440ffd3b91b6b294769243a Mon Sep 17 00:00:00 2001
From: Matt Armstrong <matt@rfc20.org>
Date: Fri, 19 Feb 2021 15:39:15 -0800
Subject: [PATCH v4 2/2] File unlock errors now issue warnings (Bug#46397)
The primary idea is to allow `kill-buffer' and `kill-emacs' to
complete even if Emacs has trouble unlocking the buffer's file.
* lisp/userlock.el (userlock--handle-unlock-error): New function, call
`display-error'.
* src/filelock.c (unlock_file_body): New function, do what
'unlock_file' used to.
(unlock_file_handle_error): New function, call
`userlock--handle-unlock-error' with the captured error.
(unlock_file): Handle `file-error' conditions by calling the handler
defined above.
* test/src/filelock-tests.el (filelock-tests-kill-buffer-spoiled):
(filelock-tests-unlock-spoiled): Modify to test new behavior.
---
doc/lispref/files.texi | 2 ++
etc/NEWS | 6 ++++++
lisp/userlock.el | 10 ++++++++++
src/filelock.c | 26 +++++++++++++++++++++++---
test/src/filelock-tests.el | 34 ++++++++++++++++++++++------------
5 files changed, 63 insertions(+), 15 deletions(-)
diff --git a/doc/lispref/files.texi b/doc/lispref/files.texi
index 2828b50cad..a8b921eb9f 100644
--- a/doc/lispref/files.texi
+++ b/doc/lispref/files.texi
@@ -764,6 +764,8 @@ File Locks
if the buffer is modified. If the buffer is not modified, then
the file should not be locked, so this function does nothing. It also
does nothing if the current buffer is not visiting a file, or is not locked.
+This function handles file system errors by calling @code{display-warning}
+and otherwise ignores the error.
@end defun
@defopt create-lockfiles
diff --git a/etc/NEWS b/etc/NEWS
index c602166397..232241217a 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -2490,6 +2490,12 @@ back in Emacs 23.1. The affected functions are: 'make-obsolete',
\f
* Lisp Changes in Emacs 28.1
++++
+** 'unlock-buffer' displays warnings instead of signaling.
+Instead of signaling 'file-error' conditions for file system level
+errors, the function now calls 'display-warning' and continues as if
+the error did not occur.
+
+++
** New function 'always'.
This is identical to 'ignore', but returns t instead.
diff --git a/lisp/userlock.el b/lisp/userlock.el
index 57311ac99c..4a75815318 100644
--- a/lisp/userlock.el
+++ b/lisp/userlock.el
@@ -224,4 +224,14 @@ ask-user-about-supersession-help
revert-buffer-binding))
(help-mode)))))
+;;;###autoload
+(defun userlock--handle-unlock-error (error)
+ "Report an ERROR that occurred while unlocking a file."
+ (display-warning
+ '(unlock-file)
+ ;; There is no need to explain that this is an unlock error because
+ ;; ERR is a `file-error' condition, which explains this.
+ (message "%s, ignored" (error-message-string error))
+ :warning))
+
;;; userlock.el ends here
diff --git a/src/filelock.c b/src/filelock.c
index 373fc00a42..446a262a1c 100644
--- a/src/filelock.c
+++ b/src/filelock.c
@@ -719,8 +719,8 @@ lock_file (Lisp_Object fn)
}
}
-void
-unlock_file (Lisp_Object fn)
+static Lisp_Object
+unlock_file_body (Lisp_Object fn)
{
char *lfname;
USE_SAFE_ALLOCA;
@@ -737,6 +737,23 @@ unlock_file (Lisp_Object fn)
report_file_errno ("Unlocking file", filename, err);
SAFE_FREE ();
+ return Qnil;
+}
+
+static Lisp_Object
+unlock_file_handle_error (Lisp_Object err)
+{
+ call1 (intern ("userlock--handle-unlock-error"), err);
+ return Qnil;
+}
+
+void
+unlock_file (Lisp_Object fn)
+{
+ internal_condition_case_1 (unlock_file_body,
+ fn,
+ list1(Qfile_error),
+ unlock_file_handle_error);
}
#else /* MSDOS */
@@ -790,7 +807,10 @@ DEFUN ("unlock-buffer", Funlock_buffer, Sunlock_buffer,
0, 0, 0,
doc: /* Unlock the file visited in the current buffer.
If the buffer is not modified, this does nothing because the file
-should not be locked in that case. */)
+should not be locked in that case. It also does nothing if the
+current buffer is not visiting a file, or is not locked. Handles file
+system errors by calling `display-warning' and continuing as if the
+error did not occur. */)
(void)
{
if (SAVE_MODIFF < MODIFF
diff --git a/test/src/filelock-tests.el b/test/src/filelock-tests.el
index c6f55efd49..a96d6d6728 100644
--- a/test/src/filelock-tests.el
+++ b/test/src/filelock-tests.el
@@ -138,11 +138,16 @@ filelock-tests-unlock-spoiled
(should-not (file-locked-p buffer-file-truename))
(filelock-tests--spoil-lock-file buffer-file-truename)
- ;; FIXME: Unlocking buffers should not signal errors related to
- ;; their lock files (bug#46397).
- (let ((err (should-error (unlock-buffer))))
- (should (equal (cl-subseq err 0 2)
- '(file-error "Unlocking file")))))))
+ ;; Errors from `unlock-buffer' should call
+ ;; `userlock--handle-unlock-error' (bug#46397).
+ (let (errors)
+ (cl-letf (((symbol-function 'userlock--handle-unlock-error)
+ (lambda (err) (push err errors))))
+ (unlock-buffer))
+ (should (consp errors))
+ (should (equal '(file-error "Unlocking file")
+ (seq-subseq (car errors) 0 2)))
+ (should (equal (length errors) 1))))))
(ert-deftest filelock-tests-kill-buffer-spoiled ()
"Check that `kill-buffer' fails if a lockfile is \"spoiled\"."
@@ -161,13 +166,18 @@ filelock-tests-kill-buffer-spoiled
;; a function that fakes a "yes" answer for the "Buffer modified;
;; kill anyway?" prompt.
;;
- ;; FIXME: Killing buffers should not signal errors related to
- ;; their lock files (bug#46397).
- (let* ((err (cl-letf (((symbol-function 'yes-or-no-p)
- (lambda (&rest _) t)))
- (should-error (kill-buffer)))))
- (should (equal (seq-subseq err 0 2)
- '(file-error "Unlocking file")))))))
+ ;; File errors from unlocking files should call
+ ;; `userlock--handle-unlock-error' (bug#46397).
+ (let (errors)
+ (cl-letf (((symbol-function 'yes-or-no-p)
+ (lambda (&rest _) t))
+ ((symbol-function 'userlock--handle-unlock-error)
+ (lambda (err) (push err errors))))
+ (kill-buffer))
+ (should (consp errors))
+ (should (equal '(file-error "Unlocking file")
+ (seq-subseq (car errors) 0 2)))
+ (should (equal (length errors) 1))))))
(provide 'filelock-tests)
;;; filelock-tests.el ends here
--
2.30.2
next prev parent reply other threads:[~2021-03-22 1:43 UTC|newest]
Thread overview: 55+ messages / expand[flat|nested] mbox.gz Atom feed top
2021-02-09 9:47 bug#46397: 27.1; Cannot delete buffer pointing to a file in a path that includes a file Peter
2021-02-09 23:47 ` Matt Armstrong
2021-02-10 0:23 ` Matt Armstrong
2021-02-10 15:05 ` Eli Zaretskii
2021-02-10 19:23 ` Paul Eggert
2021-02-10 19:45 ` Eli Zaretskii
2021-02-10 22:39 ` Matt Armstrong
2021-02-12 7:43 ` Eli Zaretskii
2021-02-12 9:36 ` Paul Eggert
2021-02-12 11:33 ` Eli Zaretskii
2021-02-12 23:59 ` Matt Armstrong
2021-02-13 8:07 ` Eli Zaretskii
2021-02-11 22:14 ` Matt Armstrong
2021-02-12 2:20 ` Paul Eggert
2021-02-12 7:15 ` Eli Zaretskii
2021-02-13 1:15 ` Matt Armstrong
2021-02-13 1:26 ` Paul Eggert
2021-02-13 8:21 ` Eli Zaretskii
2021-02-13 8:28 ` Eli Zaretskii
2021-02-14 0:49 ` Matt Armstrong
2021-02-14 19:22 ` Eli Zaretskii
2021-02-14 22:16 ` Matt Armstrong
2021-02-15 15:09 ` Eli Zaretskii
2021-02-16 0:49 ` Matt Armstrong
2021-02-16 1:55 ` Paul Eggert
2021-02-16 15:06 ` Eli Zaretskii
2021-02-16 11:53 ` Lars Ingebrigtsen
2021-02-22 19:24 ` bug#46397: [PATCH] " Matt Armstrong
2021-02-19 19:10 ` Matt Armstrong
2021-02-19 19:23 ` Eli Zaretskii
2021-02-19 21:46 ` Matt Armstrong
2021-02-20 9:09 ` Eli Zaretskii
2021-02-21 0:36 ` Matt Armstrong
2021-02-21 23:43 ` Mike Kupfer
2021-02-22 1:42 ` Matt Armstrong
2021-03-14 18:03 ` Bill Wohler
2021-03-17 23:36 ` Matt Armstrong
2021-02-24 17:37 ` Matt Armstrong
2021-02-24 18:50 ` Eli Zaretskii
2021-03-01 16:59 ` Eli Zaretskii
2021-03-05 22:19 ` Matt Armstrong
2021-03-06 9:36 ` Eli Zaretskii
2021-03-06 23:39 ` Matt Armstrong
2021-03-07 2:50 ` Paul Eggert
2021-03-07 5:57 ` Matt Armstrong
2021-02-19 19:45 ` Paul Eggert
2021-02-19 21:52 ` Matt Armstrong
2021-03-08 2:18 ` Matt Armstrong
2021-03-11 14:34 ` Eli Zaretskii
2021-03-17 23:49 ` Matt Armstrong
2021-03-17 23:51 ` Matt Armstrong
2021-03-20 10:43 ` Eli Zaretskii
2021-03-22 1:43 ` Matt Armstrong [this message]
2021-03-27 9:20 ` Eli Zaretskii
2021-02-10 0:26 ` Matt Armstrong
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
List information: https://www.gnu.org/software/emacs/
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=874kh4gdym.fsf@rfc20.org \
--to=matt@rfc20.org \
--cc=46397@debbugs.gnu.org \
--cc=craven@gmx.net \
--cc=eggert@cs.ucla.edu \
--cc=eliz@gnu.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
Code repositories for project(s) associated with this public inbox
https://git.savannah.gnu.org/cgit/emacs.git
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).