]> git.eshelyaron.com Git - emacs.git/commitdiff
Add Tramp sshfs method
authorMichael Albinus <michael.albinus@gmx.de>
Mon, 8 Mar 2021 11:05:29 +0000 (12:05 +0100)
committerMichael Albinus <michael.albinus@gmx.de>
Mon, 8 Mar 2021 11:05:29 +0000 (12:05 +0100)
* doc/misc/tramp.texi (Top, Configuration): Insert sections 'FUSE-based
methods' and 'FUSE setup' in menu.
(Quick Start Guide): Fix @anchors.  Add doas.  Extend section
'Using @command{rclone}' to 'Using @acronym{FUSE}-based methods'.
(External methods): Remove rclone paragraph.
(FUSE-based methods, FUSE setup): New nodes.
(Predefined connection information): Mention "mount-point".

* etc/NEWS: Mention Tramp sshfs method.
Fix typos and other oddities.

* lisp/net/tramp-fuse.el: New file.

* lisp/net/tramp-rclone.el (tramp-fuse): Require.
(tramp-rclone-file-name-handler-alist): Replace `tramp-rclone-handle-*'
by `tramp-fuse-handle-*' where appropriate.
(tramp-rclone-handle-delete-directory)
(tramp-rclone-handle-delete-file)
(tramp-rclone-handle-directory-files)
(tramp-rclone-handle-file-attributes)
(tramp-rclone-handle-file-executable-p)
(tramp-rclone-handle-file-name-all-completions)
(tramp-rclone-handle-file-readable-p)
(tramp-rclone-handle-insert-directory)
(tramp-rclone-handle-insert-file-contents)
(tramp-rclone-handle-make-directory, tramp-rclone-mount-point)
(tramp-rclone-mounted-p, tramp-rclone-local-file-name):
Remove.  Functionality moved to tramp-fuse.el.
(tramp-rclone-remote-file-name)
(tramp-rclone-maybe-open-connection): Use `tramp-fuse-*' functions.

* lisp/net/tramp-sh.el (tramp-do-copy-or-rename-file-out-of-band):
Simplify check.

* lisp/net/tramp-sshfs.el: New file.

* lisp/net/tramp.el: Remove TODO item.

* test/lisp/net/tramp-tests.el (tramp--test-sshfs-p): New defun.
(tramp-test14-delete-directory): Use it.

doc/misc/tramp.texi
etc/NEWS
lisp/net/tramp-fuse.el [new file with mode: 0644]
lisp/net/tramp-rclone.el
lisp/net/tramp-sh.el
lisp/net/tramp-sshfs.el [new file with mode: 0644]
lisp/net/tramp.el
test/lisp/net/tramp-tests.el

index 2c9348f6d0dc1d8488d6f67cfa49e0e8e88d1b94..5958162d937b60a967f194af360aafac1c9b94ed 100644 (file)
@@ -126,6 +126,7 @@ Configuring @value{tramp} for use
 * Inline methods::              Inline methods.
 * External methods::            External methods.
 * GVFS-based methods::          @acronym{GVFS}-based external methods.
+* FUSE-based methods::          @acronym{FUSE}-based external methods.
 * Default Method::              Selecting a default method.
 * Default User::                Selecting a default user.
 * Default Host::                Selecting a default host.
@@ -139,6 +140,7 @@ Configuring @value{tramp} for use
                                 Setting own connection related information.
 * Remote programs::             How @value{tramp} finds and uses programs on the remote host.
 * Remote shell setup::          Remote shell setup hints.
+* FUSE setup::                  @acronym{FUSE} setup hints.
 * Android shell setup::         Android shell setup hints.
 * Auto-save and Backup::        Auto-save and Backup.
 * Keeping files encrypted::     Protect remote files by encryption.
@@ -433,7 +435,7 @@ remote host, when the buffer you call the process from has a remote
 @code{default-directory}.
 
 
-@anchor{Quick Start Guide: File name syntax}
+@anchor{Quick Start Guide File name syntax}
 @section File name syntax
 @cindex file name syntax
 
@@ -459,7 +461,7 @@ connection methods also support a notation for the port to be used, in
 which case it is written as @code{host#port}.
 
 
-@anchor{Quick Start Guide: @option{ssh} and @option{plink} methods}
+@anchor{Quick Start Guide ssh and plink methods}
 @section Using @option{ssh} and @option{plink}
 @cindex method @option{ssh}
 @cindex @option{ssh} method
@@ -478,28 +480,31 @@ an @command{ssh} server:
 @file{@trampfn{plink,user@@host,/path/to/file}}.
 
 
-@anchor{Quick Start Guide: @option{su}, @option{sudo} and @option{sg} methods}
-@section Using @option{su}, @option{sudo} and @option{sg}
+@anchor{Quick Start Guide su, sudo, doas and sg methods}
+@section Using @option{su}, @option{sudo}, @option{doas} and @option{sg}
 @cindex method @option{su}
 @cindex @option{su} method
 @cindex method @option{sudo}
 @cindex @option{sudo} method
+@cindex method @option{doas}
+@cindex @option{doas} method
 @cindex method @option{sg}
 @cindex @option{sg} method
 
 Sometimes, it is necessary to work on your local host under different
 permissions.  For this, you can use the @option{su} or @option{sudo}
-connection method.  Both methods use @samp{root} as default user name
-and the return value of @code{(system-name)} as default host name.
-Therefore, it is convenient to open a file as
+connection method.  On OpenBSD systems, the @option{doas} connection
+method offers the same functionality.  These methods use @samp{root}
+as default user name and the return value of @code{(system-name)} as
+default host name.  Therefore, it is convenient to open a file as
 @file{@trampfn{sudo,,/path/to/file}}.
 
 The method @option{sg} stands for ``switch group''; here the user name
 is used as the group to change to.  The default host name is the same.
 
 
-@anchor{Quick Start Guide: @option{ssh}, @option{plink}, @option{su}, @option{sudo} and @option{sg} methods}
-@section Combining @option{ssh} or @option{plink} with @option{su} or @option{sudo}
+@anchor{Quick Start Guide Combining ssh, plink, su, sudo and doas methods}
+@section Combining @option{ssh} or @option{plink} with @option{su}, @option{sudo} or @option{doas}
 @cindex method @option{ssh}
 @cindex @option{ssh} method
 @cindex method @option{plink}
@@ -508,18 +513,20 @@ is used as the group to change to.  The default host name is the same.
 @cindex @option{su} method
 @cindex method @option{sudo}
 @cindex @option{sudo} method
+@cindex method @option{doas}
+@cindex @option{doas} method
 
-If the @option{su} or @option{sudo} option should be performed on
-another host, it can be comnbined with a leading @option{ssh} or
-@option{plink} option.  That means that @value{tramp} connects first to
-the other host with non-administrative credentials, and changes to
-administrative credentials on that host afterwards.  In a simple case,
-the syntax looks like
+If the @option{su}, @option{sudo} or @option{doas} option should be
+performed on another host, it can be comnbined with a leading
+@option{ssh} or @option{plink} option.  That means that @value{tramp}
+connects first to the other host with non-administrative credentials,
+and changes to administrative credentials on that host afterwards.  In
+a simple case, the syntax looks like
 @file{@value{prefix}ssh@value{postfixhop}user@@host|sudo@value{postfixhop}@value{postfix}/path/to/file}.
 @xref{Ad-hoc multi-hops}.
 
 
-@anchor{Quick Start Guide: @option{sudoedit} method}
+@anchor{Quick Start Guide sudoedit method}
 @section Using @command{sudoedit}
 @cindex method @option{sudoedit}
 @cindex @option{sudoedit} method
@@ -532,7 +539,7 @@ method, it is restricted to @samp{localhost} only, and it does not
 support external processes.
 
 
-@anchor{Quick Start Guide: @option{smb} method}
+@anchor{Quick Start Guide smb method}
 @section Using @command{smbclient}
 @cindex method @option{smb}
 @cindex @option{smb} method
@@ -546,7 +553,7 @@ of the local file name is the share exported by the remote host,
 @samp{path} in this example.
 
 
-@anchor{Quick Start Guide: GVFS-based methods}
+@anchor{Quick Start Guide GVFS-based methods}
 @section Using @acronym{GVFS}-based methods
 @cindex methods, gvfs
 @cindex gvfs-based methods
@@ -570,7 +577,7 @@ file system), @file{@trampfn{dav,user@@host,/path/to/file}},
 @file{@trampfn{mtp,device,/path/to/file}} (for media devices).
 
 
-@anchor{Quick Start Guide: GNOME Online Accounts based methods}
+@anchor{Quick Start Guide GNOME Online Accounts based methods}
 @section Using @acronym{GNOME} Online Accounts based methods
 @cindex @acronym{GNOME} Online Accounts
 @cindex method @option{gdrive}
@@ -590,21 +597,18 @@ account), or @file{@trampfn{nextcloud,user@@host#8081,/path/to/file}}
 (@samp{8081} stands for the port number) for OwnCloud/NextCloud files.
 
 
-@anchor{Quick Start Guide: Android}
-@section Using Android
-@cindex method @option{adb}
-@cindex @option{adb} method
-@cindex android
-
-An Android device, which is connected via USB to your local host, can
-be accessed via the @command{adb} command.  No user or host name is
-needed.  The file name syntax is @file{@trampfn{adb,,/path/to/file}}.
-
-
-@anchor{Quick Start Guide: @option{rclone} method}
-@section Using @command{rclone}
+@anchor{Quick Start Guide FUSE-based methods}
+@section Using @acronym{FUSE}-based methods
+@cindex methods, fuse
+@cindex fuse-based methods
 @cindex method @option{rclone}
 @cindex @option{rclone} method
+@cindex method @option{sshfs}
+@cindex @option{sshfs} method
+
+@acronym{FUSE, Filesystem in Userspace} allows users to mount a
+virtual file system.  It is also used by @acronym{GVFS} internally,
+but here we discuss methods which do not use the @acronym{GVFS} API.
 
 A convenient way to access system storages is the @command{rclone}
 program.  If you have configured a storage in @command{rclone} under a
@@ -612,6 +616,24 @@ name @samp{storage} (for example), you can access it via the remote
 file name syntax @file{@trampfn{rclone,storage,/path/to/file}}.  User
 names are not needed.
 
+On local hosts which have installed the @command{sshfs} client for
+mounting a file system based on @command{sftp}, this method can be
+used.  All remote files are available via the local mount point.
+@value{tramp} aids in mounting the file system if it isn't mounted
+yet, and it supports the access with the usual file name syntax
+@file{@trampfn{sshfs,user@@host,/path/to/file}}.
+
+
+@anchor{Quick Start Guide Android}
+@section Using Android
+@cindex method @option{adb}
+@cindex @option{adb} method
+@cindex android
+
+An Android device, which is connected via USB to your local host, can
+be accessed via the @command{adb} command.  No user or host name is
+needed.  The file name syntax is @file{@trampfn{adb,,/path/to/file}}.
+
 
 @node Configuration
 @chapter Configuring @value{tramp}
@@ -650,6 +672,7 @@ may be used in your init file:
 * Inline methods::              Inline methods.
 * External methods::            External methods.
 * GVFS-based methods::          @acronym{GVFS}-based external methods.
+* FUSE-based methods::          @acronym{FUSE}-based external methods.
 * Default Method::              Selecting a default method.
                                   Here we also try to help those who
                                   don't have the foggiest which method
@@ -666,6 +689,7 @@ may be used in your init file:
                                 Setting own connection related information.
 * Remote programs::             How @value{tramp} finds and uses programs on the remote host.
 * Remote shell setup::          Remote shell setup hints.
+* FUSE setup::                  @acronym{FUSE} setup hints.
 * Android shell setup::         Android shell setup hints.
 * Auto-save and Backup::        Auto-save and Backup.
 * Keeping files encrypted::     Protect remote files by encryption.
@@ -1110,7 +1134,6 @@ UNC file name specification does not allow the specification of a
 different user name for authentication like the @command{smbclient}
 can.
 
-
 @item @option{adb}
 @cindex method @option{adb}
 @cindex @option{adb} method
@@ -1150,45 +1173,6 @@ specified using @file{device#42} host name syntax or @value{tramp} can
 use the default value as declared in @command{adb} command.  Port
 numbers are not applicable to Android devices connected through USB@.
 
-
-@item @option{rclone}
-@cindex method @option{rclone}
-@cindex @option{rclone} method
-
-@vindex tramp-rclone-program
-The program @command{rclone} allows to access different system
-storages in the cloud, see @url{https://rclone.org/} for a list of
-supported systems.  If the @command{rclone} program isn't found in
-your @env{PATH} environment variable, you can tell @value{tramp} its
-absolute path via the user option @code{tramp-rclone-program}.
-
-A system storage must be configured via the @command{rclone config}
-command, outside Emacs.  If you have configured a storage in
-@command{rclone} under a name @samp{storage} (for example), you could
-access it via the remote file name
-
-@example
-@trampfn{rclone,storage,/path/to/file}
-@end example
-
-User names are part of the @command{rclone} configuration, and not
-needed in the remote file name.  If a user name is contained in the
-remote file name, it is ignored.
-
-Internally, @value{tramp} mounts the remote system storage at location
-@file{/tmp/tramp.rclone.storage}, with @file{storage} being the name
-of the configured system storage.
-
-Optional flags to the different @option{rclone} operations could be
-passed as connection property, @xref{Predefined connection
-information}.  Supported properties are @t{"mount-args"},
-@t{"copyto-args"}, @t{"moveto-args"} and @t{"about-args"}.
-
-Access via @option{rclone} is slow.  If you have an alternative method
-for accessing the system storage, you should use it.
-@ref{GVFS-based methods} for example, methods @option{gdrive} and
-@option{nextcloud}.
-
 @end table
 
 
@@ -1200,8 +1184,8 @@ for accessing the system storage, you should use it.
 
 @acronym{GVFS} is the virtual file system for the @acronym{GNOME}
 Desktop, @uref{https://en.wikipedia.org/wiki/GVFS}.  Remote files on
-@acronym{GVFS} are mounted locally through FUSE and @value{tramp} uses
-this locally mounted directory internally.
+@acronym{GVFS} are mounted locally through @acronym{FUSE} and
+@value{tramp} uses this locally mounted directory internally.
 
 Emacs uses the D-Bus mechanism to communicate with @acronym{GVFS}@.
 Emacs must have the message bus system, D-Bus integration active,
@@ -1317,6 +1301,88 @@ respectively:
 @end defopt
 
 
+@node FUSE-based methods
+@section @acronym{FUSE}-based external methods
+@cindex methods, fuse
+@cindex fuse-based methods
+
+Besides @acronym{GVFS}, there are other virtual file systems using the
+@acronym{FUSE} interface.  Remote files are mounted locally through
+@acronym{FUSE} and @value{tramp} uses this locally mounted directory
+internally.  When possible, @value{tramp} maps the remote file names
+to their respective local file name, and applies the file name
+operation on them.  For some of the file name operations this is not
+possible, @value{tramp} emulates those operations otherwise.
+
+@table @asis
+@item @option{rclone}
+@cindex method @option{rclone}
+@cindex @option{rclone} method
+
+@vindex tramp-rclone-program
+The program @command{rclone} allows to access different system
+storages in the cloud, see @url{https://rclone.org/} for a list of
+supported systems.  If the @command{rclone} program isn't found in
+your @env{PATH} environment variable, you can tell @value{tramp} its
+absolute path via the user option @code{tramp-rclone-program}.
+
+A system storage must be configured via the @command{rclone config}
+command, outside Emacs.  If you have configured a storage in
+@command{rclone} under a name @samp{storage} (for example), you could
+access it via the remote file name
+
+@example
+@trampfn{rclone,storage,/path/to/file}
+@end example
+
+User names are part of the @command{rclone} configuration, and not
+needed in the remote file name.  If a user name is contained in the
+remote file name, it is ignored.
+
+Internally, @value{tramp} mounts the remote system storage at location
+@file{/tmp/tramp.rclone.storage}, with @file{storage} being the name
+of the configured system storage.
+
+The mount point and optional flags to the different @option{rclone}
+operations could be passed as connection properties, @xref{Setup of
+rclone method}.
+
+Access via @option{rclone} is slow.  If you have an alternative method
+for accessing the system storage, you should use it.
+@ref{GVFS-based methods} for example, methods @option{gdrive} and
+@option{nextcloud}.
+
+@item @option{sshfs}
+@cindex method @option{sshfs}
+@cindex @option{sshfs} method
+
+@vindex tramp-sshfs-program
+On local hosts which have installed the @command{sshfs} client for
+mounting a file system based on @command{sftp}, this method can be
+used, see
+@url{https://github.com/libfuse/sshfs/blob/master/README.rst/}.  If
+the @command{sshfs} program isn't found in your @env{PATH} environment
+variable, you can tell @value{tramp} its absolute path via the user
+option @code{tramp-sshfs-program}.
+
+All remote files are available via the local mount point.
+@value{tramp} aids in mounting the file system if it isn't mounted
+yet.  The remote file name syntax is
+
+@example
+@trampfn{sshfs,user@@host#port,/path/to/file}
+@end example
+
+User name and port number are optional.  This method does not support
+password handling, the file system must either be mounted already, or
+the connection must be established passwordless via ssh keys.
+
+The mount point and mount arguments could be passed as connection
+properties, @xref{Setup of sshfs method}.
+
+@end table
+
+
 @node Default Method
 @section Selecting a default method
 @cindex default method
@@ -2102,6 +2168,13 @@ The default value of this property is @code{t} (not specified in
 @code{tramp-methods}).  If the remote host runs native MS Windows,
 this propery has no effect.
 
+@item @t{"mount-point"}
+
+The directory file name an @acronym{FUSE}-based file system is mounted
+on.  The default value of this property is
+@t{"/tmp/tramp.method.user@@host#port"} (not specified in
+@code{tramp-methods}).
+
 @item @t{"mount-args"}@*
 @t{"copyto-args"}@*
 @t{"moveto-args"}@*
@@ -2430,7 +2503,6 @@ match the end of the connection buffer.  Due to performance reasons,
 this search starts at the end of the buffer, and it is limited to 256
 characters backwards.
 
-
 @item Conflicting names for users and variables in @file{.profile}
 
 When a user name is the same as a variable name in a local file, such
@@ -2440,7 +2512,6 @@ variable name to something different from the user name.  For example,
 if the user name is @env{FRUMPLE}, then change the variable name to
 @env{FRUMPLE_DIR}.
 
-
 @item Non-Bourne commands in @file{.profile}
 
 When the remote host's @file{.profile} is also used for shells other
@@ -2465,7 +2536,6 @@ To accommodate using non-Bourne shells on that remote, use other
 shell-specific config files.  For example, bash can use
 @file{~/.bash_profile} and ignore @file{.profile}.
 
-
 @item Interactive shell prompt
 
 @vindex INSIDE_EMACS@r{, environment variable}
@@ -2533,6 +2603,57 @@ where @samp{192.168.0.1} is the remote host IP address
 @end table
 
 
+@node FUSE setup
+@section @acronym{FUSE} setup hints
+
+The @acronym{FUSE} file systems are mounted per default at
+@file{/tmp/tramp.method.user@@host#port}.  The user name and port
+number are optional.  If the file system is already mounted, it will
+be used as it is.  If the mount point does not exist yet,
+@value{tramp} creates this directory.
+
+The mount point can be overwritten by the connection property
+@t{"mount-point"}, @ref{Predefined connection information}.
+Example:
+
+@lisp
+@group
+(add-to-list 'tramp-connection-properties
+             `(,(regexp-quote "@trampfn{sshfs,user@@host,}")
+               "mount-point"
+               ,(expand-file-name "sshfs.user@@host" user-emacs-directory)))
+@end group
+@end lisp
+
+
+@anchor{Setup of rclone method}
+@subsection @option{rclone} setup
+@cindex rclone setup
+
+The default arguments of the @command{rclone} operations
+@command{mount}, @command{coopyto}, @command{moveto} and
+@command{about} are declared in the variable @code{tramp-methods} as
+method specific parameters.  Usually, they don't need to be overwritten.
+
+If needed, these parameters can be overwritten as connection
+properties @t{"mount-args"}, @t{"copyto-args"}, @t{"moveto-args"} and
+@t{"about-args"}, @xref{Predefined connection information}.  All of
+them are list of strings.
+
+Be careful changing @t{"--dir-cache-time"}, this could delay
+visibility of files.
+
+
+@anchor{Setup of sshfs method}
+@subsection @option{sshfs} setup
+@cindex sshfs setup
+
+The method @option{sshfs} declares only the mount arguments, passed to
+the @command{sshfs} command.  This is a list of list of strings, and
+can be overwritten by the connection property @t{"mount-args"},
+@xref{Predefined connection information}.
+
+
 @node Android shell setup
 @section Android shell setup hints
 @cindex android shell setup for ssh
