Zsh Mailing List Archive
Messages sorted by: Reverse Date, Date, Thread, Author

PATCH: sshfs user-side automount


This certainly won't go in until the dust has settled, both on the
release and the proposed change, but I'd quite like to add some
user-side automount capability, in particular for the cdr system that I
use quite a lot (though you can slot it in anywhere, including your own
function front-end to cd).

The point is, in case it's not obvious:  I use sshfs quite a lot for
access to remote files these days, and as it's all done in user space
there's no automatic way of getting an ssh directory mounted.  This is
only really useful if you have seamless sshfs mounting, i.e. an ssh
agent in the background.

Obviously, please let me know whatever you don't like about it.  It'll
probably grow, at least for my own purposes.

It's documented as a style for cdr, which is how it's currently exposed,
but if it goes in, chpwd_check_mount probably needs a separate entry in
that section.

The syntax is currently a bit of a mouthful, but it's hard to make it
neater without also reducing flexibility.


commit 6b1b417fb0da581f8186fbfaadfe8a8b07dc2b8c
Author: Peter Stephenson <p.stephenson@xxxxxxxxxxx>
Date:   Mon Feb 17 15:16:29 2020 +0000

    Not yet posted: Add chpwd_check_mount to handle automatic sshfs mounting.
    Use this in cdr.

diff --git a/Doc/Zsh/contrib.yo b/Doc/Zsh/contrib.yo
index c6bf745b7..252487fbb 100644
--- a/Doc/Zsh/contrib.yo
+++ b/Doc/Zsh/contrib.yo
@@ -386,6 +386,7 @@ findex(_cdr)
 sect(Remembering Recent Directories)
 The function tt(cdr) allows you to change the working directory to a
@@ -615,6 +616,39 @@ directory stack is completely separate from the list of files saved
 by the mechanism used in this file there is no obvious reason to do
+This style takes pairs of arguments.  Each pair consists of a local
+directory path var(local-path), and a remote path for the form
+var(method):var(mount-spec).  If a directory to which tt(cdr) needs
+to change does not exit, the list of var(local-path)s is checked
+to see if one of them is a prefix of the target directory.
+If one is found, the remote path is used to provide the directory.
+Currently the only var(method) handled is tt(sshfs), for which
+var(mount-spec) should be a standard sshfs remote directory reference,
+so var(mount-spec) may be e.g. tt(user@remotehost:/path/to/dir).
+This causes tt(sshfs) to be invoked with the option
+tt(workaround=rename); this is likely to become configurable in future.
+This is most useful when there is an ssh agent running in the
+background, so that the mount action is invisible to the user.
+The source path is not rendered canonical (symbolic links are not
+removed), so it may be necessary to have multiple local directories
+with the same remote path.  For this purpose, the local directory
+path may take the form var(check-directory):var(mount-point).
+For example, var(check-directory) may be tt(/home/mydir/doc),
+a symbolic link to a directory under the var(mount-point)
+Note that recursive mounting is also not currently handled, so
+the var(local-path) directories should not be subdirectories of one
+This style is provided by the function tt(chpwd_check_mount), which
+can be run separately.  It takes a single argument, the path
+to check.  This does not have to be a directory as prefixes
+are examined.
 subsect(Use with dynamic directory naming)
