]> git.eshelyaron.com Git - emacs.git/commitdiff
Add file notification handler for Tramp's "smb" method.
authorMichael Albinus <michael.albinus@gmx.de>
Sat, 5 Jul 2025 17:21:48 +0000 (19:21 +0200)
committerEshel Yaron <me@eshelyaron.com>
Thu, 24 Jul 2025 07:42:27 +0000 (09:42 +0200)
* etc/NEWS: Mention new file notification handler for Tramp "smb".

* lisp/filenotify.el (file-notify--expand-file-name): Fix the
remote case.
(file-notify-callback): Extend for "smb-notify".
(file-notify--call-handler): Fix debug message.

* lisp/net/tramp-gvfs.el (tramp-gvfs-handle-file-notify-add-watch):
* lisp/net/tramp-sh.el (tramp-sh-gio-monitor-process-filter):
Use connection property "file-monitor".

* lisp/net/tramp-smb.el (tramp-smb-file-name-handler-alist):
Use `tramp-smb-handle-file-notify-add-watch'.
(tramp-smb-handle-delete-directory): Do not error if there is a
pending deletion of a directory under file-watch.
(tramp-smb-handle-file-notify-add-watch)
(tramp-smb-notify-process-filter): New defuns.
(tramp-smb-send-command): New optional argument NOOUTPUT.
(tramp-smb-wait-for-output): Improve debug message.

* lisp/net/tramp.el (tramp-directory-watched): New defun.
(tramp-accept-process-output, tramp-wait-for-regexp):
Improve debug message.

* test/lisp/filenotify-tests.el (top): Filter also for
"smb-notify".  Set some other Tramp related variables.
(file-notify--test-wait-for-events)
(file-notify--test-with-actions-check)
(file-notify--test-with-actions): Add debug message.
(file-notify--test-cleanup, file-notify--deftest-remote):
Keep Tramp debugs buffer.
(file-notify--test-monitor): Check for "smb-notify".
(file-notify--deftest-remote): Call `file-notify-rm-all-watches'.
(file-notify--test-make-temp-name): Use a better name for parent
directory.
(file-notify--test-event-handler): Use `string-match-p'.
(file-notify--test-with-actions): Check also for the `stopped'
event as limit.
(file-notify-test03-events, file-notify-test04-autorevert)
(file-notify-test05-file-validity)
(file-notify-test07-many-events, file-notify-test08-backup)
(file-notify-test09-watched-file-in-watched-dir): Adapt tests for
"smb-notify".
(file-notify-test12-unmount): Skip for "smb-notify".

(cherry picked from commit aa8afabd493ce67924685bc6cfafec551380a094)

lisp/filenotify.el
lisp/net/tramp-gvfs.el
lisp/net/tramp-sh.el
lisp/net/tramp-smb.el
lisp/net/tramp.el
test/lisp/filenotify-tests.el