@@ -4197,6 +4318,7 @@ Disable excessive traces.  Set @code{tramp-verbose} to 3 or lower,
 default being 3.  Increase trace levels temporarily when hunting for
 bugs.
 
+
 @item
 @value{tramp} does not connect to the remote host
 
@@ -4448,6 +4570,7 @@ disable @samp{--color=yes} or @samp{--color=auto} in the remote host's
 @file{.bashrc} or @file{.profile}.  Turn this alias on and off to see
 if file name completion works.
 
+
 @item
 File name completion does not work in directories with large number of
 files
@@ -4846,6 +4969,7 @@ In BBDB buffer, access an entry by pressing the key @kbd{F}.
 
 Thanks to @value{tramp} users for contributing to these recipes.
 
+
 @item
 Why saved multi-hop file names do not work in a new Emacs session?
 
index ce337e75171f95b2884b6371a41c8f9a5c8cc24d..26bed2af1816389e02d2243dfeb723d106ce1c00 100644 (file)
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -266,8 +266,8 @@ current mode.
 
 +++
 ** New user option 'read-extended-command-predicate'.
-This option controls how 'M-x' performs completion of commands when
-you type TAB.  By default, any command that matches what you have
+This user option controls how 'M-x' performs completion of commands when
+you type 'TAB'.  By default, any command that matches what you have
 typed is considered a completion candidate, but you can customize this
 option to exclude commands that are not applicable to the current
 buffer's major and minor modes, and respect the command's completion
