- From e442d43bb8577e9d85b4a9e72436b47a049105a4 Mon Sep 17 00:00:00 2001
- From: Roland Mainz <roland.mainz@nrubsig.org>
- Date: Mon, 9 Oct 2023 18:33:23 +0200
- Subject: [PATCH 2/2] Adding initial Cygin README and devel/utility scripts
- cygwin/README.txt
- cygwin/devel/msnfs41client.bash
- cygwin/utils/mount_sshnfs/README.txt
- cygwin/utils/mount_sshnfs/mount_sshnfs.ksh
- cygwin/utils/sshnfs/README.txt
- cygwin/utils/sshnfs/sshnfs.ksh
- Signed-off-by: Cedric Blancher <cedric.blancher@gmail.com>
- ---
- cygwin/README.txt | 69 ++
- cygwin/devel/msnfs41client.bash | 287 +++++++
- cygwin/utils/mount_sshnfs/README.txt | 21 +
- cygwin/utils/mount_sshnfs/mount_sshnfs.ksh | 905 +++++++++++++++++++++
- cygwin/utils/sshnfs/README.txt | 21 +
- cygwin/utils/sshnfs/sshnfs.ksh | 537 ++++++++++++
- 6 files changed, 1840 insertions(+)
- create mode 100644 cygwin/README.txt
- create mode 100644 cygwin/devel/msnfs41client.bash
- create mode 100644 cygwin/utils/mount_sshnfs/README.txt
- create mode 100644 cygwin/utils/mount_sshnfs/mount_sshnfs.ksh
- create mode 100644 cygwin/utils/sshnfs/README.txt
- create mode 100644 cygwin/utils/sshnfs/sshnfs.ksh
- diff --git a/cygwin/README.txt b/cygwin/README.txt
- new file mode 100644
- index 0000000..974e7b3
- --- /dev/null
- +++ b/cygwin/README.txt
- @@ -0,0 +1,69 @@
- +# ms-nfs41-client/cygwin/README.txt
- +
- +#### Building ms-nfs41-client using Cygwin:
- +** Required software:
- +- Visual Studio 19
- +- WDK for Windows 10, version 2004, from
- + https://go.microsoft.com/fwlink/?linkid=2128854
- +
- +** Building the project using GUI:
- +1. Start Visual Studio 19
- +2. Load the project file "build.vc19/nfs41-client.sln"
- +3. Select menu item "Build/Build solution" as "Debug/x64"
- +4. Select menu item "Build/Build solution" as "Release/x64"
- +
- +** Build the project using Cygwin command line (bash/ksh93):
- +export PATH+=":/cygdrive/c/Program Files (x86)/Microsoft Visual Studio/2019/Community/MSBuild/Current/Bin/"
- +git clone https://github.com/kofemann/ms-nfs41-client.git
- +cd ms-nfs41-client
- +MSBuild.exe build.vc19/nfs41-client.sln -t:Build -p:Configuration=Debug -p:Platform=x64
- +MSBuild.exe build.vc19/nfs41-client.sln -t:Build -p:Configuration=Release -p:Platform=x64
- +
- +** Make release blob:
- +mkdir dist
- +cd dist/
- +cp ../build.vc19/x64/Debug/nfsd.exe nfsd_debug.exe
- +cp ../build.vc19/x64/Release/* .
- +cp ../nfs41rdr.inf .
- +cp ../etc_netconfig .
- +cp ../ms-nfs41-idmap.conf .
- +
- +
- +#### Install release blob (requires mintty.exe running as "Adminstrator"):
- +cd ms-nfs41-client/dist
- +bash ../cygwin/devel/msnfs41client.bash install
- +
- +
- +#### Run nfs41 client:
- +** Run deamon:
- +(requires to modify "msnfs41client.bash")
- +bash ../cygwin/devel/msnfs41client.bash run_daemon
- +
- +
- +** mount home dir:
- +(requires to modify "msnfs41client.bash")
- +bash ../cygwin/devel/msnfs41client.bash mount_homedir
- +
- +
- +#### Testing:
- +** "cthon04" test suite:
- +git clone https://github.com/kofemann/ms-nfs41-client.git
- +git clone git://git.linux-nfs.org/projects/steved/cthon04.git
- +cd cthon04/
- +git checkout 8cefaa2ecf8d5c1240f1573530f07cfbbfc092ea
- +git am ../ms-nfs41-client/tests/*.patch
- +make 2>&1 | tee buildlog.log
- +mkdir testdir1
- +./runtests -a -t "$PWD/testdir1" 2>&1 | tee testrun.log
- +
- +
- +#### ToDo:
- +- POSIX Makefile for easier build, release blob generaetion, local test
- +installation, running cthon4 etc
- +- DocBook/XML based documentation
- +- Document how to get and build ksh93 for Cygwin
- +- Cygwin-specific binary release blob
- +- Document the usage of utils/mount_sshnfs/ and utils/sshnfs/
- +- Add test code for SID etc mapping
- +
- +# EOF.
- diff --git a/cygwin/devel/msnfs41client.bash b/cygwin/devel/msnfs41client.bash
- new file mode 100644
- index 0000000..e4dd5df
- --- /dev/null
- +++ b/cygwin/devel/msnfs41client.bash
- @@ -0,0 +1,287 @@
- +#!/bin/bash
- +
- +#
- +# msnfs41client.bash - simple Cygwin frontent for the msnfsv41
- +# NFSv4.1 filesystem driver
- +#
- +
- +#
- +# Written by Roland Mainz <roland.mainz@nrubsig.org>
- +#
- +
- +#
- +# Examples:
- +#
- +# 1. Mount for current users:
- +# (requires PsExec from https://download.sysinternals.com/files/PSTools.zip
- +# in /home/roland_mainz/work/win_pstools/)
- +# * Usage:
- +# Shell1: cd /cygdrive/c/Users/roland_mainz/Downloads/ms-nfs41-client-x64/ms-nfs41-client-x64 && bash ../msnfs41client.bash run_deamon
- +# Shell2: cd /cygdrive/c/Users/roland_mainz/Downloads/ms-nfs41-client-x64/ms-nfs41-client-x64 && bash ../msnfs41client.bash mount_homedir
- +#
- +# 2. Mount for all users:
- +# * Requires:
- +# - Windows admin rights (Cygwin --> Run terminal as Adminstrator)
- +# - PsExec from https://download.sysinternals.com/files/PSTools.zip in /home/roland_mainz/work/win_pstools/)
- +# * Usage:
- +# Shell1: cd /cygdrive/c/Users/roland_mainz/Downloads/ms-nfs41-client-x64/ms-nfs41-client-x64 && bash ../msnfs41client.bash sys_run_deamon
- +# Shell2: cd /cygdrive/c/Users/roland_mainz/Downloads/ms-nfs41-client-x64/ms-nfs41-client-x64 && bash ../msnfs41client.bash sys_mount_homedir
- +#
- +
- +function is_windows_admin_account
- +{
- + #
- + # Test whether we have the Windows permissions to install DLLs
- + # and the kernel module
- + #
- + # Usually Windows Adminstrator rights are indicated by the
- + # membership in group "544(Administratoren)" (Cygwin maps
- + # "SID S-1-5-32-544" to GID 544)
- + #
- + if [[ "$(id -G)" =~ (^|[[:space:]]+)544([[:space:]]+|$) ]] ; then
- + return 0
- + fi
- + return 1
- +}
- +
- +function nfsclient_install
- +{
- + set -o nounset
- + set -o xtrace
- + set -o errexit
- +
- + if ! is_windows_admin_account ; then
- + printf $"%s: Install requires Windows Adminstator permissions.\n" "$0"
- + return 1
- + fi
- +
- + # make sure all binaries are executable, Windows cmd does
- + # not care, but Cygwin&bash do.
- + # If *.ddl are not executable nfs*.exe fail with 0xc0000022
- + chmod a+x *.exe *.dll
- +
- + if false ; then
- + # install.bat needs PATH to include $PWD
- + PATH="$PWD:$PATH" cmd /c install.bat
- + else
- + nfs_install
- + rundll32 setupapi.dll,InstallHinfSection DefaultInstall 132 ./nfs41rdr.inf
- + fi
- +
- + mkdir -p /cygdrive/c/etc
- + cp etc_netconfig /cygdrive/c/etc/netconfig
- + cp ms-nfs41-idmap.conf /cygdrive/c/etc/.
- +
- + bcdedit /set testsigning on
- + regtool -s set '/HKEY_LOCAL_MACHINE/SYSTEM/CurrentControlSet/Services/Tcpip/Parameters/Domain' 'GLOBAL.LOC'
- +
- + sc query Dfsc
- + sc stop Dfsc || true
- + sc config Dfsc start=disabled
- +
- + sc query nfs41_driver
- + domainname
- +
- + openfiles /local ON
- +
- + # check whether the driver really has been installed
- + md5sum \
- + "$PWD/nfs41_driver.sys" \
- + '/cygdrive/c/Windows/System32/drivers/nfs41_driver.sys'
- +
- + sync
- +
- + return 0
- +}
- +
- +function nfsclient_rundeamon
- +{
- + set -o xtrace
- + set -o nounset
- +
- + gdb -ex=run --args nfsd_debug -d 0 --noldap --gid 1616 --uid 1616
- + return $?
- +}
- +
- +function nfsclient_system_rundeamon
- +{
- + set -o xtrace
- + set -o nounset
- +
- + su_system gdb -ex=run --args nfsd_debug -d 0 --noldap --gid 1616 --uid 1616
- + return $?
- +}
- +
- +function nfsclient_mount_homedir
- +{
- + set -o xtrace
- + set -o nounset
- + set -o errexit
- +
- + #nfs_mount -p -o sec=sys H 'derfwpc5131:/export/home/rmainz'
- + # fixme: Specifying IPv6 addresses do not work yet, as soon as
- + # they come as UNC paths (e.g.
- + # $ cd '//[fe80::219:99ff:feae:73ce]@2049/nfs4/export/home/rmainz' #
- + # they get corrupted once they arrive in nfsd_debug.exe)
- + #nfs_mount -p -o sec=sys H '[fe80::219:99ff:feae:73ce]:/export/home/rmainz'
- + nfs_mount -p -o sec=sys H 'derfwpc5131_ipv6:/export/home/rmainz'
- + mkdir -p '/home/rmainz'
- + mount -o bind,posix=1 '/cygdrive/h' '/home/rmainz'
- + return $?
- +}
- +
- +function nfsclient_system_mount_homedir
- +{
- + set -o xtrace
- + set -o nounset
- + set -o errexit
- +
- + # purge any leftover persistent mappings to device H:
- + su_system net use H: /delete || true
- +
- + #su_system nfs_mount -p -o sec=sys H 'derfwpc5131:/export/home/rmainz'
- + # fixme: Specifying IPv6 addresses do not work yet, as soon as
- + # they come as UNC paths (e.g.
- + # $ cd '//[fe80::219:99ff:feae:73ce]@2049/nfs4/export/home/rmainz' #
- + # they get corrupted once they arrive in nfsd_debug.exe)
- + #su_system nfs_mount -p -o sec=sys H '[fe80::219:99ff:feae:73ce]:/export/home/rmainz'
- + su_system nfs_mount -p -o sec=sys H 'derfwpc5131_ipv6:/export/home/rmainz'
- +
- + return $?
- +}
- +
- +function nfsclient_umount_homedir
- +{
- + set -o xtrace
- + set -o nounset
- + typeset -i res
- +
- + nfs_mount -d H
- + (( res=$? ))
- +
- + if (( res == 0 )) ; then
- + # remove bind mount
- + umount '/home/rmainz' && rmdir '/home/rmainz'
- + fi
- +
- + return $res
- +}
- +
- +function require_cmd
- +{
- + typeset cmd="$1"
- +
- + if ! which "$cmd" >'/dev/null' 2>&1 ; then
- + printf $"%s: %q not found in %q\n" "$0" "$cmd" "$PWD" 1>&2
- + return 1
- + fi
- + return 0
- +}
- +
- +# execute cmd as Windows user "SYSTEM"
- +function su_system
- +{
- + typeset cmd="$1"
- + shift
- +
- + typeset abspath_cmd="$(which "$cmd")"
- + if [[ ! -x "$abspath_cmd" ]] ; then
- + printf "%s: Command %q not found." $"su_system" "$abspath_cmd" 1>&2
- + return 127
- + fi
- +
- + PsExec \
- + -accepteula -nobanner \
- + -s \
- + -w "$(cygpath -w "$PWD")" \
- + "$(cygpath -w "$abspath_cmd")" "$@"
- +}
- +
- +function sys_terminal
- +{
- + # su_system does not work, mintty requires PsExec -i
- + PsExec -accepteula -nobanner \
- + -i \
- + -s -w "$(cygpath -w "$PWD")" \
- + 'C:\cygwin64\bin\mintty.exe'
- +}
- +
- +function main
- +{
- + typeset cmd="$1"
- +
- + # "$PATH:/usr/bin:/bin" is used for PsExec where $PATH might be empty
- + export PATH="$PWD:$PATH:/usr/bin:/bin"
- +
- + # my own path to pstools
- + PATH+=':/home/roland_mainz/work/win_pstools/'
- +
- + case "$cmd" in
- + 'install')
- + nfsclient_install
- + return $?
- + ;;
- + 'run_deamon' | 'run_daemon')
- + require_cmd 'nfsd.exe' || return 1
- + require_cmd 'nfsd_debug.exe' || return 1
- + require_cmd 'nfs_mount.exe' || return 1
- + nfsclient_rundeamon
- + return $?
- + ;;
- + 'sys_run_deamon' | 'sys_run_daemon')
- + require_cmd 'PsExec.exe' || return 1
- + require_cmd 'nfsd.exe' || return 1
- + require_cmd 'nfsd_debug.exe' || return 1
- + require_cmd 'nfs_mount.exe' || return 1
- + if ! is_windows_admin_account ; then
- + printf $"%s: %q requires Windows Adminstator permissions.\n" "$0" "$cmd"
- + return 1
- + fi
- + nfsclient_system_rundeamon
- + return $?
- + ;;
- + 'sys_mount_homedir')
- + require_cmd 'nfs_mount.exe' || return 1
- + if ! is_windows_admin_account ; then
- + printf $"%s: %q requires Windows Adminstator permissions.\n" "$0" "$cmd"
- + return 1
- + fi
- + nfsclient_system_mount_homedir
- + return $?
- + ;;
- + 'mount_homedir')
- + require_cmd 'nfs_mount.exe' || return 1
- + nfsclient_mount_homedir
- + return $?
- + ;;
- + 'umount_homedir')
- + require_cmd 'nfs_mount.exe' || return 1
- + nfsclient_umount_homedir
- + return $?
- + ;;
- + # misc
- + 'sys_terminal')
- + require_cmd 'mintty.exe' || return 1
- + if ! is_windows_admin_account ; then
- + printf $"%s: %q requires Windows Adminstator permissions.\n" "$0" "$cmd"
- + return 1
- + fi
- + sys_terminal
- + return $?
- + ;;
- + *)
- + printf $"%s: Unknown cmd %q\n" "$0" "$cmd" 1>&2
- + return 1
- + ;;
- + esac
- + return 1
- +}
- +
- +
- +#
- +# main
- +#
- +main "$@"
- +exit $?
- +
- +# EOF.
- diff --git a/cygwin/utils/mount_sshnfs/README.txt b/cygwin/utils/mount_sshnfs/README.txt
- new file mode 100644
- index 0000000..4d8a2ac
- --- /dev/null
- +++ b/cygwin/utils/mount_sshnfs/README.txt
- @@ -0,0 +1,21 @@
- +#
- +# mount_sshnfs/README.txt
- +#
- +
- +**** ToDo:
- +- Add umount -f option
- +- Add umount -v option
- +- Add mount -v/-vv/-vvv option
- +- Fix FIXME stuff
- +- on mount: Check whether the mount point exists
- +- mounting should enforce that we only try NFSv4, and not try NFSv3
- +- Implement "status" command to check on mount point and ssh
- + forwarding process
- +- Implement "restart_forwarding" command to restart the ssh
- + forwarding process if it terminated for some reason
- +- Debug messages should go to stderr
- +- Linux: Add mount.nfs -o nconnect=4 (see
- + https://www.suse.com/support/kb/doc/?id=000019933)
- +- Add Linux umount helper support, see umount(8) HELPER section
- +
- +# EOF.
- diff --git a/cygwin/utils/mount_sshnfs/mount_sshnfs.ksh b/cygwin/utils/mount_sshnfs/mount_sshnfs.ksh
- new file mode 100644
- index 0000000..c485c81
- --- /dev/null
- +++ b/cygwin/utils/mount_sshnfs/mount_sshnfs.ksh
- @@ -0,0 +1,905 @@
- +#!/bin/ksh93
- +
- +#
- +# mount_sshnfs - mount NFSv4 filesystem through ssh tunnel
- +#
- +
- +#
- +# Example usage:
- +#
- +# 1. UNIX/Linux: Mount&&unmount /export/home/rmainz on NFS server "/export/home/rmainz":
- +# $ mkdir -p /foobarmnt
- +# $ ksh mount_sshnfs.ksh mount ssh+nfs://rmainz@derfwpc5131/export/home/rmainz /foobarmnt
- +# $ mount_sshnfs.ksh umount /foobarmnt
- +#
- +#
- +# 2. UNIX/Linux: Mount&&unmount /export/home/rmainz on NFS server "/export/home/rmainz" via SSH jumphost rmainz@10.49.20.131:
- +# $ mkdir -p /foobarmnt
- +# $ ksh mount_sshnfs.ksh mount -o ro,mount_sshnfs_jumphost=rmainz@10.49.20.131 ssh+nfs://rmainz@derfwpc5131/export/home/rmainz /foobarmnt
- +# $ mount_sshnfs.ksh umount /foobarmnt
- +#
- +
- +#
- +# For more examples see help and subcommand help:
- +# $ mount_sshnfs.ksh --man
- +# $ mount_sshnfs.ksh mount --man
- +# $ mount_sshnfs.ksh umount --man
- +#
- +
- +#
- +# Written by Roland Mainz <roland.mainz@nrubsig.org>
- +#
- +
- +function usage
- +{
- + (( OPTIND=0 ))
- + getopts -a "${1}" "${2}" OPT '-?'
- + return 2
- +}
- +
- +
- +#
- +# simple netstat -n parser
- +#
- +function netstat_list_connections
- +{
- + set -o nounset
- + nameref data=$1
- +
- + compound out=( typeset stdout stderr ; integer res )
- +
- + out.stderr="${ { out.stdout="${ LC_ALL='POSIX' PATH='/usr/bin:/bin' netstat -a -n ; (( out.res=$? )) ; }" ; } 2>&1 ; }"
- + if (( out.res != 0 )) ; then
- + print -u2 -f $"%s: netstat returned %d exit code.\n" \
- + "$0" out.res
- + return 1
- + fi
- + if [[ "${out.stderr}" != '' ]] ; then
- + #
- + # Handle known Linux netstat warnings
- + #
- + if [[ "${out.stderr}" != $'warning, got bogus unix line.' ]] ; then
- + print -u2 -f $"%s: netstat returned unknown error message %q.\n" \
- + "$0" "${out.stderr}"
- + return 1
- + fi
- + fi
- +
- + typeset -a data.connections
- + typeset l
- + typeset leftover
- + integer dci=0 # data.connections array index
- +
- + while read l ; do
- + leftover="${l/~(Elrx)
- + (?: # non-capturing group
- + #
- + # regex group for tcp,udp
- + #
- + (tcp|tcp6|udp|udp6|raw|raw6|sctp|sctp6) # Proto
- + [[:space:]]+
- + ([[:digit:]]+) # Recv-Q
- + [[:space:]]+
- + ([[:digit:]]+) # Send-Q
- + [[:space:]]+
- + ([^[:space:]]+) # Local Address
- + [[:space:]]+
- + ([^[:space:]]+) # Foreign Address
- + (?:
- + |
- + [[:space:]]+
- + ([^[:space:]]*?) # State (Optional)
- + )
- + |
- + #
- + # regex for unix
- + #
- + (unix) # Proto
- + [[:space:]]+
- + ([[:digit:]]+) # RefCnt
- + [[:space:]]+
- + (\[.+?\]) # Flags
- + [[:space:]]+
- + ([^[:space:]]+) # Type
- + [[:space:]]+
- + ([^[:space:]]*?) # State (optional)
- + [[:space:]]+
- + ([[:digit:]]+) # I-Node
- + (?:
- + |
- + [[:space:]]+
- + ([^[:space:]]+) # Path (optional)
- + )
- + )
- + /X}"
- +
- + # If the regex above did not match then .sh.match
- + # remains untouched, so we might see data from the
- + # previous round.
- + # So we check the "leftover" var whether it just
- + # contains the dummy value of "X" to indicate a
- + # successful regex match
- + if [[ "$leftover" == 'X' ]] ; then
- + #print -v .sh.match
- +
- + if [[ "${.sh.match[1]-}" != '' ]] ; then
- + nameref dcn=data.connections[$dci]
- +
- + typeset dcn.proto="${.sh.match[1]}"
- + typeset dcn.recv_q="${.sh.match[2]}"
- + typeset dcn.send_q="${.sh.match[3]}"
- + typeset dcn.local_address="${.sh.match[4]}"
- + typeset dcn.foreign_address="${.sh.match[5]}"
- + typeset dcn.state="${.sh.match[6]}"
- + ((dci++))
- + elif [[ "${.sh.match[7]-}" != '' ]] ; then
- + nameref dcn=data.connections[$dci]
- +
- + typeset dcn.proto="${.sh.match[7]}"
- + typeset dcn.refcnt="${.sh.match[8]}"
- + typeset dcn.flags="${.sh.match[9]}"
- + typeset dcn.type="${.sh.match[10]}"
- + [[ "${.sh.match[11]}" != '' ]] && typeset dcn.state="${.sh.match[11]}"
- + typeset dcn.inode="${.sh.match[12]}"
- + [[ "${.sh.match[13]}" != '' ]] && typeset dcn.path="${.sh.match[13]}"
- + ((dci++))
- + fi
- + else
- + true
- + #printf $"leftover=%q\n" "${leftover}"
- + fi
- + done <<<"${out.stdout}"
- +
- + return 0
- +}
- +
- +function netstat_list_active_local_tcp_connections
- +{
- + set -o nounset
- + nameref ar=$1
- + compound c
- + integer port
- + integer i
- +
- + netstat_list_connections c || return 1
- + #print -v c
- +
- + [[ -v ar ]] || integer -a ar
- +
- + for i in "${!c.connections[@]}" ; do
- + nameref n=c.connections[$i]
- +
- + # look for only for TCP connections which match
- + # 127.0.*.* or IPv6 ::1 for localhost
- + # 0.0.0.0 or IPv6 :: for all addresses (e.g. servers)
- + if [[ "${n.proto}" == ~(El)tcp && \
- + "${n.local_address}" == ~(Elr)((127\.0\..+|::1)|(::|0\.0\.0\.0|)):[[:digit:]]+ ]] ; then
- +
- + port="${n.local_address##*:}"
- + #printf $"port = %d\n" port
- +
- + (( ar[port]=1 ))
- + fi
- + done
- +
- + return 0
- +}
- +
- +function netstat_find_next_free_local_tcp_port
- +{
- + set -o nounset
- + compound c=( integer -a ar )
- + nameref ret_free_port=$1
- + integer start_port
- + integer end_port
- + integer i
- +
- + netstat_list_active_local_tcp_connections c.ar || return 1
- +
- + #print -v c
- +
- + (( start_port=$2 ))
- + if (( $# > 2 )) ; then
- + (( end_port=$3 ))
- + else
- + (( end_port=65535 ))
- + fi
- +
- + for ((i=start_port ; i < end_port ; i++ )) ; do
- + if [[ ! -v c.ar[i] ]] ; then
- + (( ret_free_port=i ))
- + return 0
- + fi
- + done
- +
- + return 1
- +}
- +
- +
- +#
- +# parse_rfc1738_url - parse RFC 1838 URLs
- +#
- +# Output variables are named after RFC 1838 Section 5 ("BNF for
- +# specific URL schemes")
- +#
- +function parse_rfc1738_url
- +{
- + set -o nounset
- +
- + typeset url="$2"
- + typeset leftover
- + nameref data="$1" # output compound variable
- +
- + # ~(E) is POSIX extended regular expression matching (instead
- + # of shell pattern), "x" means "multiline", "l" means "left
- + # anchor", "r" means "right anchor"
- + leftover="${url/~(Elrx)
- + (.+?) # scheme
- + :\/\/ # '://'
- + ( # login
- + (?:
- + (.+?) # user (optional)
- + (?::(.+))? # password (optional)
- + @
- + )?
- + ( # hostport
- + (.+?) # host
- + (?::([[:digit:]]+))? # port (optional)
- + )
- + )
- + (?:\/(.*?))?/X}" # path (optional)
- +
- + # All parsed data should be captured via eregex in .sh.match - if
- + # there is anything left (except the 'X') then the input string did
- + # not properly match the eregex
- + [[ "$leftover" == 'X' ]] ||
- + { print -u2 -f $"%s: Parser error, leftover=%q\n" \
- + "$0" "$leftover" ; return 1 ; }
- +
- + data.url="${.sh.match[0]}"
- + data.scheme="${.sh.match[1]}"
- + data.login="${.sh.match[2]}"
- + # FIXME: This should use [[ ! -v .sh.match[3] ]], but ksh93u has bugs
- + [[ "${.sh.match[3]-}" != '' ]] && data.user="${.sh.match[3]}"
- + [[ "${.sh.match[4]-}" != '' ]] && data.password="${.sh.match[4]}"
- + data.hostport="${.sh.match[5]}"
- + data.host="${.sh.match[6]}"
- + [[ "${.sh.match[7]-}" != '' ]] && integer data.port="${.sh.match[7]}"
- + [[ "${.sh.match[8]-}" != '' ]] && data.uripath="${.sh.match[8]}"
- +
- + return 0
- +}
- +
- +
- +function parse_sshnfs_url
- +{
- + typeset url="$2"
- + nameref data="$1"
- +
- + parse_rfc1738_url data "$url" || return 1
- +
- + [[ "${data.scheme}" == ~(Elr)(ssh\+nfs|nfs) ]] || \
- + { print -u2 -f $"%s: Not a nfs:// or ssh+nfs:// url\n" "$0" ; return 1 ; }
- + [[ "${data.host}" != '' ]] || { print -u2 -f $"%s: NFS hostname missing\n" "$0" ; return 1 ; }
- + [[ "${data.uripath}" != '' ]] || { print -u2 -f $"%s: NFS path missing\n" "$0" ; return 1 ; }
- +
- + return 0
- +}
- +
- +
- +function mountpoint2configfilename
- +{
- + nameref configfilename=$1
- + typeset mountpoint="$2"
- +
- + #
- + # FIXME:
- + # - We should urlencode more than just '/'
- + # - We should strip the leading '/'
- + # - We should use realpath(1) for mountpoints here
- + #
- +
- + # .cpv means ComPound Variable"
- + configfilename="/tmp/mount_sshnfs/${mountpoint//\//%2f}.cpv"
- + return 0
- +}
- +
- +
- +function cmd_mount
- +{
- + set -o nounset
- + nameref c=$1
- +
- + # fixme: Need better text layout for $ mount_sshnfs mount --man #
- + typeset -r mount_sshnfs_cmdmount_usage=$'+
- + [-?\n@(#)\$Id: mount_sshnfs mount (Roland Mainz) 2023-07-24 \$\n]
- + [-author?Roland Mainz <roland.mainz@nrubsig.org>]
- + [+NAME?mount_sshnfs mount - mount NFSv4 filesystem through ssh
- + tunnel]
- + [+DESCRIPTION?\bmount_sshnfs mount\b mounts a NFSv4 filesystem
- + through a ssh tunnel.]
- + [r:readonly?Mount file system readonly.]
- + [w:readwrite?Mount file system read-write.]
- + [o:options?Use the specified mount options.
- + The opts argument is a comma-separated list.\n
- + options starting with mount_sshnfs_jumphost_* will be
- + consumed by mount_sshnfs, all other options will be
- + passed through to mount.nfs.]:[options]{
- + [+?mount_sshnfs options are:]{
- + [+?-o mount_sshnfs_jumphost=user@host:port - ssh jumphost]
- + [+?-o mount_sshnfs_local_forward_port=port - local TCP port
- + for SSH-forwarded NFS connection to server.
- + Defaults is to use netstat(1) to find a free TCP port]
- + }
- + }
- +
- + url mountpoint
- +
- + [+NOTES?]{
- + [+?The original CITI Windows NFSv4 nfs_mount.exe does not
- + support the port= option.\nUse -o mount_sshnfs_local_forward_port=2049
- + as workaround. Newer versions from https://github.com/kofemann/ms-nfs41-client
- + support the -o port=... option.]
- + }
- + [+SEE ALSO?\bksh93\b(1),\bssh\b(1),\bmount.nfs\b(8),\bnfs\b(5)]
- + '
- + typeset mydebug=false # fixme: should be "bool" for ksh93v
- + typeset c.url
- + typeset c.mountpoint
- + typeset config_filename
- +
- + typeset -a c.mount_nfs_options
- + integer i
- + integer saved_optind_m1 # saved OPTIND-1
- + typeset s # generic temporary string variable
- +
- + # remove subcmd name (in this case 'mount')
- + unset c.args[0]
- +
- + #
- + # Expand MOUNT_SSHNFS_CMDMOUNT_OPTIONS before arguments given to
- + # mount_sshnfs.ksh.
- + # By default we use IFS=$' \t\n' for argument splitting
- + #
- + c.args=( ${MOUNT_SSHNFS_CMDMOUNT_OPTIONS-} "${c.args[@]}" )
- +
- + #
- + # Argument parsing
- + #
- + while getopts -a "${progname} mount" "${mount_sshnfs_cmdmount_usage}" OPT "${c.args[@]}" ; do
- + case "${OPT}" in
- + 'r')
- + c.mount_nfs_options+=( 'ro' )
- + ;;
- + 'w')
- + c.mount_nfs_options+=( 'rw' )
- + ;;
- + 'o')
- + #
- + # Split options like "-o foo=bar,baz=BAM"
- + # into "-o foo=bar -o baz=BAM" for easier
- + # processing below
- + IFS=$','
- + c.mount_nfs_options=( "${c.mount_nfs_options[@]}" ${OPTARG} )
- + IFS=$' \t\n'
- + ;;
- + *)
- + usage "${progname} mount" "${mount_sshnfs_cmdmount_usage}"
- + return $?
- + ;;
- + esac
- + done
- +
- + (( saved_optind_m1=OPTIND-1 ))
- +
- + # remove options we just parsed from c.args
- + for ((i=0 ; i < saved_optind_m1 ; i++)) ; do
- + unset c.args[$i]
- + done
- +
- +
- + #
- + # Get remaining arguments
- + #
- + c.url="${c.args[saved_optind_m1+0]-}"
- + c.mountpoint="${c.args[saved_optind_m1+1]-}"
- +
- +
- + #
- + # Filter out our options, other options are passed to mount.nfs
- + #
- + for ((i=0 ; i < ${#c.mount_nfs_options[@]} ; i++)) ; do
- + s="${c.mount_nfs_options[$i]}"
- +
- + #
- + # Intercept options starting with eregex mount_sshnfs.+
- + #
- + if [[ "$s" == ~(Elr)mount_sshnfs.+=.+ ]] ; then
- + case "$s" in
- + ~(Eli)mount_sshnfs_jumphost=)
- + [[ ! -v c.ssh_jumphost_args ]] && typeset -a c.ssh_jumphost_args
- + c.ssh_jumphost_args+=( "-J" "${c.mount_nfs_options[i]/~(Eli)mount_sshnfs_jumphost=}" )
- + ;;
- + ~(Eli)mount_sshnfs_local_forward_port=)
- + # command(1) prevents that the shell interpreter
- + # exits if typeset produces a syntax error
- + command integer c.local_forward_port="${c.mount_nfs_options[i]/~(Eli)mount_sshnfs_local_forward_port=}" || return 1
- + ;;
- + *)
- + usage "${progname} mount" "${mount_sshnfs_cmdmount_usage}"
- + return $?
- + ;;
- + esac
- + unset c.mount_nfs_options[$i]
- + fi
- + done
- +
- +
- + #
- + # Parse url
- + #
- + parse_sshnfs_url c.nfs_server "${c.url}" || return 1
- +
- + mountpoint2configfilename config_filename "${c.mountpoint}"
- +
- + if [[ -f "${config_filename}" ]] ; then
- + print -u2 -f $"%s: Config file %q for mount point %q found.\n" \
- + "$0" \
- + "$config_filename" \
- + "${c.mountpoint}"
- + return 1
- + fi
- +
- + #
- + # Prechecks for writing the config file
- + #
- + mkdir -p '/tmp/mount_sshnfs/'
- + if [[ ! -w '/tmp/mount_sshnfs/' ]] ; then
- + print -u2 -f $"%s: mount_nfs data directory %q not writeable.\n" \
- + "$0" \
- + '/tmp/mount_sshnfs/'
- + return 1
- + fi
- +
- + ${mydebug} && print -v c
- +
- + case "${c.nfs_server.scheme}" in
- + 'ssh+nfs')
- + #
- + # Find free local forwarding port
- + #
- +
- + # Note: Original CITI ms-nfsv41 client
- + # nfs_mount.exe
- + # does not support -o port=..., so we set a default
- + # here if it was not set yet
- + if (( c.is_ccygwin == 1 )) && [[ ! -v c.local_forward_port ]] ; then
- + integer c.local_forward_port=2049
- + fi
- +
- + # port on THIS machine
- + if [[ ! -v c.local_forward_port ]] ; then
- + integer c.local_forward_port
- +
- + (( i=34049 ))
- + if ! netstat_find_next_free_local_tcp_port c.local_forward_port $i ; then
- + print -u2 -f "%s: netstat_find_next_free_local_tcp_port failed.\n" "$0"
- + return 1
- + fi
- + fi
- +
- +
- + c.ssh_control_socket_name="/tmp/mount_sshnfs/mount_sshnfs_ssh-control-socket_logname${LOGNAME}_ppid${PPID}_pid$$"
- +
- + #
- + # Find SSH login user name for NFS server
- + #
- + if [[ -v c.nfs_server.user ]] ; then
- + typeset c.nfsserver_ssh_login_name="${c.nfs_server.user}"
- + fi
- + # fixme: Implement NFSServerSSHLoginName
- + if [[ ! -v c.nfsserver_ssh_login_name ]] ; then
- + # default user name if neither URL nor
- + # "-o NFSServerSSHLoginName=..." were given
- + typeset c.nfsserver_ssh_login_name="$LOGNAME"
- + fi
- +
- + #
- + # Forward NFS port from server to local machine
- + #
- + # Notes:
- + # - We use $ ssh -M ... # here as a way to terminate the port
- + # forwarding process later using "-O exit" without the need
- + # for a pid
- + #
- + print -u2 -f $"# Please enter the login data for NFS server (%s):\n" \
- + "${c.nfsserver_ssh_login_name}@${c.nfs_server.host}"
- +
- + #
- + # Notes:
- + # - fixme: c.nfs_server.port is fixed
- + # for ssh+nfs://-URLs, so for now we
- + # have to hardcode TCP/2049 for now
- + # - We use aes128-cbc,aes128-ctr ciphers for better
- + # throughput (see https://bash-prompt.net/guides/bash-ssh-ciphers/
- + # for a benchmark) and lower latency, as NFS is
- + # a bit latency-sensitive
- + # - We turn compression off, as it incrases latency
- + #
- + ssh \
- + -L "${c.local_forward_port}:localhost:2049" \
- + -M -S "${c.ssh_control_socket_name}" \
- + -N \
- + -f -o 'ExitOnForwardFailure=yes' \
- + -o 'Compression=no' \
- + -o 'Ciphers=aes128-cbc,aes128-ctr' \
- + "${c.ssh_jumphost_args[@]}" \
- + "${c.nfsserver_ssh_login_name}@${c.nfs_server.host}"
- + if (( $? != 0 )) ; then
- + print -u2 -f $"%s: NFS forwarding ssh failed with error code %d\n" "$0" $?
- + return 1
- + fi
- +
- + # debug
- + ${mydebug} && \
- + ssh \
- + -S "${c.ssh_control_socket_name}" \
- + -O 'check' \
- + "${c.nfsserver_ssh_login_name}@${c.nfs_server.host}"
- +
- +
- + if (( c.is_ccygwin == 1 )) ; then
- + #
- + # Build argument list for nfs_mount.exe ...
- + #
- + typeset -a mount_args
- + for s in "${c.mount_nfs_options[@]}" ; do
- + mount_args+=( '-o' "$s" )
- + done
- +
- + if (( c.local_forward_port != 2049 )) ; then
- + #
- + # The original CITI NFSv4 nfs_mount.exe does not
- + # support the port= option, so only set it
- + # if we do not use the default port
- + #
- + mount_args+=( '-o' "port=${c.local_forward_port}" )
- + fi
- + # fixme: can we remove -o sec=sys ?
- + mount_args+=( '-o' 'sec=sys' )
- + # '*' == Let nfs_mount.exe should pick drive letter itself
- + mount_args+=( '*' )
- + mount_args+=( "localhost:/${c.nfs_server.uripath}" )
- +
- + #
- + # ... and do the mount
- + #
- + typeset stdout dummy
- +
- + # fixme: we should set LC_ALL=C because below we depend on
- + # a l10n message
- + stdout="${ "${c.msnfsv41_nfsmountcmd}" "${mount_args[@]}" ; (( retval=$? )) ;}"
- + cat <<<"$stdout"
- +
- + if (( retval == 0 )) ; then
- + # Parse stdout for drive letter
- + dummy="${stdout/~(E)Successfully mounted (.+) to drive (.+):/dummy}"
- +
- + # fixme: we should test whether c.windows_drive_letter is empty or not
- + typeset c.windows_drive_letter="${.sh.match[2]}"
- +
- + print -u2 -f $"%s: NFS filesystem mounted to drive %q.\n" \
- + "$0" "${c.windows_drive_letter}"
- +
- + # Cygwin bind mount
- + mount -o bind "/cygdrive/${c.windows_drive_letter}" "${c.mountpoint}"
- + fi
- + else
- + #
- + # Build argument list for mount.nfs ...
- + #
- + typeset -a mount_args
- + mount_args+=( '-vvv' )
- + mount_args+=( '-t' 'nfs' )
- + for s in "${c.mount_nfs_options[@]}" ; do
- + mount_args+=( '-o' "$s" )
- + done
- + mount_args+=( '-o' 'vers=4.2' )
- + mount_args+=( '-o' "port=${c.local_forward_port}" )
- + mount_args+=( "localhost:/${c.nfs_server.uripath}" )
- + mount_args+=( "${c.mountpoint}" )
- +
- + #
- + # ... and do the mount
- + #
- + mount "${mount_args[@]}"
- + (( retval=$? ))
- + fi
- +
- + if (( retval != 0 )) ; then
- + #
- + # Quit ssh port forwarding process
- + #
- + ssh \
- + -S "${c.ssh_control_socket_name}" \
- + -O 'exit' \
- + "${c.nfsserver_ssh_login_name}@${c.nfs_server.host}"
- + return $retval
- + fi
- +
- +
- + #
- + # Save status data
- + #
- + compound mnt_config=(
- + typeset url="${c.url}"
- + typeset mountpoint="${c.mountpoint}"
- + typeset ssh_control_socket_name="${c.ssh_control_socket_name}"
- + typeset nfsserver_ssh_login_name="${c.nfsserver_ssh_login_name}"
- + typeset nfsserver_host="${c.nfs_server.host}"
- + )
- +
- + if (( c.is_ccygwin == 1 )) ; then
- + typeset mnt_config.windows_drive_letter="${c.windows_drive_letter}"
- + fi
- +
- + print -v mnt_config >"$config_filename"
- +
- + return 0
- + ;;
- + # fixme: Implement nfs://-URLs
- + *)
- + print -u2 -f $"%s: Unknown URL scheme %q\n" "$0" "${c.nfs_server.scheme}"
- + return 2
- + ;;
- + esac
- +
- + # notreached
- +}
- +
- +
- +function cmd_umount
- +{
- + set -o nounset
- + nameref c=$1
- + integer retval
- + integer saved_optind_m1 # saved OPTIND-1
- +
- + typeset mydebug=false # fixme: should be "bool" for ksh93v
- + # fixme: Need better text layout for $ mount_sshnfs mount --man #
- + typeset -r mount_sshnfs_cmdumount_usage=$'+
- + [-?\n@(#)\$Id: mount_sshnfs umount (Roland Mainz) 2023-07-24 \$\n]
- + [-author?Roland Mainz <roland.mainz@nrubsig.org>]
- + [+NAME?mount_sshnfs umount - unmount NFSv4 filesystem mounted
- + via mount_sshnfs mount]
- + [+DESCRIPTION?\bmount_sshnfs umount\b unmounts a NFSv4
- + filesystem previously mounted via mount_sshnfs mount.]
- +
- + mountpoint
- +
- + [+SEE ALSO?\bksh93\b(1),\bssh\b(1),\bmount.nfs\b(8),\bnfs\b(5)]
- + '
- +
- + # remove subcmd name (in this case 'umount')
- + unset c.args[0]
- +
- + #
- + # Expand MOUNT_SSHNFS_CMDUMOUNT_OPTIONS before arguments given to
- + # mount_sshnfs.ksh.
- + # By default we use IFS=$' \t\n' for argument splitting
- + #
- + c.args=( ${MOUNT_SSHNFS_CMDUMOUNT_OPTIONS-} "${c.args[@]}" )
- +
- + #
- + # Argument parsing
- + #
- + while getopts -a "${progname} umount" "${mount_sshnfs_cmdumount_usage}" OPT "${c.args[@]}" ; do
- + case "${OPT}" in
- + *)
- + usage "${progname} umount" "${mount_sshnfs_cmdumount_usage}"
- + return $?
- + ;;
- + esac
- + done
- +
- + (( saved_optind_m1=OPTIND-1 ))
- +
- + # remove options we just parsed from c.args
- + for ((i=0 ; i < saved_optind_m1 ; i++)) ; do
- + unset c.args[$i]
- + done
- +
- +
- + #
- + # Get remaining arguments
- + #
- + c.mountpoint="${c.args[saved_optind_m1+0]-}"
- +
- + #
- + # Read configuration file for this mountpoint
- + #
- + typeset config_filename
- + mountpoint2configfilename config_filename "${c.mountpoint}"
- +
- + if [[ ! -f "${config_filename}" ]] ; then
- + print -u2 -f $"%s: Config file %q for mount point %q not found.\n" \
- + "$0" \
- + "$config_filename" \
- + "${c.mountpoint}"
- + return 1
- + fi
- +
- + compound mnt_config
- + read -C mnt_config <"${config_filename}" || return 1
- +
- + ${mydebug} && print -v mnt_config
- +
- + #
- + # Do the unmount
- + #
- + if (( c.is_ccygwin == 1 )) ; then
- + # unmount the NFS filesystem
- + "${c.msnfsv41_nfsmountcmd}" -d "${mnt_config.windows_drive_letter}"
- + (( retval=$? ))
- +
- + # remove the Cygwin bind mount
- + (( retval == 0 )) && umount "${c.mountpoint}"
- + else
- + umount "${c.mountpoint}"
- + (( retval=$? ))
- + fi
- +
- + if (( retval != 0 )) ; then
- + return $retval
- + fi
- +
- + #
- + # Quit ssh port forwarding process
- + #
- + ssh \
- + -S "${mnt_config.ssh_control_socket_name}" \
- + -O 'exit' \
- + "${mnt_config.nfsserver_ssh_login_name}@${mnt_config.nfsserver_host}"
- +
- + rm -f "${config_filename}"
- + return 0
- +}
- +
- +
- +function main
- +{
- + set -o nounset
- +
- + # fixme: Need better text layout for $ mount_sshnfs --man #
- + typeset -r mount_sshnfs_usage=$'+
- + [-?\n@(#)\$Id: mount_sshnfs (Roland Mainz) 2023-07-24 \$\n]
- + [-author?Roland Mainz <roland.mainz@nrubsig.org>]
- + [+NAME?mount_sshnfs - mount/umount NFSv4 filesystem via ssh
- + tunnel]
- + [+DESCRIPTION?\bmount_sshnfs\b mounts/unmounts a NFSv4
- + filesystem via ssh tunnel.]
- + [D:debug?Enable debugging.]
- +
- + mount [options]
- + umount [options]
- + status [options]
- + restart_forwarding [options]
- +
- + [+EXAMPLES]{
- + [+?Example 1:][+?Mount&&unmount /export/home/rmainz on NFS server "/export/home/rmainz"]{
- +[+\n# mkdir -p /foobarmnt
- +# ksh mount_sshnfs.ksh mount ssh+nfs:://rmainz@derfwpc5131/export/home/rmainz /foobarmnt
- +# mount_sshnfs.ksh umount /foobarmnt
- +]
- +}
- + [+?Example 2:][+?Mount&&unmount /export/home/rmainz on NFS server "/export/home/rmainz" via SSH jumphost rmainz@10.49.20.131]{
- +[+\n# mkdir -p /foobarmnt
- +# ksh mount_sshnfs.ksh mount -o ro,mount_sshnfs_jumphost=rmainz@10.49.20.131 ssh+nfs:://rmainz@derfwpc5131/export/home/rmainz /foobarmnt
- +# mount_sshnfs.ksh umount /foobarmnt
- +]
- + }
- + }
- + [+SEE ALSO?\bksh93\b(1),\bssh\b(1),\bmount.nfs\b(8),\bnfs\b(5)]
- + '
- +
- + compound c
- + typeset -a c.args
- + integer saved_optind_m1 # saved OPTIND-1
- +
- + if [[ "${ uname -o ;}" == 'Cygwin' ]] ; then
- + integer c.is_ccygwin=1
- +
- + # only for testing!!
- + PATH+=':/cygdrive/c/Users/roland_mainz/Downloads/ms-nfs41-client-x64/ms-nfs41-client-x64/'
- +
- + typeset c.msnfsv41_nfsmountcmd="$(which 'nfs_mount.exe')"
- +
- + if [[ ! -x "${c.msnfsv41_nfsmountcmd}" ]] ; then
- + print -u2 -f $"%s: Cannot find MS-NFSV41 nfs_mount.exe command\n" "$0"
- + return 1
- + fi
- + else
- + integer c.is_ccygwin=0
- + fi
- +
- + # Cygwin does not set logname
- + [[ ! -v LOGNAME ]] && export LOGNAME="$(logname)"
- +
- + #
- + # Expand MOUNT_SSHNFS_OPTIONS before arguments given to
- + # mount_sshnfs.ksh.
- + # By default we use IFS=$' \t\n' for argument splitting
- + #
- + c.args=( ${MOUNT_SSHNFS_OPTIONS-} "$@" )
- +
- + #
- + # Argument parsing
- + #
- + while getopts -a "${progname}" "${mount_sshnfs_usage}" OPT "${c.args[@]}" ; do
- + case "${OPT}" in
- + 'D')
- + # fixme: Implement debugging option
- + ;;
- + *)
- + usage "${progname}" "${mount_sshnfs_usage}"
- + return $?
- + ;;
- + esac
- + done
- +
- + (( saved_optind_m1=OPTIND-1 ))
- +
- + # remove options we just parsed from c.args
- + for ((i=0 ; i < saved_optind_m1 ; i++)) ; do
- + unset c.args[$i]
- + done
- +
- + #
- + # c.args mighth be a sparse array (e.g. "([1]=aaa [2]=bbb [4]=ccc)")
- + # right now after we removed processed options/arguments.
- + # For easier processing below we "reflow" the array back to a
- + # normal linear layout (e.g. ([0]=aaa [1]=bbb [2]=ccc)
- + #
- + c.args=( "${c.args[@]}" )
- +
- + #
- + # Subcommand dispatcher
- + #
- + case "${c.args[0]-}" in
- + 'mount')
- + cmd_mount c
- + return $?
- + ;;
- + 'umount')
- + cmd_umount c
- + return $?
- + ;;
- + 'status' | 'restart_forwarding')
- + print -u2 -f $"%s: not implemented yet\n" "$0"
- + return 2
- + ;;
- + *)
- + print -u2 -f $"%s: Unknown command %q\n" \
- + "$0" "${c.args[0]-}"
- + usage "${progname}" "${mount_sshnfs_usage}"
- + return 1
- + ;;
- + esac
- +
- + # notreached
- +}
- +
- +
- +#
- +# main
- +#
- +builtin cat
- +builtin mkdir
- +builtin basename
- +
- +typeset progname="${ basename "${0}" ; }"
- +
- +main "$@"
- +exit $?
- +
- +# EOF.
- diff --git a/cygwin/utils/sshnfs/README.txt b/cygwin/utils/sshnfs/README.txt
- new file mode 100644
- index 0000000..9e6e66c
- --- /dev/null
- +++ b/cygwin/utils/sshnfs/README.txt
- @@ -0,0 +1,21 @@
- +#
- +# sshnfs/README.txt
- +#
- +
- +**** ToDo:
- +- ksh93 getopt argument parsing
- +- Correct POSIX exit codes
- +- c.destination_nfs_port should be a command line option
- +- How can a non-standard (TCP/2049) NFS port be specified for
- + ssh+nfs:// URLs be specified ?
- +- Debug messages should go to stderr
- +- Linux: Add mount.nfs -o nconnect=4 (see
- + https://www.suse.com/support/kb/doc/?id=000019933)
- +
- +
- +**** Testing:
- +- Check whether SSH -p port works
- +- Check whether user in ssh+nfs:// URLs works
- +- Check whether ports in ssh+nfs:// URLs works
- +
- +# EOF.
- diff --git a/cygwin/utils/sshnfs/sshnfs.ksh b/cygwin/utils/sshnfs/sshnfs.ksh
- new file mode 100644
- index 0000000..7f28ed2
- --- /dev/null
- +++ b/cygwin/utils/sshnfs/sshnfs.ksh
- @@ -0,0 +1,537 @@
- +#!/bin/ksh93
- +
- +#
- +# sshnfs - remote login client with NFSv4 forwarding
- +#
- +
- +#
- +# Example usage:
- +# $ ksh sshnfs.ksh -o NFSURL=ssh+nfs://localhost/export/home/rmainz root@10.49.28.10 #
- +# $ ksh sshnfs.ksh -o NFSURL=nfs://localhost/export/home/rmainz root@10.49.20.207 #
- +# $ ksh sshnfs.ksh -o NFSURL=nfs://derfwpc5131/export/home/rmainz root@10.49.28.10 #
- +# $ ksh sshnfs.ksh -o NFSURL=ssh+nfs://derfwpc5131/export/home/rmainz -o SSHNFSJumphost=rmainz@derfwpc5131,roland.mainz@derfwnb8353 -J rmainz@derfwpc5131,roland.mainz@derfwnb8353 root@10.49.20.207
- +# $ ksh sshnfs.ksh -o NFSURL=ssh+nfs://derfwpc5131/export/home/rmainz target@fe80::d6f5:27ff:fe2b:8588%enp2s0
- +# $ ksh sshnfs.ksh -o NFSURL=ssh+nfs://root@derfwpc5131/export/home/rmainz root@10.49.28.56
- +# $ ksh sshnfs.ksh -o NFSServerSSHLoginName=root -o NFSURL=ssh+nfs://derfwpc5131/export/home/rmainz root@10.49.28.56
- +# $ SSHNFS_OPTIONS='-o NFSServerSSHLoginName=root -o NFSURL=ssh+nfs://derfwpc5131/export/home/rmainz' sshnfs.ksh root@10.49.28.56
- +#
- +
- +#
- +# Written by Roland Mainz <roland.mainz@nrubsig.org>
- +#
- +
- +#
- +# simple netstat -n parser
- +#
- +function netstat_list_connections
- +{
- + set -o nounset
- + nameref data=$1
- +
- + compound out=( typeset stdout stderr ; integer res )
- +
- + out.stderr="${ { out.stdout="${ LC_ALL='POSIX' PATH='/usr/bin:/bin' netstat -a -n ; (( out.res=$? )) ; }" ; } 2>&1 ; }"
- + if (( out.res != 0 )) ; then
- + print -u2 -f $"%s: netstat returned %d exit code.\n" \
- + "$0" out.res
- + return 1
- + fi
- + if [[ "${out.stderr}" != '' ]] ; then
- + #
- + # Handle known Linux netstat warnings
- + #
- + if [[ "${out.stderr}" != $'warning, got bogus unix line.' ]] ; then
- + print -u2 -f $"%s: netstat returned unknown error message %q.\n" \
- + "$0" "${out.stderr}"
- + return 1
- + fi
- + fi
- +
- + typeset -a data.connections
- + typeset l
- + typeset leftover
- + integer dci=0 # data.connections array index
- +
- + while read l ; do
- + leftover="${l/~(Elrx)
- + (?: # non-capturing group
- + #
- + # regex group for tcp,udp
- + #
- + (tcp|tcp6|udp|udp6|raw|raw6|sctp|sctp6) # Proto
- + [[:space:]]+
- + ([[:digit:]]+) # Recv-Q
- + [[:space:]]+
- + ([[:digit:]]+) # Send-Q
- + [[:space:]]+
- + ([^[:space:]]+) # Local Address
- + [[:space:]]+
- + ([^[:space:]]+) # Foreign Address
- + (?:
- + |
- + [[:space:]]+
- + ([^[:space:]]*?) # State (Optional)
- + )
- + |
- + #
- + # regex for unix
- + #
- + (unix) # Proto
- + [[:space:]]+
- + ([[:digit:]]+) # RefCnt
- + [[:space:]]+
- + (\[.+?\]) # Flags
- + [[:space:]]+
- + ([^[:space:]]+) # Type
- + [[:space:]]+
- + ([^[:space:]]*?) # State (optional)
- + [[:space:]]+
- + ([[:digit:]]+) # I-Node
- + (?:
- + |
- + [[:space:]]+
- + ([^[:space:]]+) # Path (optional)
- + )
- + )
- + /X}"
- +
- + # If the regex above did not match then .sh.match
- + # remains untouched, so we might see data from the
- + # previous round.
- + # So we check the "leftover" var whether it just
- + # contains the dummy value of "X" to indicate a
- + # successful regex match
- + if [[ "$leftover" == 'X' ]] ; then
- + #print -v .sh.match
- +
- + if [[ "${.sh.match[1]-}" != '' ]] ; then
- + nameref dcn=data.connections[$dci]
- +
- + typeset dcn.proto="${.sh.match[1]}"
- + typeset dcn.recv_q="${.sh.match[2]}"
- + typeset dcn.send_q="${.sh.match[3]}"
- + typeset dcn.local_address="${.sh.match[4]}"
- + typeset dcn.foreign_address="${.sh.match[5]}"
- + typeset dcn.state="${.sh.match[6]}"
- + ((dci++))
- + elif [[ "${.sh.match[7]-}" != '' ]] ; then
- + nameref dcn=data.connections[$dci]
- +
- + typeset dcn.proto="${.sh.match[7]}"
- + typeset dcn.refcnt="${.sh.match[8]}"
- + typeset dcn.flags="${.sh.match[9]}"
- + typeset dcn.type="${.sh.match[10]}"
- + [[ "${.sh.match[11]}" != '' ]] && typeset dcn.state="${.sh.match[11]}"
- + typeset dcn.inode="${.sh.match[12]}"
- + [[ "${.sh.match[13]}" != '' ]] && typeset dcn.path="${.sh.match[13]}"
- + ((dci++))
- + fi
- + else
- + true
- + #printf $"leftover=%q\n" "${leftover}"
- + fi
- + done <<<"${out.stdout}"
- +
- + return 0
- +}
- +
- +function netstat_list_active_local_tcp_connections
- +{
- + set -o nounset
- + nameref ar=$1
- + compound c
- + integer port
- + integer i
- +
- + netstat_list_connections c || return 1
- + #print -v c
- +
- + [[ -v ar ]] || integer -a ar
- +
- + for i in "${!c.connections[@]}" ; do
- + nameref n=c.connections[$i]
- +
- + # look for only for TCP connections which match
- + # 127.0.*.* or IPv6 ::1 for localhost
- + # 0.0.0.0 or IPv6 :: for all addresses (e.g. servers)
- + if [[ "${n.proto}" == ~(El)tcp && \
- + "${n.local_address}" == ~(Elr)((127\.0\..+|::1)|(::|0\.0\.0\.0|)):[[:digit:]]+ ]] ; then
- +
- + port="${n.local_address##*:}"
- + #printf $"port = %d\n" port
- +
- + (( ar[port]=1 ))
- + fi
- + done
- +
- + return 0
- +}
- +
- +function netstat_find_next_free_local_tcp_port
- +{
- + set -o nounset
- + compound c=( integer -a ar )
- + nameref ret_free_port=$1
- + integer start_port
- + integer end_port
- + integer i
- +
- + netstat_list_active_local_tcp_connections c.ar || return 1
- +
- + #print -v c
- +
- + (( start_port=$2 ))
- + if (( $# > 2 )) ; then
- + (( end_port=$3 ))
- + else
- + (( end_port=65535 ))
- + fi
- +
- + for ((i=start_port ; i < end_port ; i++ )) ; do
- + if [[ ! -v c.ar[i] ]] ; then
- + (( ret_free_port=i ))
- + return 0
- + fi
- + done
- +
- + return 1
- +}
- +
- +#
- +# parse_rfc1738_url - parse RFC 1838 URLs
- +#
- +# Output variables are named after RFC 1838 Section 5 ("BNF for
- +# specific URL schemes")
- +#
- +function parse_rfc1738_url
- +{
- + set -o nounset
- +
- + typeset url="$2"
- + typeset leftover
- + nameref data="$1" # output compound variable
- +
- + # ~(E) is POSIX extended regular expression matching (instead
- + # of shell pattern), "x" means "multiline", "l" means "left
- + # anchor", "r" means "right anchor"
- + leftover="${url/~(Elrx)
- + (.+?) # scheme
- + :\/\/ # '://'
- + ( # login
- + (?:
- + (.+?) # user (optional)
- + (?::(.+))? # password (optional)
- + @
- + )?
- + ( # hostport
- + (.+?) # host
- + (?::([[:digit:]]+))? # port (optional)
- + )
- + )
- + (?:\/(.*?))?/X}" # path (optional)
- +
- + # All parsed data should be captured via eregex in .sh.match - if
- + # there is anything left (except the 'X') then the input string did
- + # not properly match the eregex
- + [[ "$leftover" == 'X' ]] ||
- + { print -u2 -f $"%s: Parser error, leftover=%q\n" \
- + "$0" "$leftover" ; return 1 ; }
- +
- + data.url="${.sh.match[0]}"
- + data.scheme="${.sh.match[1]}"
- + data.login="${.sh.match[2]}"
- + # FIXME: This should use [[ ! -v .sh.match[3] ]], but ksh93u has bugs
- + [[ "${.sh.match[3]-}" != '' ]] && data.user="${.sh.match[3]}"
- + [[ "${.sh.match[4]-}" != '' ]] && data.password="${.sh.match[4]}"
- + data.hostport="${.sh.match[5]}"
- + data.host="${.sh.match[6]}"
- + [[ "${.sh.match[7]-}" != '' ]] && integer data.port="${.sh.match[7]}"
- + [[ "${.sh.match[8]-}" != '' ]] && data.uripath="${.sh.match[8]}"
- +
- + return 0
- +}
- +
- +
- +function parse_sshnfs_url
- +{
- + typeset url="$2"
- + nameref data="$1"
- +
- + parse_rfc1738_url data "$url" || return 1
- +
- + [[ "${data.scheme}" == ~(Elr)(ssh\+nfs|nfs) ]] || \
- + { print -u2 -f $"%s: Not a nfs:// or ssh+nfs:// url\n" "$0" ; return 1 ; }
- + [[ "${data.host}" != '' ]] || { print -u2 -f $"%s: NFS hostname missing\n" "$0" ; return 1 ; }
- + [[ "${data.uripath}" != '' ]] || { print -u2 -f $"%s: NFS path missing\n" "$0" ; return 1 ; }
- +
- + return 0
- +}
- +
- +
- +function main
- +{
- + set -o nounset
- + typeset mydebug=false # fixme: should be "bool" for ksh93v
- + integer i
- + integer retval
- + compound c
- +
- + #
- + # Expand SSHNFS_OPTIONS before arguments given to sshnfs.ksh
- + # By default we use IFS=$' \t\n' for argument splitting
- + #
- + typeset -a c.args=( ${SSHNFS_OPTIONS-} "$@" )
- +
- + for ((i=0 ; i < ${#c.args[@]} ; i++)) ; do
- + if [[ "${c.args[i]}" == '-o' ]] ; then
- + case "${c.args[i+1]-}" in
- + ~(Eli)NFSServerSSHLoginName=)
- + # User name for SSH login to NFS server
- + typeset c.nfsserver_ssh_login_name="${c.args[i+1]/~(Eli)NFSServerSSHLoginName=}"
- +
- + unset c.args[$i] c.args[$((i+1))]
- + ((i++))
- + ;;
- +
- + ~(Eli)NFSURL=)
- + unset c.nfs_server
- + compound c.nfs_server
- + typeset c.url="${c.args[i+1]/~(Eli)NFSURL=}"
- + parse_sshnfs_url c.nfs_server "${c.url}" || return 1
- +
- + unset c.args[$i] c.args[$((i+1))]
- + ((i++))
- + ;;
- +
- + ~(Eli)SSHNFSJumphost=)
- + [[ ! -v c.ssh_jumphost_args ]] && typeset -a c.ssh_jumphost_args
- + c.ssh_jumphost_args+=( "-J" "${c.args[i+1]/~(Eli)SSHNFSJumphost=}" )
- +
- + unset c.args[$i] c.args[$((i+1))]
- + ((i++))
- + ;;
- +
- + ~(Eli)SSHNFSlocal_forward_port=)
- + # command(1) prevents that the shell interpreter
- + # exits if typeset produces a syntax error
- + command integer c.local_forward_port="${c.args[i+1]/~(Eli)SSHNFSlocal_forward_port=}" || return 1
- +
- + unset c.args[$i] c.args[$((i+1))]
- + ((i++))
- + ;;
- + esac
- + fi
- + done
- +
- + if [[ -v c.nfs_server ]] ; then
- + if [[ ! -v c.nfs_server.port ]] ; then
- + # use # default NFSv4 TCP port number (see
- + # $ getent services nfs #)
- + integer c.nfs_server.port=2049
- + fi
- +
- + case "${c.nfs_server.scheme}" in
- + 'ssh+nfs')
- + #
- + # Find free local forwarding port...
- + #
- +
- + # TCP port on destination machine where we forward the
- + # NFS port from the server
- + integer c.destination_nfs_port=33049
- +
- + # port on THIS machine
- + if [[ ! -v c.local_forward_port ]] ; then
- + integer c.local_forward_port
- +
- + (( i=34049 ))
- + if ! netstat_find_next_free_local_tcp_port c.local_forward_port $i ; then
- + print -u2 -f "%s: netstat_find_next_free_local_tcp_port failed.\n" "$0"
- + return 1
- + fi
- +
- + #
- + # ... and adjust c.destination_nfs_port by the same offset
- + # we do that so that multiple sshnfs.ksh logins to the same
- + # machine do try to use the same ports on that machine
- + #
- + (( c.destination_nfs_port += ((c.local_forward_port-i) % 65535) ))
- +
- + # TCP ports below 1024 are reserved for the system, so stay away from them
- + (( (c.destination_nfs_port <= 1024) && (c.destination_nfs_port += 34049) ))
- + fi
- +
- + ${mydebug} && printf $"debug: c.local_forward_port=%d, c.destination_nfs_port=%d\n" \
- + c.local_forward_port \
- + c.destination_nfs_port
- +
- + c.ssh_control_socket_name="/tmp/sshnfs_ssh-control-socket_logname${LOGNAME}_ppid${PPID}_pid$$"
- +
- + #
- + # Find SSH login user name for NFS server
- + #
- + if [[ -v c.nfs_server.user ]] ; then
- + typeset c.nfsserver_ssh_login_name="${c.nfs_server.user}"
- + fi
- + if [[ ! -v c.nfsserver_ssh_login_name ]] ; then
- + # default user name if neither URL nor
- + # "-o NFSServerSSHLoginName=..." were given
- + typeset c.nfsserver_ssh_login_name="$LOGNAME"
- + fi
- +
- + #
- + # Forward NFS port from server to local machine
- + #
- + # Notes:
- + # - We use $ ssh -M ... # here as a way to terminate the port
- + # forwarding process later using "-O exit" without the need
- + # for a pid
- + #
- + print -u2 -f $"# Please enter the login data for NFS server (%s):\n" \
- + "${c.nfsserver_ssh_login_name}@${c.nfs_server.host}"
- +
- + #
- + # Notes:
- + # - fixme: c.nfs_server.port is fixed
- + # for ssh+nfs://-URLs, so for now we
- + # have to hardcode TCP/2049 for now
- + # - We use aes128-cbc,aes128-ctr ciphers for better
- + # throughput (see https://bash-prompt.net/guides/bash-ssh-ciphers/
- + # for a benchmark) and lower latency, as NFS is
- + # a bit latency-sensitive
- + # - We turn compression off, as it incrases latency
- + #
- + ssh \
- + -L "${c.local_forward_port}:localhost:2049" \
- + -M -S "${c.ssh_control_socket_name}" \
- + -N \
- + -f -o 'ExitOnForwardFailure=yes' \
- + -o 'Compression=no' \
- + -o 'Ciphers=aes128-cbc,aes128-ctr' \
- + "${c.ssh_jumphost_args[@]}" \
- + "${c.nfsserver_ssh_login_name}@${c.nfs_server.host}"
- + if (( $? != 0 )) ; then
- + print -u2 -f $"%s: NFS forwarding ssh failed with error code %d\n" "$0" $?
- + return 1
- + fi
- +
- + # debug
- + ${mydebug} && \
- + ssh \
- + -S "${c.ssh_control_socket_name}" \
- + -O 'check' \
- + "${c.nfsserver_ssh_login_name}@${c.nfs_server.host}"
- +
- + print -u2 -f $"# Use this to mount the directory:\n"
- + print -u2 -f $"# $ mkdir /mnt_nfs\n"
- + print -u2 -f $"# $ mount -vvv -t nfs -o vers=4.2,port=%d localhost:/%s /mnt_nfs\n" \
- + c.destination_nfs_port \
- + "${c.nfs_server.uripath}"
- +
- + #
- + # add NFS forwarding options to main ssh argument list
- + #
- + # Notes:
- + # - We use aes128-cbc,aes128-ctr ciphers for better
- + # throughput (see https://bash-prompt.net/guides/bash-ssh-ciphers/
- + # for a benchmark) and lower latency, as NFS is
- + # a bit latency-sensitive
- + # - We turn compression off, as it incrases latency
- + #
- + c.args=(
- + '-R' "${c.destination_nfs_port}:localhost:${c.local_forward_port}"
- + '-o' 'ExitOnForwardFailure=yes'
- + '-o' 'Compression=no'
- + '-o' 'Ciphers=aes128-cbc,aes128-ctr'
- + "${c.args[@]}"
- + )
- + ;;
- + 'nfs')
- + #
- + # Validate configuration
- + #
- + if [[ -v c.ssh_jumphost_args ]] ; then
- + print -u2 -f $"%s: Error: SSHNFSJumphost cannot be used for nfs://-URLs\n" "$0"
- + return 2
- + fi
- + if [[ -v c.nfs_server.user ]] ; then
- + print -u2 -f $"%s: Error: 'user' in URLs is not used in nfs://-URLs\n" "$0"
- + return 2
- + fi
- + if [[ -v c.nfs_server.password ]] ; then
- + print -u2 -f $"%s: Error: 'password' in URLs is not used in nfs://-URLs\n" "$0"
- + return 2
- + fi
- +
- + #
- + # Guess a TCP port number which might be
- + # free on the destination machine
- + #
- + integer myuid=$(id -u)
- + integer mypid=$$ # used to circumvent ksh93 -n warning
- +
- + # TCP port on destination machine where we forward the
- + # NFS port from the server
- + integer c.destination_nfs_port=33049
- +
- + # try to adjust c.destination_nfs_port so that multiple sshnfs.ksh
- + # sessions do intefere with each other
- + # (16381 is a prime number)
- + (( c.destination_nfs_port += (mypid+myuid+PPID) % 16381 ))
- +
- + print -u2 -f $"# Use this to mount the directory:\n"
- + print -u2 -f $"# $ mkdir /mnt_nfs\n"
- + print -u2 -f $"# $ mount -vvv -t nfs -o vers=4.2,port=%d localhost:/%s /mnt_nfs\n" \
- + c.destination_nfs_port \
- + "${c.nfs_server.uripath}"
- +
- + #
- + # add NFS forwarding options to main ssh argument list
- + #
- + # Notes:
- + # - We use aes128-cbc,aes128-ctr ciphers for better
- + # throughput (see https://bash-prompt.net/guides/bash-ssh-ciphers/
- + # for a benchmark) and lower latency, as NFS is
- + # a bit latency-sensitive
- + # - We turn compression off, as it incrases latency
- + #
- + c.args=(
- + '-R' "${c.destination_nfs_port}:${c.nfs_server.host}:${c.nfs_server.port}"
- + '-o' 'ExitOnForwardFailure=yes'
- + '-o' 'Compression=no'
- + '-o' 'Ciphers=aes128-cbc,aes128-ctr'
- + "${c.args[@]}"
- + )
- + ;;
- + *)
- + print -u2 -f $"%s: Unknown URL scheme %q\n" "$0" "${c.nfs_server.scheme}"
- + return 2
- + ;;
- + esac
- + fi
- +
- + # debug: print application data (compound c)
- + ${mydebug} && print -v c
- +
- + print -u2 -f $"# ssh login data for destination machine:\n"
- + ssh "${c.args[@]}" ; (( retval=$? ))
- +
- + if [[ -v c.ssh_control_socket_name ]] ; then
- + ssh \
- + -S "${c.ssh_control_socket_name}" \
- + -O 'exit' \
- + "${c.nfsserver_ssh_login_name}@${c.nfs_server.host}"
- + fi
- +
- + wait
- +
- + return $retval
- +}
- +
- +#
- +# main
- +#
- +main "$@"
- +exit $?
- +
- +# EOF.
- --
- 2.39.0
msnfs42client: Cygwin scripts
Posted by Anonymous on Mon 9th Oct 2023 17:46
raw | new post
Submit a correction or amendment below (click here to make a fresh posting)
After submitting an amendment, you'll be able to view the differences between the old and new posts easily.