emacs-diffs
[Top][All Lists]
Advanced

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

master d35868b: Implement file locks for remote files (Bug#49261)


From: Michael Albinus
Subject: master d35868b: Implement file locks for remote files (Bug#49261)
Date: Wed, 7 Jul 2021 12:37:02 -0400 (EDT)

branch: master
commit d35868bec96718705c9bc8aaac3bc583c837033f
Author: Michael Albinus <michael.albinus@gmx.de>
Commit: Michael Albinus <michael.albinus@gmx.de>

    Implement file locks for remote files (Bug#49261)
    
    * doc/lispref/files.texi (Magic File Names): Add file-locked-p,
    lock-file and unlock-file.
    
    * etc/NEWS: Tramp supports file locks now.
    
    * lisp/net/tramp-adb.el (tramp-adb-file-name-handler-alist):
    Add `file-locked-p', `lock-file' and `unlock-file'.
    (tramp-adb-handle-write-region): Handle LOCKNAME.
    
    * lisp/net/tramp-archive.el (tramp-archive-file-name-handler-alist):
    Add `file-locked-p', `lock-file' and `unlock-file'.
    
    * lisp/net/tramp-crypt.el (tramp-crypt-file-name-handler-alist):
    Add `file-locked-p', `lock-file' and `unlock-file'.
    (tramp-crypt-handle-file-locked-p, tramp-crypt-handle-lock-file)
    (tramp-crypt-handle-unlock-file): New defun.
    
    * lisp/net/tramp-fuse.el (tramp-fuse-mounted-p): Simplify.
    (tramp-fuse-unmount): New defun.
    
    * lisp/net/tramp-gvfs.el (tramp-gvfs-file-name-handler-alist):
    Add `file-locked-p', `lock-file' and `unlock-file'.
    (tramp-gvfs-maybe-open-connection): Set "lock-pid" connection property.
    
    * lisp/net/tramp-rclone.el (tramp-rclone-file-name-handler-alist):
    Add `file-locked-p', `lock-file' and `unlock-file'.
    (tramp-rclone-maybe-open-connection): Set "lock-pid" connection property.
    
    * lisp/net/tramp-sh.el (tramp-sh-file-name-handler-alist):
    Add `file-locked-p', `lock-file' and `unlock-file'.
    (tramp-sh-handle-write-region): Handle LOCKNAME.
    
    * lisp/net/tramp-smb.el (tramp-smb-file-name-handler-alist):
    Add `file-locked-p', `lock-file' and `unlock-file'.
    (tramp-smb-handle-copy-directory): Use `sleep-for'.
    (tramp-smb-handle-write-region): Handle LOCKNAME.
    
    * lisp/net/tramp-sshfs.el (tramp-sshfs-file-name-handler-alist):
    Add `file-locked-p', `lock-file' and `unlock-file'.
    (tramp-sshfs-handle-write-region): Handle LOCKNAME.
    (tramp-sshfs-maybe-open-connection): Set "lock-pid" connection property.
    
    * lisp/net/tramp-sudoedit.el (tramp-sudoedit-file-name-handler-alist):
    Add `file-locked-p', `lock-file' and `unlock-file'.
    (tramp-sudoedit-maybe-open-connection):
    Set "lock-pid" connection property.
    
    * lisp/net/tramp.el (tramp-file-name-for-operation):
    Add `file-locked-p', `lock-file' and `unlock-file'.
    (tramp-make-lock-name, tramp-get-lock-file, tramp-get-lock-pid)
    (tramp-handle-file-locked-p, tramp-handle-lock-file)
    (tramp-handle-unlock-file): New defuns.
    (tramp-lock-file-contents-regexp): New regexp.
    (tramp-handle-write-region): Handle LOCKNAME.
    
    * src/filelock.c (lock_file, unlock_file_body, Ffile_locked_p):
    Call handler if exists.
    (Flock_file, Funlock_file): New defuns.
    (Qlock_file, Qunlock_file, Qfile_locked_p): Declare symbols.
    (Slock_file, Sunlock_file): Declare subroutines.
    
    * test/lisp/net/tramp-archive-tests.el
    (tramp-archive-test40-make-nearby-temp-file)
    (tramp-archive-test43-file-system-info): Rename.
    
    * test/lisp/net/tramp-tests.el (top): Set `create-lockfiles' to nil.
    (tramp--test-fuse-p): New defun.
    (tramp-test14-delete-directory): Use it.
    (tramp-test39-lock-file): New test.
    (tramp-test40-make-nearby-temp-file)
    (tramp-test41-special-characters)
    (tramp-test41-special-characters-with-stat)
    (tramp-test41-special-characters-with-perl)
    (tramp-test41-special-characters-with-ls, tramp-test42-utf8)
    (tramp-test42-utf8-with-stat, tramp-test42-utf8-with-perl)
    (tramp-test42-utf8-with-ls, tramp-test43-file-system-info)
    (tramp-test44-asynchronous-requests, tramp-test45-auto-load)
    (tramp-test45-delay-load, tramp-test45-recursive-load)
    (tramp-test45-remote-load-path, tramp-test46-unload): Rename.
    (tramp--test-special-characters, tramp--test-utf8)
    (tramp--test-asynchronous-requests-timeout): Modify docstring.
---
 doc/lispref/files.texi               |  10 +--
 etc/NEWS                             |   6 +-
 lisp/net/tramp-adb.el                |  30 +++++++--
 lisp/net/tramp-archive.el            |   3 +
 lisp/net/tramp-cache.el              |   2 +
 lisp/net/tramp-crypt.el              |  18 +++++
 lisp/net/tramp-fuse.el               |  17 +++--
 lisp/net/tramp-gvfs.el               |   6 ++
 lisp/net/tramp-rclone.el             |   7 ++
 lisp/net/tramp-sh.el                 |  26 +++++++-
 lisp/net/tramp-smb.el                |  28 ++++++--
 lisp/net/tramp-sshfs.el              |  46 ++++++++++---
 lisp/net/tramp-sudoedit.el           |   7 ++
 lisp/net/tramp.el                    | 109 +++++++++++++++++++++++++++---
 src/filelock.c                       |  58 +++++++++++++++-
 test/lisp/net/tramp-archive-tests.el |   4 +-
 test/lisp/net/tramp-tests.el         | 125 ++++++++++++++++++++++++++++-------
 17 files changed, 432 insertions(+), 70 deletions(-)

diff --git a/doc/lispref/files.texi b/doc/lispref/files.texi
index 5238597..ae763a2 100644
--- a/doc/lispref/files.texi
+++ b/doc/lispref/files.texi
@@ -3273,7 +3273,7 @@ first, before handlers for jobs such as remote file 
access.
 @code{file-equal-p},
 @code{file-executable-p}, @code{file-exists-p},
 @code{file-in-directory-p},
-@code{file-local-copy},
+@code{file-local-copy}, @code{file-locked-p},
 @code{file-modes}, @code{file-name-all-completions},
 @code{file-name-as-directory},
 @code{file-name-case-insensitive-p},
@@ -3292,7 +3292,7 @@ first, before handlers for jobs such as remote file 
access.
 @code{get-file-buffer},
 @code{insert-directory},
 @code{insert-file-contents},@*
-@code{load},
+@code{load}, @code{lock-file},
 @code{make-auto-save-file-name},
 @code{make-directory},
 @code{make-directory-internal},
@@ -3307,6 +3307,7 @@ first, before handlers for jobs such as remote file 
access.
 @code{substitute-in-file-name},@*
 @code{temporary-file-directory},
 @code{unhandled-file-name-directory},
+@code{unlock-file},
 @code{vc-registered},
 @code{verify-visited-file-modtime},@*
 @code{write-region}.
@@ -3331,7 +3332,7 @@ first, before handlers for jobs such as remote file 
access.
 @code{file-equal-p},
 @code{file-executable-p}, @code{file-exists-p},
 @code{file-in-directory-p},
-@code{file-local-copy},
+@code{file-local-copy}, @code{file-locked-p},
 @code{file-modes}, @code{file-name-all-completions},
 @code{file-name-as-directory},
 @code{file-name-case-insensitive-p},
@@ -3350,7 +3351,7 @@ first, before handlers for jobs such as remote file 
access.
 @code{get-file-buffer},
 @code{insert-directory},
 @code{insert-file-contents},
-@code{load},
+@code{load}, @code{lock-file},
 @code{make-auto-save-file-name},
 @code{make-direc@discretionary{}{}{}tory},
 @code{make-direc@discretionary{}{}{}tory-internal},
@@ -3363,6 +3364,7 @@ first, before handlers for jobs such as remote file 
access.
 @code{start-file-process},
 @code{substitute-in-file-name},
 @code{unhandled-file-name-directory},
+@code{unlock-file},
 @code{vc-regis@discretionary{}{}{}tered},
 @code{verify-visited-file-modtime},
 @code{write-region}.
diff --git a/etc/NEWS b/etc/NEWS
index 7bf8c1d..0e8a846 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -323,6 +323,7 @@ emulators by using the new input-meta-mode with the special 
value
 ** New frame parameter 'drag-with-tab-line'.
 This parameter, similar to 'drag-with-header-line', allows moving frames
 by dragging the tab lines of their topmost windows with the mouse.
+
 
 * Editing Changes in Emacs 28.1
 
@@ -1467,6 +1468,9 @@ rare cases) Tramp blocks Emacs, and we need further debug 
information.
 directory must be confirmed.  In order to suppress this confirmation,
 set user option 'tramp-allow-unsafe-temporary-files' to t.
 
++++
+*** Tramp supports file locks now.
+
 ** Tempo
 
 ---
@@ -2932,7 +2936,7 @@ The former is now declared obsolete.
 * Lisp Changes in Emacs 28.1
 
 ---
-*** :safe settings in 'defcustom' are now propagated to the loaddefs files.
+*** ':safe' settings in 'defcustom' are now propagated to the loaddefs files.
 
 +++
 ** New function 'syntax-class-to-char'.
diff --git a/lisp/net/tramp-adb.el b/lisp/net/tramp-adb.el
index f956952..9c1c8ac 100644
--- a/lisp/net/tramp-adb.el
+++ b/lisp/net/tramp-adb.el
@@ -133,6 +133,7 @@ It is used for TCP/IP devices."
     (file-exists-p . tramp-handle-file-exists-p)
     (file-in-directory-p . tramp-handle-file-in-directory-p)
     (file-local-copy . tramp-adb-handle-file-local-copy)
+    (file-locked-p . tramp-handle-file-locked-p)
     (file-modes . tramp-handle-file-modes)
     (file-name-all-completions . tramp-adb-handle-file-name-all-completions)
     (file-name-as-directory . tramp-handle-file-name-as-directory)
@@ -159,6 +160,7 @@ It is used for TCP/IP devices."
     (insert-directory . tramp-handle-insert-directory)
     (insert-file-contents . tramp-handle-insert-file-contents)
     (load . tramp-handle-load)
+    (lock-file . tramp-handle-lock-file)
     (make-auto-save-file-name . tramp-handle-make-auto-save-file-name)
     (make-directory . tramp-adb-handle-make-directory)
     (make-directory-internal . ignore)
@@ -180,6 +182,7 @@ It is used for TCP/IP devices."
     (tramp-get-remote-uid . ignore)
     (tramp-set-file-uid-gid . ignore)
     (unhandled-file-name-directory . ignore)
+    (unlock-file . tramp-handle-unlock-file)
     (vc-registered . ignore)
     (verify-visited-file-modtime . tramp-handle-verify-visited-file-modtime)
     (write-region . tramp-adb-handle-write-region))
@@ -533,9 +536,10 @@ But handle the case, if the \"test\" command is not 
available."
                       rw-path)))))))
 
 (defun tramp-adb-handle-write-region
-  (start end filename &optional append visit _lockname mustbenew)
+  (start end filename &optional append visit lockname mustbenew)
   "Like `write-region' for Tramp files."
-  (setq filename (expand-file-name filename))
+  (setq filename (expand-file-name filename)
+       lockname (file-truename (or lockname filename)))
   (with-parsed-tramp-file-name filename nil
     (when (and mustbenew (file-exists-p filename)
               (or (eq mustbenew 'excl)
@@ -544,15 +548,26 @@ But handle the case, if the \"test\" command is not 
available."
                     (format "File %s exists; overwrite anyway? " filename)))))
       (tramp-error v 'file-already-exists filename))
 
-    (let* ((curbuf (current-buffer))
+    (let* ((auto-saving
+           (string-match-p "^#.+#$" (file-name-nondirectory filename)))
+          file-locked
+          (curbuf (current-buffer))
           (tmpfile (tramp-compat-make-temp-file filename)))
+
+      ;; Lock file.
+      (when (and (not auto-saving) (file-remote-p lockname)
+                (not (eq (file-locked-p lockname) t)))
+       (setq file-locked t)
+       ;; `lock-file' exists since Emacs 28.1.
+       (tramp-compat-funcall 'lock-file lockname))
+
       (when (and append (file-exists-p filename))
        (copy-file filename tmpfile 'ok)
        (set-file-modes tmpfile (logior (or (file-modes tmpfile) 0) #o0600)))
       (write-region start end tmpfile append 'no-message)
       (with-tramp-progress-reporter
-        v 3 (format-message
-             "Moving tmp file `%s' to `%s'" tmpfile filename)
+         v 3 (format-message
+              "Moving tmp file `%s' to `%s'" tmpfile filename)
        (unwind-protect
            (unless (tramp-adb-execute-adb-command
                     v "push" tmpfile (tramp-compat-file-name-unquote 
localname))
@@ -575,6 +590,11 @@ But handle the case, if the \"test\" command is not 
available."
              (file-attributes filename))
             (current-time))))
 
+      ;; Unlock file.
+      (when (and file-locked (eq (file-locked-p lockname) t))
+       ;; `unlock-file' exists since Emacs 28.1.
+       (tramp-compat-funcall 'unlock-file lockname))
+
       ;; The end.
       (when (and (null noninteractive)
                 (or (eq visit t) (null visit) (stringp visit)))
diff --git a/lisp/net/tramp-archive.el b/lisp/net/tramp-archive.el
index d723fd5..a6f479b 100644
--- a/lisp/net/tramp-archive.el
+++ b/lisp/net/tramp-archive.el
@@ -236,6 +236,7 @@ It must be supported by libarchive(3).")
     (file-exists-p . tramp-handle-file-exists-p)
     (file-in-directory-p . tramp-handle-file-in-directory-p)
     (file-local-copy . tramp-archive-handle-file-local-copy)
+    (file-locked-p . ignore)
     (file-modes . tramp-handle-file-modes)
     (file-name-all-completions . 
tramp-archive-handle-file-name-all-completions)
     ;; `file-name-as-directory' performed by default handler.
@@ -262,6 +263,7 @@ It must be supported by libarchive(3).")
     (insert-directory . tramp-archive-handle-insert-directory)
     (insert-file-contents . tramp-archive-handle-insert-file-contents)
     (load . tramp-archive-handle-load)
+    (lock-file . ignore)
     (make-auto-save-file-name . ignore)
     (make-directory . tramp-archive-handle-not-implemented)
     (make-directory-internal . tramp-archive-handle-not-implemented)
@@ -283,6 +285,7 @@ It must be supported by libarchive(3).")
     (tramp-get-remote-uid . ignore)
     (tramp-set-file-uid-gid . ignore)
     (unhandled-file-name-directory . ignore)
+    (unlock-file . ignore)
     (vc-registered . ignore)
     (verify-visited-file-modtime . tramp-handle-verify-visited-file-modtime)
     (write-region . tramp-archive-handle-not-implemented))
diff --git a/lisp/net/tramp-cache.el b/lisp/net/tramp-cache.el
index a41620a..579234f 100644
--- a/lisp/net/tramp-cache.el
+++ b/lisp/net/tramp-cache.el
@@ -49,6 +49,8 @@
 ;;   an open connection.  Examples: "scripts" keeps shell script
 ;;   definitions already sent to the remote shell, "last-cmd-time" is
 ;;   the time stamp a command has been sent to the remote process.
+;;   "lock-pid" is the timestamp a (network) process is created, it is
+;;   used instead of the pid in file locks.
 ;;
 ;; - The key is nil.  These are temporary properties related to the
 ;;   local machine.  Examples: "parse-passwd" and "parse-group" keep
diff --git a/lisp/net/tramp-crypt.el b/lisp/net/tramp-crypt.el
index 1d8c0ad..31988bc 100644
--- a/lisp/net/tramp-crypt.el
+++ b/lisp/net/tramp-crypt.el
@@ -182,6 +182,7 @@ If NAME doesn't belong to a crypted remote directory, retun 
nil."
     (file-exists-p . tramp-handle-file-exists-p)
     (file-in-directory-p . tramp-handle-file-in-directory-p)
     (file-local-copy . tramp-handle-file-local-copy)
+    (file-locked-p . tramp-crypt-handle-file-locked-p)
     (file-modes . tramp-handle-file-modes)
     (file-name-all-completions . tramp-crypt-handle-file-name-all-completions)
     ;; `file-name-as-directory' performed by default handler.
@@ -208,6 +209,7 @@ If NAME doesn't belong to a crypted remote directory, retun 
nil."
     (insert-directory . tramp-crypt-handle-insert-directory)
     ;; `insert-file-contents' performed by default handler.
     (load . tramp-handle-load)
+    (lock-file . tramp-crypt-handle-lock-file)
     (make-auto-save-file-name . tramp-handle-make-auto-save-file-name)
     (make-directory . tramp-crypt-handle-make-directory)
     (make-directory-internal . ignore)
@@ -229,6 +231,7 @@ If NAME doesn't belong to a crypted remote directory, retun 
nil."
     ;; `tramp-get-remote-uid' performed by default handler.
     (tramp-set-file-uid-gid . tramp-crypt-handle-set-file-uid-gid)
     (unhandled-file-name-directory . ignore)
+    (unlock-file . tramp-crypt-handle-unlock-file)
     (vc-registered . ignore)
     (verify-visited-file-modtime . tramp-handle-verify-visited-file-modtime)
     (write-region . tramp-handle-write-region))
@@ -734,6 +737,11 @@ absolute file names."
   (let (tramp-crypt-enabled)
     (file-executable-p (tramp-crypt-encrypt-file-name filename))))
 
+(defun tramp-crypt-handle-file-locked-p (filename)
+  "Like `file-locked-p' for Tramp files."
+  (let (tramp-crypt-enabled)
+    (file-locked-p (tramp-crypt-encrypt-file-name filename))))
+
 (defun tramp-crypt-handle-file-name-all-completions (filename directory)
   "Like `file-name-all-completions' for Tramp files."
   (all-completions
@@ -797,6 +805,11 @@ WILDCARD is not supported."
          (delete-region (prop-match-beginning match) (prop-match-end match))
          (insert (propertize string 'dired-filename t)))))))
 
+(defun tramp-crypt-handle-lock-file (filename)
+  "Like `lock-file' for Tramp files."
+  (let (tramp-crypt-enabled)
+    (lock-file (tramp-crypt-encrypt-file-name filename))))
+
 (defun tramp-crypt-handle-make-directory (dir &optional parents)
   "Like `make-directory' for Tramp files."
   (with-parsed-tramp-file-name (expand-file-name dir) nil
@@ -848,6 +861,11 @@ WILDCARD is not supported."
       (tramp-set-file-uid-gid
        (tramp-crypt-encrypt-file-name filename) uid gid))))
 
+(defun tramp-crypt-handle-unlock-file (filename)
+  "Like `unlock-file' for Tramp files."
+  (let (tramp-crypt-enabled)
+    (unlock-file (tramp-crypt-encrypt-file-name filename))))
+
 (add-hook 'tramp-unload-hook
          (lambda ()
            (unload-feature 'tramp-crypt 'force)))
diff --git a/lisp/net/tramp-fuse.el b/lisp/net/tramp-fuse.el
index ec1db86..93b184a 100644
--- a/lisp/net/tramp-fuse.el
+++ b/lisp/net/tramp-fuse.el
@@ -164,10 +164,9 @@
     (or (tramp-get-connection-property
          (tramp-get-connection-process vec) "mounted" nil)
         (let* ((default-directory (tramp-compat-temporary-file-directory))
-               (fuse (concat "fuse." (tramp-file-name-method vec)))
-               (mount (shell-command-to-string (format "mount -t %s" fuse))))
-          (tramp-message vec 6 "%s %s" "mount -t" fuse)
-          (tramp-message vec 6 "\n%s" mount)
+               (command (format "mount -t fuse.%s" (tramp-file-name-method 
vec)))
+              (mount (shell-command-to-string command)))
+          (tramp-message vec 6 "%s\n%s" command mount)
           (tramp-set-connection-property
            (tramp-get-connection-process vec) "mounted"
            (when (string-match
@@ -176,6 +175,16 @@
                  mount)
              (match-string 1 mount)))))))
 
+(defun tramp-fuse-unmount (vec)
+  "Unmount fuse volume determined by VEC."
+  (let ((default-directory (tramp-compat-temporary-file-directory))
+        (command (format "fusermount3 -u %s" (tramp-fuse-mount-point vec))))
+    (tramp-message vec 6 "%s\n%s" command (shell-command-to-string command))
+    (tramp-flush-connection-property
+     (tramp-get-connection-process vec) "mounted")
+    ;; Give the caches a chance to expire.
+    (sleep-for 1)))
+
 (defun tramp-fuse-local-file-name (filename)
   "Return local mount name of FILENAME."
   (setq filename (tramp-compat-file-name-unquote (expand-file-name filename)))
diff --git a/lisp/net/tramp-gvfs.el b/lisp/net/tramp-gvfs.el
index f1d24dc..e784ea8 100644
--- a/lisp/net/tramp-gvfs.el
+++ b/lisp/net/tramp-gvfs.el
@@ -774,6 +774,7 @@ It has been changed in GVFS 1.14.")
     (file-exists-p . tramp-handle-file-exists-p)
     (file-in-directory-p . tramp-handle-file-in-directory-p)
     (file-local-copy . tramp-handle-file-local-copy)
+    (file-locked-p . tramp-handle-file-locked-p)
     (file-modes . tramp-handle-file-modes)
     (file-name-all-completions . tramp-gvfs-handle-file-name-all-completions)
     (file-name-as-directory . tramp-handle-file-name-as-directory)
@@ -800,6 +801,7 @@ It has been changed in GVFS 1.14.")
     (insert-directory . tramp-handle-insert-directory)
     (insert-file-contents . tramp-handle-insert-file-contents)
     (load . tramp-handle-load)
+    (lock-file . tramp-handle-lock-file)
     (make-auto-save-file-name . tramp-handle-make-auto-save-file-name)
     (make-directory . tramp-gvfs-handle-make-directory)
     (make-directory-internal . ignore)
@@ -821,6 +823,7 @@ It has been changed in GVFS 1.14.")
     (tramp-get-remote-uid . tramp-gvfs-handle-get-remote-uid)
     (tramp-set-file-uid-gid . tramp-gvfs-handle-set-file-uid-gid)
     (unhandled-file-name-directory . ignore)
+    (unlock-file . tramp-handle-unlock-file)
     (vc-registered . ignore)
     (verify-visited-file-modtime . tramp-handle-verify-visited-file-modtime)
     (write-region . tramp-handle-write-region))
@@ -2144,6 +2147,9 @@ connection if a previous connection has died for some 
reason."
       (process-put p 'vector vec)
       (set-process-query-on-exit-flag p nil)
 
+      ;; Mark process for filelock.
+      (tramp-set-connection-property p "lock-pid" (truncate (time-to-seconds)))
+
       ;; Set connection-local variables.
       (tramp-set-connection-local-variables vec)))
 
diff --git a/lisp/net/tramp-rclone.el b/lisp/net/tramp-rclone.el
index 3b6de3e..6c710dd 100644
--- a/lisp/net/tramp-rclone.el
+++ b/lisp/net/tramp-rclone.el
@@ -96,6 +96,7 @@
     (file-exists-p . tramp-handle-file-exists-p)
     (file-in-directory-p . tramp-handle-file-in-directory-p)
     (file-local-copy . tramp-handle-file-local-copy)
+    (file-locked-p . tramp-handle-file-locked-p)
     (file-modes . tramp-handle-file-modes)
     (file-name-all-completions . tramp-fuse-handle-file-name-all-completions)
     (file-name-as-directory . tramp-handle-file-name-as-directory)
@@ -122,6 +123,7 @@
     (insert-directory . tramp-handle-insert-directory)
     (insert-file-contents . tramp-handle-insert-file-contents)
     (load . tramp-handle-load)
+    (lock-file . tramp-handle-lock-file)
     (make-auto-save-file-name . tramp-handle-make-auto-save-file-name)
     (make-directory . tramp-fuse-handle-make-directory)
     (make-directory-internal . ignore)
@@ -143,6 +145,7 @@
     (tramp-get-remote-uid . ignore)
     (tramp-set-file-uid-gid . ignore)
     (unhandled-file-name-directory . ignore)
+    (unlock-file . tramp-handle-unlock-file)
     (vc-registered . ignore)
     (verify-visited-file-modtime . tramp-handle-verify-visited-file-modtime)
     (write-region . tramp-handle-write-region))
@@ -358,6 +361,10 @@ connection if a previous connection has died for some 
reason."
          (process-put p 'vector vec)
          (set-process-query-on-exit-flag p nil)
 
+         ;; Mark process for filelock.
+         (tramp-set-connection-property
+          p "lock-pid" (truncate (time-to-seconds)))
+
          ;; Set connection-local variables.
          (tramp-set-connection-local-variables vec)))
 
diff --git a/lisp/net/tramp-sh.el b/lisp/net/tramp-sh.el
index 5f597ff..1103722 100644
--- a/lisp/net/tramp-sh.el
+++ b/lisp/net/tramp-sh.el
@@ -962,6 +962,7 @@ Format specifiers \"%s\" are replaced before the script is 
used.")
     (file-exists-p . tramp-sh-handle-file-exists-p)
     (file-in-directory-p . tramp-handle-file-in-directory-p)
     (file-local-copy . tramp-sh-handle-file-local-copy)
+    (file-locked-p . tramp-handle-file-locked-p)
     (file-modes . tramp-handle-file-modes)
     (file-name-all-completions . tramp-sh-handle-file-name-all-completions)
     (file-name-as-directory . tramp-handle-file-name-as-directory)
@@ -988,6 +989,7 @@ Format specifiers \"%s\" are replaced before the script is 
used.")
     (insert-directory . tramp-sh-handle-insert-directory)
     (insert-file-contents . tramp-handle-insert-file-contents)
     (load . tramp-handle-load)
+    (lock-file . tramp-handle-lock-file)
     (make-auto-save-file-name . tramp-handle-make-auto-save-file-name)
     (make-directory . tramp-sh-handle-make-directory)
     ;; `make-directory-internal' performed by default handler.
@@ -1009,6 +1011,7 @@ Format specifiers \"%s\" are replaced before the script 
is used.")
     (tramp-get-remote-uid . tramp-sh-handle-get-remote-uid)
     (tramp-set-file-uid-gid . tramp-sh-handle-set-file-uid-gid)
     (unhandled-file-name-directory . ignore)
+    (unlock-file . tramp-handle-unlock-file)
     (vc-registered . tramp-sh-handle-vc-registered)
     (verify-visited-file-modtime . tramp-sh-handle-verify-visited-file-modtime)
     (write-region . tramp-sh-handle-write-region))
@@ -3233,9 +3236,10 @@ implementation will be used."
       tmpfile)))
 
 (defun tramp-sh-handle-write-region
-  (start end filename &optional append visit _lockname mustbenew)
+  (start end filename &optional append visit lockname mustbenew)
   "Like `write-region' for Tramp files."
-  (setq filename (expand-file-name filename))
+  (setq filename (expand-file-name filename)
+       lockname (file-truename (or lockname filename)))
   (with-parsed-tramp-file-name filename nil
     (when (and mustbenew (file-exists-p filename)
               (or (eq mustbenew 'excl)
@@ -3244,13 +3248,23 @@ implementation will be used."
                     (format "File %s exists; overwrite anyway? " filename)))))
       (tramp-error v 'file-already-exists filename))
 
-    (let ((uid (or (tramp-compat-file-attribute-user-id
+    (let ((auto-saving
+          (string-match-p "^#.+#$" (file-name-nondirectory filename)))
+         file-locked
+         (uid (or (tramp-compat-file-attribute-user-id
                    (file-attributes filename 'integer))
                   (tramp-get-remote-uid v 'integer)))
          (gid (or (tramp-compat-file-attribute-group-id
                    (file-attributes filename 'integer))
                   (tramp-get-remote-gid v 'integer))))
 
+      ;; Lock file.
+      (when (and (not auto-saving) (file-remote-p lockname)
+                (not (eq (file-locked-p lockname) t)))
+       (setq file-locked t)
+       ;; `lock-file' exists since Emacs 28.1.
+       (tramp-compat-funcall 'lock-file lockname))
+
       (if (and (tramp-local-host-p v)
               ;; `file-writable-p' calls `file-expand-file-name'.  We
               ;; cannot use `tramp-run-real-handler' therefore.
@@ -3465,6 +3479,12 @@ implementation will be used."
        ;; Set the ownership.
         (when need-chown
           (tramp-set-file-uid-gid filename uid gid))
+
+       ;; Unlock file.
+       (when (and file-locked (eq (file-locked-p lockname) t))
+         ;; `unlock-file' exists since Emacs 28.1.
+         (tramp-compat-funcall 'unlock-file lockname))
+
        (when (and (null noninteractive)
                   (or (eq visit t) (null visit) (stringp visit)))
          (tramp-message v 0 "Wrote %s" filename))
diff --git a/lisp/net/tramp-smb.el b/lisp/net/tramp-smb.el
index 13edf16..500245b 100644
--- a/lisp/net/tramp-smb.el
+++ b/lisp/net/tramp-smb.el
@@ -247,6 +247,7 @@ See `tramp-actions-before-shell' for more info.")
     (file-exists-p . tramp-handle-file-exists-p)
     (file-in-directory-p . tramp-handle-file-in-directory-p)
     (file-local-copy . tramp-smb-handle-file-local-copy)
+    (file-locked-p . tramp-handle-file-locked-p)
     (file-modes . tramp-handle-file-modes)
     (file-name-all-completions . tramp-smb-handle-file-name-all-completions)
     (file-name-as-directory . tramp-handle-file-name-as-directory)
@@ -273,6 +274,7 @@ See `tramp-actions-before-shell' for more info.")
     (insert-directory . tramp-smb-handle-insert-directory)
     (insert-file-contents . tramp-handle-insert-file-contents)
     (load . tramp-handle-load)
+    (lock-file . tramp-handle-lock-file)
     (make-auto-save-file-name . tramp-handle-make-auto-save-file-name)
     (make-directory . tramp-smb-handle-make-directory)
     (make-directory-internal . tramp-smb-handle-make-directory-internal)
@@ -294,6 +296,7 @@ See `tramp-actions-before-shell' for more info.")
     (tramp-get-remote-uid . ignore)
     (tramp-set-file-uid-gid . ignore)
     (unhandled-file-name-directory . ignore)
+    (unlock-file . tramp-handle-unlock-file)
     (vc-registered . ignore)
     (verify-visited-file-modtime . tramp-handle-verify-visited-file-modtime)
     (write-region . tramp-smb-handle-write-region))
@@ -532,7 +535,7 @@ arguments to pass to the OPERATION."
                      (tramp-process-actions p v nil tramp-smb-actions-with-tar)
 
                      (while (process-live-p p)
-                       (sit-for 0.1))
+                       (sleep-for 0.1))
                      (tramp-message v 6 "\n%s" (buffer-string))))
 
                ;; Reset the transfer process properties.
@@ -1573,9 +1576,10 @@ errors for shares like \"C$/\", which are common in 
Microsoft Windows."
       (error filename))))
 
 (defun tramp-smb-handle-write-region
-  (start end filename &optional append visit _lockname mustbenew)
+  (start end filename &optional append visit lockname mustbenew)
   "Like `write-region' for Tramp files."
-  (setq filename (expand-file-name filename))
+  (setq filename (expand-file-name filename)
+       lockname (file-truename (or lockname filename)))
   (with-parsed-tramp-file-name filename nil
     (when (and mustbenew (file-exists-p filename)
               (or (eq mustbenew 'excl)
@@ -1584,8 +1588,19 @@ errors for shares like \"C$/\", which are common in 
Microsoft Windows."
                     (format "File %s exists; overwrite anyway? " filename)))))
       (tramp-error v 'file-already-exists filename))
 
-    (let ((curbuf (current-buffer))
+    (let ((auto-saving
+          (string-match-p "^#.+#$" (file-name-nondirectory filename)))
+         file-locked
+         (curbuf (current-buffer))
          (tmpfile (tramp-compat-make-temp-file filename)))
+
+      ;; Lock file.
+      (when (and (not auto-saving) (file-remote-p lockname)
+                (not (eq (file-locked-p lockname) t)))
+       (setq file-locked t)
+       ;; `lock-file' exists since Emacs 28.1.
+       (tramp-compat-funcall 'lock-file lockname))
+
       (when (and append (file-exists-p filename))
        (copy-file filename tmpfile 'ok))
       ;; We say `no-message' here because we don't want the visited file
@@ -1618,6 +1633,11 @@ errors for shares like \"C$/\", which are common in 
Microsoft Windows."
              (file-attributes filename))
             (current-time))))
 
+      ;; Unlock file.
+      (when (and file-locked (eq (file-locked-p lockname) t))
+       ;; `unlock-file' exists since Emacs 28.1.
+       (tramp-compat-funcall 'unlock-file lockname))
+
       ;; The end.
       (when (and (null noninteractive)
                 (or (eq visit t) (null visit) (stringp visit)))
diff --git a/lisp/net/tramp-sshfs.el b/lisp/net/tramp-sshfs.el
index cac8c40..babd770 100644
--- a/lisp/net/tramp-sshfs.el
+++ b/lisp/net/tramp-sshfs.el
@@ -96,6 +96,7 @@
     (file-exists-p . tramp-handle-file-exists-p)
     (file-in-directory-p . tramp-handle-file-in-directory-p)
     (file-local-copy . tramp-handle-file-local-copy)
+    (file-locked-p . tramp-handle-file-locked-p)
     (file-modes . tramp-handle-file-modes)
     (file-name-all-completions . tramp-fuse-handle-file-name-all-completions)
     (file-name-as-directory . tramp-handle-file-name-as-directory)
@@ -122,6 +123,7 @@
     (insert-directory . tramp-handle-insert-directory)
     (insert-file-contents . tramp-sshfs-handle-insert-file-contents)
     (load . tramp-handle-load)
+    (lock-file . tramp-handle-lock-file)
     (make-auto-save-file-name . tramp-handle-make-auto-save-file-name)
     (make-directory . tramp-fuse-handle-make-directory)
     (make-directory-internal . ignore)
@@ -143,6 +145,7 @@
     (tramp-get-remote-uid . ignore)
     (tramp-set-file-uid-gid . ignore)
     (unhandled-file-name-directory . ignore)
+    (unlock-file . tramp-handle-unlock-file)
     (vc-registered . ignore)
     (verify-visited-file-modtime . tramp-handle-verify-visited-file-modtime)
     (write-region . tramp-sshfs-handle-write-region))
@@ -279,9 +282,10 @@ arguments to pass to the OPERATION."
        (tramp-fuse-local-file-name filename) mode flag))))
 
 (defun tramp-sshfs-handle-write-region
-  (start end filename &optional append visit _lockname mustbenew)
+  (start end filename &optional append visit lockname mustbenew)
   "Like `write-region' for Tramp files."
-  (setq filename (expand-file-name filename))
+  (setq filename (expand-file-name filename)
+       lockname (file-truename (or lockname filename)))
   (with-parsed-tramp-file-name filename nil
     (when (and mustbenew (file-exists-p filename)
               (or (eq mustbenew 'excl)
@@ -290,15 +294,32 @@ arguments to pass to the OPERATION."
                     (format "File %s exists; overwrite anyway? " filename)))))
       (tramp-error v 'file-already-exists filename))
 
-    (write-region
-     start end (tramp-fuse-local-file-name filename) append 'nomessage)
-    (tramp-flush-file-properties v localname)
-
-    ;; The end.
-    (when (and (null noninteractive)
-              (or (eq visit t) (null visit) (stringp visit)))
-      (tramp-message v 0 "Wrote %s" filename))
-    (run-hooks 'tramp-handle-write-region-hook)))
+    (let ((auto-saving
+          (string-match-p "^#.+#$" (file-name-nondirectory filename)))
+         file-locked)
+
+      ;; Lock file.
+      (when (and (not auto-saving) (file-remote-p lockname)
+                (not (eq (file-locked-p lockname) t)))
+       (setq file-locked t)
+       ;; `lock-file' exists since Emacs 28.1.
+       (tramp-compat-funcall 'lock-file lockname))
+
+      (let (create-lockfiles)
+       (write-region
+        start end (tramp-fuse-local-file-name filename) append 'nomessage)
+       (tramp-flush-file-properties v localname))
+
+      ;; Unlock file.
+      (when (and file-locked (eq (file-locked-p lockname) t))
+       ;; `unlock-file' exists since Emacs 28.1.
+       (tramp-compat-funcall 'unlock-file lockname))
+
+      ;; The end.
+      (when (and (null noninteractive)
+                (or (eq visit t) (null visit) (stringp visit)))
+       (tramp-message v 0 "Wrote %s" filename))
+      (run-hooks 'tramp-handle-write-region-hook))))
 
 
 ;; File name conversions.
@@ -321,6 +342,9 @@ connection if a previous connection has died for some 
reason."
       (process-put p 'vector vec)
       (set-process-query-on-exit-flag p nil)
 
+      ;; Mark process for filelock.
+      (tramp-set-connection-property p "lock-pid" (truncate (time-to-seconds)))
+
       ;; Set connection-local variables.
       (tramp-set-connection-local-variables vec)
 
diff --git a/lisp/net/tramp-sudoedit.el b/lisp/net/tramp-sudoedit.el
index d641709..aa6f85e 100644
--- a/lisp/net/tramp-sudoedit.el
+++ b/lisp/net/tramp-sudoedit.el
@@ -88,6 +88,7 @@ See `tramp-actions-before-shell' for more info.")
     (file-exists-p . tramp-sudoedit-handle-file-exists-p)
     (file-in-directory-p . tramp-handle-file-in-directory-p)
     (file-local-copy . tramp-handle-file-local-copy)
+    (file-locked-p . tramp-handle-file-locked-p)
     (file-modes . tramp-handle-file-modes)
     (file-name-all-completions
      . tramp-sudoedit-handle-file-name-all-completions)
@@ -115,6 +116,7 @@ See `tramp-actions-before-shell' for more info.")
     (insert-directory . tramp-handle-insert-directory)
     (insert-file-contents . tramp-handle-insert-file-contents)
     (load . tramp-handle-load)
+    (lock-file . tramp-handle-lock-file)
     (make-auto-save-file-name . tramp-handle-make-auto-save-file-name)
     (make-directory . tramp-sudoedit-handle-make-directory)
     (make-directory-internal . ignore)
@@ -136,6 +138,7 @@ See `tramp-actions-before-shell' for more info.")
     (tramp-get-remote-uid . tramp-sudoedit-handle-get-remote-uid)
     (tramp-set-file-uid-gid . tramp-sudoedit-handle-set-file-uid-gid)
     (unhandled-file-name-directory . ignore)
+    (unlock-file . tramp-handle-unlock-file)
     (vc-registered . ignore)
     (verify-visited-file-modtime . tramp-handle-verify-visited-file-modtime)
     (write-region . tramp-sudoedit-handle-write-region))
@@ -713,6 +716,7 @@ ID-FORMAT valid values are `string' and `integer'."
 (defun tramp-sudoedit-handle-write-region
   (start end filename &optional append visit lockname mustbenew)
   "Like `write-region' for Tramp files."
+  (setq filename (expand-file-name filename))
   (with-parsed-tramp-file-name filename nil
     (let* ((uid (or (tramp-compat-file-attribute-user-id
                     (file-attributes filename 'integer))
@@ -776,6 +780,9 @@ connection if a previous connection has died for some 
reason."
       (process-put p 'vector vec)
       (set-process-query-on-exit-flag p nil)
 
+      ;; Mark process for filelock.
+      (tramp-set-connection-property p "lock-pid" (truncate (time-to-seconds)))
+
       ;; Set connection-local variables.
       (tramp-set-connection-local-variables vec)
 
diff --git a/lisp/net/tramp.el b/lisp/net/tramp.el
index 04ec06d..37d60e8 100644
--- a/lisp/net/tramp.el
+++ b/lisp/net/tramp.el
@@ -2455,6 +2455,8 @@ Must be handled by the callers."
              file-name-case-insensitive-p
              ;; Emacs 27+ only.
              file-system-info
+             ;; Emacs 28+ only.
+             file-locked-p lock-file unlock-file
              ;; Tramp internal magic file name function.
              tramp-set-file-uid-gid))
     (if (file-name-absolute-p (nth 0 args))
@@ -3816,6 +3818,76 @@ User is always nil."
       ;; Result.
       (cons (expand-file-name filename) (cdr result)))))
 
+(defun tramp-make-lock-name (file)
+  "Implement MAKE_LOCK_NAME of filelock.c."
+  (expand-file-name
+   (concat ".#" (file-name-nondirectory file)) (file-name-directory file)))
+
+(defun tramp-get-lock-file (file)
+  "Read lockfile of FILE.
+Return nil when there is no lockfile"
+  (let ((lockname (tramp-make-lock-name file)))
+    (or (file-symlink-p lockname)
+       (and (file-readable-p lockname)
+            (with-temp-buffer
+              (insert-file-contents-literally lockname)
+              (buffer-string))))))
+
+(defun tramp-get-lock-pid (file)
+  "Determine pid for lockfile of FILE."
+  ;; Some Tramp methods do not offer a connection process, but just a
+  ;; network process as a place holder.  Those processes use the
+  ;; "lock-pid" connection property as fake pid, in fact it is the
+  ;; time stamp the process is created.
+  (let ((p (tramp-get-process  (tramp-dissect-file-name file))))
+    (number-to-string
+     (or (process-id p)
+        (tramp-get-connection-property p "lock-pid" (emacs-pid))))))
+
+(defconst tramp-lock-file-contents-regexp
+  ;; USER@HOST.PID[:BOOT_TIME]
+  "\\`\\(.+\\)@\\(.+\\)\\.\\([[:digit:]]+\\)\\(?::\\([[:digit:]]+\\)\\)?\\'"
+  "The format of a lock file.")
+
+(defun tramp-handle-file-locked-p (file)
+  "Like `file-locked-p' for Tramp files."
+  (when-let ((contents (tramp-get-lock-file file))
+            (match (string-match tramp-lock-file-contents-regexp contents)))
+    (or (and (string-equal (match-string 1 contents) (user-login-name))
+            (string-equal (match-string 2 contents) (system-name))
+            (string-equal (match-string 3 contents) (tramp-get-lock-pid file)))
+       (match-string 1 contents))))
+
+(defun tramp-handle-lock-file (file)
+  "Like `lock-file' for Tramp files."
+  ;; See if this file is visited and has changed on disk since it
+  ;; was visited.
+  (catch 'dont-lock
+    (unless (or (null create-lockfiles)
+               (eq (file-locked-p file) t)) ;; Locked by me.
+      (when-let ((contents (tramp-get-lock-file file))
+                (match (string-match tramp-lock-file-contents-regexp 
contents)))
+       (unless (ask-user-about-lock
+                file (format
+                      "%s@%s (pid %s)" (match-string 1 contents)
+                      (match-string 2 contents) (match-string 3 contents)))
+         (throw 'dont-lock nil)))
+
+      (let ((lockname (tramp-make-lock-name file))
+           ;; USER@HOST.PID[:BOOT_TIME]
+           (contents
+            (format
+             "%s@%s.%s" (user-login-name) (system-name)
+             (tramp-get-lock-pid file)))
+           create-lockfiles signal-hook-function)
+       (condition-case nil
+           (make-symbolic-link contents lockname 'ok-if-already-exists)
+         (error (write-region contents nil lockname)))))))
+
+(defun tramp-handle-unlock-file (file)
+  "Like `unlock-file' for Tramp files."
+  (delete-file (tramp-make-lock-name file)))
+
 (defun tramp-handle-load (file &optional noerror nomessage nosuffix 
must-suffix)
   "Like `load' for Tramp files."
   (with-parsed-tramp-file-name (expand-file-name file) nil
@@ -4355,9 +4427,10 @@ of."
           (t (tramp-compat-time-equal-p mt tramp-time-doesnt-exist))))))))
 
 (defun tramp-handle-write-region
-  (start end filename &optional append visit _lockname mustbenew)
+  (start end filename &optional append visit lockname mustbenew)
   "Like `write-region' for Tramp files."
-  (setq filename (expand-file-name filename))
+  (setq filename (expand-file-name filename)
+       lockname (file-truename (or lockname filename)))
   (with-parsed-tramp-file-name filename nil
     (when (and mustbenew (file-exists-p filename)
               (or (eq mustbenew 'excl)
@@ -4366,7 +4439,10 @@ of."
                     (format "File %s exists; overwrite anyway? " filename)))))
       (tramp-error v 'file-already-exists filename))
 
-    (let ((tmpfile (tramp-compat-make-temp-file filename))
+    (let ((auto-saving
+          (string-match-p "^#.+#$" (file-name-nondirectory filename)))
+         file-locked
+         (tmpfile (tramp-compat-make-temp-file filename))
          (modes (tramp-default-file-modes
                  filename (and (eq mustbenew 'excl) 'nofollow)))
          (uid (or (tramp-compat-file-attribute-user-id
@@ -4375,6 +4451,14 @@ of."
          (gid (or (tramp-compat-file-attribute-group-id
                    (file-attributes filename 'integer))
                   (tramp-get-remote-gid v 'integer))))
+
+      ;; Lock file.
+      (when (and (not auto-saving) (file-remote-p lockname)
+                (not (eq (file-locked-p lockname) t)))
+       (setq file-locked t)
+       ;; `lock-file' exists since Emacs 28.1.
+       (tramp-compat-funcall 'lock-file lockname))
+
       (when (and append (file-exists-p filename))
        (copy-file filename tmpfile 'ok))
       ;; The permissions of the temporary file should be set.  If
@@ -4404,13 +4488,18 @@ of."
             (current-time))))
 
       ;; Set the ownership.
-      (tramp-set-file-uid-gid filename uid gid))
-
-    ;; The end.
-    (when (and (null noninteractive)
-              (or (eq visit t) (null visit) (stringp visit)))
-      (tramp-message v 0 "Wrote %s" filename))
-    (run-hooks 'tramp-handle-write-region-hook)))
+      (tramp-set-file-uid-gid filename uid gid)
+
+      ;; Unlock file.
+      (when (and file-locked (eq (file-locked-p lockname) t))
+       ;; `unlock-file' exists since Emacs 28.1.
+       (tramp-compat-funcall 'unlock-file lockname))
+
+      ;; The end.
+      (when (and (null noninteractive)
+                (or (eq visit t) (null visit) (stringp visit)))
+       (tramp-message v 0 "Wrote %s" filename))
+      (run-hooks 'tramp-handle-write-region-hook))))
 
 ;; This is used in tramp-sh.el and tramp-sudoedit.el.
 (defconst tramp-stat-marker "/////"
diff --git a/src/filelock.c b/src/filelock.c
index 446a262..dcdc635 100644
--- a/src/filelock.c
+++ b/src/filelock.c
@@ -671,6 +671,16 @@ lock_file (Lisp_Object fn)
   if (will_dump_p ())
     return;
 
+  /* If the file name has special constructs in it,
+     call the corresponding file name handler.  */
+  Lisp_Object handler;
+  handler = Ffind_file_name_handler (fn, Qlock_file);
+  if (!NILP (handler))
+    {
+      call2 (handler, Qlock_file, fn);
+      return;
+    }
+
   orig_fn = fn;
   fn = Fexpand_file_name (fn, Qnil);
 #ifdef WINDOWSNT
@@ -725,6 +735,16 @@ unlock_file_body (Lisp_Object fn)
   char *lfname;
   USE_SAFE_ALLOCA;
 
+  /* If the file name has special constructs in it,
+     call the corresponding file name handler.  */
+  Lisp_Object handler;
+  handler = Ffind_file_name_handler (fn, Qunlock_file);
+  if (!NILP (handler))
+    {
+      call2 (handler, Qunlock_file, fn);
+      return Qnil;
+    }
+
   Lisp_Object filename = Fexpand_file_name (fn, Qnil);
   fn = ENCODE_FILE (filename);
 
@@ -784,6 +804,27 @@ unlock_all_files (void)
     }
 }
 
+DEFUN ("lock-file", Flock_file, Slock_file,
+       0, 1, 0,
+       doc: /* Lock FILE.
+If the option `create-lockfiles' is nil, this does nothing.  */)
+  (Lisp_Object file)
+{
+  CHECK_STRING (file);
+  lock_file (file);
+  return Qnil;
+}
+
+DEFUN ("unlock-file", Funlock_file, Sunlock_file,
+       0, 1, 0,
+       doc: /* Unlock FILE.  */)
+  (Lisp_Object file)
+{
+  CHECK_STRING (file);
+  unlock_file (file);
+  return Qnil;
+}
+
 DEFUN ("lock-buffer", Flock_buffer, Slock_buffer,
        0, 1, 0,
        doc: /* Lock FILE, if current buffer is modified.
@@ -844,6 +885,15 @@ t if it is locked by you, else a string saying which user 
has locked it.  */)
   lock_info_type locker;
   USE_SAFE_ALLOCA;
 
+  /* If the file name has special constructs in it,
+     call the corresponding file name handler.  */
+  Lisp_Object handler;
+  handler = Ffind_file_name_handler (filename, Qfile_locked_p);
+  if (!NILP (handler))
+    {
+      return call2 (handler, Qfile_locked_p, filename);
+    }
+
   filename = Fexpand_file_name (filename, Qnil);
   Lisp_Object encoded_filename = ENCODE_FILE (filename);
   MAKE_LOCK_NAME (lfname, encoded_filename);
@@ -876,7 +926,13 @@ The name of the (per-buffer) lockfile is constructed by 
prepending a
 Info node `(emacs)Interlocking'.  */);
   create_lockfiles = true;
 
-  defsubr (&Sunlock_buffer);
+  DEFSYM (Qlock_file, "lock-file");
+  DEFSYM (Qunlock_file, "unlock-file");
+  DEFSYM (Qfile_locked_p, "file-locked-p");
+
+  defsubr (&Slock_file);
+  defsubr (&Sunlock_file);
   defsubr (&Slock_buffer);
+  defsubr (&Sunlock_buffer);
   defsubr (&Sfile_locked_p);
 }
diff --git a/test/lisp/net/tramp-archive-tests.el 
b/test/lisp/net/tramp-archive-tests.el
index ca1163b..aac1b13 100644
--- a/test/lisp/net/tramp-archive-tests.el
+++ b/test/lisp/net/tramp-archive-tests.el
@@ -856,7 +856,7 @@ This tests also `file-executable-p', `file-writable-p' and 
`set-file-modes'."
       (tramp-archive-cleanup-hash))))
 
 ;; The functions were introduced in Emacs 26.1.
-(ert-deftest tramp-archive-test39-make-nearby-temp-file ()
+(ert-deftest tramp-archive-test40-make-nearby-temp-file ()
   "Check `make-nearby-temp-file' and `temporary-file-directory'."
   (skip-unless tramp-archive-enabled)
   ;; Since Emacs 26.1.
@@ -893,7 +893,7 @@ This tests also `file-executable-p', `file-writable-p' and 
`set-file-modes'."
     (delete-directory tmp-file)
     (should-not (file-exists-p tmp-file))))
 
-(ert-deftest tramp-archive-test42-file-system-info ()
+(ert-deftest tramp-archive-test43-file-system-info ()
   "Check that `file-system-info' returns proper values."
   (skip-unless tramp-archive-enabled)
   ;; Since Emacs 27.1.
diff --git a/test/lisp/net/tramp-tests.el b/test/lisp/net/tramp-tests.el
index 7f89444..0e70f8e 100644
--- a/test/lisp/net/tramp-tests.el
+++ b/test/lisp/net/tramp-tests.el
@@ -33,7 +33,7 @@
 ;; remote host, set this environment variable to "/dev/null" or
 ;; whatever is appropriate on your system.
 
-;; For slow remote connections, `tramp-test43-asynchronous-requests'
+;; For slow remote connections, `tramp-test44-asynchronous-requests'
 ;; might be too heavy.  Setting $REMOTE_PARALLEL_PROCESSES to a proper
 ;; value less than 10 could help.
 
@@ -122,6 +122,7 @@
 (setq auth-source-save-behavior nil
       password-cache-expiry nil
       remote-file-name-inhibit-cache nil
+      create-lockfiles nil
       tramp-cache-read-persistent-data t ;; For auth-sources.
       tramp-copy-size-limit nil
       tramp-persistency-file-name nil
@@ -2463,6 +2464,8 @@ This checks also `file-name-as-directory', 
`file-name-directory',
                          "^\\'")
                        tramp--test-messages))))))))
 
+           ;; We do not test lockname here.  See `tramp-test39-lock-file'.
+
            ;; Do not overwrite if excluded.
            (cl-letf (((symbol-function #'y-or-n-p) #'tramp--test-always)
                      ;; Ange-FTP.
@@ -2833,8 +2836,7 @@ This tests also `file-directory-p' and 
`file-accessible-directory-p'."
           (delete-directory tmp-name1 nil 'trash)
           ;; tramp-rclone.el and tramp-sshfs.el call the local
           ;; `delete-directory'.  This raises another error.
-          :type (if (or (tramp--test-rclone-p) (tramp--test-sshfs-p))
-                    'error 'file-error))
+          :type (if (tramp--test-fuse-p) 'error 'file-error))
          (delete-directory tmp-name1 'recursive 'trash)
          (should-not (file-directory-p tmp-name1))
          (should
@@ -5741,8 +5743,77 @@ Use direct async.")
        (ignore-errors (delete-file tmp-name1))
        (tramp-cleanup-connection tramp-test-vec 'keep-debug 'keep-password)))))
 
+;; The functions were introduced in Emacs 28.1.
+(ert-deftest tramp-test39-lock-file ()
+  "Check `lock-file', `unlock-file' and `file-locked-p'."
+  (skip-unless (tramp--test-enabled))
+  (skip-unless (not (tramp--test-ange-ftp-p)))
+  ;; Since Emacs 28.1.
+  (skip-unless (and (fboundp 'lock-file) (fboundp 'unlock-file)))
+
+  (dolist (quoted (if (tramp--test-expensive-test) '(nil t) '(nil)))
+    (let ((tmp-name (tramp--test-make-temp-name nil quoted))
+         (remote-file-name-inhibit-cache t)
+         (create-lockfiles t)
+          (inhibit-message t)
+         ;; tramp-rclone.el and tramp-sshfs.el cache the mounted files.
+         (tramp-cleanup-connection-hook
+          (append
+           (and (tramp--test-fuse-p) '(tramp-fuse-unmount))
+           tramp-cleanup-connection-hook))
+         noninteractive)
+
+      (unwind-protect
+         (progn
+           ;; A simple file lock.
+           (should-not (file-locked-p tmp-name))
+           (lock-file tmp-name)
+           (should (eq (file-locked-p tmp-name) t))
+
+           ;; If it is locked already, nothing changes.
+           (lock-file tmp-name)
+           (should (eq (file-locked-p tmp-name) t))
+
+           ;; A new connection changes process id, and also the
+           ;; lockname contents.
+           (tramp-cleanup-connection tramp-test-vec 'keep-debug 'keep-password)
+           (should (stringp (file-locked-p tmp-name)))
+
+           ;; Steal the file lock.
+           (tramp-cleanup-connection tramp-test-vec 'keep-debug 'keep-password)
+           (cl-letf (((symbol-function #'read-char) (lambda (&rest _args) ?s)))
+             (lock-file tmp-name))
+           (should (eq (file-locked-p tmp-name) t))
+
+           ;; Ignore the file lock.
+           (tramp-cleanup-connection tramp-test-vec 'keep-debug 'keep-password)
+           (cl-letf (((symbol-function #'read-char) (lambda (&rest _args) ?p)))
+             (lock-file tmp-name))
+           (should (stringp (file-locked-p tmp-name)))
+
+           ;; Quit the file lock machinery.
+           (tramp-cleanup-connection tramp-test-vec 'keep-debug 'keep-password)
+           (cl-letf (((symbol-function #'read-char) (lambda (&rest _args) ?q)))
+             (should-error (lock-file tmp-name) :type 'file-locked))
+           (should (stringp (file-locked-p tmp-name)))
+
+           ;; The same for `write-region'.
+           (tramp-cleanup-connection tramp-test-vec 'keep-debug 'keep-password)
+           (cl-letf (((symbol-function #'read-char) (lambda (&rest _args) ?q)))
+             (should-error (write-region "foo" nil tmp-name) :type 
'file-locked)
+             (should-error
+              (write-region "foo" nil tmp-name nil nil tmp-name)
+              :type 'file-locked))
+           (should (stringp (file-locked-p tmp-name)))
+           (should-not (file-exists-p tmp-name)))
+
+       ;; Cleanup.
+       (ignore-errors (delete-file tmp-name))
+       (unlock-file tmp-name)
+       (should-not (file-locked-p tmp-name))))))
+
 ;; The functions were introduced in Emacs 26.1.
-(ert-deftest tramp-test39-make-nearby-temp-file ()
+(ert-deftest tramp-test40-make-nearby-temp-file ()
   "Check `make-nearby-temp-file' and `temporary-file-directory'."
   (skip-unless (tramp--test-enabled))
   (skip-unless (not (tramp--test-ange-ftp-p)))
@@ -5825,6 +5896,10 @@ This does not support globbing characters in file names 
(yet)."
   (string-match-p
    "ftp$" (file-remote-p tramp-test-temporary-file-directory 'method)))
 
+(defun tramp--test-fuse-p ()
+  "Check, whether an FUSE file system isused."
+  (or (tramp--test-rclone-p) (tramp--test-sshfs-p)))
+
 (defun tramp--test-gdrive-p ()
   "Check, whether the gdrive method is used."
   (string-equal
@@ -6115,7 +6190,7 @@ This requires restrictions of file name syntax."
        (ignore-errors (delete-directory tmp-name2 'recursive))))))
 
 (defun tramp--test-special-characters ()
-  "Perform the test in `tramp-test40-special-characters*'."
+  "Perform the test in `tramp-test41-special-characters*'."
   ;; Newlines, slashes and backslashes in file names are not
   ;; supported.  So we don't test.  And we don't test the tab
   ;; character on Windows or Cygwin, because the backslash is
@@ -6173,7 +6248,7 @@ This requires restrictions of file name syntax."
               files (list (mapconcat #'identity files ""))))))
 
 ;; These tests are inspired by Bug#17238.
-(ert-deftest tramp-test40-special-characters ()
+(ert-deftest tramp-test41-special-characters ()
   "Check special characters in file names."
   (skip-unless (tramp--test-enabled))
   (skip-unless (not (tramp--test-rsync-p)))
@@ -6181,7 +6256,7 @@ This requires restrictions of file name syntax."
 
   (tramp--test-special-characters))
 
-(ert-deftest tramp-test40-special-characters-with-stat ()
+(ert-deftest tramp-test41-special-characters-with-stat ()
   "Check special characters in file names.
 Use the `stat' command."
   :tags '(:expensive-test)
@@ -6199,7 +6274,7 @@ Use the `stat' command."
          tramp-connection-properties)))
     (tramp--test-special-characters)))
 
-(ert-deftest tramp-test40-special-characters-with-perl ()
+(ert-deftest tramp-test41-special-characters-with-perl ()
   "Check special characters in file names.
 Use the `perl' command."
   :tags '(:expensive-test)
@@ -6220,7 +6295,7 @@ Use the `perl' command."
          tramp-connection-properties)))
     (tramp--test-special-characters)))
 
-(ert-deftest tramp-test40-special-characters-with-ls ()
+(ert-deftest tramp-test41-special-characters-with-ls ()
   "Check special characters in file names.
 Use the `ls' command."
   :tags '(:expensive-test)
@@ -6241,7 +6316,7 @@ Use the `ls' command."
     (tramp--test-special-characters)))
 
 (defun tramp--test-utf8 ()
-  "Perform the test in `tramp-test41-utf8*'."
+  "Perform the test in `tramp-test42-utf8*'."
   (let* ((utf8 (if (and (eq system-type 'darwin)
                        (memq 'utf-8-hfs (coding-system-list)))
                   'utf-8-hfs 'utf-8))
@@ -6287,7 +6362,7 @@ Use the `ls' command."
             (replace-regexp-in-string "[ \t\n/.?]" "" x)))
          language-info-alist)))))))
 
-(ert-deftest tramp-test41-utf8 ()
+(ert-deftest tramp-test42-utf8 ()
   "Check UTF8 encoding in file names and file contents."
   (skip-unless (tramp--test-enabled))
   (skip-unless (not (tramp--test-docker-p)))
@@ -6300,7 +6375,7 @@ Use the `ls' command."
 
   (tramp--test-utf8))
 
-(ert-deftest tramp-test41-utf8-with-stat ()
+(ert-deftest tramp-test42-utf8-with-stat ()
   "Check UTF8 encoding in file names and file contents.
 Use the `stat' command."
   :tags '(:expensive-test)
@@ -6322,7 +6397,7 @@ Use the `stat' command."
          tramp-connection-properties)))
     (tramp--test-utf8)))
 
-(ert-deftest tramp-test41-utf8-with-perl ()
+(ert-deftest tramp-test42-utf8-with-perl ()
   "Check UTF8 encoding in file names and file contents.
 Use the `perl' command."
   :tags '(:expensive-test)
@@ -6347,7 +6422,7 @@ Use the `perl' command."
          tramp-connection-properties)))
     (tramp--test-utf8)))
 
-(ert-deftest tramp-test41-utf8-with-ls ()
+(ert-deftest tramp-test42-utf8-with-ls ()
   "Check UTF8 encoding in file names and file contents.
 Use the `ls' command."
   :tags '(:expensive-test)
@@ -6371,7 +6446,7 @@ Use the `ls' command."
          tramp-connection-properties)))
     (tramp--test-utf8)))
 
-(ert-deftest tramp-test42-file-system-info ()
+(ert-deftest tramp-test43-file-system-info ()
   "Check that `file-system-info' returns proper values."
   (skip-unless (tramp--test-enabled))
   ;; Since Emacs 27.1.
@@ -6388,11 +6463,11 @@ Use the `ls' command."
                 (numberp (nth 1 fsi))
                 (numberp (nth 2 fsi))))))
 
-;; `tramp-test43-asynchronous-requests' could be blocked.  So we set a
+;; `tramp-test44-asynchronous-requests' could be blocked.  So we set a
 ;; timeout of 300 seconds, and we send a SIGUSR1 signal after 300
 ;; seconds.  Similar check is performed in the timer function.
 (defconst tramp--test-asynchronous-requests-timeout 300
-  "Timeout for `tramp-test43-asynchronous-requests'.")
+  "Timeout for `tramp-test44-asynchronous-requests'.")
 
 (defmacro tramp--test-with-proper-process-name-and-buffer (proc &rest body)
   "Set \"process-name\" and \"process-buffer\" connection properties.
@@ -6428,7 +6503,7 @@ This is needed in timer functions as well as process 
filters and sentinels."
         (tramp-flush-connection-property v "process-buffer")))))
 
 ;; This test is inspired by Bug#16928.
-(ert-deftest tramp-test43-asynchronous-requests ()
+(ert-deftest tramp-test44-asynchronous-requests ()
   "Check parallel asynchronous requests.
 Such requests could arrive from timers, process filters and
 process sentinels.  They shall not disturb each other."
@@ -6628,11 +6703,11 @@ process sentinels.  They shall not disturb each other."
         (ignore-errors (cancel-timer timer))
         (ignore-errors (delete-directory tmp-name 'recursive))))))
 
-;; (tramp--test--deftest-direct-async-process 
tramp-test43-asynchronous-requests
+;; (tramp--test--deftest-direct-async-process 
tramp-test44-asynchronous-requests
 ;;   "Check parallel direct asynchronous requests." 'unstable)
 
 ;; This test is inspired by Bug#29163.
-(ert-deftest tramp-test44-auto-load ()
+(ert-deftest tramp-test45-auto-load ()
   "Check that Tramp autoloads properly."
   ;; If we use another syntax but `default', Tramp is already loaded
   ;; due to the `tramp-change-syntax' call.
@@ -6657,7 +6732,7 @@ process sentinels.  They shall not disturb each other."
        (mapconcat #'shell-quote-argument load-path " -L ")
        (shell-quote-argument code)))))))
 
-(ert-deftest tramp-test44-delay-load ()
+(ert-deftest tramp-test45-delay-load ()
   "Check that Tramp is loaded lazily, only when needed."
   ;; The autoloaded Tramp objects are different since Emacs 26.1.  We
   ;; cannot test older Emacsen, therefore.
@@ -6690,7 +6765,7 @@ process sentinels.  They shall not disturb each other."
          (mapconcat #'shell-quote-argument load-path " -L ")
          (shell-quote-argument (format code tm)))))))))
 
-(ert-deftest tramp-test44-recursive-load ()
+(ert-deftest tramp-test45-recursive-load ()
   "Check that Tramp does not fail due to recursive load."
   (skip-unless (tramp--test-enabled))
 
@@ -6714,7 +6789,7 @@ process sentinels.  They shall not disturb each other."
          (mapconcat #'shell-quote-argument load-path " -L ")
          (shell-quote-argument code))))))))
 
-(ert-deftest tramp-test44-remote-load-path ()
+(ert-deftest tramp-test45-remote-load-path ()
   "Check that Tramp autoloads its packages with remote `load-path'."
   ;; The autoloaded Tramp objects are different since Emacs 26.1.  We
   ;; cannot test older Emacsen, therefore.
@@ -6743,7 +6818,7 @@ process sentinels.  They shall not disturb each other."
        (mapconcat #'shell-quote-argument load-path " -L ")
        (shell-quote-argument code)))))))
 
-(ert-deftest tramp-test45-unload ()
+(ert-deftest tramp-test46-unload ()
   "Check that Tramp and its subpackages unload completely.
 Since it unloads Tramp, it shall be the last test to run."
   :tags '(:expensive-test)
@@ -6826,7 +6901,7 @@ If INTERACTIVE is non-nil, the tests are run 
interactively."
 ;; * Implement `tramp-test31-interrupt-process' for `adb', `sshfs' and
 ;;   for direct async processes.
 ;; * Check, why direct async processes do not work for
-;;   `tramp-test43-asynchronous-requests'.
+;;   `tramp-test44-asynchronous-requests'.
 
 (provide 'tramp-tests)
 



reply via email to

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