index f31a4353513b1b059990b5f6bbadf623623b1e90..300c9babfdbedc848d89312dee4f2a267bb16fb4 100644 (file)
@@ -123,7 +123,9 @@ It is nil or a `file-notify--rename' defstruct where the cookie can be nil.")
 (defun file-notify--expand-file-name (watch file)
   "Full file name of FILE reported for WATCH."
   (directory-file-name
-   (expand-file-name file (file-notify--watch-directory watch))))
+   (if (file-name-absolute-p file)
+       (concat (file-remote-p (file-notify--watch-directory watch)) file)
+     (expand-file-name file (file-notify--watch-directory watch)))))
 
 (cl-defun file-notify--callback-inotify ((desc actions file
                                           &optional file1-or-cookie))
@@ -189,7 +191,7 @@ It is nil or a `file-notify--rename' defstruct where the cookie can be nil.")
   "Notification callback for file name handlers."
   (file-notify--handle-event
    desc
-   ;; File name handlers use gfilenotify or inotify actions.
+   ;; File name handlers use gfilenotify, inotify or w32notify actions.
    (delq nil (mapcar
               (lambda (action)
                 (cond
@@ -205,7 +207,12 @@ It is nil or a `file-notify--rename' defstruct where the cookie can be nil.")
                  ((memq action '(delete delete-self move-self)) 'deleted)
                  ((eq action 'moved-from) 'renamed-from)
                  ((eq action 'moved-to) 'renamed-to)
-                 ((memq action '(ignored unmount)) 'stopped)))
+                 ((memq action '(ignored unmount)) 'stopped)
+                 ;; w32notify actions:
+                 ((eq action 'added) 'created)
+                 ((eq action 'modified) 'changed)
+                 ((eq action 'removed) 'deleted)
+                 ((memq action '(renamed-from renamed-to)) action)))
               (if (consp actions) actions (list actions))))
    file file1-or-cookie))
 
@@ -237,7 +244,7 @@ It is nil or a `file-notify--rename' defstruct where the cookie can be nil.")
     (when (file-notify--watch-callback watch)
       (when file-notify-debug
         (message
-         "file-notify-callback %S %S %S %S %S %S %S"
+         "file-notify--call-handler %S %S %S %S %S %S %S"
          desc action file file1 watch
          (file-notify--watch-absolute-filename watch)
          (file-notify--watch-directory watch)))
index fa32d5763ccf563f8defe389bfe85d7a1164f061..7bc8f7ccfbde1cd6084681048f8fe452e4313d49 100644 (file)
@@ -1516,6 +1516,7 @@ If FILE-SYSTEM is non-nil, return file system attributes."
       (if (not (processp p))
          (tramp-error
           v 'file-notify-error "Monitoring not supported for `%s'" file-name)
+       ;; Needed for process filter.
        (process-put p 'tramp-events events)
        (process-put p 'tramp-watch-name localname)
        (set-process-filter p #'tramp-gvfs-monitor-process-filter)
@@ -1527,9 +1528,9 @@ If FILE-SYSTEM is non-nil, return file system attributes."
        (unless (process-live-p p)
          (tramp-error
           p 'file-notify-error "Monitoring not supported for `%s'" file-name))
-       ;; Set "gio-file-monitor" property.  We believe, that "gio
+       ;; Set "file-monitor" property.  We believe, that "gio
        ;; monitor" uses polling when applied for mounted files.
-       (tramp-set-connection-property p "gio-file-monitor" 'GPollFileMonitor)
+       (tramp-set-connection-property p "file-monitor" 'GPollFileMonitor)
        p))))
 
 (defun tramp-gvfs-monitor-process-filter (proc string)
index db098aae53c77262a01511aa646af2275e798d6c..41fecbf02c6e6ab6438dd8195bd8ad17e67d5d4b 100644 (file)
@@ -3893,9 +3893,9 @@ Fall back to normal file name handler if no Tramp handler exists."
         (throw 'doesnt-work nil))
 
       ;; Determine monitor name.
-      (unless (tramp-connection-property-p proc "gio-file-monitor")
+      (unless (tramp-connection-property-p proc "file-monitor")
         (tramp-set-connection-property
-         proc "gio-file-monitor"
+         proc "file-monitor"
          (cond
           ;; We have seen this on cygwin gio and on emba.  Let's make
           ;; some assumptions.
index e9ee3228a6d9b809d947410dfcc112ab29336236..85b25d5ba10e05b810eef853ae71abe7fa2295ec 100644 (file)
@@ -127,10 +127,10 @@ this variable \"client min protocol=NT1\"."
        "ERRnomem"
        "ERRnosuchshare"
        ;; See /usr/include/samba-4.0/core/ntstatus.h.
-       ;; Windows 4.0 (Windows NT), Windows 5.0 (Windows 2000),
-       ;; Windows 5.1 (Windows XP), Windows 5.2 (Windows Server 2003),
-       ;; Windows 6.0 (Windows Vista), Windows 6.1 (Windows 7),
-       ;; Windows 6.3 (Windows Server 2012, Windows 10).
+       ;; <https://learn.microsoft.com/en-us/windows/win32/sysinfo/operating-system-version>
+       ;; Tested with Windows NT, Windows 2000, Windows XP, Windows
+       ;; Server 2003, Windows Vista, Windows 7, Windows Server 2012,
+       ;; Windows 10, Windows 11.
        "NT_STATUS_ACCESS_DENIED"
        "NT_STATUS_ACCOUNT_LOCKED_OUT"
        "NT_STATUS_BAD_NETWORK_NAME"
@@ -261,7 +261,7 @@ See `tramp-actions-before-shell' for more info.")
     (file-name-nondirectory . tramp-handle-file-name-nondirectory)
     ;; `file-name-sans-versions' performed by default handler.
     (file-newer-than-file-p . tramp-handle-file-newer-than-file-p)
-    (file-notify-add-watch . tramp-handle-file-notify-add-watch)
+    (file-notify-add-watch . tramp-smb-handle-file-notify-add-watch)
     (file-notify-rm-watch . tramp-handle-file-notify-rm-watch)
     (file-notify-valid-p . tramp-handle-file-notify-valid-p)
     (file-ownership-preserved-p . ignore)
@@ -686,8 +686,10 @@ PRESERVE-UID-GID and PRESERVE-EXTENDED-ATTRIBUTES are completely ignored."
          (tramp-error v 'file-error "%s `%s'" (match-string 0) directory)))
 
       ;; "rmdir" does not report an error.  So we check ourselves.
-      (when (file-exists-p directory)
-       (tramp-error v 'file-error "`%s' not removed" directory)))))
+      ;; Deletion of a watched directory could be pending.
+      (when (and (not (tramp-directory-watched directory))
+                (file-exists-p directory))
+        (tramp-error v 'file-error "`%s' not removed" directory)))))
 
 (defun tramp-smb-handle-delete-file (filename &optional trash)
   "Like `delete-file' for Tramp files."
@@ -964,6 +966,108 @@ PRESERVE-UID-GID and PRESERVE-EXTENDED-ATTRIBUTES are completely ignored."
        (tramp-error
         v 'file-error "Cannot make local copy of file `%s'" filename)))))
 
+;; The "notify" command has been added to smbclient 4.3.0.
+(defun tramp-smb-handle-file-notify-add-watch (file-name flags _callback)
+  "Like `file-notify-add-watch' for Tramp files."
+  (setq file-name (expand-file-name file-name))
+  (with-parsed-tramp-file-name file-name nil
+    (let ((default-directory (file-name-directory file-name))
+          (command (format "notify %s" (tramp-smb-shell-quote-localname v)))
+         (events
+          (cond
+           ((memq 'change flags)
+            '(added removed modified renamed-from renamed-to))
+           ((memq 'attribute-change flags) '(modified))))
+         p)
+      ;; Start process.
+      (with-tramp-saved-connection-properties
+         v '(" process-name" " process-buffer")
+       ;; Set the new process properties.
+       (tramp-set-connection-property
+         v " process-name" (tramp-get-unique-process-name "smb-notify"))
+        (tramp-set-connection-property
+         v " process-buffer" (generate-new-buffer " *smb-notify*"))
+       (tramp-flush-connection-property v " process-exit-status")
+       (tramp-smb-send-command v command 'nooutput)
+        (setq p (tramp-get-connection-process v))
+        ;; Return the process object as watch-descriptor.
+        (if (not (processp p))
+           (tramp-error
+            v 'file-notify-error
+            "`%s' failed to start on remote host" command)
+         ;; Needed for process filter.
+         (process-put p 'tramp-events events)
+         (process-put p 'tramp-watch-name localname)
+         (set-process-filter p #'tramp-smb-notify-process-filter)
+         (set-process-sentinel p #'tramp-file-notify-process-sentinel)
+         (tramp-post-process-creation p v)
+         ;; There might be an error if the monitor is not supported.
+         ;; Give the filter a chance to read the output.
+         (while (tramp-accept-process-output p))
+         (unless (process-live-p p)
+           (tramp-error
+            p 'file-notify-error "Monitoring not supported for `%s'" file-name))
+         ;; Set "file-monitor" property.  The existence of the "ADMIN$"
+         ;; share is an indication for a remote MS Windows host.
+         (tramp-set-connection-property
+          p "file-monitor"
+          (if (member
+               "ADMIN$" (directory-files (tramp-make-tramp-file-name v "/")))
+              'SMBWindows 'SMBSamba))
+         p)))))
+
+;; FileChangeNotify subsystem was added to Smaba 4.3.0.
+;; <https://www.samba.org/samba/history/samba-4.3.0.html>
+(defun tramp-smb-notify-process-filter (proc string)
+  "Read output from \"notify\" and add corresponding `file-notify' events."
+  (let ((events (process-get proc 'tramp-events)))
+    (tramp-message proc 6 "%S\n%s" proc string)
+    (dolist (line (split-string string (rx (+ (any "\r\n"))) 'omit))
+      (catch 'next
+       ;; Watched directory is removed.
+       (when (string-match-p "NT_STATUS_DELETE_PENDING" line)
+         (setq line (concat "0002 " (process-get proc 'tramp-watch-name))))
+       ;; Stopped.
+       (when (string-match-p tramp-smb-prompt line)
+          (throw 'next 'next))
+
+       ;; Check, whether there is a problem.
+       (unless (string-match
+                (rx bol (group (+ digit))
+                    (+ blank) (group (+ (not (any "\r\n")))))
+                line)
+          (tramp-error proc 'file-notify-error line))
+
+       ;; See libsmbclient.h.
+       ;; #define SMBC_NOTIFY_ACTION_ADDED             1
+       ;; #define SMBC_NOTIFY_ACTION_REMOVED           2
+       ;; #define SMBC_NOTIFY_ACTION_MODIFIED          3
+       ;; #define SMBC_NOTIFY_ACTION_OLD_NAME          4
+       ;; #define SMBC_NOTIFY_ACTION_NEW_NAME          5
+       ;; #define SMBC_NOTIFY_ACTION_ADDED_STREAM      6
+       ;; #define SMBC_NOTIFY_ACTION_REMOVED_STREAM    7
+       ;; #define SMBC_NOTIFY_ACTION_MODIFIED_STREAM   8
+       (let ((object
+              (list
+               proc
+               (pcase (string-to-number (match-string 1 line))
+                  (1 '(added))
+                  (2 '(removed))
+                  (3 '(modified))
+                  (4 '(renamed-from))
+                  (5 '(renamed-to))
+                 ;; Ignore stream events.
+                  (_ (throw 'next 'next)))
+               (string-replace "\\" "/" (match-string 2 line)))))
+          ;; Add an Emacs event now.
+          ;; `insert-special-event' exists since Emacs 31.
+         (when (member (caadr object) events)
+            (tramp-compat-funcall
+               (if (fboundp 'insert-special-event)
+                    'insert-special-event
+                 (lookup-key special-event-map [file-notify]))
+             `(file-notify ,object file-notify-callback))))))))
+
 ;; This function should return "foo/" for directories and "bar" for
 ;; files.
 (defun tramp-smb-handle-file-name-all-completions (filename directory)
@@ -1823,13 +1927,14 @@ are listed.  Result is the list (LOCALNAME MODE SIZE MTIME)."
 
 ;; Connection functions.
 
-(defun tramp-smb-send-command (vec command)
+(defun tramp-smb-send-command (vec command &optional nooutput)
   "Send the COMMAND to connection VEC.
-Returns nil if there has been an error message from smbclient."
+Returns nil if there has been an error message from smbclient.  The
+function waits for output unless NOOUTPUT is set."
   (tramp-smb-maybe-open-connection vec)
   (tramp-message vec 6 "%s" command)
   (tramp-send-string vec command)
-  (tramp-smb-wait-for-output vec))
+  (unless nooutput (tramp-smb-wait-for-output vec)))
 
 (defun tramp-smb-maybe-open-connection (vec &optional argument)
   "Maybe open a connection to HOST, log in as USER, using `tramp-smb-program'.
@@ -2003,7 +2108,7 @@ Removes smb prompt.  Returns nil if an error message has appeared."
       (while (not (search-forward-regexp tramp-smb-prompt nil t))
        (while (tramp-accept-process-output p))
        (goto-char (point-min)))
-      (tramp-message vec 6 "\n%s" (buffer-string))
+      (tramp-message vec 6 "%S\n%s" p (buffer-string))
 
       ;; Remove prompt.
       (goto-char (point-min))
@@ -2084,4 +2189,6 @@ Removes smb prompt.  Returns nil if an error message has appeared."
 ;;
 ;; * Keep a permanent connection process for `process-file'.
 
+;; * Implement "scopy" (since Samba 4.3.0).
+
 ;;; tramp-smb.el ends here
index 0d7b157c85765d4d3e916d81334d947f7387102a..d64f6ba37acc643249dc865ac157d51c933b0a75 100644 (file)
@@ -5743,6 +5743,16 @@ of."
     (tramp-message proc 5 "Sentinel called: `%S' `%s'" proc event)
     (file-notify-rm-watch proc)))
 
+(defun tramp-directory-watched (directory)
+  "Check, whether a directory is watched."
+  (let (result)
+    (dolist (p (process-list) result)
+      (setq result
+           (or result
+               (and-let* ((dir (process-get p 'tramp-watch-name))
+                          ((string-equal
+                            dir (tramp-file-local-name directory))))))))))
+
 ;;; Functions for establishing connection:
 
 ;; The following functions are actions to be taken when seeing certain
@@ -6120,7 +6130,7 @@ If the user quits via `C-g', it is propagated up to `tramp-file-name-handler'."
        (if (with-local-quit
              (setq result (accept-process-output proc 0 nil t)) t)
            (tramp-message
-            proc 10 "%s %s %s\n%s"
+            proc 10 "%S %S %s\n%s"
             proc (process-status proc) result (buffer-string))
          ;; Propagate quit.
          (keyboard-quit)))
@@ -6197,7 +6207,7 @@ nil."
     ;; timeout of sudo.  The process buffer does not exist any longer then.
     (ignore-errors
       (tramp-message
-       proc 6 "\n%s" (tramp-get-buffer-string (process-buffer proc))))
+       proc 6 "%S\n%s" proc (tramp-get-buffer-string (process-buffer proc))))
     (unless found
       (if timeout
          (tramp-error
index 8744dc4987aa4e6f2a1ad3a757867560c19386de..dada26fc6182bbc6879edcd866a47a9895053777 100644 (file)
@@ -37,7 +37,7 @@
 ;; of a respective command.  The first command found is used.  In
 ;; order to use a dedicated one, the environment variable
 ;; $REMOTE_FILE_NOTIFY_LIBRARY shall be set, possible values are
-;; "inotifywait", "gio-monitor" and "gvfs-monitor-dir".
+;; "inotifywait", "gio-monitor", "gvfs-monitor-dir", and "smb-notify".
 
 ;; Local file-notify libraries are auto-detected during Emacs
 ;; configuration.  This can be changed with a respective configuration
@@ -58,7 +58,7 @@
 
 ;; Filter suppressed remote file-notify libraries.
 (when (stringp (getenv "REMOTE_FILE_NOTIFY_LIBRARY"))
-  (dolist (lib '("inotifywait" "gio-monitor" "gvfs-monitor-dir"))
+  (dolist (lib '("inotifywait" "gio-monitor" "gvfs-monitor-dir" "smb-notify"))
     (unless (string-equal (getenv "REMOTE_FILE_NOTIFY_LIBRARY") lib)
       (add-to-list 'tramp-connection-properties `(nil ,lib nil)))))
 
@@ -104,6 +104,9 @@ There are different timeouts for local and remote file notification libraries."
 TIMEOUT is the maximum time to wait for, in seconds."
   `(with-timeout (,timeout (ignore))
      (while (null ,until)
+       (when file-notify-debug
+         (message "file-notify--test-wait-for-events received: %s"
+                  (file-notify--test-event-actions)))
        (file-notify--test-wait-event))))
 
 (defun file-notify--test-no-descriptors ()
@@ -159,7 +162,7 @@ Return nil when any other file notification watch is still active."
   (ignore-errors
     (when (file-remote-p temporary-file-directory)
       (tramp-cleanup-connection
-       (tramp-dissect-file-name temporary-file-directory) nil 'keep-password)))
+       (tramp-dissect-file-name temporary-file-directory) t 'keep-password)))
 
   (when (hash-table-p file-notify-descriptors)
     (clrhash file-notify-descriptors))
@@ -176,9 +179,13 @@ Return nil when any other file notification watch is still active."
         file-notify--test-events nil
         file-notify--test-monitors nil))
 
-(setq file-notify-debug nil
+(setq auth-source-cache-expiry nil
+      auth-source-save-behavior nil
+      file-notify-debug nil
       password-cache-expiry nil
-      ;; tramp-verbose (if (getenv "EMACS_EMBA_CI") 10 0)
+      remote-file-name-inhibit-cache nil
+      tramp-allow-unsafe-temporary-files t
+      tramp-cache-read-persistent-data t ;; For auth-sources.
       tramp-verbose 0
       ;; When the remote user id is 0, Tramp refuses unsafe temporary files.
       tramp-allow-unsafe-temporary-files
@@ -241,13 +248,17 @@ watch descriptor."
   ;; We cache the result, because after `file-notify-rm-watch',
   ;; `gfile-monitor-name' does not return a proper result anymore.
   ;; But we still need this information.  So far, we know the monitors
-  ;; GFamFileMonitor (gfilenotify on cygwin), GFamDirectoryMonitor
-  ;; (gfilenotify on Solaris), GInotifyFileMonitor (gfilenotify and
-  ;; gio on GNU/Linux), GKqueueFileMonitor (gfilenotify and gio on
-  ;; FreeBSD) and GPollFileMonitor (gio on cygwin).
+  ;; - GFamFileMonitor (gfilenotify on cygwin)
+  ;; - GFamDirectoryMonitor (gfilenotify on Solaris)
+  ;; - GInotifyFileMonitor (gfilenotify and gio on GNU/Linux)
+  ;; - GKqueueFileMonitor (gfilenotify and gio on FreeBSD)
+  ;; - GPollFileMonitor (gio on cygwin)
+  ;; - SMBSamba (smb-notify on Samba server)
+  ;; - SMBWindows (smb-notify on MS Windows).
   (when file-notify--test-desc
     (or (alist-get file-notify--test-desc file-notify--test-monitors)
-        (when (member (file-notify--test-library) '("gfilenotify" "gio"))
+        (when (member
+               (file-notify--test-library) '("gfilenotify" "gio" "smb-notify"))
          (add-to-list
           'file-notify--test-monitors
           (cons file-notify--test-desc
@@ -255,10 +266,10 @@ watch descriptor."
                      ;; `file-notify--test-desc' is the connection process.
                      (progn
                        (while (not (tramp-connection-property-p
-                                   file-notify--test-desc "gio-file-monitor"))
+                                   file-notify--test-desc "file-monitor"))
                          (accept-process-output file-notify--test-desc 0))
                       (tramp-get-connection-property
-                       file-notify--test-desc "gio-file-monitor" nil))
+                       file-notify--test-desc "file-monitor" nil))
                   (and (functionp 'gfile-monitor-name)
                        (gfile-monitor-name file-notify--test-desc)))))
           ;; If we don't know the monitor, there are good chances the
@@ -282,7 +293,8 @@ If UNSTABLE is non-nil, the test is tagged as `:unstable'."
        ;; Needs further investigation.
        (skip-when (string-equal (file-notify--test-library) "gio"))
        (tramp-cleanup-connection
-       (tramp-dissect-file-name temporary-file-directory) nil 'keep-password)
+       (tramp-dissect-file-name temporary-file-directory) t 'keep-password)
+       (file-notify-rm-all-watches)
        (funcall (ert-test-body ert-test)))))
 
 (ert-deftest file-notify-test00-availability ()
@@ -315,7 +327,7 @@ If UNSTABLE is non-nil, the test is tagged as `:unstable'."
   (unless (stringp file-notify--test-tmpdir)
     (setq file-notify--test-tmpdir
           (expand-file-name
-           (make-temp-name "file-notify-test") temporary-file-directory)))
+           (make-temp-name "file-notify-test-parent") temporary-file-directory)))
   (unless (file-directory-p file-notify--test-tmpdir)
     (make-directory file-notify--test-tmpdir))
   (expand-file-name
@@ -558,7 +570,7 @@ and the event to `file-notify--test-events'."
          (result
           (ert-run-test (make-ert-test :body 'file-notify--test-event-test))))
     ;; Do not add lock files, this would confuse the checks.
-    (unless (string-match
+    (unless (string-match-p
             (regexp-quote ".#")
             (file-notify--test-event-file file-notify--test-event))
       (when file-notify-debug
@@ -575,6 +587,8 @@ and the event to `file-notify--test-events'."
 
 (defun file-notify--test-with-actions-check (actions)
   "Check whether received actions match one of the ACTIONS alternatives."
+  (when file-notify-debug
+    (message "file-notify--test-with-actions-check"))
   (let (result)
     (dolist (elt actions result)
       (setq result
@@ -632,11 +646,14 @@ delivered."
       (not (input-pending-p)))
      (setq file-notify--test-events nil
            file-notify--test-results nil)
+     (when file-notify-debug
+        (message "file-notify--test-with-actions expected: %s" actions))
      ,@body
      (file-notify--test-wait-for-events
       ;; More actions need more time.  Use some fudge factor.
       (* (ceiling max-length 100) (file-notify--test-timeout))
-      (= max-length (length file-notify--test-events)))
+      (or (= max-length (length file-notify--test-events))
+          (memq 'stopped (file-notify--test-event-actions))))
      ;; Check the result sequence just to make sure that all actions
      ;; are as expected.
      (dolist (result file-notify--test-results)
@@ -666,6 +683,9 @@ delivered."
                 '(change) #'file-notify--test-event-handler)))
         (file-notify--test-with-actions
             (cond
+             ;; SMBSamba reports three `changed' events.
+            ((eq (file-notify--test-monitor) 'SMBSamba)
+              '(created changed changed changed deleted stopped))
             ;; GFam{File,Directory}Monitor, GKqueueFileMonitor and
             ;; GPollFileMonitor do not report the `changed' event.
             ((memq (file-notify--test-monitor)
@@ -697,6 +717,9 @@ delivered."
                '(change) #'file-notify--test-event-handler)))
         (file-notify--test-with-actions
            (cond
+             ;; SMBSamba reports four `changed' events.
+            ((eq (file-notify--test-monitor) 'SMBSamba)
+              '(changed changed changed changed deleted stopped))
             ;; GFam{File,Directory}Monitor and GPollFileMonitor do
              ;; not detect the `changed' event reliably.
             ((memq (file-notify--test-monitor)
@@ -739,6 +762,9 @@ delivered."
             ;; events for the watched directory.
             ((string-equal (file-notify--test-library) "w32notify")
              '(created changed deleted))
+             ;; SMBSamba reports three `changed' events.
+            ((eq (file-notify--test-monitor) 'SMBSamba)
+              '(created changed changed changed deleted deleted stopped))
              ;; On emba, `deleted' and `stopped' events of the
              ;; directory are not detected.
              ((getenv "EMACS_EMBA_CI")
@@ -789,6 +815,10 @@ delivered."
              '(created changed created changed
                changed changed changed
                deleted deleted))
+             ;; SMBSamba reports three `changed' events.
+            ((eq (file-notify--test-monitor) 'SMBSamba)
+              '(created changed changed changed created changed changed changed
+                deleted deleted deleted stopped))
             ;; There are three `deleted' events, for two files and
             ;; for the directory.  Except for
             ;; GFam{File,Directory}Monitor, GPollFileMonitor and
@@ -843,6 +873,10 @@ delivered."
             ;; events for the watched directory.
             ((string-equal (file-notify--test-library) "w32notify")
              '(created changed renamed deleted))
+             ;; SMBSamba reports three `changed' events.
+            ((eq (file-notify--test-monitor) 'SMBSamba)
+              '(created changed changed changed
+                renamed changed changed deleted deleted stopped))
              ;; On emba, `deleted' and `stopped' events of the
              ;; directory are not detected.
              ((getenv "EMACS_EMBA_CI")
@@ -897,6 +931,14 @@ delivered."
             ((string-equal (file-notify--test-library) "w32notify")
              '((changed changed)
                (changed changed changed changed)))
+             ;; SMBWindows does not distinguish between `changed' and
+            ;; `attribute-changed'.
+            ((eq (file-notify--test-monitor) 'SMBWindows)
+             '(changed changed))
+             ;; SMBSamba does not distinguish between `changed' and
+            ;; `attribute-changed'.
+            ((eq (file-notify--test-monitor) 'SMBSamba)
+              '(changed changed changed changed))
             ;; GFam{File,Directory}Monitor, GKqueueFileMonitor and
             ;; GPollFileMonitor do not report the `attribute-changed'
             ;; event.
@@ -948,6 +990,7 @@ delivered."
          (timeout (if (file-remote-p temporary-file-directory)
                       60   ; FIXME: can this be shortened?
                     (* auto-revert-interval 2.5)))
+         (text-quoting-style 'grave)
          buf)
     (auto-revert-set-timer)
     (unwind-protect
@@ -995,10 +1038,11 @@ delivered."
                 ;; Check, that the buffer has been reverted.
                 (file-notify--test-wait-for-events
                  timeout
-                 (string-match
-                  (format-message "Reverting buffer `%s'." (buffer-name buf))
+                 (string-match-p
+                  (rx bol "Reverting buffer `"
+                      (literal (buffer-name buf)) "'" eol)
                   captured-messages))
-                (should (string-match "another text" (buffer-string)))))
+                (should (string-match-p "another text" (buffer-string)))))
 
             ;; Stop file notification.  Autorevert shall still work via polling.
            (file-notify-rm-watch auto-revert-notify-watch-descriptor)
@@ -1020,10 +1064,11 @@ delivered."
                 ;; Check, that the buffer has been reverted.
                 (file-notify--test-wait-for-events
                  timeout
-                 (string-match
-                  (format-message "Reverting buffer `%s'." (buffer-name buf))
+                 (string-match-p
+                  (rx bol "Reverting buffer `"
+                      (literal (buffer-name buf)) "'" eol)
                   captured-messages))
-                (should (string-match "foo bla" (buffer-string)))))
+                (should (string-match-p "foo bla" (buffer-string)))))
 
             ;; Stop autorevert, in order to cleanup descriptor.
             (auto-revert-mode -1))
@@ -1077,6 +1122,9 @@ delivered."
        (should (file-notify-valid-p file-notify--test-desc))
         (file-notify--test-with-actions
            (cond
+             ;; SMBSamba reports three `changed' events.
+            ((eq (file-notify--test-monitor) 'SMBSamba)
+              '(changed changed changed changed deleted stopped))
             ;; GFam{File,Directory}Monitor do not
              ;; detect the `changed' event reliably.
             ((memq (file-notify--test-monitor)
@@ -1093,6 +1141,7 @@ delivered."
            "another text" nil file-notify--test-tmpfile nil 'no-message)
          (file-notify--test-wait-event)
          (delete-file file-notify--test-tmpfile))
+       (file-notify--test-wait-event)
        ;; After deleting the file, the descriptor is not valid anymore.
         (should-not (file-notify-valid-p file-notify--test-desc))
         (file-notify-rm-watch file-notify--test-desc)
@@ -1122,6 +1171,9 @@ delivered."
               ;; events for the watched directory.
               ((string-equal (file-notify--test-library) "w32notify")
                '(created changed deleted))
+               ;; SMBSamba reports three `changed' events.
+              ((eq (file-notify--test-monitor) 'SMBSamba)
+                '(created changed changed changed deleted deleted stopped))
               ;; There are two `deleted' events, for the file and for
               ;; the directory.  Except for
               ;; GFam{File,Directory}Monitor, GPollFileMonitor and
@@ -1247,7 +1299,14 @@ delivered."
                (push (expand-file-name (format "y%d" i)) target-file-list))
            (push (expand-file-name (format "y%d" i)) source-file-list)
            (push (expand-file-name (format "x%d" i)) target-file-list)))
-        (file-notify--test-with-actions (make-list (+ n n) 'created)
+        (file-notify--test-with-actions
+           (cond
+             ;; SMBSamba fires both `created' and `changed' events.
+            ((eq (file-notify--test-monitor) 'SMBSamba)
+             (let (r)
+               (dotimes (_i (+ n n) r)
+                 (setq r (append '(created changed) r)))))
+            (t (make-list (+ n n) 'created)))
           (let ((source-file-list source-file-list)
                 (target-file-list target-file-list))
             (while (and source-file-list target-file-list)
@@ -1260,18 +1319,26 @@ delivered."
             ;; w32notify fires both `deleted' and `renamed' events.
             ((string-equal (file-notify--test-library) "w32notify")
              (let (r)
-               (dotimes (_i n)
-                 (setq r (append '(deleted renamed) r)))
-               r))
-            ;; GFam{File,Directory}Monitor and GPollFileMonitor fire
+               (dotimes (_i n r)
+                 (setq r (append '(deleted renamed) r)))))
+             ;; SMBWindows fires both `changed' and `deleted' events.
+            ((eq (file-notify--test-monitor) 'SMBWindows)
+             (let (r)
+               (dotimes (_i n r)
+                 (setq r (append '(changed deleted) r)))))
+             ;; SMBSamba fires both `changed' and `deleted' events.
+            ((eq (file-notify--test-monitor) 'SMBSamba)
+             (let (r)
+               (dotimes (_i n r)
+                 (setq r (append '(changed changed deleted) r)))))
+             ;; GFam{File,Directory}Monitor and GPollFileMonitor fire
             ;; `changed' and `deleted' events, sometimes in random
             ;; order.
             ((memq (file-notify--test-monitor)
                     '(GFamFileMonitor GFamDirectoryMonitor GPollFileMonitor))
              (let (r)
-               (dotimes (_i n)
-                 (setq r (append '(changed deleted) r)))
-               (cons :random r)))
+               (dotimes (_i n (cons :random r))
+                 (setq r (append '(changed deleted) r)))))
             (t (make-list n 'renamed)))
           (let ((source-file-list source-file-list)
                 (target-file-list target-file-list))
@@ -1315,6 +1382,9 @@ delivered."
         (should (file-notify-valid-p file-notify--test-desc))
         (file-notify--test-with-actions
            (cond
+             ;; SMBSamba reports four `changed' events.
+            ((eq (file-notify--test-monitor) 'SMBSamba)
+              '(changed changed changed changed))
             ;; GKqueueFileMonitor does not report the `changed' event.
             ((eq (file-notify--test-monitor) 'GKqueueFileMonitor) '())
              ;; There could be one or two `changed' events.
@@ -1354,6 +1424,12 @@ delivered."
         (should (file-notify-valid-p file-notify--test-desc))
         (file-notify--test-with-actions
             (cond
+             ;; SMBWindows reports two `changed' events.
+            ((eq (file-notify--test-monitor) 'SMBWindows)
+              '(changed changed))
+             ;; SMBSamba reports four `changed' events.
+            ((eq (file-notify--test-monitor) 'SMBSamba)
+              '(changed changed changed changed))
             ;; GFam{File,Directory}Monitor and GPollFileMonitor
             ;; report only the `changed' event.
             ((memq (file-notify--test-monitor)
@@ -1438,7 +1514,27 @@ the file watch."
           (file-notify--test-with-actions
               ;; There could be one or two `changed' events.
               (list
-              ;; cygwin.
+               ;; SMBSamba.  Sometimes, tha last `changed' event is
+               ;; missing, so we add two alternatives.
+               (append
+                '(:random)
+                ;; Just the file monitor.
+                (make-list (* (/ n 2) 5) 'changed)
+                ;; Just the directory monitor.  Strange, not all
+                ;; `changed' events do arrive.
+                (make-list (1- (* (/ n 2) 10)) 'changed)
+                (make-list (/ n 2) 'created)
+                (make-list (/ n 2) 'created))
+                (append
+                '(:random)
+                ;; Just the file monitor.
+                (make-list (* (/ n 2) 5) 'changed)
+                ;; Just the directory monitor.  This is the alternative
+                ;; with all `changed' events.
+                (make-list (* (/ n 2) 10) 'changed)
+                (make-list (/ n 2) 'created)
+                (make-list (/ n 2) 'created))
+               ;; cygwin.
                (append
                 '(:random)
                 (make-list (/ n 2) 'changed)
@@ -1482,7 +1578,9 @@ the file watch."
         ;; directory and the file monitor.  The `stopped' event is
         ;; from the file monitor.  It's undecided in which order the
         ;; directory and the file monitor are triggered.
-        (file-notify--test-with-actions '(:random deleted deleted stopped)
+        (file-notify--test-with-actions
+            '((:random deleted deleted stopped)
+              (:random deleted deleted deleted stopped))
           (delete-file file-notify--test-tmpfile1))
         (should (file-notify-valid-p file-notify--test-desc1))
         (should-not (file-notify-valid-p file-notify--test-desc2))
@@ -1715,8 +1813,8 @@ the file watch."
   "Check that file notification stop after unmounting the filesystem."
   :tags '(:expensive-test)
   (skip-unless (file-notify--test-local-enabled))
-  ;; This test does not work for w32notify.
-  (skip-when (string-equal (file-notify--test-library) "w32notify"))
+  ;; This test does not work for w32notify snd smb-notify.
+  (skip-when (member (file-notify--test-library) '("w32notify" "smb-notify")))
 
   (unwind-protect
       (progn
@@ -1789,8 +1887,8 @@ the file watch."
 ;;   the missing directory monitor.
 ;; * For w32notify, no `deleted' and `stopped' events arrive when a
 ;;   directory is removed.
-;; * For cygwin and w32notify, no `attribute-changed' events arrive.
-;;   They send `changed' events instead.
+;; * For cygwin, w32notify, and smb-notify, no `attribute-changed'
+;;   events arrive.  They send `changed' events instead.
 ;; * cygwin does not send all expected `changed' and `deleted' events.
 ;;   Probably due to timing issues.