diff --git a/Functions/Chpwd/cdr b/Functions/Chpwd/cdr
index 4bed88b13..7fc4345f6 100644
--- a/Functions/Chpwd/cdr
+++ b/Functions/Chpwd/cdr
@@ -268,6 +268,8 @@ else
   elif [[ $# -ne 1 || $1 != <-> ]]; then
     if zstyle -t ':chpwd:' recent-dirs-default; then
+      # See below.
+      (( $# == 1 )) && chpwd_check_mount $1
       cd "$@"
@@ -329,6 +331,12 @@ if (( $1 > ${#reply} )); then
+# This checks if the directory is available or can be mounted,
+# and returns failure if not, but we'll simply pass back the
+# (in context, more obvious) failure from pushd or cd.
+autoload -Uz chpwd_check_mount
+chwpd_check_mount $dir
 if zstyle -t ':chpwd:' recent-dirs-pushd; then
   pushd -- $dir
diff --git a/Functions/Chpwd/chpwd_check_mount b/Functions/Chpwd/chpwd_check_mount
new file mode 100644
index 000000000..adc0bc65f
--- /dev/null
+++ b/Functions/Chpwd/chpwd_check_mount
@@ -0,0 +1,107 @@
+# This function is designed to do a poor person's automount in
+# the user space, typically of a sshfs file system as this is
+# entirely controlled by the user.
+# Return 0 if the path is available, possibly after mounting, 1 if
+# it is still not available at the end of the function.
+# The style mount-path in context :chpwd: is set to an array of pairs of
+# paths and URL-style references looking like
+# /local/dir method:path-to-dir
+# If the argument to the function is a path that doesn't exist, the
+# system checks to see if the path is under /local/dir.  If, so the
+# other element of the pair is examined.  If "method" is a known method
+# for moutning the remote path path-to-dir the path, it is mounted and
+# the function returns success.
+# Currently the only method knonwn is sshfs, in which case path-to-dir
+# is a standard ssh path e.g. "rhost:/home/mydir".  Mounting is done
+# simply: "sshfs -o workaround=rename path-to_dir /local/dir".  This
+# may become more configurable in future.  It is assumed the user
+# has an appropriate ssh agent running, else the call may prompt for
+# login info at an unexpected place.
+# Does not currently handle (the unusual case of) recursive mounts,
+# i.e. /local/dir and /local/dir/under/that does not handle the second case.
+# The return status is 0 if the path exists; 1 if it does not exist
+# (even if a mount was made in an attempt to provide it); 2 if some
+# condition other than a missing directory was found, in particular
+# bad zstyle configuration or an sshfs failure.
+emulate -L zsh
+setopt extendedglob cbases
+# Nothing to if path exists.
+# We'll allow the path to be something other than a directory as we
+# are in any case going to check prefixes.
+if [[ -e $1 ]]; then
+  if [[ -d $1 ]]; then
+    # As this may be the mount point itself, we'll assume it
+    # should be non-empty, though we don't know for sure.
+    local -a files
+    files=($1/*(DN))
+    (( ${#files} )) && return 0
+  else
+    # Not a directory, so assume everything is OK.
+    return 0
+  fi
+local -a match mbegin mend
+local dir
+if [[ $1 = /* ]]; then
+  dir="$1"
+  # Hmm... We can't use (:a) or (:A) as the path doesn't yet exist in
+  # the file system.  We'll just bang it on the end of $PWD..
+  # It's not clear whether we should remove symbolic links from
+  # $PWD as we don't know whether the user has or has not rationalised
+  # the zstyle accordingly.
+  dir="$PWD/$1"
+  # Rationalise ..'s --- a bit naive, but hopefully good enough...
+  while [[ $dir = (#b)(*/)([^/]##)/..(/*|) ]]; do
+    dir="$match[1]${match[3]##/}"
+  done
+  dir=${dir%%/#}
+local -a mpath
+# If no directories we could mount, fail silently
+zstyle -a ':chpwd:' mount-path mpath || return 1
+local locdir remote mpoint
+for locdir remote in $mpath; do
+  # To be clever here we would look for the shortest matching path
+  # and work our way down.
+  if [[ $dir = ${locdir%%:*}(|/*) ]]; then
+    mpoint=${locdir#*:}
+    case $remote in
+      ((#b)sshfs:(*))
+      if ! sshfs -o workaround=rename $match[1] $mpoint; then
+        # Bad mount: it probably complained, but let's be clear.
+	print "$0: attempt to mount $remote for $locdir failed." >&2
+	return 2
+      fi
+      # Success / failure depending on whether path is now provided.
+      # To be clever (see above) could loop further.
+      [[ -e $dir ]]
+      ;;
+      (*)
+      # Incorrect configuration, so return noisily.
+      print "$0: method ${remote%%:*} not handled" >&2
+      return 2
+      ;;
+    esac
+  fi
+# Final test in case we found something that might be a mount point.
+# If we couldn't mount it but this is the directory being referred to,
+# assume it's OK.
+[[ -e $dir ]]

Messages sorted by: Reverse Date, Date, Thread, Author