@@ -369,25 +369,26 @@ Typing 'TAB' on a heading line cycles the current section between
 anywhere in the buffer cycles the whole buffer between "only top-level
 headings", "all headings and subheadings", and "show all" states.
 
-*** New option 'outline-minor-mode-cycle'.
-This option customizes 'outline-minor-mode', with the difference
+*** New user option 'outline-minor-mode-cycle'.
+This user option customizes 'outline-minor-mode', with the difference
 that 'TAB' and 'S-TAB' on heading lines cycle heading visibility.
 Typing 'TAB' on a heading line cycles the current section between
 "hide all", "subheadings", and "show all" states.  Typing 'S-TAB' on a
 heading line cycles the whole buffer between "only top-level
 headings", "all headings and subheadings", and "show all" states.
 
-*** New option 'outline-minor-mode-highlight'.
-This option customizes 'outline-minor-mode'.  It puts highlighting
-on heading lines using standard outline faces.  This works well only
-when there are no conflicts with faces used by the major mode.
+*** New user option 'outline-minor-mode-highlight'.
+This user option customizes 'outline-minor-mode'.  It puts
+highlighting on heading lines using standard outline faces.  This
+works well only when there are no conflicts with faces used by the
+major mode.
 
 \f
 * Changes in Specialized Modes and Packages in Emacs 28.1
 
 ** Macroexp
 ---
-*** New function 'macroexp-file-name' to know the name of the current file
+*** New function 'macroexp-file-name' to know the name of the current file.
 ---
 *** New function 'macroexp-compiling-p' to know if we're compiling.
 ---
@@ -400,17 +401,18 @@ It used to be enabled when Emacs is started in GUI mode but not when started
 in text mode.  The cursor still only actually blinks in GUI frames.
 
 ** Bindat
+
 +++
 *** New 'Bindat type expression' description language.
 This new system is provided by the new macro 'bindat-type' and
 obsoletes the old data layout specifications.  It supports
 arbitrary-size integers, recursive types, and more.  See the Info node
-'Byte Packing' in the ELisp manual for more details.
+"(elisp) Byte Packing" in the ELisp manual for more details.
 
 ** pcase
 
 +++
-*** The 'or' pattern now binds the union of the vars of its sub-patterns
+*** The 'or' pattern now binds the union of the vars of its sub-patterns.
 If a variable is not bound by the subpattern that matched, it gets bound
 to nil.  This was already sometimes the case, but it is now guaranteed.
 
@@ -1031,10 +1033,9 @@ To customize obsolete user options, use 'customize-option' or
 
 ** Edebug
 
----
 *** Obsoletions
+---
 **** 'get-edebug-spec' is obsolete, replaced by 'edebug-get-spec'.
-
 +++
 **** The spec operator ':name NAME' is obsolete, use '&name' instead.
 +++
@@ -1066,7 +1067,7 @@ use) and HEAD is the code that matched SPEC.
 +++
 *** New user option 'eldoc-echo-area-display-truncation-message'.
 If non-nil (the default), eldoc will display a message saying
-something like "(Documentation truncated. Use `M-x eldoc-doc-buffer'
+something like "(Documentation truncated.  Use `M-x eldoc-doc-buffer'
 to see rest)" when a message has been truncated.  If nil, truncated
 messages will be marked with just "..." at the end.
 
@@ -1134,6 +1135,10 @@ preferred over the eudcb-mab.el backend.
 *** New connection method "mtp", which allows accessing media devices
 like cell phones, tablets or cameras.
 
