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

Re: PATCH: sshfs user-side automount

Here's an updated version I intend to commit if there are not further comments.

I've taken on board most of Daniel's comments, thanks.

(Lines are marked as "preformatted" in Evolution --- I hope that removes
hard spaces.)


commit e8e950151b501011d0041c1955a55e151c564fde
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 0909cd4f5..55285657a 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,8 +616,77 @@ 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 is provided by the function tt(chpwd_check_mount), which
+is documented below in the subsection `Automatic sshfs mounting'.  This
+allows user-side automounting of directories provide by tt(sshfs).
+tt(cdr) already calls this function, so setting the style is
+sufficient to enable this behaviour.
+subsect(Automatic sshfs mounting)
+The function tt(chpwd_check_mount) is provided to allow user-side
+automounting of directories by the tt(sshfs) command, a widely used
+utility that can provide a user with no system privileges direct file
+access to files visible via an tt(ssh) connection.
+The function takes a single argument, the path to check.  This does not
+have to be a directory as prefixes are examined.
+This function is called by tt(cdr) so that this ability is provided
+seamlessly in this case.  However, the function may also be called
+directly, for example in a user's function front-end to the tt(cd)
+To enable this feature, the tt(mount-path) style should be set in the
+tt(:chpwd:) context.  The 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).
+By default this causes tt(sshfs) to be invoked with the option
+tt(workaround=rename); to change this, set the style tt(command-args)
+in the context `tt(:chpwd:sshfs:)'.  The value should be an
+array of command arguments preceding the remote directory and mount
+point starting with the command itself, typically `tt(sshfs)', so
+the default behaviour is equivalent to the following value:
+example(zstyle ':chwpd:sshfs:' command-path sshfs -o workaround=rename)
+This is most useful when there is an ssh agent running in the
+background, so that the mount action is invisible to the user.
+See the manual page for manref(ssh-agent)(1) if you are unfamiliar with
+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
+Here is a full style definition using the example above.  If
+tt(cdr) attempts to switch to a non-existent directory below
+tt(/home/mydir/doc) (or to tt(/home/mydir/doc) itself, if that exists
+but is empy), an attempt will be made to mount files from
+tt(myuser@remhost:/home/myuser) via tt(sshfs) at the mount point
+tt(/home/mydir/mnt/remhost).  The resulting tt(cd) succeeds if the
+originally requested directory is made available by this means.
+example(zstyle :chpwd: mount-point \
+  /home/mydir/doc:/home/mydir/mnt/remhost \
+  sshfs:myuser@remhost:/home/myuser)
 subsect(Use with dynamic directory naming)
 It is possible to refer to recent directories using the dynamic directory
diff --git a/Functions/Chpwd/cdr b/Functions/Chpwd/cdr
index 4bed88b13..d1818cd7d 100644
--- a/Functions/Chpwd/cdr
+++ b/Functions/Chpwd/cdr
@@ -268,6 +268,11 @@ else
   elif [[ $# -ne 1 || $1 != <-> ]]; then
     if zstyle -t ':chpwd:' recent-dirs-default; then
+      if (( $# == 1 )); then
+	# See below.
+	autoload -Uz chpwd_check_mount
+	chpwd_check_mount $1
+      fi
       cd "$@"
@@ -329,6 +334,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
+chpwd_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..371fa181a
--- /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.
+# 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.
+# 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 mounting 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.
+emulate -L zsh
+setopt extendedglob cbases
+local -a mpath
+# If no directories we could mount, fail silently;
+# no point even looking at the argument.
+zstyle -a ':chpwd:' mount-path mpath || return 1
+# 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 [[ -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
+  # Glob dots; expand to empty if no matches; don't bother globbing
+  # more than one file, we just want to know if there are any
+  files=($1/*(DNY1))
+  (( ${#files} )) && return 0
+elif [[ -e $1 ]]; then
+  # Not a directory, so assume everything is OK.
+  return 0
+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 locdir remote mpoint cmdargs
+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:(*))
+      zstyle -a ':chpwd:sshfs:' command-args cmdargs ||
+	  cmdargs=(sshfs -o workaround=rename)
+      if ! "${cmdargs[@]}" "${match[1]}" "$mpoint"; then
+        # Bad mount: it probably complained, but let's be clear.
+	print -r -- "$0: attempt to mount $remote for $locdir failed." >&2
+	return 2
+      fi
+      # Break for success / failure depending on whether path is now provided.
+      # To be clever (see above) could loop further.
+      break
+      ;;
+      (*)
+      # Incorrect configuration, so return noisily.
+      print -r -- "$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