pastebin - collaborative debugging tool
rovema.kpaste.net RSS


msnfs42client: Cygwin scripts
Posted by Anonymous on Mon 9th Oct 2023 17:46
raw | new post

  1. From e442d43bb8577e9d85b4a9e72436b47a049105a4 Mon Sep 17 00:00:00 2001
  2. From: Roland Mainz <roland.mainz@nrubsig.org>
  3. Date: Mon, 9 Oct 2023 18:33:23 +0200
  4. Subject: [PATCH 2/2] Adding initial Cygin README and devel/utility scripts
  5.  
  6. cygwin/README.txt
  7. cygwin/devel/msnfs41client.bash
  8. cygwin/utils/mount_sshnfs/README.txt
  9. cygwin/utils/mount_sshnfs/mount_sshnfs.ksh
  10. cygwin/utils/sshnfs/README.txt
  11. cygwin/utils/sshnfs/sshnfs.ksh
  12.  
  13. Signed-off-by: Cedric Blancher <cedric.blancher@gmail.com>
  14. ---
  15. cygwin/README.txt                          |  69 ++
  16.  cygwin/devel/msnfs41client.bash            | 287 +++++++
  17.  cygwin/utils/mount_sshnfs/README.txt       |  21 +
  18.  cygwin/utils/mount_sshnfs/mount_sshnfs.ksh | 905 +++++++++++++++++++++
  19.  cygwin/utils/sshnfs/README.txt             |  21 +
  20.  cygwin/utils/sshnfs/sshnfs.ksh             | 537 ++++++++++++
  21.  6 files changed, 1840 insertions(+)
  22.  create mode 100644 cygwin/README.txt
  23.  create mode 100644 cygwin/devel/msnfs41client.bash
  24.  create mode 100644 cygwin/utils/mount_sshnfs/README.txt
  25.  create mode 100644 cygwin/utils/mount_sshnfs/mount_sshnfs.ksh
  26.  create mode 100644 cygwin/utils/sshnfs/README.txt
  27.  create mode 100644 cygwin/utils/sshnfs/sshnfs.ksh
  28.  
  29. diff --git a/cygwin/README.txt b/cygwin/README.txt
  30. new file mode 100644
  31. index 0000000..974e7b3
  32. --- /dev/null
  33. +++ b/cygwin/README.txt
  34. @@ -0,0 +1,69 @@
  35. +# ms-nfs41-client/cygwin/README.txt
  36. +
  37. +#### Building ms-nfs41-client using Cygwin:
  38. +** Required software:
  39. +- Visual Studio 19
  40. +- WDK for Windows 10, version 2004, from
  41. +  https://go.microsoft.com/fwlink/?linkid=2128854
  42. +
  43. +** Building the project using GUI:
  44. +1. Start Visual Studio 19
  45. +2. Load the project file "build.vc19/nfs41-client.sln"
  46. +3. Select menu item "Build/Build solution" as "Debug/x64"
  47. +4. Select menu item "Build/Build solution" as "Release/x64"
  48. +
  49. +** Build the project using Cygwin command line (bash/ksh93):
  50. +export PATH+=":/cygdrive/c/Program Files (x86)/Microsoft Visual Studio/2019/Community/MSBuild/Current/Bin/"
  51. +git clone https://github.com/kofemann/ms-nfs41-client.git
  52. +cd ms-nfs41-client
  53. +MSBuild.exe build.vc19/nfs41-client.sln -t:Build -p:Configuration=Debug -p:Platform=x64
  54. +MSBuild.exe build.vc19/nfs41-client.sln -t:Build -p:Configuration=Release -p:Platform=x64
  55. +
  56. +** Make release blob:
  57. +mkdir dist
  58. +cd dist/
  59. +cp ../build.vc19/x64/Debug/nfsd.exe nfsd_debug.exe
  60. +cp ../build.vc19/x64/Release/* .
  61. +cp ../nfs41rdr.inf .
  62. +cp ../etc_netconfig .
  63. +cp ../ms-nfs41-idmap.conf .
  64. +
  65. +
  66. +#### Install release blob (requires mintty.exe running as "Adminstrator"):
  67. +cd ms-nfs41-client/dist
  68. +bash ../cygwin/devel/msnfs41client.bash install
  69. +
  70. +
  71. +#### Run nfs41 client:
  72. +** Run deamon:
  73. +(requires to modify "msnfs41client.bash")
  74. +bash ../cygwin/devel/msnfs41client.bash run_daemon
  75. +
  76. +
  77. +** mount home dir:
  78. +(requires to modify "msnfs41client.bash")
  79. +bash ../cygwin/devel/msnfs41client.bash mount_homedir
  80. +
  81. +
  82. +#### Testing:
  83. +** "cthon04" test suite:
  84. +git clone https://github.com/kofemann/ms-nfs41-client.git
  85. +git clone git://git.linux-nfs.org/projects/steved/cthon04.git
  86. +cd cthon04/
  87. +git checkout 8cefaa2ecf8d5c1240f1573530f07cfbbfc092ea
  88. +git am ../ms-nfs41-client/tests/*.patch
  89. +make 2>&1 | tee buildlog.log
  90. +mkdir testdir1
  91. +./runtests -a -t "$PWD/testdir1" 2>&1 | tee testrun.log
  92. +
  93. +
  94. +#### ToDo:
  95. +- POSIX Makefile for easier build, release blob generaetion, local test
  96. +installation, running cthon4 etc
  97. +- DocBook/XML based documentation
  98. +- Document how to get and build ksh93 for Cygwin
  99. +- Cygwin-specific binary release blob
  100. +- Document the usage of utils/mount_sshnfs/ and utils/sshnfs/
  101. +- Add test code for SID etc mapping
  102. +
  103. +# EOF.
  104. diff --git a/cygwin/devel/msnfs41client.bash b/cygwin/devel/msnfs41client.bash
  105. new file mode 100644
  106. index 0000000..e4dd5df
  107. --- /dev/null
  108. +++ b/cygwin/devel/msnfs41client.bash
  109. @@ -0,0 +1,287 @@
  110. +#!/bin/bash
  111. +
  112. +#
  113. +# msnfs41client.bash - simple Cygwin frontent for the msnfsv41
  114. +# NFSv4.1 filesystem driver
  115. +#
  116. +
  117. +#
  118. +# Written by Roland Mainz <roland.mainz@nrubsig.org>
  119. +#
  120. +
  121. +#
  122. +# Examples:
  123. +#
  124. +# 1. Mount for current users:
  125. +# (requires PsExec from https://download.sysinternals.com/files/PSTools.zip
  126. +# in /home/roland_mainz/work/win_pstools/)
  127. +# * Usage:
  128. +# Shell1: cd /cygdrive/c/Users/roland_mainz/Downloads/ms-nfs41-client-x64/ms-nfs41-client-x64 && bash ../msnfs41client.bash run_deamon
  129. +# Shell2: cd /cygdrive/c/Users/roland_mainz/Downloads/ms-nfs41-client-x64/ms-nfs41-client-x64 && bash ../msnfs41client.bash mount_homedir
  130. +#
  131. +# 2. Mount for all users:
  132. +# * Requires:
  133. +# - Windows admin rights (Cygwin --> Run terminal as Adminstrator)
  134. +# - PsExec from https://download.sysinternals.com/files/PSTools.zip in /home/roland_mainz/work/win_pstools/)
  135. +# * Usage:
  136. +# Shell1: cd /cygdrive/c/Users/roland_mainz/Downloads/ms-nfs41-client-x64/ms-nfs41-client-x64 && bash ../msnfs41client.bash sys_run_deamon
  137. +# Shell2: cd /cygdrive/c/Users/roland_mainz/Downloads/ms-nfs41-client-x64/ms-nfs41-client-x64 && bash ../msnfs41client.bash sys_mount_homedir
  138. +#
  139. +
  140. +function is_windows_admin_account
  141. +{
  142. +       #
  143. +       # Test whether we have the Windows permissions to install DLLs
  144. +       # and the kernel module
  145. +       #
  146. +       # Usually Windows Adminstrator rights are indicated by the
  147. +       # membership in group "544(Administratoren)" (Cygwin maps
  148. +       # "SID S-1-5-32-544" to GID 544)
  149. +       #
  150. +       if [[ "$(id -G)" =~ (^|[[:space:]]+)544([[:space:]]+|$) ]] ; then
  151. +               return 0
  152. +       fi
  153. +       return 1
  154. +}
  155. +
  156. +function nfsclient_install
  157. +{
  158. +       set -o nounset
  159. +       set -o xtrace
  160. +       set -o errexit
  161. +
  162. +       if ! is_windows_admin_account ; then
  163. +               printf $"%s: Install requires Windows Adminstator permissions.\n" "$0"
  164. +               return 1
  165. +       fi
  166. +
  167. +       # make sure all binaries are executable, Windows cmd does
  168. +       # not care, but Cygwin&bash do.
  169. +       # If *.ddl are not executable nfs*.exe fail with 0xc0000022
  170. +       chmod a+x *.exe *.dll
  171. +
  172. +       if false ; then
  173. +               # install.bat needs PATH to include $PWD
  174. +               PATH="$PWD:$PATH" cmd /c install.bat
  175. +       else
  176. +               nfs_install
  177. +               rundll32 setupapi.dll,InstallHinfSection DefaultInstall 132 ./nfs41rdr.inf
  178. +       fi
  179. +
  180. +       mkdir -p /cygdrive/c/etc
  181. +       cp etc_netconfig /cygdrive/c/etc/netconfig
  182. +       cp ms-nfs41-idmap.conf /cygdrive/c/etc/.
  183. +
  184. +       bcdedit /set testsigning on
  185. +       regtool -s set '/HKEY_LOCAL_MACHINE/SYSTEM/CurrentControlSet/Services/Tcpip/Parameters/Domain' 'GLOBAL.LOC'
  186. +
  187. +       sc query Dfsc
  188. +       sc stop Dfsc || true
  189. +       sc config Dfsc start=disabled
  190. +
  191. +       sc query nfs41_driver
  192. +       domainname
  193. +
  194. +       openfiles /local ON
  195. +
  196. +       # check whether the driver really has been installed
  197. +       md5sum \
  198. +               "$PWD/nfs41_driver.sys" \
  199. +               '/cygdrive/c/Windows/System32/drivers/nfs41_driver.sys'
  200. +
  201. +       sync
  202. +
  203. +       return 0
  204. +}
  205. +
  206. +function nfsclient_rundeamon
  207. +{
  208. +       set -o xtrace
  209. +       set -o nounset
  210. +
  211. +       gdb -ex=run --args nfsd_debug -d 0 --noldap --gid 1616 --uid 1616
  212. +       return $?
  213. +}
  214. +
  215. +function nfsclient_system_rundeamon
  216. +{
  217. +       set -o xtrace
  218. +       set -o nounset
  219. +
  220. +       su_system gdb -ex=run --args nfsd_debug -d 0 --noldap --gid 1616 --uid 1616
  221. +       return $?
  222. +}
  223. +
  224. +function nfsclient_mount_homedir
  225. +{
  226. +       set -o xtrace
  227. +       set -o nounset
  228. +       set -o errexit
  229. +
  230. +       #nfs_mount -p -o sec=sys H 'derfwpc5131:/export/home/rmainz'
  231. +       # fixme: Specifying IPv6 addresses do not work yet, as soon as
  232. +       # they come as UNC paths (e.g.
  233. +       # $ cd '//[fe80::219:99ff:feae:73ce]@2049/nfs4/export/home/rmainz' #
  234. +       # they get corrupted once they arrive in nfsd_debug.exe)
  235. +       #nfs_mount -p -o sec=sys H '[fe80::219:99ff:feae:73ce]:/export/home/rmainz'
  236. +       nfs_mount -p -o sec=sys H 'derfwpc5131_ipv6:/export/home/rmainz'
  237. +       mkdir -p '/home/rmainz'
  238. +       mount -o bind,posix=1 '/cygdrive/h' '/home/rmainz'
  239. +       return $?
  240. +}
  241. +
  242. +function nfsclient_system_mount_homedir
  243. +{
  244. +       set -o xtrace
  245. +       set -o nounset
  246. +       set -o errexit
  247. +
  248. +       # purge any leftover persistent mappings to device H:
  249. +       su_system net use H: /delete || true
  250. +
  251. +       #su_system nfs_mount -p -o sec=sys H 'derfwpc5131:/export/home/rmainz'
  252. +       # fixme: Specifying IPv6 addresses do not work yet, as soon as
  253. +       # they come as UNC paths (e.g.
  254. +       # $ cd '//[fe80::219:99ff:feae:73ce]@2049/nfs4/export/home/rmainz' #
  255. +       # they get corrupted once they arrive in nfsd_debug.exe)
  256. +       #su_system nfs_mount -p -o sec=sys H '[fe80::219:99ff:feae:73ce]:/export/home/rmainz'
  257. +       su_system nfs_mount -p -o sec=sys H 'derfwpc5131_ipv6:/export/home/rmainz'
  258. +
  259. +       return $?
  260. +}
  261. +
  262. +function nfsclient_umount_homedir
  263. +{
  264. +       set -o xtrace
  265. +       set -o nounset
  266. +       typeset -i res
  267. +
  268. +       nfs_mount -d H
  269. +       (( res=$? ))
  270. +
  271. +       if (( res == 0 )) ; then
  272. +               # remove bind mount
  273. +               umount '/home/rmainz' && rmdir '/home/rmainz'
  274. +       fi
  275. +
  276. +       return $res
  277. +}
  278. +
  279. +function require_cmd
  280. +{
  281. +       typeset cmd="$1"
  282. +
  283. +       if ! which "$cmd" >'/dev/null' 2>&1 ; then
  284. +               printf $"%s: %q not found in %q\n" "$0" "$cmd" "$PWD" 1>&2
  285. +               return 1
  286. +       fi
  287. +       return 0
  288. +}
  289. +
  290. +# execute cmd as Windows user "SYSTEM"
  291. +function su_system
  292. +{
  293. +       typeset cmd="$1"
  294. +       shift
  295. +      
  296. +       typeset abspath_cmd="$(which "$cmd")"
  297. +       if [[ ! -x "$abspath_cmd" ]] ; then
  298. +               printf "%s: Command %q not found." $"su_system" "$abspath_cmd" 1>&2
  299. +               return 127
  300. +       fi
  301. +
  302. +       PsExec \
  303. +               -accepteula -nobanner \
  304. +               -s \
  305. +               -w "$(cygpath -w "$PWD")" \
  306. +               "$(cygpath -w "$abspath_cmd")" "$@"
  307. +}
  308. +
  309. +function sys_terminal
  310. +{
  311. +       # su_system does not work, mintty requires PsExec -i
  312. +       PsExec -accepteula -nobanner \
  313. +               -i \
  314. +               -s -w "$(cygpath -w "$PWD")" \
  315. +               'C:\cygwin64\bin\mintty.exe'
  316. +}
  317. +
  318. +function main
  319. +{
  320. +       typeset cmd="$1"
  321. +
  322. +       # "$PATH:/usr/bin:/bin" is used for PsExec where $PATH might be empty
  323. +       export PATH="$PWD:$PATH:/usr/bin:/bin"
  324. +      
  325. +       # my own path to pstools
  326. +       PATH+=':/home/roland_mainz/work/win_pstools/'
  327. +
  328. +       case "$cmd" in
  329. +               'install')
  330. +                       nfsclient_install
  331. +                       return $?
  332. +                       ;;
  333. +               'run_deamon' | 'run_daemon')
  334. +                       require_cmd 'nfsd.exe' || return 1
  335. +                       require_cmd 'nfsd_debug.exe' || return 1
  336. +                       require_cmd 'nfs_mount.exe' || return 1
  337. +                       nfsclient_rundeamon
  338. +                       return $?
  339. +                       ;;
  340. +               'sys_run_deamon' | 'sys_run_daemon')
  341. +                       require_cmd 'PsExec.exe' || return 1
  342. +                       require_cmd 'nfsd.exe' || return 1
  343. +                       require_cmd 'nfsd_debug.exe' || return 1
  344. +                       require_cmd 'nfs_mount.exe' || return 1
  345. +                       if ! is_windows_admin_account ; then
  346. +                               printf $"%s: %q requires Windows Adminstator permissions.\n" "$0" "$cmd"
  347. +                               return 1
  348. +                       fi
  349. +                       nfsclient_system_rundeamon
  350. +                       return $?
  351. +                       ;;
  352. +               'sys_mount_homedir')
  353. +                       require_cmd 'nfs_mount.exe' || return 1
  354. +                       if ! is_windows_admin_account ; then
  355. +                               printf $"%s: %q requires Windows Adminstator permissions.\n" "$0" "$cmd"
  356. +                               return 1
  357. +                       fi
  358. +                       nfsclient_system_mount_homedir
  359. +                       return $?
  360. +                       ;;
  361. +               'mount_homedir')
  362. +                       require_cmd 'nfs_mount.exe' || return 1
  363. +                       nfsclient_mount_homedir
  364. +                       return $?
  365. +                       ;;
  366. +               'umount_homedir')
  367. +                       require_cmd 'nfs_mount.exe' || return 1
  368. +                       nfsclient_umount_homedir
  369. +                       return $?
  370. +                       ;;
  371. +               # misc
  372. +               'sys_terminal')
  373. +                       require_cmd 'mintty.exe' || return 1
  374. +                       if ! is_windows_admin_account ; then
  375. +                               printf $"%s: %q requires Windows Adminstator permissions.\n" "$0" "$cmd"
  376. +                               return 1
  377. +                       fi
  378. +                       sys_terminal
  379. +                       return $?
  380. +                       ;;
  381. +               *)
  382. +                       printf $"%s: Unknown cmd %q\n" "$0" "$cmd" 1>&2
  383. +                       return 1
  384. +                       ;;
  385. +       esac
  386. +       return 1
  387. +}
  388. +
  389. +
  390. +#
  391. +# main
  392. +#
  393. +main "$@"
  394. +exit $?
  395. +
  396. +# EOF.
  397. diff --git a/cygwin/utils/mount_sshnfs/README.txt b/cygwin/utils/mount_sshnfs/README.txt
  398. new file mode 100644
  399. index 0000000..4d8a2ac
  400. --- /dev/null
  401. +++ b/cygwin/utils/mount_sshnfs/README.txt
  402. @@ -0,0 +1,21 @@
  403. +#
  404. +# mount_sshnfs/README.txt
  405. +#
  406. +
  407. +**** ToDo:
  408. +- Add umount -f option
  409. +- Add umount -v option
  410. +- Add mount -v/-vv/-vvv option
  411. +- Fix FIXME stuff
  412. +- on mount: Check whether the mount point exists
  413. +- mounting should enforce that we only try NFSv4, and not try NFSv3
  414. +- Implement "status" command to check on mount point and ssh
  415. +  forwarding process
  416. +- Implement "restart_forwarding" command to restart the ssh
  417. +  forwarding process if it terminated for some reason
  418. +- Debug messages should go to stderr
  419. +- Linux: Add mount.nfs -o nconnect=4 (see
  420. +  https://www.suse.com/support/kb/doc/?id=000019933)
  421. +- Add Linux umount helper support, see umount(8) HELPER section
  422. +
  423. +# EOF.
  424. diff --git a/cygwin/utils/mount_sshnfs/mount_sshnfs.ksh b/cygwin/utils/mount_sshnfs/mount_sshnfs.ksh
  425. new file mode 100644
  426. index 0000000..c485c81
  427. --- /dev/null
  428. +++ b/cygwin/utils/mount_sshnfs/mount_sshnfs.ksh
  429. @@ -0,0 +1,905 @@
  430. +#!/bin/ksh93
  431. +
  432. +#
  433. +# mount_sshnfs - mount NFSv4 filesystem through ssh tunnel
  434. +#
  435. +
  436. +#
  437. +# Example usage:
  438. +#
  439. +# 1. UNIX/Linux: Mount&&unmount /export/home/rmainz on NFS server "/export/home/rmainz":
  440. +# $ mkdir -p /foobarmnt
  441. +# $ ksh mount_sshnfs.ksh mount ssh+nfs://rmainz@derfwpc5131/export/home/rmainz /foobarmnt
  442. +# $ mount_sshnfs.ksh umount /foobarmnt
  443. +#
  444. +#
  445. +# 2. UNIX/Linux: Mount&&unmount /export/home/rmainz on NFS server "/export/home/rmainz" via SSH jumphost rmainz@10.49.20.131:
  446. +# $ mkdir -p /foobarmnt
  447. +# $ ksh mount_sshnfs.ksh mount -o ro,mount_sshnfs_jumphost=rmainz@10.49.20.131 ssh+nfs://rmainz@derfwpc5131/export/home/rmainz /foobarmnt
  448. +# $ mount_sshnfs.ksh umount /foobarmnt
  449. +#
  450. +
  451. +#
  452. +# For more examples see help and subcommand help:
  453. +# $ mount_sshnfs.ksh --man
  454. +# $ mount_sshnfs.ksh mount --man
  455. +# $ mount_sshnfs.ksh umount --man
  456. +#
  457. +
  458. +#
  459. +# Written by Roland Mainz <roland.mainz@nrubsig.org>
  460. +#
  461. +
  462. +function usage
  463. +{
  464. +       (( OPTIND=0 ))
  465. +       getopts -a "${1}" "${2}" OPT '-?'
  466. +       return 2
  467. +}
  468. +
  469. +
  470. +#    
  471. +# simple netstat -n parser
  472. +#    
  473. +function netstat_list_connections
  474. +{
  475. +       set -o nounset
  476. +       nameref data=$1
  477. +
  478. +       compound out=( typeset stdout stderr ; integer res )
  479. +
  480. +       out.stderr="${ { out.stdout="${ LC_ALL='POSIX' PATH='/usr/bin:/bin' netstat -a -n ; (( out.res=$? )) ; }" ; } 2>&1 ; }"
  481. +       if (( out.res != 0 )) ; then
  482. +               print -u2 -f $"%s: netstat returned %d exit code.\n" \
  483. +                       "$0" out.res
  484. +               return 1
  485. +       fi
  486. +       if [[ "${out.stderr}" != '' ]] ; then
  487. +               #
  488. +               # Handle known Linux netstat warnings
  489. +               #
  490. +               if [[ "${out.stderr}" != $'warning, got bogus unix line.' ]] ; then
  491. +                       print -u2 -f $"%s: netstat returned unknown error message %q.\n" \
  492. +                               "$0" "${out.stderr}"
  493. +                       return 1
  494. +               fi
  495. +       fi
  496. +
  497. +       typeset -a data.connections
  498. +       typeset l
  499. +       typeset leftover
  500. +       integer dci=0 # data.connections array index
  501. +
  502. +       while read l ; do
  503. +               leftover="${l/~(Elrx)
  504. +               (?: # non-capturing group
  505. +                       #
  506. +                       # regex group for tcp,udp
  507. +                       #
  508. +                       (tcp|tcp6|udp|udp6|raw|raw6|sctp|sctp6) # Proto
  509. +                       [[:space:]]+
  510. +                       ([[:digit:]]+)                  # Recv-Q
  511. +                       [[:space:]]+
  512. +                       ([[:digit:]]+)                  # Send-Q
  513. +                       [[:space:]]+
  514. +                       ([^[:space:]]+)                 # Local Address
  515. +                       [[:space:]]+
  516. +                       ([^[:space:]]+)                 # Foreign Address
  517. +                       (?:
  518. +                               |
  519. +                               [[:space:]]+
  520. +                               ([^[:space:]]*?)        # State (Optional)
  521. +                       )
  522. +               |
  523. +                       #
  524. +                       # regex for unix
  525. +                       #
  526. +                       (unix)                          # Proto
  527. +                       [[:space:]]+
  528. +                       ([[:digit:]]+)                  # RefCnt
  529. +                       [[:space:]]+
  530. +                       (\[.+?\])                       # Flags
  531. +                       [[:space:]]+
  532. +                       ([^[:space:]]+)                 # Type
  533. +                       [[:space:]]+
  534. +                       ([^[:space:]]*?)                # State (optional)
  535. +                       [[:space:]]+
  536. +                       ([[:digit:]]+)                  # I-Node
  537. +                       (?:
  538. +                               |
  539. +                               [[:space:]]+
  540. +                               ([^[:space:]]+)         # Path (optional)
  541. +                       )
  542. +               )
  543. +                       /X}"
  544. +
  545. +               # If the regex above did not match then .sh.match
  546. +               # remains untouched, so we might see data from the
  547. +               # previous round.
  548. +               # So we check the "leftover" var whether it just
  549. +               # contains the dummy value of "X" to indicate a
  550. +               # successful regex match
  551. +               if [[ "$leftover" == 'X' ]] ; then
  552. +                       #print -v .sh.match
  553. +
  554. +                       if [[ "${.sh.match[1]-}" != '' ]] ; then
  555. +                               nameref dcn=data.connections[$dci]
  556. +
  557. +                               typeset dcn.proto="${.sh.match[1]}"
  558. +                               typeset dcn.recv_q="${.sh.match[2]}"
  559. +                               typeset dcn.send_q="${.sh.match[3]}"
  560. +                               typeset dcn.local_address="${.sh.match[4]}"
  561. +                               typeset dcn.foreign_address="${.sh.match[5]}"
  562. +                               typeset dcn.state="${.sh.match[6]}"
  563. +                               ((dci++))
  564. +                       elif [[ "${.sh.match[7]-}" != '' ]] ; then
  565. +                               nameref dcn=data.connections[$dci]
  566. +
  567. +                               typeset dcn.proto="${.sh.match[7]}"
  568. +                               typeset dcn.refcnt="${.sh.match[8]}"
  569. +                               typeset dcn.flags="${.sh.match[9]}"
  570. +                               typeset dcn.type="${.sh.match[10]}"
  571. +                               [[ "${.sh.match[11]}" != '' ]] && typeset dcn.state="${.sh.match[11]}"
  572. +                               typeset dcn.inode="${.sh.match[12]}"
  573. +                               [[ "${.sh.match[13]}" != '' ]] && typeset dcn.path="${.sh.match[13]}"
  574. +                               ((dci++))
  575. +                       fi
  576. +               else
  577. +                       true
  578. +                       #printf $"leftover=%q\n" "${leftover}"
  579. +               fi
  580. +       done <<<"${out.stdout}"
  581. +
  582. +       return 0
  583. +}
  584. +
  585. +function netstat_list_active_local_tcp_connections
  586. +{
  587. +       set -o nounset
  588. +       nameref ar=$1
  589. +       compound c
  590. +       integer port
  591. +       integer i
  592. +
  593. +       netstat_list_connections c || return 1
  594. +       #print -v c
  595. +
  596. +       [[ -v ar ]] || integer -a ar
  597. +
  598. +       for i in "${!c.connections[@]}" ; do
  599. +               nameref n=c.connections[$i]
  600. +
  601. +               # look for only for TCP connections which match
  602. +               # 127.0.*.* or IPv6 ::1 for localhost
  603. +               # 0.0.0.0 or IPv6 :: for all addresses (e.g. servers)
  604. +               if [[ "${n.proto}" == ~(El)tcp && \
  605. +                       "${n.local_address}" == ~(Elr)((127\.0\..+|::1)|(::|0\.0\.0\.0|)):[[:digit:]]+ ]] ; then
  606. +
  607. +                       port="${n.local_address##*:}"
  608. +                       #printf $"port = %d\n" port
  609. +
  610. +                       (( ar[port]=1 ))
  611. +               fi
  612. +       done
  613. +
  614. +       return 0
  615. +}
  616. +
  617. +function netstat_find_next_free_local_tcp_port
  618. +{
  619. +       set -o nounset
  620. +       compound c=( integer -a ar )
  621. +       nameref ret_free_port=$1
  622. +       integer start_port
  623. +       integer end_port
  624. +       integer i
  625. +
  626. +       netstat_list_active_local_tcp_connections c.ar || return 1
  627. +
  628. +       #print -v c
  629. +
  630. +       (( start_port=$2 ))
  631. +       if (( $# > 2 )) ; then
  632. +               (( end_port=$3 ))
  633. +       else
  634. +               (( end_port=65535 ))
  635. +       fi
  636. +
  637. +       for ((i=start_port ; i < end_port ; i++ )) ; do
  638. +               if [[ ! -v c.ar[i] ]] ; then
  639. +                       (( ret_free_port=i ))
  640. +                       return 0
  641. +               fi
  642. +       done
  643. +
  644. +       return 1
  645. +}
  646. +
  647. +
  648. +#
  649. +# parse_rfc1738_url - parse RFC 1838 URLs
  650. +#
  651. +# Output variables are named after RFC 1838 Section 5 ("BNF for
  652. +# specific URL schemes")
  653. +#
  654. +function parse_rfc1738_url
  655. +{
  656. +       set -o nounset
  657. +
  658. +       typeset url="$2"
  659. +       typeset leftover
  660. +       nameref data="$1" # output compound variable
  661. +
  662. +       # ~(E) is POSIX extended regular expression matching (instead
  663. +       # of shell pattern), "x" means "multiline", "l" means "left
  664. +       # anchor", "r" means "right anchor"
  665. +       leftover="${url/~(Elrx)
  666. +               (.+?)                           # scheme
  667. +               :\/\/                           # '://'
  668. +               (                               # login
  669. +                       (?:
  670. +                               (.+?)           # user (optional)
  671. +                               (?::(.+))?      # password (optional)
  672. +                               @
  673. +                       )?
  674. +                       (                       # hostport
  675. +                               (.+?)           # host
  676. +                               (?::([[:digit:]]+))? # port (optional)
  677. +                       )
  678. +               )
  679. +               (?:\/(.*?))?/X}"                # path (optional)
  680. +
  681. +       # All parsed data should be captured via eregex in .sh.match - if
  682. +       # there is anything left (except the 'X') then the input string did
  683. +       # not properly match the eregex
  684. +       [[ "$leftover" == 'X' ]] ||
  685. +               { print -u2 -f $"%s: Parser error, leftover=%q\n" \
  686. +                       "$0" "$leftover" ; return 1 ; }
  687. +
  688. +       data.url="${.sh.match[0]}"
  689. +       data.scheme="${.sh.match[1]}"
  690. +       data.login="${.sh.match[2]}"
  691. +       # FIXME: This should use [[ ! -v .sh.match[3] ]], but ksh93u has bugs
  692. +       [[ "${.sh.match[3]-}" != '' ]] && data.user="${.sh.match[3]}"
  693. +       [[ "${.sh.match[4]-}" != '' ]] && data.password="${.sh.match[4]}"
  694. +       data.hostport="${.sh.match[5]}"
  695. +       data.host="${.sh.match[6]}"
  696. +       [[ "${.sh.match[7]-}" != '' ]] && integer data.port="${.sh.match[7]}"
  697. +       [[ "${.sh.match[8]-}" != '' ]] && data.uripath="${.sh.match[8]}"
  698. +
  699. +       return 0
  700. +}
  701. +
  702. +
  703. +function parse_sshnfs_url
  704. +{
  705. +       typeset url="$2"
  706. +       nameref data="$1"
  707. +
  708. +       parse_rfc1738_url data "$url" || return 1
  709. +
  710. +       [[ "${data.scheme}" == ~(Elr)(ssh\+nfs|nfs) ]] || \
  711. +               { print -u2 -f $"%s: Not a nfs:// or ssh+nfs:// url\n" "$0" ; return 1 ; }
  712. +       [[ "${data.host}" != '' ]] || { print -u2 -f $"%s: NFS hostname missing\n" "$0" ; return 1 ; }
  713. +       [[ "${data.uripath}" != '' ]] || { print -u2 -f $"%s: NFS path missing\n" "$0" ; return 1 ; }
  714. +
  715. +       return 0
  716. +}
  717. +
  718. +
  719. +function mountpoint2configfilename
  720. +{
  721. +       nameref configfilename=$1
  722. +       typeset mountpoint="$2"
  723. +
  724. +       #
  725. +       # FIXME:
  726. +       # - We should urlencode more than just '/'
  727. +       # - We should strip the leading '/'
  728. +       # - We should use realpath(1) for mountpoints here
  729. +       #
  730. +
  731. +       # .cpv means ComPound Variable"
  732. +       configfilename="/tmp/mount_sshnfs/${mountpoint//\//%2f}.cpv"
  733. +       return 0
  734. +}
  735. +
  736. +
  737. +function cmd_mount
  738. +{
  739. +       set -o nounset
  740. +       nameref c=$1
  741. +
  742. +       # fixme: Need better text layout for $ mount_sshnfs mount --man #
  743. +       typeset -r mount_sshnfs_cmdmount_usage=$'+
  744. +       [-?\n@(#)\$Id: mount_sshnfs mount (Roland Mainz) 2023-07-24 \$\n]
  745. +       [-author?Roland Mainz <roland.mainz@nrubsig.org>]
  746. +       [+NAME?mount_sshnfs mount - mount NFSv4 filesystem through ssh
  747. +               tunnel]
  748. +       [+DESCRIPTION?\bmount_sshnfs mount\b mounts a NFSv4 filesystem
  749. +               through a ssh tunnel.]
  750. +       [r:readonly?Mount file system readonly.]
  751. +       [w:readwrite?Mount file system read-write.]
  752. +       [o:options?Use the specified mount options.
  753. +               The opts argument is a comma-separated list.\n
  754. +               options starting with mount_sshnfs_jumphost_* will be
  755. +               consumed by mount_sshnfs, all other options will be
  756. +               passed through to mount.nfs.]:[options]{
  757. +               [+?mount_sshnfs options are:]{
  758. +                       [+?-o mount_sshnfs_jumphost=user@host:port - ssh jumphost]
  759. +                       [+?-o mount_sshnfs_local_forward_port=port - local TCP port
  760. +                               for SSH-forwarded NFS connection to server.
  761. +                               Defaults is to use netstat(1) to find a free TCP port]
  762. +                       }
  763. +               }
  764. +
  765. +       url mountpoint
  766. +      
  767. +       [+NOTES?]{
  768. +               [+?The original CITI Windows NFSv4 nfs_mount.exe does not
  769. +               support the port= option.\nUse -o mount_sshnfs_local_forward_port=2049
  770. +               as workaround. Newer versions from https://github.com/kofemann/ms-nfs41-client
  771. +               support the -o port=... option.]
  772. +               }
  773. +       [+SEE ALSO?\bksh93\b(1),\bssh\b(1),\bmount.nfs\b(8),\bnfs\b(5)]
  774. +       '
  775. +       typeset mydebug=false   # fixme: should be "bool" for ksh93v
  776. +       typeset c.url
  777. +       typeset c.mountpoint
  778. +       typeset config_filename
  779. +
  780. +       typeset -a c.mount_nfs_options
  781. +       integer i
  782. +       integer saved_optind_m1 # saved OPTIND-1
  783. +       typeset s               # generic temporary string variable
  784. +
  785. +       # remove subcmd name (in this case 'mount')
  786. +       unset c.args[0]
  787. +
  788. +       #
  789. +       # Expand MOUNT_SSHNFS_CMDMOUNT_OPTIONS before arguments given to
  790. +       # mount_sshnfs.ksh.
  791. +       # By default we use IFS=$' \t\n' for argument splitting
  792. +       #
  793. +       c.args=( ${MOUNT_SSHNFS_CMDMOUNT_OPTIONS-} "${c.args[@]}" )
  794. +
  795. +       #
  796. +       # Argument parsing
  797. +       #
  798. +       while getopts -a "${progname} mount" "${mount_sshnfs_cmdmount_usage}" OPT "${c.args[@]}" ; do
  799. +               case "${OPT}" in
  800. +                       'r')
  801. +                               c.mount_nfs_options+=( 'ro' )
  802. +                               ;;
  803. +                       'w')
  804. +                               c.mount_nfs_options+=( 'rw' )
  805. +                               ;;
  806. +                       'o')
  807. +                               #
  808. +                               # Split options like "-o foo=bar,baz=BAM"
  809. +                               # into "-o foo=bar -o baz=BAM" for easier
  810. +                               # processing below
  811. +                               IFS=$','
  812. +                               c.mount_nfs_options=( "${c.mount_nfs_options[@]}" ${OPTARG} )
  813. +                               IFS=$' \t\n'
  814. +                               ;;
  815. +                       *)
  816. +                               usage "${progname} mount" "${mount_sshnfs_cmdmount_usage}"
  817. +                               return $?
  818. +                               ;;
  819. +               esac
  820. +       done
  821. +
  822. +       (( saved_optind_m1=OPTIND-1 ))
  823. +
  824. +       # remove options we just parsed from c.args
  825. +       for ((i=0 ; i < saved_optind_m1 ; i++)) ; do
  826. +               unset c.args[$i]
  827. +       done
  828. +
  829. +
  830. +       #
  831. +       # Get remaining arguments
  832. +       #
  833. +       c.url="${c.args[saved_optind_m1+0]-}"
  834. +       c.mountpoint="${c.args[saved_optind_m1+1]-}"
  835. +
  836. +
  837. +       #
  838. +       # Filter out our options, other options are passed to mount.nfs
  839. +       #
  840. +       for ((i=0 ; i < ${#c.mount_nfs_options[@]} ; i++)) ; do
  841. +               s="${c.mount_nfs_options[$i]}"
  842. +
  843. +               #
  844. +               # Intercept options starting with eregex mount_sshnfs.+
  845. +               #
  846. +               if [[ "$s" == ~(Elr)mount_sshnfs.+=.+ ]] ; then
  847. +                       case "$s" in
  848. +                               ~(Eli)mount_sshnfs_jumphost=)
  849. +                                        [[ ! -v c.ssh_jumphost_args ]] && typeset -a c.ssh_jumphost_args
  850. +                                        c.ssh_jumphost_args+=( "-J" "${c.mount_nfs_options[i]/~(Eli)mount_sshnfs_jumphost=}" )
  851. +                                       ;;
  852. +                               ~(Eli)mount_sshnfs_local_forward_port=)
  853. +                                       # command(1) prevents that the shell interpreter
  854. +                                       # exits if typeset produces a syntax error
  855. +                                       command integer c.local_forward_port="${c.mount_nfs_options[i]/~(Eli)mount_sshnfs_local_forward_port=}" || return 1
  856. +                                       ;;
  857. +                               *)
  858. +                                       usage "${progname} mount" "${mount_sshnfs_cmdmount_usage}"
  859. +                                       return $?
  860. +                                       ;;
  861. +                       esac
  862. +                       unset c.mount_nfs_options[$i]
  863. +               fi
  864. +       done
  865. +
  866. +
  867. +       #
  868. +       # Parse url
  869. +       #
  870. +       parse_sshnfs_url c.nfs_server "${c.url}" || return 1
  871. +
  872. +       mountpoint2configfilename config_filename "${c.mountpoint}"
  873. +
  874. +       if [[ -f "${config_filename}" ]] ; then
  875. +               print -u2 -f $"%s: Config file %q for mount point %q found.\n" \
  876. +                       "$0" \
  877. +                       "$config_filename" \
  878. +                       "${c.mountpoint}"
  879. +               return 1
  880. +       fi
  881. +
  882. +       #
  883. +       # Prechecks for writing the config file
  884. +       #
  885. +       mkdir -p '/tmp/mount_sshnfs/'
  886. +       if [[ ! -w '/tmp/mount_sshnfs/' ]] ; then
  887. +               print -u2 -f $"%s: mount_nfs data directory %q not writeable.\n" \
  888. +                       "$0" \
  889. +                       '/tmp/mount_sshnfs/'
  890. +               return 1
  891. +       fi
  892. +
  893. +       ${mydebug} && print -v c
  894. +
  895. +       case "${c.nfs_server.scheme}" in
  896. +               'ssh+nfs')
  897. +                       #
  898. +                       # Find free local forwarding port
  899. +                       #
  900. +
  901. +                       # Note: Original CITI ms-nfsv41 client
  902. +                       # nfs_mount.exe
  903. +                       # does not support -o port=..., so we set a default
  904. +                       # here if it was not set yet
  905. +                       if (( c.is_ccygwin == 1 )) && [[ ! -v c.local_forward_port ]] ; then
  906. +                               integer c.local_forward_port=2049
  907. +                       fi
  908. +
  909. +                       # port on THIS machine
  910. +                       if [[ ! -v c.local_forward_port ]] ; then
  911. +                               integer c.local_forward_port
  912. +
  913. +                               (( i=34049 ))
  914. +                               if ! netstat_find_next_free_local_tcp_port c.local_forward_port $i ; then
  915. +                                       print -u2 -f "%s: netstat_find_next_free_local_tcp_port failed.\n" "$0"
  916. +                                       return 1
  917. +                               fi
  918. +                       fi
  919. +
  920. +
  921. +                       c.ssh_control_socket_name="/tmp/mount_sshnfs/mount_sshnfs_ssh-control-socket_logname${LOGNAME}_ppid${PPID}_pid$$"
  922. +
  923. +                       #
  924. +                       # Find SSH login user name for NFS server
  925. +                       #
  926. +                       if [[ -v c.nfs_server.user ]] ; then
  927. +                               typeset c.nfsserver_ssh_login_name="${c.nfs_server.user}"
  928. +                       fi
  929. +                       # fixme: Implement NFSServerSSHLoginName
  930. +                       if [[ ! -v c.nfsserver_ssh_login_name ]] ; then
  931. +                               # default user name if neither URL nor
  932. +                               # "-o NFSServerSSHLoginName=..." were given
  933. +                               typeset c.nfsserver_ssh_login_name="$LOGNAME"
  934. +                       fi
  935. +
  936. +                       #
  937. +                       # Forward NFS port from server to local machine
  938. +                       #
  939. +                       # Notes:
  940. +                       # - We use $ ssh -M ... # here as a way to terminate the port
  941. +                       # forwarding process later using "-O exit" without the need
  942. +                       # for a pid
  943. +                       #
  944. +                       print -u2 -f $"# Please enter the login data for NFS server (%s):\n" \
  945. +                               "${c.nfsserver_ssh_login_name}@${c.nfs_server.host}"
  946. +
  947. +                       #
  948. +                       # Notes:
  949. +                       # - fixme: c.nfs_server.port is fixed
  950. +                       # for ssh+nfs://-URLs, so for now we
  951. +                       # have to hardcode TCP/2049 for now
  952. +                       # - We use aes128-cbc,aes128-ctr ciphers for better
  953. +                       # throughput (see https://bash-prompt.net/guides/bash-ssh-ciphers/
  954. +                       # for a benchmark) and lower latency, as NFS is
  955. +                       # a bit latency-sensitive
  956. +                       # - We turn compression off, as it incrases latency
  957. +                       #
  958. +                       ssh \
  959. +                               -L "${c.local_forward_port}:localhost:2049" \
  960. +                               -M -S "${c.ssh_control_socket_name}" \
  961. +                               -N \
  962. +                               -f -o 'ExitOnForwardFailure=yes' \
  963. +                               -o 'Compression=no' \
  964. +                               -o 'Ciphers=aes128-cbc,aes128-ctr' \
  965. +                               "${c.ssh_jumphost_args[@]}" \
  966. +                               "${c.nfsserver_ssh_login_name}@${c.nfs_server.host}"
  967. +                       if (( $? != 0 )) ; then
  968. +                               print -u2 -f $"%s: NFS forwarding ssh failed with error code %d\n" "$0" $?
  969. +                               return 1
  970. +                       fi
  971. +
  972. +                       # debug
  973. +                       ${mydebug} && \
  974. +                               ssh \
  975. +                                       -S "${c.ssh_control_socket_name}" \
  976. +                                       -O 'check' \
  977. +                                       "${c.nfsserver_ssh_login_name}@${c.nfs_server.host}"
  978. +
  979. +
  980. +                       if (( c.is_ccygwin == 1 )) ; then
  981. +                               #
  982. +                               # Build argument list for nfs_mount.exe ...
  983. +                               #
  984. +                               typeset -a mount_args
  985. +                               for s in "${c.mount_nfs_options[@]}" ; do
  986. +                                       mount_args+=( '-o' "$s" )
  987. +                               done
  988. +
  989. +                               if (( c.local_forward_port != 2049 )) ; then
  990. +                                       #
  991. +                                       # The original CITI NFSv4 nfs_mount.exe does not
  992. +                                       # support the port= option, so only set it
  993. +                                       # if we do not use the default port
  994. +                                       #
  995. +                                       mount_args+=( '-o' "port=${c.local_forward_port}" )
  996. +                               fi
  997. +                               # fixme: can we remove -o sec=sys ?
  998. +                               mount_args+=( '-o' 'sec=sys' )
  999. +                               # '*' == Let nfs_mount.exe should pick drive letter itself
  1000. +                               mount_args+=( '*' )
  1001. +                               mount_args+=( "localhost:/${c.nfs_server.uripath}" )
  1002. +
  1003. +                               #
  1004. +                               # ... and do the mount
  1005. +                               #
  1006. +                               typeset stdout dummy
  1007. +
  1008. +                               # fixme: we should set LC_ALL=C because below we depend on
  1009. +                               # a l10n message
  1010. +                               stdout="${ "${c.msnfsv41_nfsmountcmd}" "${mount_args[@]}" ; (( retval=$? )) ;}"
  1011. +                               cat <<<"$stdout"
  1012. +
  1013. +                               if (( retval == 0 )) ; then
  1014. +                                       # Parse stdout for drive letter
  1015. +                                       dummy="${stdout/~(E)Successfully mounted (.+) to drive (.+):/dummy}"
  1016. +
  1017. +                                       # fixme: we should test whether c.windows_drive_letter is empty or not
  1018. +                                       typeset c.windows_drive_letter="${.sh.match[2]}"
  1019. +
  1020. +                                       print -u2 -f $"%s: NFS filesystem mounted to drive %q.\n" \
  1021. +                                               "$0" "${c.windows_drive_letter}"
  1022. +
  1023. +                                       # Cygwin bind mount
  1024. +                                       mount -o bind "/cygdrive/${c.windows_drive_letter}" "${c.mountpoint}"
  1025. +                               fi
  1026. +                       else
  1027. +                               #
  1028. +                               # Build argument list for mount.nfs ...
  1029. +                               #
  1030. +                               typeset -a mount_args
  1031. +                               mount_args+=( '-vvv' )
  1032. +                               mount_args+=( '-t' 'nfs' )
  1033. +                               for s in "${c.mount_nfs_options[@]}" ; do
  1034. +                                       mount_args+=( '-o' "$s" )
  1035. +                               done
  1036. +                               mount_args+=( '-o' 'vers=4.2' )
  1037. +                               mount_args+=( '-o' "port=${c.local_forward_port}" )
  1038. +                               mount_args+=( "localhost:/${c.nfs_server.uripath}" )
  1039. +                               mount_args+=( "${c.mountpoint}" )
  1040. +
  1041. +                               #
  1042. +                               # ... and do the mount
  1043. +                               #
  1044. +                               mount "${mount_args[@]}"
  1045. +                               (( retval=$? ))
  1046. +                       fi
  1047. +
  1048. +                       if (( retval != 0 )) ; then
  1049. +                               #
  1050. +                               # Quit ssh port forwarding process
  1051. +                               #
  1052. +                               ssh \
  1053. +                                       -S "${c.ssh_control_socket_name}" \
  1054. +                                       -O 'exit' \
  1055. +                                       "${c.nfsserver_ssh_login_name}@${c.nfs_server.host}"
  1056. +                               return $retval
  1057. +                       fi
  1058. +
  1059. +
  1060. +                       #
  1061. +                       # Save status data
  1062. +                       #
  1063. +                       compound mnt_config=(
  1064. +                               typeset url="${c.url}"
  1065. +                               typeset mountpoint="${c.mountpoint}"
  1066. +                               typeset ssh_control_socket_name="${c.ssh_control_socket_name}"
  1067. +                               typeset nfsserver_ssh_login_name="${c.nfsserver_ssh_login_name}"
  1068. +                               typeset nfsserver_host="${c.nfs_server.host}"
  1069. +                       )
  1070. +
  1071. +                       if (( c.is_ccygwin == 1 )) ; then
  1072. +                               typeset mnt_config.windows_drive_letter="${c.windows_drive_letter}"
  1073. +                       fi
  1074. +
  1075. +                       print -v mnt_config >"$config_filename"
  1076. +
  1077. +                       return 0
  1078. +                       ;;
  1079. +               # fixme: Implement nfs://-URLs
  1080. +               *)
  1081. +                       print -u2 -f $"%s: Unknown URL scheme %q\n" "$0" "${c.nfs_server.scheme}"
  1082. +                       return 2
  1083. +                       ;;
  1084. +       esac
  1085. +
  1086. +       # notreached
  1087. +}
  1088. +
  1089. +
  1090. +function cmd_umount
  1091. +{
  1092. +       set -o nounset
  1093. +       nameref c=$1
  1094. +       integer retval
  1095. +       integer saved_optind_m1 # saved OPTIND-1
  1096. +
  1097. +       typeset mydebug=false # fixme: should be "bool" for ksh93v
  1098. +       # fixme: Need better text layout for $ mount_sshnfs mount --man #
  1099. +       typeset -r mount_sshnfs_cmdumount_usage=$'+
  1100. +       [-?\n@(#)\$Id: mount_sshnfs umount (Roland Mainz) 2023-07-24 \$\n]
  1101. +       [-author?Roland Mainz <roland.mainz@nrubsig.org>]
  1102. +       [+NAME?mount_sshnfs umount - unmount NFSv4 filesystem mounted
  1103. +               via mount_sshnfs mount]
  1104. +       [+DESCRIPTION?\bmount_sshnfs umount\b unmounts a NFSv4
  1105. +               filesystem previously mounted via mount_sshnfs mount.]
  1106. +
  1107. +       mountpoint
  1108. +      
  1109. +       [+SEE ALSO?\bksh93\b(1),\bssh\b(1),\bmount.nfs\b(8),\bnfs\b(5)]
  1110. +       '
  1111. +
  1112. +       # remove subcmd name (in this case 'umount')
  1113. +       unset c.args[0]
  1114. +
  1115. +       #
  1116. +       # Expand MOUNT_SSHNFS_CMDUMOUNT_OPTIONS before arguments given to
  1117. +       # mount_sshnfs.ksh.
  1118. +       # By default we use IFS=$' \t\n' for argument splitting
  1119. +       #
  1120. +       c.args=( ${MOUNT_SSHNFS_CMDUMOUNT_OPTIONS-} "${c.args[@]}" )
  1121. +
  1122. +       #
  1123. +       # Argument parsing
  1124. +       #
  1125. +       while getopts -a "${progname} umount" "${mount_sshnfs_cmdumount_usage}" OPT "${c.args[@]}" ; do
  1126. +               case "${OPT}" in
  1127. +                       *)
  1128. +                               usage "${progname} umount" "${mount_sshnfs_cmdumount_usage}"
  1129. +                               return $?
  1130. +                               ;;
  1131. +               esac
  1132. +       done
  1133. +
  1134. +       (( saved_optind_m1=OPTIND-1 ))
  1135. +
  1136. +       # remove options we just parsed from c.args
  1137. +       for ((i=0 ; i < saved_optind_m1 ; i++)) ; do
  1138. +               unset c.args[$i]
  1139. +       done
  1140. +
  1141. +
  1142. +       #
  1143. +       # Get remaining arguments
  1144. +       #
  1145. +       c.mountpoint="${c.args[saved_optind_m1+0]-}"
  1146. +
  1147. +       #
  1148. +       # Read configuration file for this mountpoint
  1149. +       #
  1150. +       typeset config_filename
  1151. +       mountpoint2configfilename config_filename "${c.mountpoint}"
  1152. +
  1153. +       if [[ ! -f "${config_filename}" ]] ; then
  1154. +               print -u2 -f $"%s: Config file %q for mount point %q not found.\n" \
  1155. +                       "$0" \
  1156. +                       "$config_filename" \
  1157. +                       "${c.mountpoint}"
  1158. +               return 1
  1159. +       fi
  1160. +
  1161. +       compound mnt_config
  1162. +       read -C mnt_config <"${config_filename}" || return 1
  1163. +
  1164. +       ${mydebug} && print -v mnt_config
  1165. +
  1166. +       #
  1167. +       # Do the unmount
  1168. +       #
  1169. +       if (( c.is_ccygwin == 1 )) ; then
  1170. +               # unmount the NFS filesystem
  1171. +               "${c.msnfsv41_nfsmountcmd}" -d "${mnt_config.windows_drive_letter}"
  1172. +               (( retval=$? ))
  1173. +
  1174. +               # remove the Cygwin bind mount
  1175. +               (( retval == 0 )) && umount "${c.mountpoint}"
  1176. +       else
  1177. +               umount "${c.mountpoint}"
  1178. +               (( retval=$? ))
  1179. +       fi
  1180. +
  1181. +       if (( retval != 0 )) ; then
  1182. +               return $retval
  1183. +       fi
  1184. +
  1185. +       #
  1186. +       # Quit ssh port forwarding process
  1187. +       #
  1188. +       ssh \
  1189. +               -S "${mnt_config.ssh_control_socket_name}" \
  1190. +               -O 'exit' \
  1191. +               "${mnt_config.nfsserver_ssh_login_name}@${mnt_config.nfsserver_host}"
  1192. +
  1193. +       rm -f "${config_filename}"
  1194. +       return 0
  1195. +}
  1196. +
  1197. +
  1198. +function main
  1199. +{
  1200. +       set -o nounset
  1201. +
  1202. +       # fixme: Need better text layout for $ mount_sshnfs --man #
  1203. +       typeset -r mount_sshnfs_usage=$'+
  1204. +       [-?\n@(#)\$Id: mount_sshnfs (Roland Mainz) 2023-07-24 \$\n]
  1205. +       [-author?Roland Mainz <roland.mainz@nrubsig.org>]
  1206. +       [+NAME?mount_sshnfs - mount/umount NFSv4 filesystem via ssh
  1207. +               tunnel]
  1208. +       [+DESCRIPTION?\bmount_sshnfs\b mounts/unmounts a NFSv4
  1209. +               filesystem via ssh tunnel.]
  1210. +       [D:debug?Enable debugging.]
  1211. +
  1212. +       mount [options]
  1213. +       umount [options]
  1214. +       status [options]
  1215. +       restart_forwarding [options]
  1216. +
  1217. +       [+EXAMPLES]{
  1218. +               [+?Example 1:][+?Mount&&unmount /export/home/rmainz on NFS server "/export/home/rmainz"]{
  1219. +[+\n# mkdir -p /foobarmnt
  1220. +# ksh mount_sshnfs.ksh mount ssh+nfs:://rmainz@derfwpc5131/export/home/rmainz /foobarmnt
  1221. +# mount_sshnfs.ksh umount /foobarmnt
  1222. +]
  1223. +}
  1224. +               [+?Example 2:][+?Mount&&unmount /export/home/rmainz on NFS server "/export/home/rmainz" via SSH jumphost rmainz@10.49.20.131]{
  1225. +[+\n# mkdir -p /foobarmnt
  1226. +# ksh mount_sshnfs.ksh mount -o ro,mount_sshnfs_jumphost=rmainz@10.49.20.131 ssh+nfs:://rmainz@derfwpc5131/export/home/rmainz /foobarmnt
  1227. +# mount_sshnfs.ksh umount /foobarmnt
  1228. +]
  1229. +               }
  1230. +       }
  1231. +       [+SEE ALSO?\bksh93\b(1),\bssh\b(1),\bmount.nfs\b(8),\bnfs\b(5)]
  1232. +       '
  1233. +
  1234. +       compound c
  1235. +       typeset -a c.args
  1236. +       integer saved_optind_m1 # saved OPTIND-1
  1237. +      
  1238. +       if [[ "${ uname -o ;}" == 'Cygwin' ]] ; then
  1239. +               integer c.is_ccygwin=1
  1240. +              
  1241. +               # only for testing!!
  1242. +               PATH+=':/cygdrive/c/Users/roland_mainz/Downloads/ms-nfs41-client-x64/ms-nfs41-client-x64/'
  1243. +
  1244. +               typeset c.msnfsv41_nfsmountcmd="$(which 'nfs_mount.exe')"
  1245. +              
  1246. +               if [[ ! -x "${c.msnfsv41_nfsmountcmd}" ]] ; then
  1247. +                       print -u2 -f $"%s: Cannot find MS-NFSV41 nfs_mount.exe command\n" "$0"
  1248. +                       return 1
  1249. +               fi
  1250. +       else
  1251. +               integer c.is_ccygwin=0
  1252. +       fi
  1253. +
  1254. +       # Cygwin does not set logname
  1255. +       [[ ! -v LOGNAME ]] && export LOGNAME="$(logname)"
  1256. +
  1257. +       #
  1258. +       # Expand MOUNT_SSHNFS_OPTIONS before arguments given to
  1259. +       # mount_sshnfs.ksh.
  1260. +       # By default we use IFS=$' \t\n' for argument splitting
  1261. +       #
  1262. +       c.args=( ${MOUNT_SSHNFS_OPTIONS-} "$@" )
  1263. +
  1264. +       #
  1265. +       # Argument parsing
  1266. +       #
  1267. +       while getopts -a "${progname}" "${mount_sshnfs_usage}" OPT "${c.args[@]}" ; do
  1268. +               case "${OPT}" in
  1269. +                       'D')
  1270. +                               # fixme: Implement debugging option
  1271. +                               ;;
  1272. +                       *)
  1273. +                               usage "${progname}" "${mount_sshnfs_usage}"
  1274. +                               return $?
  1275. +                               ;;
  1276. +               esac
  1277. +       done
  1278. +
  1279. +       (( saved_optind_m1=OPTIND-1 ))
  1280. +
  1281. +       # remove options we just parsed from c.args
  1282. +       for ((i=0 ; i < saved_optind_m1 ; i++)) ; do
  1283. +               unset c.args[$i]
  1284. +       done
  1285. +
  1286. +       #
  1287. +       # c.args mighth be a sparse array (e.g. "([1]=aaa [2]=bbb [4]=ccc)")
  1288. +       # right now after we removed processed options/arguments.
  1289. +       # For easier processing below we "reflow" the array back to a
  1290. +       # normal linear layout (e.g. ([0]=aaa [1]=bbb [2]=ccc)
  1291. +       #
  1292. +       c.args=( "${c.args[@]}" )
  1293. +
  1294. +       #
  1295. +       # Subcommand dispatcher
  1296. +       #
  1297. +       case "${c.args[0]-}" in
  1298. +               'mount')
  1299. +                       cmd_mount c
  1300. +                       return $?
  1301. +                       ;;
  1302. +               'umount')
  1303. +                       cmd_umount c
  1304. +                       return $?
  1305. +                       ;;
  1306. +               'status' | 'restart_forwarding')
  1307. +                       print -u2 -f $"%s: not implemented yet\n" "$0"
  1308. +                       return 2
  1309. +                       ;;
  1310. +               *)
  1311. +                       print -u2 -f $"%s: Unknown command %q\n" \
  1312. +                               "$0" "${c.args[0]-}"
  1313. +                       usage "${progname}" "${mount_sshnfs_usage}"
  1314. +                       return 1
  1315. +                       ;;
  1316. +       esac
  1317. +
  1318. +       # notreached
  1319. +}
  1320. +
  1321. +
  1322. +#
  1323. +# main
  1324. +#
  1325. +builtin cat
  1326. +builtin mkdir
  1327. +builtin basename
  1328. +
  1329. +typeset progname="${ basename "${0}" ; }"
  1330. +
  1331. +main "$@"
  1332. +exit $?
  1333. +
  1334. +# EOF.
  1335. diff --git a/cygwin/utils/sshnfs/README.txt b/cygwin/utils/sshnfs/README.txt
  1336. new file mode 100644
  1337. index 0000000..9e6e66c
  1338. --- /dev/null
  1339. +++ b/cygwin/utils/sshnfs/README.txt
  1340. @@ -0,0 +1,21 @@
  1341. +#
  1342. +# sshnfs/README.txt
  1343. +#
  1344. +
  1345. +**** ToDo:
  1346. +- ksh93 getopt argument parsing
  1347. +- Correct POSIX exit codes
  1348. +- c.destination_nfs_port should be a command line option
  1349. +- How can a non-standard (TCP/2049) NFS port be specified for
  1350. +  ssh+nfs:// URLs be specified ?
  1351. +- Debug messages should go to stderr
  1352. +- Linux: Add mount.nfs -o nconnect=4 (see
  1353. +  https://www.suse.com/support/kb/doc/?id=000019933)
  1354. +
  1355. +
  1356. +**** Testing:
  1357. +- Check whether SSH -p port works
  1358. +- Check whether user in ssh+nfs:// URLs works
  1359. +- Check whether ports in ssh+nfs:// URLs works
  1360. +
  1361. +# EOF.
  1362. diff --git a/cygwin/utils/sshnfs/sshnfs.ksh b/cygwin/utils/sshnfs/sshnfs.ksh
  1363. new file mode 100644
  1364. index 0000000..7f28ed2
  1365. --- /dev/null
  1366. +++ b/cygwin/utils/sshnfs/sshnfs.ksh
  1367. @@ -0,0 +1,537 @@
  1368. +#!/bin/ksh93
  1369. +
  1370. +#
  1371. +# sshnfs - remote login client with NFSv4 forwarding
  1372. +#
  1373. +
  1374. +#
  1375. +# Example usage:
  1376. +# $ ksh sshnfs.ksh -o NFSURL=ssh+nfs://localhost/export/home/rmainz root@10.49.28.10 #
  1377. +# $ ksh sshnfs.ksh -o NFSURL=nfs://localhost/export/home/rmainz root@10.49.20.207 #
  1378. +# $ ksh sshnfs.ksh -o NFSURL=nfs://derfwpc5131/export/home/rmainz root@10.49.28.10 #
  1379. +# $ 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
  1380. +# $ ksh sshnfs.ksh -o NFSURL=ssh+nfs://derfwpc5131/export/home/rmainz target@fe80::d6f5:27ff:fe2b:8588%enp2s0
  1381. +# $ ksh sshnfs.ksh -o NFSURL=ssh+nfs://root@derfwpc5131/export/home/rmainz root@10.49.28.56
  1382. +# $ ksh sshnfs.ksh -o NFSServerSSHLoginName=root -o NFSURL=ssh+nfs://derfwpc5131/export/home/rmainz root@10.49.28.56
  1383. +# $ SSHNFS_OPTIONS='-o NFSServerSSHLoginName=root -o NFSURL=ssh+nfs://derfwpc5131/export/home/rmainz' sshnfs.ksh root@10.49.28.56
  1384. +#
  1385. +
  1386. +#
  1387. +# Written by Roland Mainz <roland.mainz@nrubsig.org>
  1388. +#
  1389. +
  1390. +#    
  1391. +# simple netstat -n parser
  1392. +#    
  1393. +function netstat_list_connections
  1394. +{
  1395. +       set -o nounset
  1396. +       nameref data=$1
  1397. +
  1398. +       compound out=( typeset stdout stderr ; integer res )
  1399. +
  1400. +       out.stderr="${ { out.stdout="${ LC_ALL='POSIX' PATH='/usr/bin:/bin' netstat -a -n ; (( out.res=$? )) ; }" ; } 2>&1 ; }"
  1401. +       if (( out.res != 0 )) ; then
  1402. +               print -u2 -f $"%s: netstat returned %d exit code.\n" \
  1403. +                       "$0" out.res
  1404. +               return 1
  1405. +       fi
  1406. +       if [[ "${out.stderr}" != '' ]] ; then
  1407. +               #
  1408. +               # Handle known Linux netstat warnings
  1409. +               #
  1410. +               if [[ "${out.stderr}" != $'warning, got bogus unix line.' ]] ; then
  1411. +                       print -u2 -f $"%s: netstat returned unknown error message %q.\n" \
  1412. +                               "$0" "${out.stderr}"
  1413. +                       return 1
  1414. +               fi
  1415. +       fi
  1416. +
  1417. +       typeset -a data.connections
  1418. +       typeset l
  1419. +       typeset leftover
  1420. +       integer dci=0 # data.connections array index
  1421. +
  1422. +       while read l ; do
  1423. +               leftover="${l/~(Elrx)
  1424. +               (?: # non-capturing group
  1425. +                       #
  1426. +                       # regex group for tcp,udp
  1427. +                       #
  1428. +                       (tcp|tcp6|udp|udp6|raw|raw6|sctp|sctp6) # Proto
  1429. +                       [[:space:]]+
  1430. +                       ([[:digit:]]+)                  # Recv-Q
  1431. +                       [[:space:]]+
  1432. +                       ([[:digit:]]+)                  # Send-Q
  1433. +                       [[:space:]]+
  1434. +                       ([^[:space:]]+)                 # Local Address
  1435. +                       [[:space:]]+
  1436. +                       ([^[:space:]]+)                 # Foreign Address
  1437. +                       (?:
  1438. +                               |
  1439. +                               [[:space:]]+
  1440. +                               ([^[:space:]]*?)        # State (Optional)
  1441. +                       )
  1442. +               |
  1443. +                       #
  1444. +                       # regex for unix
  1445. +                       #
  1446. +                       (unix)                          # Proto
  1447. +                       [[:space:]]+
  1448. +                       ([[:digit:]]+)                  # RefCnt
  1449. +                       [[:space:]]+
  1450. +                       (\[.+?\])                       # Flags
  1451. +                       [[:space:]]+
  1452. +                       ([^[:space:]]+)                 # Type
  1453. +                       [[:space:]]+
  1454. +                       ([^[:space:]]*?)                # State (optional)
  1455. +                       [[:space:]]+
  1456. +                       ([[:digit:]]+)                  # I-Node
  1457. +                       (?:
  1458. +                               |
  1459. +                               [[:space:]]+
  1460. +                               ([^[:space:]]+)         # Path (optional)
  1461. +                       )
  1462. +               )
  1463. +                       /X}"
  1464. +
  1465. +               # If the regex above did not match then .sh.match
  1466. +               # remains untouched, so we might see data from the
  1467. +               # previous round.
  1468. +               # So we check the "leftover" var whether it just
  1469. +               # contains the dummy value of "X" to indicate a
  1470. +               # successful regex match
  1471. +               if [[ "$leftover" == 'X' ]] ; then
  1472. +                       #print -v .sh.match
  1473. +
  1474. +                       if [[ "${.sh.match[1]-}" != '' ]] ; then
  1475. +                               nameref dcn=data.connections[$dci]
  1476. +
  1477. +                               typeset dcn.proto="${.sh.match[1]}"
  1478. +                               typeset dcn.recv_q="${.sh.match[2]}"
  1479. +                               typeset dcn.send_q="${.sh.match[3]}"
  1480. +                               typeset dcn.local_address="${.sh.match[4]}"
  1481. +                               typeset dcn.foreign_address="${.sh.match[5]}"
  1482. +                               typeset dcn.state="${.sh.match[6]}"
  1483. +                               ((dci++))
  1484. +                       elif [[ "${.sh.match[7]-}" != '' ]] ; then
  1485. +                               nameref dcn=data.connections[$dci]
  1486. +
  1487. +                               typeset dcn.proto="${.sh.match[7]}"
  1488. +                               typeset dcn.refcnt="${.sh.match[8]}"
  1489. +                               typeset dcn.flags="${.sh.match[9]}"
  1490. +                               typeset dcn.type="${.sh.match[10]}"
  1491. +                               [[ "${.sh.match[11]}" != '' ]] && typeset dcn.state="${.sh.match[11]}"
  1492. +                               typeset dcn.inode="${.sh.match[12]}"
  1493. +                               [[ "${.sh.match[13]}" != '' ]] && typeset dcn.path="${.sh.match[13]}"
  1494. +                               ((dci++))
  1495. +                       fi
  1496. +               else
  1497. +                       true
  1498. +                       #printf $"leftover=%q\n" "${leftover}"
  1499. +               fi
  1500. +       done <<<"${out.stdout}"
  1501. +
  1502. +       return 0
  1503. +}
  1504. +
  1505. +function netstat_list_active_local_tcp_connections
  1506. +{
  1507. +       set -o nounset
  1508. +       nameref ar=$1
  1509. +       compound c
  1510. +       integer port
  1511. +       integer i
  1512. +
  1513. +       netstat_list_connections c || return 1
  1514. +       #print -v c
  1515. +      
  1516. +       [[ -v ar ]] || integer -a ar
  1517. +
  1518. +       for i in "${!c.connections[@]}" ; do
  1519. +               nameref n=c.connections[$i]
  1520. +              
  1521. +               # look for only for TCP connections which match
  1522. +               # 127.0.*.* or IPv6 ::1 for localhost
  1523. +               # 0.0.0.0 or IPv6 :: for all addresses (e.g. servers)
  1524. +               if [[ "${n.proto}" == ~(El)tcp && \
  1525. +                       "${n.local_address}" == ~(Elr)((127\.0\..+|::1)|(::|0\.0\.0\.0|)):[[:digit:]]+ ]] ; then
  1526. +
  1527. +                       port="${n.local_address##*:}"
  1528. +                       #printf $"port = %d\n" port
  1529. +
  1530. +                       (( ar[port]=1 ))
  1531. +               fi
  1532. +       done
  1533. +
  1534. +       return 0
  1535. +}
  1536. +
  1537. +function netstat_find_next_free_local_tcp_port
  1538. +{
  1539. +       set -o nounset
  1540. +       compound c=( integer -a ar )
  1541. +       nameref ret_free_port=$1
  1542. +       integer start_port
  1543. +       integer end_port
  1544. +       integer i
  1545. +
  1546. +       netstat_list_active_local_tcp_connections c.ar || return 1
  1547. +
  1548. +       #print -v c
  1549. +
  1550. +       (( start_port=$2 ))
  1551. +       if (( $# > 2 )) ; then
  1552. +               (( end_port=$3 ))
  1553. +       else
  1554. +               (( end_port=65535 ))
  1555. +       fi
  1556. +
  1557. +       for ((i=start_port ; i < end_port ; i++ )) ; do
  1558. +               if [[ ! -v c.ar[i] ]] ; then
  1559. +                       (( ret_free_port=i ))
  1560. +                       return 0
  1561. +               fi
  1562. +       done
  1563. +      
  1564. +       return 1
  1565. +}
  1566. +
  1567. +#
  1568. +# parse_rfc1738_url - parse RFC 1838 URLs
  1569. +#
  1570. +# Output variables are named after RFC 1838 Section 5 ("BNF for
  1571. +# specific URL schemes")
  1572. +#
  1573. +function parse_rfc1738_url
  1574. +{
  1575. +       set -o nounset
  1576. +
  1577. +       typeset url="$2"
  1578. +       typeset leftover
  1579. +       nameref data="$1" # output compound variable
  1580. +      
  1581. +       # ~(E) is POSIX extended regular expression matching (instead
  1582. +       # of shell pattern), "x" means "multiline", "l" means "left
  1583. +       # anchor", "r" means "right anchor"
  1584. +       leftover="${url/~(Elrx)
  1585. +               (.+?)                           # scheme
  1586. +               :\/\/                           # '://'
  1587. +               (                               # login
  1588. +                       (?:
  1589. +                               (.+?)           # user (optional)
  1590. +                               (?::(.+))?      # password (optional)
  1591. +                               @
  1592. +                       )?
  1593. +                       (                       # hostport
  1594. +                               (.+?)           # host
  1595. +                               (?::([[:digit:]]+))? # port (optional)
  1596. +                       )
  1597. +               )
  1598. +               (?:\/(.*?))?/X}"                # path (optional)
  1599. +
  1600. +       # All parsed data should be captured via eregex in .sh.match - if
  1601. +       # there is anything left (except the 'X') then the input string did
  1602. +       # not properly match the eregex
  1603. +       [[ "$leftover" == 'X' ]] ||
  1604. +               { print -u2 -f $"%s: Parser error, leftover=%q\n" \
  1605. +                       "$0" "$leftover" ; return 1 ; }
  1606. +
  1607. +       data.url="${.sh.match[0]}"
  1608. +       data.scheme="${.sh.match[1]}"
  1609. +       data.login="${.sh.match[2]}"
  1610. +       # FIXME: This should use [[ ! -v .sh.match[3] ]], but ksh93u has bugs
  1611. +       [[ "${.sh.match[3]-}" != '' ]] && data.user="${.sh.match[3]}"
  1612. +       [[ "${.sh.match[4]-}" != '' ]] && data.password="${.sh.match[4]}"
  1613. +       data.hostport="${.sh.match[5]}"
  1614. +       data.host="${.sh.match[6]}"
  1615. +       [[ "${.sh.match[7]-}" != '' ]] && integer data.port="${.sh.match[7]}"
  1616. +       [[ "${.sh.match[8]-}" != '' ]] && data.uripath="${.sh.match[8]}"
  1617. +
  1618. +       return 0
  1619. +}
  1620. +
  1621. +
  1622. +function parse_sshnfs_url
  1623. +{
  1624. +       typeset url="$2"
  1625. +       nameref data="$1"
  1626. +      
  1627. +       parse_rfc1738_url data "$url" || return 1
  1628. +      
  1629. +       [[ "${data.scheme}" == ~(Elr)(ssh\+nfs|nfs) ]] || \
  1630. +               { print -u2 -f $"%s: Not a nfs:// or ssh+nfs:// url\n" "$0" ; return 1 ; }
  1631. +       [[ "${data.host}" != '' ]] || { print -u2 -f $"%s: NFS hostname missing\n" "$0" ; return 1 ; }
  1632. +       [[ "${data.uripath}" != '' ]] || { print -u2 -f $"%s: NFS path missing\n" "$0" ; return 1 ; }
  1633. +      
  1634. +       return 0
  1635. +}
  1636. +
  1637. +
  1638. +function main
  1639. +{
  1640. +       set -o nounset
  1641. +       typeset mydebug=false # fixme: should be "bool" for ksh93v
  1642. +       integer i
  1643. +       integer retval
  1644. +       compound c
  1645. +
  1646. +       #
  1647. +       # Expand SSHNFS_OPTIONS before arguments given to sshnfs.ksh
  1648. +       # By default we use IFS=$' \t\n' for argument splitting
  1649. +       #
  1650. +       typeset -a c.args=( ${SSHNFS_OPTIONS-} "$@" )
  1651. +
  1652. +       for ((i=0 ; i < ${#c.args[@]} ; i++)) ; do
  1653. +               if [[ "${c.args[i]}" == '-o' ]] ; then
  1654. +                       case "${c.args[i+1]-}" in
  1655. +                               ~(Eli)NFSServerSSHLoginName=)
  1656. +                                       # User name for SSH login to NFS server
  1657. +                                       typeset c.nfsserver_ssh_login_name="${c.args[i+1]/~(Eli)NFSServerSSHLoginName=}"
  1658. +
  1659. +                                       unset c.args[$i] c.args[$((i+1))]
  1660. +                                       ((i++))
  1661. +                                       ;;
  1662. +
  1663. +                               ~(Eli)NFSURL=)
  1664. +                                       unset c.nfs_server
  1665. +                                       compound c.nfs_server
  1666. +                                       typeset c.url="${c.args[i+1]/~(Eli)NFSURL=}"
  1667. +                                       parse_sshnfs_url c.nfs_server "${c.url}" || return 1
  1668. +
  1669. +                                       unset c.args[$i] c.args[$((i+1))]
  1670. +                                       ((i++))
  1671. +                                       ;;
  1672. +
  1673. +                               ~(Eli)SSHNFSJumphost=)
  1674. +                                       [[ ! -v c.ssh_jumphost_args ]] && typeset -a c.ssh_jumphost_args
  1675. +                                       c.ssh_jumphost_args+=( "-J" "${c.args[i+1]/~(Eli)SSHNFSJumphost=}" )
  1676. +
  1677. +                                       unset c.args[$i] c.args[$((i+1))]
  1678. +                                       ((i++))
  1679. +                                       ;;
  1680. +
  1681. +                               ~(Eli)SSHNFSlocal_forward_port=)
  1682. +                                       # command(1) prevents that the shell interpreter
  1683. +                                       # exits if typeset produces a syntax error
  1684. +                                       command integer c.local_forward_port="${c.args[i+1]/~(Eli)SSHNFSlocal_forward_port=}" || return 1
  1685. +
  1686. +                                       unset c.args[$i] c.args[$((i+1))]
  1687. +                                       ((i++))
  1688. +                                       ;;
  1689. +                       esac
  1690. +               fi
  1691. +       done
  1692. +
  1693. +       if [[ -v c.nfs_server ]] ; then
  1694. +               if [[ ! -v c.nfs_server.port ]] ; then
  1695. +                       # use # default NFSv4 TCP port number (see
  1696. +                       # $ getent services nfs #)
  1697. +                       integer c.nfs_server.port=2049
  1698. +               fi
  1699. +
  1700. +               case "${c.nfs_server.scheme}" in
  1701. +                       'ssh+nfs')
  1702. +                               #
  1703. +                               # Find free local forwarding port...
  1704. +                               #
  1705. +
  1706. +                               # TCP port on destination machine where we forward the
  1707. +                               # NFS port from the server
  1708. +                               integer c.destination_nfs_port=33049
  1709. +
  1710. +                               # port on THIS machine
  1711. +                               if [[ ! -v c.local_forward_port ]] ; then
  1712. +                                       integer c.local_forward_port
  1713. +
  1714. +                                       (( i=34049 ))
  1715. +                                       if ! netstat_find_next_free_local_tcp_port c.local_forward_port $i ; then
  1716. +                                               print -u2 -f "%s: netstat_find_next_free_local_tcp_port failed.\n" "$0"
  1717. +                                               return 1
  1718. +                                       fi
  1719. +
  1720. +                                       #
  1721. +                                       # ... and adjust c.destination_nfs_port by the same offset
  1722. +                                       # we do that so that multiple sshnfs.ksh logins to the same
  1723. +                                       # machine do try to use the same ports on that machine
  1724. +                                       #
  1725. +                                       (( c.destination_nfs_port += ((c.local_forward_port-i) % 65535) ))
  1726. +
  1727. +                                       # TCP ports below 1024 are reserved for the system, so stay away from them
  1728. +                                       (( (c.destination_nfs_port <= 1024) && (c.destination_nfs_port += 34049) ))
  1729. +                               fi
  1730. +
  1731. +                               ${mydebug} && printf $"debug: c.local_forward_port=%d, c.destination_nfs_port=%d\n" \
  1732. +                                       c.local_forward_port \
  1733. +                                       c.destination_nfs_port
  1734. +
  1735. +                               c.ssh_control_socket_name="/tmp/sshnfs_ssh-control-socket_logname${LOGNAME}_ppid${PPID}_pid$$"
  1736. +
  1737. +                               #
  1738. +                               # Find SSH login user name for NFS server
  1739. +                               #
  1740. +                               if [[ -v c.nfs_server.user ]] ; then
  1741. +                                       typeset c.nfsserver_ssh_login_name="${c.nfs_server.user}"
  1742. +                               fi
  1743. +                               if [[ ! -v c.nfsserver_ssh_login_name ]] ; then
  1744. +                                       # default user name if neither URL nor
  1745. +                                       # "-o NFSServerSSHLoginName=..." were given
  1746. +                                       typeset c.nfsserver_ssh_login_name="$LOGNAME"
  1747. +                               fi
  1748. +
  1749. +                               #
  1750. +                               # Forward NFS port from server to local machine
  1751. +                               #
  1752. +                               # Notes:
  1753. +                               # - We use $ ssh -M ... # here as a way to terminate the port
  1754. +                               # forwarding process later using "-O exit" without the need
  1755. +                               # for a pid
  1756. +                               #
  1757. +                               print -u2 -f $"# Please enter the login data for NFS server (%s):\n" \
  1758. +                                       "${c.nfsserver_ssh_login_name}@${c.nfs_server.host}"
  1759. +
  1760. +                               #
  1761. +                               # Notes:
  1762. +                               # - fixme: c.nfs_server.port is fixed
  1763. +                               # for ssh+nfs://-URLs, so for now we
  1764. +                               # have to hardcode TCP/2049 for now
  1765. +                               # - We use aes128-cbc,aes128-ctr ciphers for better
  1766. +                               # throughput (see https://bash-prompt.net/guides/bash-ssh-ciphers/
  1767. +                               # for a benchmark) and lower latency, as NFS is
  1768. +                               # a bit latency-sensitive
  1769. +                               # - We turn compression off, as it incrases latency
  1770. +                               #
  1771. +                               ssh \
  1772. +                                       -L "${c.local_forward_port}:localhost:2049" \
  1773. +                                       -M -S "${c.ssh_control_socket_name}" \
  1774. +                                       -N \
  1775. +                                       -f -o 'ExitOnForwardFailure=yes' \
  1776. +                                       -o 'Compression=no' \
  1777. +                                       -o 'Ciphers=aes128-cbc,aes128-ctr' \
  1778. +                                       "${c.ssh_jumphost_args[@]}" \
  1779. +                                       "${c.nfsserver_ssh_login_name}@${c.nfs_server.host}"
  1780. +                               if (( $? != 0 )) ; then
  1781. +                                       print -u2 -f $"%s: NFS forwarding ssh failed with error code %d\n" "$0" $?
  1782. +                                       return 1
  1783. +                               fi
  1784. +
  1785. +                               # debug
  1786. +                               ${mydebug} && \
  1787. +                                       ssh \
  1788. +                                               -S "${c.ssh_control_socket_name}" \
  1789. +                                               -O 'check' \
  1790. +                                               "${c.nfsserver_ssh_login_name}@${c.nfs_server.host}"
  1791. +
  1792. +                               print -u2 -f $"# Use this to mount the directory:\n"
  1793. +                               print -u2 -f $"# $ mkdir /mnt_nfs\n"
  1794. +                               print -u2 -f $"# $ mount -vvv -t nfs -o vers=4.2,port=%d localhost:/%s /mnt_nfs\n" \
  1795. +                                       c.destination_nfs_port \
  1796. +                                       "${c.nfs_server.uripath}"
  1797. +
  1798. +                               #
  1799. +                               # add NFS forwarding options to main ssh argument list
  1800. +                               #
  1801. +                               # Notes:
  1802. +                               # - We use aes128-cbc,aes128-ctr ciphers for better
  1803. +                               # throughput (see https://bash-prompt.net/guides/bash-ssh-ciphers/
  1804. +                               # for a benchmark) and lower latency, as NFS is
  1805. +                               # a bit latency-sensitive
  1806. +                               # - We turn compression off, as it incrases latency
  1807. +                               #
  1808. +                               c.args=(
  1809. +                                       '-R' "${c.destination_nfs_port}:localhost:${c.local_forward_port}"
  1810. +                                       '-o' 'ExitOnForwardFailure=yes'
  1811. +                                       '-o' 'Compression=no'
  1812. +                                       '-o' 'Ciphers=aes128-cbc,aes128-ctr'
  1813. +                                       "${c.args[@]}"
  1814. +                               )
  1815. +                               ;;
  1816. +                       'nfs')
  1817. +                               #
  1818. +                               # Validate configuration
  1819. +                               #
  1820. +                               if [[ -v c.ssh_jumphost_args ]] ; then
  1821. +                                       print -u2 -f $"%s: Error: SSHNFSJumphost cannot be used for nfs://-URLs\n" "$0"
  1822. +                                       return 2
  1823. +                               fi
  1824. +                               if [[ -v c.nfs_server.user ]] ; then
  1825. +                                       print -u2 -f $"%s: Error: 'user' in URLs is not used in nfs://-URLs\n" "$0"
  1826. +                                       return 2
  1827. +                               fi
  1828. +                               if [[ -v c.nfs_server.password ]] ; then
  1829. +                                       print -u2 -f $"%s: Error: 'password' in URLs is not used in nfs://-URLs\n" "$0"
  1830. +                                       return 2
  1831. +                               fi
  1832. +
  1833. +                               #
  1834. +                               # Guess a TCP port number which might be
  1835. +                               # free on the destination machine
  1836. +                               #
  1837. +                               integer myuid=$(id -u)
  1838. +                               integer mypid=$$ # used to circumvent ksh93 -n warning
  1839. +
  1840. +                               # TCP port on destination machine where we forward the
  1841. +                               # NFS port from the server
  1842. +                               integer c.destination_nfs_port=33049
  1843. +
  1844. +                               # try to adjust c.destination_nfs_port so that multiple sshnfs.ksh
  1845. +                               # sessions do intefere with each other
  1846. +                               # (16381 is a prime number)
  1847. +                               (( c.destination_nfs_port += (mypid+myuid+PPID) % 16381 ))
  1848. +
  1849. +                               print -u2 -f $"# Use this to mount the directory:\n"
  1850. +                               print -u2 -f $"# $ mkdir /mnt_nfs\n"
  1851. +                               print -u2 -f $"# $ mount -vvv -t nfs -o vers=4.2,port=%d localhost:/%s /mnt_nfs\n" \
  1852. +                                       c.destination_nfs_port \
  1853. +                                       "${c.nfs_server.uripath}"
  1854. +
  1855. +                               #
  1856. +                               # add NFS forwarding options to main ssh argument list
  1857. +                               #
  1858. +                               # Notes:
  1859. +                               # - We use aes128-cbc,aes128-ctr ciphers for better
  1860. +                               # throughput (see https://bash-prompt.net/guides/bash-ssh-ciphers/
  1861. +                               # for a benchmark) and lower latency, as NFS is
  1862. +                               # a bit latency-sensitive
  1863. +                               # - We turn compression off, as it incrases latency
  1864. +                               #
  1865. +                               c.args=(
  1866. +                                       '-R' "${c.destination_nfs_port}:${c.nfs_server.host}:${c.nfs_server.port}"
  1867. +                                       '-o' 'ExitOnForwardFailure=yes'
  1868. +                                       '-o' 'Compression=no'
  1869. +                                       '-o' 'Ciphers=aes128-cbc,aes128-ctr'
  1870. +                                       "${c.args[@]}"
  1871. +                               )
  1872. +                               ;;
  1873. +                       *)
  1874. +                               print -u2 -f $"%s: Unknown URL scheme %q\n" "$0" "${c.nfs_server.scheme}"
  1875. +                               return 2
  1876. +                               ;;
  1877. +               esac
  1878. +       fi
  1879. +
  1880. +       # debug: print application data (compound c)
  1881. +       ${mydebug} && print -v c
  1882. +
  1883. +       print -u2 -f $"# ssh login data for destination machine:\n"
  1884. +       ssh "${c.args[@]}" ; (( retval=$? ))
  1885. +
  1886. +       if [[ -v c.ssh_control_socket_name ]] ; then
  1887. +               ssh \
  1888. +                       -S "${c.ssh_control_socket_name}" \
  1889. +                       -O 'exit' \
  1890. +                       "${c.nfsserver_ssh_login_name}@${c.nfs_server.host}"
  1891. +       fi
  1892. +
  1893. +       wait
  1894. +
  1895. +       return $retval
  1896. +}
  1897. +
  1898. +#
  1899. +# main
  1900. +#
  1901. +main "$@"
  1902. +exit $?
  1903. +
  1904. +# EOF.
  1905. --
  1906. 2.39.0

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.

Syntax highlighting:

To highlight particular lines, prefix each line with {%HIGHLIGHT}




All content is user-submitted.
The administrators of this site (kpaste.net) are not responsible for their content.
Abuse reports should be emailed to us at