++++
+*** New connection method "sshfs", which allows accessing remote files
+via a file system mounted with 'sshfs'.
+
 +++
 *** Trashed remote files are moved to the local trash directory.
 All remote files, which are trashed, are moved to the local trash
@@ -1555,7 +1560,7 @@ have been renamed to have "proper" public names and documented
 'xref-show-definitions-buffer-at-bottom').
 
 *** New command 'xref-quit-and-pop-marker-stack' and a binding for it
-in "*xref*" buffers ('M-,'). This combination is easy to press
+in "*xref*" buffers ('M-,').  This combination is easy to press
 semi-accidentally if the user wants to go back in the middle of
 choosing the exact definition to go to, and this should do TRT.
 
@@ -2138,7 +2143,7 @@ messages, contain the error name of that message now.
 +++
 *** D-Bus events have changed their internal structure.
 They carry now the destination and the error-name of an event.  They
-also keep the type information of their arguments. Use the
+also keep the type information of their arguments.  Use the
 'dbus-event-*' accessor functions.
 
 ** CPerl Mode
@@ -2180,7 +2185,7 @@ You can type 'C-x u u' instead of 'C-x u C-x u' to undo many changes,
 'C-x { { } } ^ ^ v v' to resize the selected window interactively,
 'M-g n n p p' to navigate next-error matches.  Any other key exits
 transient mode and then is executed normally.  'repeat-exit-key'
-defines an additional key to exit mode like 'isearch-exit' (RET).
+defines an additional key to exit mode like 'isearch-exit' ('RET').
 
 \f
 * New Modes and Packages in Emacs 28.1
@@ -2296,7 +2301,7 @@ by mistake and were not useful to Lisp code.
 
 ---
 ** Loading 'generic-x' unconditionally loads all modes.
