From: Matt Armstrong <matt@rfc20.org>
To: Paul Eggert <eggert@cs.ucla.edu>, Eli Zaretskii <eliz@gnu.org>
Cc: 46397@debbugs.gnu.org, craven@gmx.net
Subject: bug#46397: 27.1; Cannot delete buffer pointing to a file in a path that includes a file
Date: Sun, 07 Mar 2021 18:18:46 -0800 [thread overview]
Message-ID: <87wnuio0ah.fsf@mdeb> (raw)
In-Reply-To: <fd4b247f-2795-cc94-eb40-ee05fca01c1e@cs.ucla.edu>
[-- Attachment #1: Type: text/plain, Size: 1211 bytes --]
Paul Eggert <eggert@cs.ucla.edu> writes:
> On 2/19/21 11:10 AM, Matt Armstrong wrote:
>> (a) Modify `kill-buffer' to call `unlock-buffer' sooner, closer to the
>> point where it is already running hooks prompting the user. Handle
>> unlock errors there by prompting.
>
> How would unlock-buffer's API change, so that kill-buffer and
> unlock-buffer's other callers can do the right thing? Would it signal an
> error, and if so which one (or a new kind of error)?
It turns out that naming a directory the same as the would-be lock file
suffices to induce errors in a portable way.
I've updated the (substantially simplified) test in patch 0001,
incorporating Eli's feedback.
Patch 0002 implements the handling of unlock errors as warnings by way
of a userlock.el function. I figure that function would be a handy hook
for people to use with `debug-on-entry', if they want.
Both ready for review now.
Things I noticed:
a) if a buffer becomes unmodified by way of `undo' it is unlocked
twice, causing two warnings.
b) the file errors generated contain the original file name, not the
lock file name.
I am treating both of of those things as not worth fixing, at least for
now.
[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #2: 0001-Add-test-coverage-for-src-filelock.c-Bug-46397.patch --]
[-- Type: text/x-diff, Size: 7668 bytes --]
From f2969e568b579e49dbe2f73cbf9961d5f32503aa Mon Sep 17 00:00:00 2001
From: Matt Armstrong <matt@rfc20.org>
Date: Mon, 15 Feb 2021 12:59:08 -0800
Subject: [PATCH 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.1
[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #3: 0002-File-unlock-errors-now-issue-warnings-Bug-46397.patch --]
[-- Type: text/x-diff, Size: 4625 bytes --]
From 7b272d53d603d9f3cfd2421d31e5891723fc2ca1 Mon Sep 17 00:00:00 2001
From: Matt Armstrong <matt@rfc20.org>
Date: Fri, 19 Feb 2021 15:39:15 -0800
Subject: [PATCH 2/2] File unlock errors now issue warnings (Bug#46397)
* 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): Use condition case to glue together the above.
* test/src/filelock-tests.el (filelock-tests-kill-buffer-spoiled):
(filelock-tests-unlock-spoiled): Modify to test new behavior.
---
lisp/userlock.el | 9 +++++++++
src/filelock.c | 21 +++++++++++++++++++--
test/src/filelock-tests.el | 34 ++++++++++++++++++++++------------
3 files changed, 50 insertions(+), 14 deletions(-)
diff --git a/lisp/userlock.el b/lisp/userlock.el
index a340ff85b2..d33089b11a 100644
--- a/lisp/userlock.el
+++ b/lisp/userlock.el
@@ -193,4 +193,13 @@ ask-user-about-supersession-help
(with-current-buffer standard-output
(help-mode))))
+;;;###autoload
+(defun userlock--handle-unlock-error (err)
+ "Report an error ERR that occurred while unlocking a file."
+ (display-warning
+ '(unlock-file)
+ (message "Error unlocking file, proceeding as if unlocked: %s"
+ (error-message-string err))
+ :warning))
+
;;; userlock.el ends here
diff --git a/src/filelock.c b/src/filelock.c
index 373fc00a42..1f5bba4447 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 */
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.1
next prev parent reply other threads:[~2021-03-08 2:18 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 [this message]
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
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
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=87wnuio0ah.fsf@mdeb \
--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 external index
https://git.savannah.gnu.org/cgit/emacs.git
https://git.savannah.gnu.org/cgit/emacs/org-mode.git
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.