-The user option `generic-extras-enable-list' is now obsolete, and
+The user option 'generic-extras-enable-list' is now obsolete, and
 setting it has no effect.
 
 ---
@@ -2343,8 +2348,8 @@ ledit.el, lmenu.el, lucid.el and old-whitespace.el.
 'dirtrack-debug-toggle', 'dynamic-completion-table',
 'easy-menu-precalculate-equivalent-keybindings',
 'epa-display-verify-result', 'epg-passphrase-callback-function',
-'erc-announced-server-name', 'erc-process',
-'erc-default-coding-system', 'erc-send-command', 'eshell-report-bug',
+'erc-announced-server-name', 'erc-default-coding-system',
+'erc-process', 'erc-send-command', 'eshell-report-bug',
 'eval-next-after-load', 'exchange-dot-and-mark', 'ffap-bug',
 'ffap-submit-bug', 'ffap-version', 'file-cache-choose-completion',
 'forward-point', 'generic-char-p', 'global-highlight-changes',
@@ -2391,7 +2396,7 @@ ledit.el, lmenu.el, lucid.el and old-whitespace.el.
 'semantic-toplevel-bovine-table', 'semanticdb-mode-hooks',
 'set-coding-priority', 'set-process-filter-multibyte',
 'shadows-compare-text-p', 'shell-dirtrack-toggle',
-'speedbar-update-speed', 'speedbar-navigating-speed', 't-mouse-mode',
+'speedbar-navigating-speed', 'speedbar-update-speed', 't-mouse-mode',
 'term-dynamic-simple-complete', 'tooltip-hook', 'tpu-have-ispell',
 'url-generate-unique-filename', 'url-temporary-directory',
 'vc-arch-command', 'vc-default-working-revision' (variable),
@@ -2413,6 +2418,8 @@ back in Emacs 23.1.  The affected functions are: 'make-obsolete',
 
 ** The variable 'keyboard-type' is obsolete and not dynamically scoped any more.
 
+** The 'values' variable is now obsolete.
+
 \f
 * Lisp Changes in Emacs 28.1
 
@@ -2449,13 +2456,13 @@ This variable holds a list of currently enabled global minor modes (as
 a list of symbols).
 
 +++
-** 'define-minor-mode'  now takes an :interactive argument.
+** 'define-minor-mode'  now takes an ':interactive' argument.
 This can be used for specifying which modes this minor mode is meant
 for, or to make the new minor mode non-interactive.  The default value
 is t.
 
 +++
-** 'define-derived-mode' now takes an :interactive argument.
+** 'define-derived-mode' now takes an ':interactive' argument.
 This can be used to control whether the defined mode is a command
 or not, and is useful when defining commands that aren't meant to be
 used by users directly.
@@ -2463,8 +2470,6 @@ used by users directly.
 ---
 ** The 'easymenu' library is now preloaded.
 
-** The 'values' variable is now obsolete.
-
 ---
 ** New variable 'indent-line-ignored-functions'.
 This allows modes to cycle through a set of indentation functions
@@ -2495,10 +2500,11 @@ When non-nil, then functions 'read-char-choice' and 'y-or-n-p' (respectively)
 use the function 'read-key' to read a character instead of using the minibuffer.
 
 ---
-** New variable 'use-short-answers' to use 'y-or-n-p' instead of 'yes-or-no-p'.
-This eliminates the need to define an alias that maps one to another
-in the init file.  The same variable also controls whether the
-function 'read-answer' accepts short answers.
+** New user option 'use-short-answers'.
+When non-nil, the function 'y-or-n-p' is used instead of
+'yes-or-no-p'.  This eliminates the need to define an alias that maps
+one to another in the init file.  The same user option also controls
+whether the function 'read-answer' accepts short answers.
 
 +++
 ** 'set-window-configuration' now takes an optional 'dont-set-frame'
@@ -2700,7 +2706,7 @@ menu handling.
 It is meant as an (experimental) aid for converting Emacs Lisp code
 to lexical binding, where dynamic (special) variables bound in one
 file can affect code in another.  For details, see the manual section
-"(Elisp) Converting to Lexical Binding".
+"(elisp) Converting to Lexical Binding".
 
 +++
 *** 'byte-recompile-directory' can now compile symlinked ".el" files.
diff --git a/lisp/net/tramp-fuse.el b/lisp/net/tramp-fuse.el
new file mode 100644 (file)
index 0000000..ec1db86
--- /dev/null
@@ -0,0 +1,205 @@
+;;; tramp-fuse.el --- Tramp access functions for FUSE mounts  -*- lexical-binding:t -*-
+
+;; Copyright (C) 2021 Free Software Foundation, Inc.
+
+;; Author: Michael Albinus <michael.albinus@gmx.de>
+;; Keywords: comm, processes
+;; Package: tramp
+
+;; This file is part of GNU Emacs.
+
+;; GNU Emacs is free software: you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; GNU Emacs is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; These are helper functions for FUSE file systems.
+
+;;; Code:
+
+(require 'tramp)
+
+;; File name primitives.
+
+(defun tramp-fuse-handle-delete-directory
+    (directory &optional recursive trash)
+  "Like `delete-directory' for Tramp files."
+  (with-parsed-tramp-file-name (expand-file-name directory) nil
+    (tramp-flush-directory-properties v localname)
+    (delete-directory (tramp-fuse-local-file-name directory) recursive trash)))
+
+(defun tramp-fuse-handle-delete-file (filename &optional trash)
+  "Like `delete-file' for Tramp files."
+  (with-parsed-tramp-file-name (expand-file-name filename) nil
+    (delete-file (tramp-fuse-local-file-name filename) trash)
+    (tramp-flush-file-properties v localname)))
+
+(defun tramp-fuse-handle-directory-files
+    (directory &optional full match nosort count)
+  "Like `directory-files' for Tramp files."
+  (unless (file-exists-p directory)
+    (tramp-compat-file-missing (tramp-dissect-file-name directory) directory))
+  (when (file-directory-p directory)
+    (setq directory (file-name-as-directory (expand-file-name directory)))
+    (with-parsed-tramp-file-name directory nil
+      (let ((result
+            (tramp-compat-directory-files
+             (tramp-fuse-local-file-name directory) full match nosort count)))
+       ;; Massage the result.
+       (when full
+         (let ((local (concat "^" (regexp-quote (tramp-fuse-mount-point v))))
+               (remote (directory-file-name
+                        (funcall
+                         (if (tramp-compat-file-name-quoted-p directory)
+                             #'tramp-compat-file-name-quote #'identity)
+                         (file-remote-p directory)))))
+           (setq result
+                 (mapcar
+                  (lambda (x) (replace-regexp-in-string local remote x))
+                  result))))
+       ;; Some storage systems do not return "." and "..".
+       (dolist (item '(".." "."))
+         (when (and (string-match-p (or match (regexp-quote item)) item)
+                    (not
+                     (member (if full (setq item (concat directory item)) item)
+                             result)))
+           (setq result (cons item result))))
+       ;; Return result.
+       (if nosort result (sort result #'string<))))))
+
+(defun tramp-fuse-handle-file-attributes (filename &optional id-format)
+  "Like `file-attributes' for Tramp files."
+  (with-parsed-tramp-file-name (expand-file-name filename) nil
+    (with-tramp-file-property
+        v localname (format "file-attributes-%s" id-format)
+      (file-attributes (tramp-fuse-local-file-name filename) id-format))))
+
+(defun tramp-fuse-handle-file-executable-p (filename)
+  "Like `file-executable-p' for Tramp files."
+  (with-parsed-tramp-file-name (expand-file-name filename) nil
+    (with-tramp-file-property v localname "file-executable-p"
+      (file-executable-p (tramp-fuse-local-file-name filename)))))
+
+(defun tramp-fuse-handle-file-name-all-completions (filename directory)
+  "Like `file-name-all-completions' for Tramp files."
+  (all-completions
+   filename
+   (delete-dups
+    (append
+     (file-name-all-completions
+      filename (tramp-fuse-local-file-name directory))
+     ;; Some storage systems do not return "." and "..".
+     (let (result)
+       (dolist (item '(".." ".") result)
+        (when (string-prefix-p filename item)
+          (catch 'match
+            (dolist (elt completion-regexp-list)
+              (unless (string-match-p elt item) (throw 'match nil)))
+            (setq result (cons (concat item "/") result))))))))))
+
+(defun tramp-fuse-handle-file-readable-p (filename)
+  "Like `file-readable-p' for Tramp files."
+  (with-parsed-tramp-file-name (expand-file-name filename) nil
+    (with-tramp-file-property v localname "file-readable-p"
+      (file-readable-p (tramp-fuse-local-file-name filename)))))
+
+;; This function isn't used.
+(defun tramp-fuse-handle-insert-directory
+    (filename switches &optional wildcard full-directory-p)
+  "Like `insert-directory' for Tramp files."
+  (insert-directory
+   (tramp-fuse-local-file-name filename) switches wildcard full-directory-p)
+  (goto-char (point-min))
+  (while (search-forward (tramp-fuse-local-file-name filename) nil 'noerror)
+    (replace-match filename)))
+
+(defun tramp-fuse-handle-make-directory (dir &optional parents)
+  "Like `make-directory' for Tramp files."
+  (with-parsed-tramp-file-name (expand-file-name dir) nil
+    (make-directory (tramp-fuse-local-file-name dir) parents)
+    ;; When PARENTS is non-nil, DIR could be a chain of non-existent
+    ;; directories a/b/c/...  Instead of checking, we simply flush the
+    ;; whole file cache.
+    (tramp-flush-file-properties v localname)
+    (tramp-flush-directory-properties
+     v (if parents "/" (file-name-directory localname)))))
+
+\f
+;; File name helper functions.
+
+(defun tramp-fuse-mount-spec (vec)
+  "Return local mount spec of VEC."
+  (if-let ((host (tramp-file-name-host vec))
+          (user (tramp-file-name-user vec)))
+      (format "%s@%s:/" user host)
+    (format "%s:/" host)))
+
+(defun tramp-fuse-mount-point (vec)
+  "Return local mount point of VEC."
+  (or (tramp-get-connection-property vec "mount-point" nil)
+      (expand-file-name
+       (concat
+       tramp-temp-name-prefix
+       (tramp-file-name-method vec) "."
+       (when (tramp-file-name-user vec)
+         (concat (tramp-file-name-user-domain vec) "@"))
+       (tramp-file-name-host-port vec))
+       (tramp-compat-temporary-file-directory))))
+
+(defun tramp-fuse-mounted-p (vec)
+  "Check, whether fuse volume determined by VEC is mounted."
+  (when (tramp-get-connection-process vec)
+    ;; We cannot use `with-connection-property', because we don't want
+    ;; to cache a nil result.
+    (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)
+          (tramp-set-connection-property
+           (tramp-get-connection-process vec) "mounted"
+           (when (string-match
+                 (format
+                   "^\\(%s\\)\\s-" (regexp-quote (tramp-fuse-mount-spec vec)))
+                 mount)
+             (match-string 1 mount)))))))
+
+(defun tramp-fuse-local-file-name (filename)
+  "Return local mount name of FILENAME."
+  (setq filename (tramp-compat-file-name-unquote (expand-file-name filename)))
+  (with-parsed-tramp-file-name filename nil
+    ;; As long as we call `tramp-*-maybe-open-connection' here,
+    ;; we cache the result.
+    (with-tramp-file-property v localname "local-file-name"
+      (funcall
+       (intern
+       (format "tramp-%s-maybe-open-connection" (tramp-file-name-method v)))
+       v)
+      (let ((quoted (tramp-compat-file-name-quoted-p localname))
+           (localname (tramp-compat-file-name-unquote localname)))
+       (funcall
+        (if quoted #'tramp-compat-file-name-quote #'identity)
+        (expand-file-name
+         (if (file-name-absolute-p localname)
+             (substring localname 1) localname)
+         (tramp-fuse-mount-point v)))))))
+
+(add-hook 'tramp-unload-hook
+         (lambda ()
+           (unload-feature 'tramp-fuse 'force)))
+
+(provide 'tramp-fuse)
+
+;;; tramp-fuse.el ends here
index a7f4c9be82cf0b72f8d64852fecf5bbc0c20c3c6..e6f9fe56ec089e7b72ce65db86fa763888659bb4 100644 (file)
@@ -35,8 +35,8 @@
 
 ;;; Code:
 
-(eval-when-compile (require 'cl-lib))
 (require 'tramp)
+(require 'tramp-fuse)
 
 ;;;###tramp-autoload
 (defconst tramp-rclone-method "rclone"
     ;; `byte-compiler-base-file-name' performed by default handler.
     (copy-directory . tramp-handle-copy-directory)
     (copy-file . tramp-rclone-handle-copy-file)
-    (delete-directory . tramp-rclone-handle-delete-directory)
-    (delete-file . tramp-rclone-handle-delete-file)
+    (delete-directory . tramp-fuse-handle-delete-directory)
+    (delete-file . tramp-fuse-handle-delete-file)
     ;; `diff-latest-backup-file' performed by default handler.
     (directory-file-name . tramp-handle-directory-file-name)
-    (directory-files . tramp-rclone-handle-directory-files)
+    (directory-files . tramp-fuse-handle-directory-files)
     (directory-files-and-attributes
      . tramp-handle-directory-files-and-attributes)
     (dired-compress-file . ignore)
     (expand-file-name . tramp-handle-expand-file-name)
     (file-accessible-directory-p . tramp-handle-file-accessible-directory-p)
     (file-acl . ignore)
-    (file-attributes . tramp-rclone-handle-file-attributes)
+    (file-attributes . tramp-fuse-handle-file-attributes)
     (file-directory-p . tramp-handle-file-directory-p)
     (file-equal-p . tramp-handle-file-equal-p)
-    (file-executable-p . tramp-rclone-handle-file-executable-p)
+    (file-executable-p . tramp-fuse-handle-file-executable-p)
     (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-modes . tramp-handle-file-modes)
-    (file-name-all-completions . tramp-rclone-handle-file-name-all-completions)
+    (file-name-all-completions . tramp-fuse-handle-file-name-all-completions)
     (file-name-as-directory . tramp-handle-file-name-as-directory)
     (file-name-case-insensitive-p . tramp-handle-file-name-case-insensitive-p)
     (file-name-completion . tramp-handle-file-name-completion)
     (file-notify-rm-watch . ignore)
     (file-notify-valid-p . ignore)
     (file-ownership-preserved-p . ignore)
-    (file-readable-p . tramp-rclone-handle-file-readable-p)
+    (file-readable-p . tramp-fuse-handle-file-readable-p)
     (file-regular-p . tramp-handle-file-regular-p)
     (file-remote-p . tramp-handle-file-remote-p)
     (file-selinux-context . tramp-handle-file-selinux-context)
     (insert-file-contents . tramp-handle-insert-file-contents)
     (load . tramp-handle-load)
     (make-auto-save-file-name . tramp-handle-make-auto-save-file-name)
-    (make-directory . tramp-rclone-handle-make-directory)
+    (make-directory . tramp-fuse-handle-make-directory)
     (make-directory-internal . ignore)
     (make-nearby-temp-file . tramp-handle-make-nearby-temp-file)
     (make-process . ignore)
@@ -277,86 +277,6 @@ file names."
      (list filename newname ok-if-already-exists keep-date
           preserve-uid-gid preserve-extended-attributes))))
 
-(defun tramp-rclone-handle-delete-directory
-    (directory &optional recursive trash)
-  "Like `delete-directory' for Tramp files."
-  (with-parsed-tramp-file-name (expand-file-name directory) nil
-    (tramp-flush-directory-properties v localname)
-    (delete-directory (tramp-rclone-local-file-name directory) recursive trash)))
-
-(defun tramp-rclone-handle-delete-file (filename &optional trash)
-  "Like `delete-file' for Tramp files."
-  (with-parsed-tramp-file-name (expand-file-name filename) nil
-    (delete-file (tramp-rclone-local-file-name filename) trash)
-    (tramp-flush-file-properties v localname)))
-
-(defun tramp-rclone-handle-directory-files
-    (directory &optional full match nosort count)
-  "Like `directory-files' for Tramp files."
-  (unless (file-exists-p directory)
-    (tramp-compat-file-missing (tramp-dissect-file-name directory) directory))
-  (when (file-directory-p directory)
-    (setq directory (file-name-as-directory (expand-file-name directory)))
-    (with-parsed-tramp-file-name directory nil
-      (let ((result
-            (tramp-compat-directory-files
-             (tramp-rclone-local-file-name directory) full match nosort count)))
-       ;; Massage the result.
-       (when full
-         (let ((local (concat "^" (regexp-quote (tramp-rclone-mount-point v))))
-               (remote (funcall (if (tramp-compat-file-name-quoted-p directory)
-                                    #'tramp-compat-file-name-quote #'identity)
-                                (file-remote-p directory))))
-           (setq result
-                 (mapcar
-                  (lambda (x) (replace-regexp-in-string local remote x))
-                  result))))
-       ;; Some storage systems do not return "." and "..".
-       (dolist (item '(".." "."))
-         (when (and (string-match-p (or match (regexp-quote item)) item)
-                    (not
-                     (member (if full (setq item (concat directory item)) item)
-                             result)))
-           (setq result (cons item result))))
-       ;; Return result.
-       (if nosort result (sort result #'string<))))))
-
-(defun tramp-rclone-handle-file-attributes (filename &optional id-format)
-  "Like `file-attributes' for Tramp files."
-  (with-parsed-tramp-file-name (expand-file-name filename) nil
-    (with-tramp-file-property
-       v localname (format "file-attributes-%s" id-format)
-      (file-attributes (tramp-rclone-local-file-name filename) id-format))))
-
-(defun tramp-rclone-handle-file-executable-p (filename)
-  "Like `file-executable-p' for Tramp files."
-  (with-parsed-tramp-file-name (expand-file-name filename) nil
-    (with-tramp-file-property v localname "file-executable-p"
-      (file-executable-p (tramp-rclone-local-file-name filename)))))
-
-(defun tramp-rclone-handle-file-name-all-completions (filename directory)
-  "Like `file-name-all-completions' for Tramp files."
-  (all-completions
-   filename
-   (delete-dups
-    (append
-     (file-name-all-completions
-      filename (tramp-rclone-local-file-name directory))
-     ;; Some storage systems do not return "." and "..".
-     (let (result)
-       (dolist (item '(".." ".") result)
-        (when (string-prefix-p filename item)
-          (catch 'match
-            (dolist (elt completion-regexp-list)
-              (unless (string-match-p elt item) (throw 'match nil)))
-            (setq result (cons (concat item "/") result))))))))))
-
-(defun tramp-rclone-handle-file-readable-p (filename)
-  "Like `file-readable-p' for Tramp files."
-  (with-parsed-tramp-file-name (expand-file-name filename) nil
-    (with-tramp-file-property v localname "file-readable-p"
-      (file-readable-p (tramp-rclone-local-file-name filename)))))
-
 (defun tramp-rclone-handle-file-system-info (filename)
   "Like `file-system-info' for Tramp files."
   (ignore-errors
@@ -384,36 +304,6 @@ file names."
          (when (and total free)
            (list total free (- total free))))))))
 
-(defun tramp-rclone-handle-insert-directory
-  (filename switches &optional wildcard full-directory-p)
-  "Like `insert-directory' for Tramp files."
-  (insert-directory
-   (tramp-rclone-local-file-name filename) switches wildcard full-directory-p)
-  (goto-char (point-min))
-  (while (search-forward (tramp-rclone-local-file-name filename) nil 'noerror)
-    (replace-match filename)))
-
-(defun tramp-rclone-handle-insert-file-contents
-  (filename &optional visit beg end replace)
-  "Like `insert-file-contents' for Tramp files."
-  (let ((result
-        (insert-file-contents
-         (tramp-rclone-local-file-name filename) visit beg end replace)))
-    (prog1
-       (list (expand-file-name filename) (cadr result))
-      (when visit (setq buffer-file-name filename)))))
-
-(defun tramp-rclone-handle-make-directory (dir &optional parents)
-  "Like `make-directory' for Tramp files."
-  (with-parsed-tramp-file-name (expand-file-name dir) nil
-    (make-directory (tramp-rclone-local-file-name dir) parents)
-    ;; When PARENTS is non-nil, DIR could be a chain of non-existent
-    ;; directories a/b/c/...  Instead of checking, we simply flush the
-    ;; whole file cache.
-    (tramp-flush-file-properties v localname)
-    (tramp-flush-directory-properties
-     v (if parents "/" (file-name-directory localname)))))
-
 (defun tramp-rclone-handle-rename-file
   (filename newname &optional ok-if-already-exists)
   "Like `rename-file' for Tramp files."
@@ -431,50 +321,6 @@ file names."
 \f
 ;; File name conversions.
 
-(defun tramp-rclone-mount-point (vec)
-  "Return local mount point of VEC."
-  (expand-file-name
-   (concat
-    tramp-temp-name-prefix (tramp-file-name-method vec)
-    "." (tramp-file-name-host vec))
-   (tramp-compat-temporary-file-directory)))
-
-(defun tramp-rclone-mounted-p (vec)
-  "Check, whether storage system determined by VEC is mounted."
-  (when (tramp-get-connection-process vec)
-    ;; We cannot use `with-connection-property', because we don't want
-    ;; to cache a nil result.
-    (or (tramp-get-connection-property
-        (tramp-get-connection-process vec) "mounted" nil)
-       (let* ((default-directory (tramp-compat-temporary-file-directory))
-              (mount (shell-command-to-string "mount -t fuse.rclone")))
-         (tramp-message vec 6 "%s" "mount -t fuse.rclone")
-         (tramp-message vec 6 "\n%s" mount)
-         (tramp-set-connection-property
-          (tramp-get-connection-process vec) "mounted"
-          (when (string-match
-                 (format
-                  "^\\(%s:\\S-*\\)" (regexp-quote (tramp-file-name-host vec)))
-                 mount)
-            (match-string 1 mount)))))))
-
-(defun tramp-rclone-local-file-name (filename)
-  "Return local mount name of FILENAME."
-  (setq filename (tramp-compat-file-name-unquote (expand-file-name filename)))
-  (with-parsed-tramp-file-name filename nil
-    ;; As long as we call `tramp-rclone-maybe-open-connection' here,
-    ;; we cache the result.
-    (with-tramp-file-property v localname "local-file-name"
-      (tramp-rclone-maybe-open-connection v)
-      (let ((quoted (tramp-compat-file-name-quoted-p localname))
-           (localname (tramp-compat-file-name-unquote localname)))
-       (funcall
-        (if quoted #'tramp-compat-file-name-quote #'identity)
-        (expand-file-name
-         (if (file-name-absolute-p localname)
-             (substring localname 1) localname)
-         (tramp-rclone-mount-point v)))))))
-
 (defun tramp-rclone-remote-file-name (filename)
   "Return FILENAME as used in the `rclone' command."
   (setq filename (tramp-compat-file-name-unquote (expand-file-name filename)))
@@ -487,7 +333,7 @@ file names."
          ;; TODO: This shall be handled by `expand-file-name'.
          (setq localname
                (replace-regexp-in-string "^\\." "" (or localname "")))
-         (format "%s%s" (tramp-rclone-mounted-p v) localname)))
+         (format "%s%s" (tramp-fuse-mounted-p v) localname)))
     ;; It is a local file name.
     filename))
 
@@ -517,20 +363,18 @@ connection if a previous connection has died for some reason."
          (tramp-set-connection-local-variables vec)))
 
       ;; Create directory.
-      (unless (file-directory-p (tramp-rclone-mount-point vec))
-       (make-directory (tramp-rclone-mount-point vec) 'parents))
+      (unless (file-directory-p (tramp-fuse-mount-point vec))
+       (make-directory (tramp-fuse-mount-point vec) 'parents))
 
       ;; Mount.  This command does not return, so we use 0 as
       ;; DESTINATION of `tramp-call-process'.
-      (unless (tramp-rclone-mounted-p vec)
+      (unless (tramp-fuse-mounted-p vec)
        (apply
         #'tramp-call-process
         vec tramp-rclone-program nil 0 nil
-        (delq nil
-              `("mount" ,(concat host ":/")
-                ,(tramp-rclone-mount-point vec)
-                ;; This could be nil.
-                ,@(tramp-get-method-parameter vec 'tramp-mount-args))))
+        "mount" (tramp-fuse-mount-spec vec)
+        (tramp-fuse-mount-point vec)
+        (tramp-get-method-parameter vec 'tramp-mount-args))
        (while (not (file-exists-p (tramp-make-tramp-file-name vec 'noloc)))
          (tramp-cleanup-connection vec 'keep-debug 'keep-password))
 
index 5730199407453cb910d405e964ee8efa9859b735..dac83b82a82d3a2d9e34c4289ee2e9ac753b855c 100644 (file)
@@ -2397,7 +2397,7 @@ The method used must be an out-of-band method."
                       (append
                        copy-args
                        (let ((y (mapcar (lambda (z) (format-spec z spec)) x)))
-                         (if (member "" y) '(" ") y))))))
+                         (unless (member "" y) y))))))
 
              copy-env
              (delq
@@ -2416,7 +2416,7 @@ The method used must be an out-of-band method."
                (append
                 remote-copy-args
                 (let ((y (mapcar (lambda (z) (format-spec z spec)) x)))
-                  (if (member "" y) '(" ") y)))))
+                  (unless (member "" y) y)))))
 
        ;; Check for local copy program.
        (unless (executable-find copy-program)
diff --git a/lisp/net/tramp-sshfs.el b/lisp/net/tramp-sshfs.el
new file mode 100644 (file)
index 0000000..feb64b8
--- /dev/null
@@ -0,0 +1,318 @@
+;;; tramp-sshfs.el --- Tramp access functions via sshfs  -*- lexical-binding:t -*-
+
+;; Copyright (C) 2021 Free Software Foundation, Inc.
+
+;; Author: Michael Albinus <michael.albinus@gmx.de>
+;; Keywords: comm, processes
+;; Package: tramp
+
+;; This file is part of GNU Emacs.
+
+;; GNU Emacs is free software: you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; GNU Emacs is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; sshfs is a program to mount a virtual file system, based on an sftp
+;; connection.  Tramp uses its mount utility to access files and
+;; directories there.
+
+;; A remote file under sshfs control has the form
+;; "/sshfs:user@host#port:/path/to/file".  User name and port number
+;; are optional.
+
+;;; Code:
+
+(require 'tramp)
+(require 'tramp-fuse)
+
+;;;###tramp-autoload
+(defconst tramp-sshfs-method "sshfs"
+  "Tramp method for sshfs mounts.")
+
+;;;###tramp-autoload
+(defcustom tramp-sshfs-program "sshfs"
+  "The sshfs mount command."
+  :group 'tramp
+  :version "28.1"
+  :type 'string)
+
+;;;###tramp-autoload
+(tramp--with-startup
+ (add-to-list 'tramp-methods
+             `(,tramp-sshfs-method
+               (tramp-mount-args
+                (("-p" "%p")
+                 ("-o" "idmap=user,reconnect")))))
+
+ (tramp-set-completion-function
+  tramp-sshfs-method tramp-completion-function-alist-ssh))
+
+\f
+;; New handlers should be added here.
+;;;###tramp-autoload
+(defconst tramp-sshfs-file-name-handler-alist
+  '((access-file . tramp-handle-access-file)
+    (add-name-to-file . tramp-handle-add-name-to-file)
+    ;; `byte-compiler-base-file-name' performed by default handler.
+    (copy-directory . tramp-handle-copy-directory)
+    (copy-file . tramp-sshfs-handle-copy-file)
+    (delete-directory . tramp-fuse-handle-delete-directory)
+    (delete-file . tramp-fuse-handle-delete-file)
+    ;; `diff-latest-backup-file' performed by default handler.
+    (directory-file-name . tramp-handle-directory-file-name)
+    (directory-files . tramp-fuse-handle-directory-files)
+    (directory-files-and-attributes
+     . tramp-handle-directory-files-and-attributes)
+    (dired-compress-file . ignore)
+    (dired-uncache . tramp-handle-dired-uncache)
+;;     (exec-path . ignore)
+    (expand-file-name . tramp-handle-expand-file-name)
+    (file-accessible-directory-p . tramp-handle-file-accessible-directory-p)
+    (file-acl . ignore)
+    (file-attributes . tramp-fuse-handle-file-attributes)
+    (file-directory-p . tramp-handle-file-directory-p)
+    (file-equal-p . tramp-handle-file-equal-p)
+    (file-executable-p . tramp-fuse-handle-file-executable-p)
+    (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-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)
+    (file-name-case-insensitive-p . tramp-handle-file-name-case-insensitive-p)
+    (file-name-completion . tramp-handle-file-name-completion)
+    (file-name-directory . tramp-handle-file-name-directory)
+    (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 . ignore)
+    (file-notify-rm-watch . ignore)
+    (file-notify-valid-p . ignore)
+    (file-ownership-preserved-p . ignore)
+    (file-readable-p . tramp-fuse-handle-file-readable-p)
+    (file-regular-p . tramp-handle-file-regular-p)
+    (file-remote-p . tramp-handle-file-remote-p)
+    (file-selinux-context . tramp-handle-file-selinux-context)
+    (file-symlink-p . tramp-handle-file-symlink-p)
+    (file-system-info . tramp-sshfs-handle-file-system-info)
+    (file-truename . tramp-handle-file-truename)
+    (file-writable-p . tramp-handle-file-writable-p)
+    (find-backup-file-name . tramp-handle-find-backup-file-name)
+    ;; `get-file-buffer' performed by default handler.
+    (insert-directory . tramp-handle-insert-directory)
+    (insert-file-contents . tramp-sshfs-handle-insert-file-contents)
+    (load . tramp-handle-load)
+    (make-auto-save-file-name . tramp-handle-make-auto-save-file-name)
+    (make-directory . tramp-fuse-handle-make-directory)
+    (make-directory-internal . ignore)
+    (make-nearby-temp-file . tramp-handle-make-nearby-temp-file)
+;;     (make-process . ignore)
+    (make-symbolic-link . tramp-handle-make-symbolic-link)
+;;     (process-file . ignore)
+    (rename-file . tramp-sshfs-handle-rename-file)
+    (set-file-acl . ignore)
+    (set-file-modes . ignore)
+    (set-file-selinux-context . ignore)
+    (set-file-times . ignore)
+    (set-visited-file-modtime . tramp-handle-set-visited-file-modtime)
+;;     (shell-command . ignore)
+;;     (start-file-process . ignore)
+    (substitute-in-file-name . tramp-handle-substitute-in-file-name)
+    (temporary-file-directory . tramp-handle-temporary-file-directory)
+;;     (tramp-get-remote-gid . ignore)
+;;     (tramp-get-remote-uid . ignore)
+;;     (tramp-set-file-uid-gid . ignore)
+    (unhandled-file-name-directory . ignore)
+    (vc-registered . ignore)
+    (verify-visited-file-modtime . tramp-handle-verify-visited-file-modtime)
+    (write-region . tramp-sshfs-handle-write-region))
+"Alist of handler functions for Tramp SSHFS method.
+Operations not mentioned here will be handled by the default Emacs primitives.")
+
+;; It must be a `defsubst' in order to push the whole code into
+;; tramp-loaddefs.el.  Otherwise, there would be recursive autoloading.
+;;;###tramp-autoload
+(defsubst tramp-sshfs-file-name-p (filename)
+  "Check if it's a FILENAME for sshfs."
+  (and (tramp-tramp-file-p filename)
+       (string= (tramp-file-name-method (tramp-dissect-file-name filename))
+               tramp-sshfs-method)))
+
+;;;###tramp-autoload
+(defun tramp-sshfs-file-name-handler (operation &rest args)
+  "Invoke the sshfs handler for OPERATION and ARGS.
+First arg specifies the OPERATION, second arg is a list of
+arguments to pass to the OPERATION."
+  (if-let ((fn (assoc operation tramp-sshfs-file-name-handler-alist)))
+      (save-match-data (apply (cdr fn) args))
+    (tramp-run-real-handler operation args)))
+
+;;;###tramp-autoload
+(tramp--with-startup
+ (tramp-register-foreign-file-name-handler
+  #'tramp-sshfs-file-name-p #'tramp-sshfs-file-name-handler))
+
+\f
+;; File name primitives.
+
+(defun tramp-sshfs-handle-copy-file
+    (filename newname &optional ok-if-already-exists keep-date
+     preserve-uid-gid preserve-extended-attributes)
+  "Like `copy-file' for Tramp files."
+  (setq filename (expand-file-name filename)
+       newname (expand-file-name newname))
+  (if (file-directory-p filename)
+      (copy-directory filename newname keep-date t)
+    (copy-file
+     (if (tramp-sshfs-file-name-p filename)
+        (tramp-fuse-local-file-name filename) filename)
+     (if (tramp-sshfs-file-name-p newname)
+        (tramp-fuse-local-file-name newname) newname)
+     ok-if-already-exists keep-date
+     preserve-uid-gid preserve-extended-attributes)
+    (when (tramp-sshfs-file-name-p newname)
+      (with-parsed-tramp-file-name newname nil
+       (tramp-flush-file-properties v localname)))))
+
+(defun tramp-sshfs-handle-file-system-info (filename)
+  "Like `file-system-info' for Tramp files."
+  ;;`file-system-info' exists since Emacs 27.1.
+  (tramp-compat-funcall 'file-system-info (tramp-fuse-local-file-name filename)))
+
+(defun tramp-sshfs-handle-insert-file-contents
+  (filename &optional visit beg end replace)
+  "Like `insert-file-contents' for Tramp files."
+  (let ((result
+        (insert-file-contents
+         (tramp-fuse-local-file-name filename) visit beg end replace)))
+    (when visit (setq buffer-file-name filename))
+    (cons (expand-file-name filename) (cdr result))))
+
+(defun tramp-sshfs-handle-rename-file
+    (filename newname &optional ok-if-already-exists)
+  "Like `rename-file' for Tramp files."
+  (setq filename (expand-file-name filename)
+       newname (expand-file-name newname))
+  (rename-file
+   (if (tramp-sshfs-file-name-p filename)
+       (tramp-fuse-local-file-name filename) filename)
+   (if (tramp-sshfs-file-name-p newname)
+       (tramp-fuse-local-file-name newname) newname)
+   ok-if-already-exists)
+  (when (tramp-sshfs-file-name-p filename)
+    (with-parsed-tramp-file-name filename nil
+      (tramp-flush-file-properties v localname)))
+  (when (tramp-sshfs-file-name-p newname)
+    (with-parsed-tramp-file-name newname nil
+      (tramp-flush-file-properties v localname))))
+
+(defun tramp-sshfs-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
+    (when (and mustbenew (file-exists-p filename)
+              (or (eq mustbenew 'excl)
+                  (not
+                   (y-or-n-p
+                    (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 lockname)
+    (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)))
+
+\f
+;; File name conversions.
+
+(defun tramp-sshfs-maybe-open-connection (vec)
+  "Maybe open a connection VEC.
+Does not do anything if a connection is already open, but re-opens the
+connection if a previous connection has died for some reason."
+  ;; During completion, don't reopen a new connection.
+  (unless (tramp-connectable-p vec)
+    (throw 'non-essential 'non-essential))
+
+  ;; We need a process bound to the connection buffer.  Therefore, we
+  ;; create a dummy process.  Maybe there is a better solution?
+  (unless (get-buffer-process (tramp-get-connection-buffer vec))
+    (let ((p (make-network-process
+             :name (tramp-get-connection-name vec)
+             :buffer (tramp-get-connection-buffer vec)
+             :server t :host 'local :service t :noquery t)))
+      (process-put p 'vector vec)
+      (set-process-query-on-exit-flag p nil)
+
+      ;; Set connection-local variables.
+      (tramp-set-connection-local-variables vec)
+
+      ;; Create directory.
+      (unless (file-directory-p (tramp-fuse-mount-point vec))
+       (make-directory (tramp-fuse-mount-point vec) 'parents))
+
+      (unless
+         (or (tramp-fuse-mounted-p vec)
+             (let* ((port (or (tramp-file-name-port vec) ""))
+                    (spec (format-spec-make ?p port))
+                    mount-args
+                    (mount-args
+                     (dolist
+                         (x
+                          (tramp-get-method-parameter vec 'tramp-mount-args)
+                          mount-args)
+                       (setq mount-args
+                             (append
+                              mount-args
+                              (let ((y (mapcar
+                                        (lambda (z) (format-spec z spec))
+                                        x)))
+                                (unless (member "" y) y)))))))
+               (with-temp-buffer
+                 (zerop
+                  (apply
+                   #'tramp-call-process
+                   vec tramp-sshfs-program nil t nil
+                   (tramp-fuse-mount-spec vec)
+                   (tramp-fuse-mount-point vec) mount-args))))
+         (tramp-error
+          vec 'file-error "Error mounting %s" (tramp-fuse-mount-spec vec))))
+
+      ;; Mark it as connected.
+      (tramp-set-connection-property
+       (tramp-get-connection-process vec) "connected" t)))
+
+  ;; In `tramp-check-cached-permissions', the connection properties
+  ;; "{uid,gid}-{integer,string}" are used.  We set them to proper values.
+  (with-tramp-connection-property
+      vec "uid-integer" (tramp-get-local-uid 'integer))
+  (with-tramp-connection-property
+      vec "gid-integer" (tramp-get-local-gid 'integer))
+  (with-tramp-connection-property
+      vec "uid-string" (tramp-get-local-uid 'string))
+  (with-tramp-connection-property
+      vec "gid-string" (tramp-get-local-gid 'string)))
+
+(add-hook 'tramp-unload-hook
+         (lambda ()
+           (unload-feature 'tramp-sshfs 'force)))
+
+(provide 'tramp-sshfs)
+
+;;; tramp-sshfs.el ends here
index 14d5f8c3b6b24f8b0719ad6958888f324e497966..47d62f38045d6fc62194573ec58fd68fae52545e 100644 (file)
@@ -5447,11 +5447,6 @@ BODY is the backend specific code."
 ;;   strange when doing zerop, we should kill the process and start
 ;;   again.  (Greg Stark)
 ;;
-;; * I was wondering if it would be possible to use tramp even if I'm
-;;   actually using sshfs.  But when I launch a command I would like
-;;   to get it executed on the remote machine where the files really
-;;   are.  (Andrea Crotti)
-;;
 ;; * Run emerge on two remote files.  Bug is described here:
 ;;   <https://www.mail-archive.com/tramp-devel@nongnu.org/msg01041.html>.
 ;;   (Bug#6850)
index 016b4d3c8f0cce521ff58056fbe2d299de48fb96..d9a8065e723583962a895565cee562d3d51bcfe9 100644 (file)
@@ -2824,9 +2824,10 @@ This tests also `file-directory-p' and `file-accessible-directory-p'."
          (should (file-exists-p (expand-file-name "bla" tmp-name2)))
          (should-error
           (delete-directory tmp-name1 nil 'trash)
-          ;; tramp-rclone.el calls the local `delete-directory'.
-          ;; This raises another error.
-          :type (if (tramp--test-rclone-p) 'error 'file-error))
+          ;; 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))
          (delete-directory tmp-name1 'recursive 'trash)
          (should-not (file-directory-p tmp-name1))
          (should
@@ -3254,8 +3255,8 @@ This tests also `file-directory-p' and `file-accessible-directory-p'."
        (ignore-errors (delete-directory tmp-name1 'recursive))))))
 
 ;; Method "smb" supports `make-symbolic-link' only if the remote host
-;; has CIFS capabilities.  tramp-adb.el, tramp-gvfs.el and
-;; tramp-rclone.el do not support symbolic links at all.
+;; has CIFS capabilities.  tramp-adb.el, tramp-gvfs.el, tramp-rclone.el
+;; and tramp-sshfs.el do not support symbolic links at all.
 (defmacro tramp--test-ignore-make-symbolic-link-error (&rest body)
   "Run BODY, ignoring \"make-symbolic-link not supported\" file error."
   (declare (indent defun) (debug (body)))
@@ -5819,6 +5820,11 @@ Additionally, ls does not support \"--dired\"."
        "^\\(afp\\|davs?\\|smb\\)$"
        (file-remote-p tramp-test-temporary-file-directory 'method))))
 
+(defun tramp--test-sshfs-p ()
+  "Check, whether the remote host is offered by sshfs.
+This requires restrictions of file name syntax."
+  (tramp-sshfs-file-name-p tramp-test-temporary-file-directory))
+
 (defun tramp--test-sudoedit-p ()
   "Check, whether the sudoedit method is used."
   (tramp-sudoedit-file-name-p tramp-test-temporary-file-directory))
@@ -6761,7 +6767,6 @@ If INTERACTIVE is non-nil, the tests are run interactively."
 ;; * Fix `tramp-test06-directory-file-name' for `ftp'.
 ;; * Implement `tramp-test31-interrupt-process' for `adb' and for
 ;;   direct async processes.
-;; * Fix `tramp-test44-threads'.
 
 (provide 'tramp-tests)