pastebin - collaborative debugging tool
rovema.kpaste.net RSS


mount_sshnfs - mount NFSv4 filesystem through ssh tunnel
Posted by Anonymous on Tue 8th Aug 2023 15:27
raw | new post
modification of post by Anonymous (view diff)

  1. #!/bin/ksh93
  2.  
  3. #
  4. # mount_sshnfs - mount NFSv4 filesystem through ssh tunnel
  5. #
  6.  
  7. #
  8. # Example usage:
  9. #
  10. # 1. Mount&&unmount /export/home/rmainz on NFS server "/export/home/rmainz":
  11. # $ mkdir -p /foobarmnt
  12. # $ ksh mount_sshnfs.ksh mount ssh+nfs://rmainz@derfwpc5131/export/home/rmainz /foobarmnt
  13. # $ mount_sshnfs.ksh umount /foobarmnt
  14. #
  15. #
  16. # 2. Mount&&unmount /export/home/rmainz on NFS server "/export/home/rmainz" via SSH jumphost rmainz@10.49.20.131:
  17. # $ mkdir -p /foobarmnt
  18. # $ ksh mount_sshnfs.ksh mount -o ro,mount_sshnfs_jumphost=rmainz@10.49.20.131 ssh+nfs://rmainz@derfwpc5131/export/home/rmainz /foobarmnt
  19. # $ mount_sshnfs.ksh umount /foobarmnt
  20. #
  21.  
  22. #
  23. # For more examples see help and subcommand help:
  24. # $ mount_sshnfs.ksh --man
  25. # $ mount_sshnfs.ksh mount --man
  26. # $ mount_sshnfs.ksh umount --man
  27. #
  28.  
  29. #
  30. # Written by Roland Mainz <roland.mainz@nrubsig.org>
  31. #
  32.  
  33. function usage
  34. {
  35.         (( OPTIND=0 ))
  36.         getopts -a "${1}" "${2}" OPT '-?'
  37.         return 2
  38. }
  39.  
  40.  
  41. #    
  42. # simple netstat -n parser
  43. #    
  44. function netstat_list_connections
  45. {
  46.         set -o nounset
  47.         nameref data=$1
  48.  
  49.         compound out=( typeset stdout stderr ; integer res )
  50.  
  51.         out.stderr="${ { out.stdout="${ LC_ALL='POSIX' PATH='/usr/bin:/bin' netstat -a -n ; (( out.res=$? )) ; }" ; } 2>&1 ; }"
  52.         if (( out.res != 0 )) ; then
  53.                 print -u2 -f $"%s: netstat returned %d exit code.\n" \
  54.                         "$0" out.res
  55.                 return 1
  56.         fi
  57.         if [[ "${out.stderr}" != '' ]] ; then
  58.                 #
  59.                 # Handle known Linux netstat warnings
  60.                 #
  61.                 if [[ "${out.stderr}" != $'warning, got bogus unix line.' ]] ; then
  62.                         print -u2 -f $"%s: netstat returned unknown error message %q.\n" \
  63.                                 "$0" "${out.stderr}"
  64.                         return 1
  65.                 fi
  66.         fi
  67.  
  68.         typeset -a data.connections
  69.         typeset l
  70.         typeset leftover
  71.         integer dci=0 # data.connections array index
  72.  
  73.         while read l ; do
  74.                 leftover="${l/~(Elrx)
  75.                 (?: # non-capturing group
  76.                         #
  77.                         # regex group for tcp,udp
  78.                         #
  79.                         (tcp|tcp6|udp|udp6|raw|raw6|sctp|sctp6) # Proto
  80.                         [[:space:]]+
  81.                         ([[:digit:]]+)                  # Recv-Q
  82.                         [[:space:]]+
  83.                         ([[:digit:]]+)                  # Send-Q
  84.                         [[:space:]]+
  85.                         ([^[:space:]]+)                 # Local Address
  86.                         [[:space:]]+
  87.                         ([^[:space:]]+)                 # Foreign Address
  88.                         (?:
  89.                                 |
  90.                                 [[:space:]]+
  91.                                 ([^[:space:]]*?)        # State (Optional)
  92.                         )
  93.                 |
  94.                         #
  95.                         # regex for unix
  96.                         #
  97.                         (unix)                          # Proto
  98.                         [[:space:]]+
  99.                         ([[:digit:]]+)                  # RefCnt
  100.                         [[:space:]]+
  101.                         (\[.+?\])                       # Flags
  102.                         [[:space:]]+
  103.                         ([^[:space:]]+)                 # Type
  104.                         [[:space:]]+
  105.                         ([^[:space:]]*?)                # State (optional)
  106.                         [[:space:]]+
  107.                         ([[:digit:]]+)                  # I-Node
  108.                         (?:
  109.                                 |
  110.                                 [[:space:]]+
  111.                                 ([^[:space:]]+)         # Path (optional)
  112.                         )
  113.                 )
  114.                         /X}"
  115.  
  116.                 # If the regex above did not match then .sh.match
  117.                 # remains untouched, so we might see data from the
  118.                 # previous round.
  119.                 # So we check the "leftover" var whether it just
  120.                 # contains the dummy value of "X" to indicate a
  121.                 # successful regex match
  122.                 if [[ "$leftover" == 'X' ]] ; then
  123.                         #print -v .sh.match
  124.  
  125.                         if [[ "${.sh.match[1]-}" != '' ]] ; then
  126.                                 nameref dcn=data.connections[$dci]
  127.  
  128.                                 typeset dcn.proto="${.sh.match[1]}"
  129.                                 typeset dcn.recv_q="${.sh.match[2]}"
  130.                                 typeset dcn.send_q="${.sh.match[3]}"
  131.                                 typeset dcn.local_address="${.sh.match[4]}"
  132.                                 typeset dcn.foreign_address="${.sh.match[5]}"
  133.                                 typeset dcn.state="${.sh.match[6]}"
  134.                                 ((dci++))
  135.                         elif [[ "${.sh.match[7]-}" != '' ]] ; then
  136.                                 nameref dcn=data.connections[$dci]
  137.  
  138.                                 typeset dcn.proto="${.sh.match[7]}"
  139.                                 typeset dcn.refcnt="${.sh.match[8]}"
  140.                                 typeset dcn.flags="${.sh.match[9]}"
  141.                                 typeset dcn.type="${.sh.match[10]}"
  142.                                 [[ "${.sh.match[11]}" != '' ]] && typeset dcn.state="${.sh.match[11]}"
  143.                                 typeset dcn.inode="${.sh.match[12]}"
  144.                                 [[ "${.sh.match[13]}" != '' ]] && typeset dcn.path="${.sh.match[13]}"
  145.                                 ((dci++))
  146.                         fi
  147.                 else
  148.                         true
  149.                         #printf $"leftover=%q\n" "${leftover}"
  150.                 fi
  151.         done <<<"${out.stdout}"
  152.  
  153.         return 0
  154. }
  155.  
  156. function netstat_list_active_local_tcp_connections
  157. {
  158.         set -o nounset
  159.         nameref ar=$1
  160.         compound c
  161.         integer port
  162.         integer i
  163.  
  164.         netstat_list_connections c || return 1
  165.         #print -v c
  166.  
  167.         [[ -v ar ]] || integer -a ar
  168.  
  169.         for i in "${!c.connections[@]}" ; do
  170.                 nameref n=c.connections[$i]
  171.  
  172.                 # look for only for TCP connections which match
  173.                 # 127.0.*.* or IPv6 ::1 for localhost
  174.                 # 0.0.0.0 or IPv6 :: for all addresses (e.g. servers)
  175.                 if [[ "${n.proto}" == ~(El)tcp && \
  176.                         "${n.local_address}" == ~(Elr)((127\.0\..+|::1)|(::|0\.0\.0\.0|)):[[:digit:]]+ ]] ; then
  177.  
  178.                         port="${n.local_address##*:}"
  179.                         #printf $"port = %d\n" port
  180.  
  181.                         (( ar[port]=1 ))
  182.                 fi
  183.         done
  184.  
  185.         return 0
  186. }
  187.  
  188. function netstat_find_next_free_local_tcp_port
  189. {
  190.         set -o nounset
  191.         compound c=( integer -a ar )
  192.         nameref ret_free_port=$1
  193.         integer start_port
  194.         integer end_port
  195.         integer i
  196.  
  197.         netstat_list_active_local_tcp_connections c.ar || return 1
  198.  
  199.         #print -v c
  200.  
  201.         (( start_port=$2 ))
  202.         if (( $# > 2 )) ; then
  203.                 (( end_port=$3 ))
  204.         else
  205.                 (( end_port=65535 ))
  206.         fi
  207.  
  208.         for ((i=start_port ; i < end_port ; i++ )) ; do
  209.                 if [[ ! -v c.ar[i] ]] ; then
  210.                         (( ret_free_port=i ))
  211.                         return 0
  212.                 fi
  213.         done
  214.  
  215.         return 1
  216. }
  217.  
  218.  
  219. #
  220. # parse_rfc1738_url - parse RFC 1838 URLs
  221. #
  222. # Output variables are named after RFC 1838 Section 5 ("BNF for
  223. # specific URL schemes")
  224. #
  225. function parse_rfc1738_url
  226. {
  227.         set -o nounset
  228.  
  229.         typeset url="$2"
  230.         typeset leftover
  231.         nameref data="$1" # output compound variable
  232.  
  233.         # ~(E) is POSIX extended regular expression matching (instead
  234.         # of shell pattern), "x" means "multiline", "l" means "left
  235.         # anchor", "r" means "right anchor"
  236.         leftover="${url/~(Elrx)
  237.                 (.+?)                           # scheme
  238.                 :\/\/                           # '://'
  239.                 (                               # login
  240.                         (?:
  241.                                 (.+?)           # user (optional)
  242.                                 (?::(.+))?      # password (optional)
  243.                                 @
  244.                         )?
  245.                         (                       # hostport
  246.                                 (.+?)           # host
  247.                                 (?::([[:digit:]]+))? # port (optional)
  248.                         )
  249.                 )
  250.                 (?:\/(.*?))?/X}"                # path (optional)
  251.  
  252.         # All parsed data should be captured via eregex in .sh.match - if
  253.         # there is anything left (except the 'X') then the input string did
  254.         # not properly match the eregex
  255.         [[ "$leftover" == 'X' ]] ||
  256.                 { print -u2 -f $"%s: Parser error, leftover=%q\n" \
  257.                         "$0" "$leftover" ; return 1 ; }
  258.  
  259.         data.url="${.sh.match[0]}"
  260.         data.scheme="${.sh.match[1]}"
  261.         data.login="${.sh.match[2]}"
  262.         # FIXME: This should use [[ ! -v .sh.match[3] ]], but ksh93u has bugs
  263.         [[ "${.sh.match[3]-}" != '' ]] && data.user="${.sh.match[3]}"
  264.         [[ "${.sh.match[4]-}" != '' ]] && data.password="${.sh.match[4]}"
  265.         data.hostport="${.sh.match[5]}"
  266.         data.host="${.sh.match[6]}"
  267.         [[ "${.sh.match[7]-}" != '' ]] && integer data.port="${.sh.match[7]}"
  268.         [[ "${.sh.match[8]-}" != '' ]] && data.uripath="${.sh.match[8]}"
  269.  
  270.         return 0
  271. }
  272.  
  273.  
  274. function parse_sshnfs_url
  275. {
  276.         typeset url="$2"
  277.         nameref data="$1"
  278.  
  279.         parse_rfc1738_url data "$url" || return 1
  280.  
  281.         [[ "${data.scheme}" == ~(Elr)(ssh\+nfs|nfs) ]] || \
  282.                 { print -u2 -f $"%s: Not a nfs:// or ssh+nfs:// url\n" "$0" ; return 1 ; }
  283.         [[ "${data.host}" != '' ]] || { print -u2 -f $"%s: NFS hostname missing\n" "$0" ; return 1 ; }
  284.         [[ "${data.uripath}" != '' ]] || { print -u2 -f $"%s: NFS path missing\n" "$0" ; return 1 ; }
  285.  
  286.         return 0
  287. }
  288.  
  289.  
  290. function mountpoint2configfilename
  291. {
  292.         nameref configfilename=$1
  293.         typeset mountpoint="$2"
  294.  
  295.         #
  296.         # FIXME:
  297.         # - We should urlencode more than just '/'
  298.         # - We should strip the leading '/'
  299.         # - We should use realpath(1) for mountpoints here
  300.         #
  301.  
  302.         # .cpv means ComPound Variable"
  303.         configfilename="/tmp/mount_sshnfs/${mountpoint//\//%2f}.cpv"
  304.         return 0
  305. }
  306.  
  307.  
  308. function cmd_mount
  309. {
  310.         set -o nounset
  311.         nameref c=$1
  312.  
  313.         # fixme: Need better text layout for $ mount_sshnfs mount --man #
  314.         typeset -r mount_sshnfs_cmdmount_usage=$'+
  315.         [-?\n@(#)\$Id: mount_sshnfs mount (Roland Mainz) 2023-07-24 \$\n]
  316.         [-author?Roland Mainz <roland.mainz@nrubsig.org>]
  317.         [+NAME?mount_sshnfs mount - mount NFSv4 filesystem through ssh
  318.                 tunnel]
  319.         [+DESCRIPTION?\bmount_sshnfs mount\b mounts a NFSv4 filesystem
  320.                 through a ssh tunnel.]
  321.         [r:readonly?Mount file system readonly.]
  322.         [w:readwrite?Mount file system read-write.]
  323.         [o:options?Use the specified mount options.
  324.                 The opts argument is a comma-separated list.\n
  325.                 options starting with mount_sshnfs_jumphost_* will be
  326.                 consumed by mount_sshnfs, all other options will be
  327.                 passed through to mount.nfs.]:[options]{
  328.                 [+?mount_sshnfs options are:]{
  329.                         [+?-o mount_sshnfs_jumphost=user@host:port - ssh jumphost]
  330.                         [+?-o mount_sshnfs_local_forward_port=port - local TCP port
  331.                                 for SSH-forwarded NFS connection to server.
  332.                                 Defaults is to use netstat(1) to find a free TCP port]
  333.                         }
  334.                 }
  335.  
  336.         url mountpoint
  337.        
  338.         [+SEE ALSO?\bksh93\b(1),\bssh\b(1),\bmount.nfs\b(8),\bnfs\b(5)]
  339.         '
  340.         typeset mydebug=false   # fixme: should be "bool" for ksh93v
  341.         typeset c.url
  342.         typeset c.mountpoint
  343.         typeset config_filename
  344.  
  345.         typeset -a c.mount_nfs_options
  346.         integer i
  347.         integer saved_optind_m1 # saved OPTIND-1
  348.         typeset s               # generic temporary string variable
  349.  
  350.         # remove subcmd name (in this case 'mount')
  351.         unset c.args[0]
  352.  
  353.         #
  354.         # Expand MOUNT_SSHNFS_CMDMOUNT_OPTIONS before arguments given to
  355.         # mount_sshnfs.ksh.
  356.         # By default we use IFS=$' \t\n' for argument splitting
  357.         #
  358.         c.args=( ${MOUNT_SSHNFS_CMDMOUNT_OPTIONS-} "${c.args[@]}" )
  359.  
  360.         #
  361.         # Argument parsing
  362.         #
  363.         while getopts -a "${progname} mount" "${mount_sshnfs_cmdmount_usage}" OPT "${c.args[@]}" ; do
  364.                 case "${OPT}" in
  365.                         'r')
  366.                                 c.mount_nfs_options+=( 'ro' )
  367.                                 ;;
  368.                         'w')
  369.                                 c.mount_nfs_options+=( 'rw' )
  370.                                 ;;
  371.                         'o')
  372.                                 #
  373.                                 # Split options like "-o foo=bar,baz=BAM"
  374.                                 # into "-o foo=bar -o baz=BAM" for easier
  375.                                 # processing below
  376.                                 IFS=$','
  377.                                 c.mount_nfs_options=( "${c.mount_nfs_options[@]}" ${OPTARG} )
  378.                                 IFS=$' \t\n'
  379.                                 ;;
  380.                         *)
  381.                                 usage "${progname} mount" "${mount_sshnfs_cmdmount_usage}"
  382.                                 return $?
  383.                                 ;;
  384.                 esac
  385.         done
  386.  
  387.         (( saved_optind_m1=OPTIND-1 ))
  388.  
  389.         # remove options we just parsed from c.args
  390.         for ((i=0 ; i < saved_optind_m1 ; i++)) ; do
  391.                 unset c.args[$i]
  392.         done
  393.  
  394.  
  395.         #
  396.         # Get remaining arguments
  397.         #
  398.         c.url="${c.args[saved_optind_m1+0]-}"
  399.         c.mountpoint="${c.args[saved_optind_m1+1]-}"
  400.  
  401.  
  402.         #
  403.         # Filter out our options, other options are passed to mount.nfs
  404.         #
  405.         for ((i=0 ; i < ${#c.mount_nfs_options[@]} ; i++)) ; do
  406.                 s="${c.mount_nfs_options[$i]}"
  407.  
  408.                 #
  409.                 # Intercept options starting with eregex mount_sshnfs.+
  410.                 #
  411.                 if [[ "$s" == ~(Elr)mount_sshnfs.+=.+ ]] ; then
  412.                         case "$s" in
  413.                                 ~(Eli)mount_sshnfs_jumphost=)
  414.                                         [[ ! -v c.ssh_jumphost_args ]] && typeset -a c.ssh_jumphost_args
  415.                                         c.ssh_jumphost_args+=( "-J" "${c.mount_nfs_options[i]/~(Eli)mount_sshnfs_jumphost=}" )
  416.                                         ;;
  417.                                 ~(Eli)mount_sshnfs_local_forward_port=)
  418.                                         # command(1) prevents that the shell interpreter
  419.                                         # exits if typeset produces a syntax error
  420.                                         command integer c.local_forward_port="${c.mount_nfs_options[i]/~(Eli)mount_sshnfs_local_forward_port=}" || return 1
  421.                                         ;;
  422.                                 *)
  423.                                         usage "${progname} mount" "${mount_sshnfs_cmdmount_usage}"
  424.                                         return $?
  425.                                         ;;
  426.                         esac
  427.                         unset c.mount_nfs_options[$i]
  428.                 fi
  429.         done
  430.  
  431.  
  432.         #
  433.         # Parse url
  434.         #
  435.         parse_sshnfs_url c.nfs_server "${c.url}" || return 1
  436.  
  437.         mountpoint2configfilename config_filename "${c.mountpoint}"
  438.  
  439.         if [[ -f "${config_filename}" ]] ; then
  440.                 print -u2 -f $"%s: Config file %q for mount point %q found.\n" \
  441.                         "$0" \
  442.                         "$config_filename" \
  443.                         "${c.mountpoint}"
  444.                 return 1
  445.         fi
  446.  
  447.         #
  448.         # Prechecks for writing the config file
  449.         #
  450.         mkdir -p '/tmp/mount_sshnfs/'
  451.         if [[ ! -w '/tmp/mount_sshnfs/' ]] ; then
  452.                 print -u2 -f $"%s: mount_nfs data directory %q not writeable.\n" \
  453.                         "$0" \
  454.                         '/tmp/mount_sshnfs/'
  455.                 return 1
  456.         fi
  457.  
  458.         ${mydebug} && print -v c
  459.  
  460.         case "${c.nfs_server.scheme}" in
  461.                 'ssh+nfs')
  462.                         #
  463.                         # Find free local forwarding port
  464.                         #
  465.  
  466.                         # fixme: ms-nfsv41 client nfs_mount.exe does
  467.                         # not support -o port=... yet, so we use the
  468.                         # default NFS TCP port
  469.                         if (( c.is_ccygwin == 1 )) ; then
  470.                                 integer c.local_forward_port=2049
  471.                         fi
  472.  
  473.                         # port on THIS machine
  474.                         if [[ ! -v c.local_forward_port ]] ; then
  475.                                 integer c.local_forward_port
  476.  
  477.                                 (( i=34049 ))
  478.                                 if ! netstat_find_next_free_local_tcp_port c.local_forward_port $i ; then
  479.                                         print -u2 -f "%s: netstat_find_next_free_local_tcp_port failed.\n" "$0"
  480.                                         return 1
  481.                                 fi
  482.                         fi
  483.  
  484.  
  485.                         c.ssh_control_socket_name="/tmp/mount_sshnfs/mount_sshnfs_ssh-control-socket_logname${LOGNAME}_ppid${PPID}_pid$$"
  486.  
  487.                         #
  488.                         # Find SSH login user name for NFS server
  489.                         #
  490.                         if [[ -v c.nfs_server.user ]] ; then
  491.                                 typeset c.nfsserver_ssh_login_name="${c.nfs_server.user}"
  492.                         fi
  493.                         # fixme: Implement NFSServerSSHLoginName
  494.                         if [[ ! -v c.nfsserver_ssh_login_name ]] ; then
  495.                                 # default user name if neither URL nor
  496.                                 # "-o NFSServerSSHLoginName=..." were given
  497.                                 typeset c.nfsserver_ssh_login_name="$LOGNAME"
  498.                         fi
  499.  
  500.                         #
  501.                         # Forward NFS port from server to local machine
  502.                         #
  503.                         # Notes:
  504.                         # - We use $ ssh -M ... # here as a way to terminate the port
  505.                         # forwarding process later using "-O exit" without the need
  506.                         # for a pid
  507.                         #
  508.                         print -u2 -f $"# Please enter the login data for NFS server (%s):\n" \
  509.                                 "${c.nfsserver_ssh_login_name}@${c.nfs_server.host}"
  510.  
  511.                         #
  512.                         # Notes:
  513.                         # - fixme: c.nfs_server.port is fixed
  514.                         # for ssh+nfs://-URLs, so for now we
  515.                         # have to hardcode TCP/2049 for now
  516.                         # - We use aes128-cbc,aes128-ctr ciphers for better
  517.                         # throughput (see https://bash-prompt.net/guides/bash-ssh-ciphers/
  518.                         # for a benchmark) and lower latency, as NFS is
  519.                         # a bit latency-sensitive
  520.                         # - We turn compression off, as it incrases latency
  521.                         #
  522.                         ssh \
  523.                                 -L "${c.local_forward_port}:localhost:2049" \
  524.                                 -M -S "${c.ssh_control_socket_name}" \
  525.                                 -N \
  526.                                 -f -o 'ExitOnForwardFailure=yes' \
  527.                                 -o 'Compression=no' \
  528.                                 -o 'Ciphers=aes128-cbc,aes128-ctr' \
  529.                                 "${c.ssh_jumphost_args[@]}" \
  530.                                 "${c.nfsserver_ssh_login_name}@${c.nfs_server.host}"
  531.                         if (( $? != 0 )) ; then
  532.                                 print -u2 -f $"%s: NFS forwarding ssh failed with error code %d\n" "$0" $?
  533.                                 return 1
  534.                         fi
  535.  
  536.                         # debug
  537.                         ${mydebug} && \
  538.                                 ssh \
  539.                                         -S "${c.ssh_control_socket_name}" \
  540.                                         -O 'check' \
  541.                                         "${c.nfsserver_ssh_login_name}@${c.nfs_server.host}"
  542.  
  543.  
  544.                         if (( c.is_ccygwin == 1 )) ; then
  545.                                 #
  546.                                 # Build argument list for nfs_mount.exe ...
  547.                                 #
  548.                                 typeset -a mount_args
  549.                                 for s in "${c.mount_nfs_options[@]}" ; do
  550.                                         mount_args+=( '-o' "$s" )
  551.                                 done
  552.                                
  553.                                 # fixme: nfs_mount.exe does not implement port=... yet
  554.                                 #mount_args+=( '-o' "port=${c.local_forward_port}" )
  555.                                 # fixme: can we remove -o sec=sys ?
  556.                                 mount_args+=( '-o' 'sec=sys' )
  557.                                 # '*' == Let nfs_mount.exe should pick drive letter itself
  558.                                 mount_args+=( '*' )
  559.                                 mount_args+=( "localhost:/${c.nfs_server.uripath}" )
  560.  
  561.                                 #
  562.                                 # ... and do the mount
  563.                                 #
  564.                                 typeset stdout dummy
  565.  
  566.                                 # fixme: we should set LC_ALL=C because below we depend on
  567.                                 # a l10n message
  568.                                 stdout="${ "${c.msnfsv41_nfsmountcmd}" "${mount_args[@]}" ; (( retval=$? )) ;}"
  569.                                 cat <<<"$stdout"
  570.  
  571.                                 if (( retval == 0 )) ; then
  572.                                         # Parse stdout for drive letter
  573.                                         dummy="${stdout/~(E)Successfully mounted (.+) to drive (.+):/dummy}"
  574.  
  575.                                         # fixme: we should test whether c.windows_drive_letter is empty or not
  576.                                         typeset c.windows_drive_letter="${.sh.match[2]}"
  577.  
  578.                                         print -u2 -f $"%s: NFS filesystem mounted to drive %q.\n" \
  579.                                                 "$0" "${c.windows_drive_letter}"
  580.  
  581.                                         # Cygwin bind mount
  582.                                         mount -o bind "/cygdrive/${c.windows_drive_letter}" "${c.mountpoint}"
  583.                                 fi
  584.                         else
  585.                                 #
  586.                                 # Build argument list for mount.nfs ...
  587.                                 #
  588.                                 typeset -a mount_args
  589.                                 mount_args+=( '-vvv' )
  590.                                 mount_args+=( '-t' 'nfs' )
  591.                                 for s in "${c.mount_nfs_options[@]}" ; do
  592.                                         mount_args+=( '-o' "$s" )
  593.                                 done
  594.                                 mount_args+=( '-o' 'vers=4.2' )
  595.                                 mount_args+=( '-o' "port=${c.local_forward_port}" )
  596.                                 mount_args+=( "localhost:/${c.nfs_server.uripath}" )
  597.                                 mount_args+=( "${c.mountpoint}" )
  598.  
  599.                                 #
  600.                                 # ... and do the mount
  601.                                 #
  602.                                 mount "${mount_args[@]}"
  603.                                 (( retval=$? ))
  604.                         fi
  605.  
  606.                         if (( retval != 0 )) ; then
  607.                                 #
  608.                                 # Quit ssh port forwarding process
  609.                                 #
  610.                                 ssh \
  611.                                         -S "${c.ssh_control_socket_name}" \
  612.                                         -O 'exit' \
  613.                                         "${c.nfsserver_ssh_login_name}@${c.nfs_server.host}"
  614.                                 return $retval
  615.                         fi
  616.  
  617.  
  618.                         #
  619.                         # Save status data
  620.                         #
  621.                         compound mnt_config=(
  622.                                 typeset url="${c.url}"
  623.                                 typeset mountpoint="${c.mountpoint}"
  624.                                 typeset ssh_control_socket_name="${c.ssh_control_socket_name}"
  625.                                 typeset nfsserver_ssh_login_name="${c.nfsserver_ssh_login_name}"
  626.                                 typeset nfsserver_host="${c.nfs_server.host}"
  627.                         )
  628.  
  629.                         if (( c.is_ccygwin == 1 )) ; then
  630.                                 typeset mnt_config.windows_drive_letter="${c.windows_drive_letter}"
  631.                         fi
  632.  
  633.                         print -v mnt_config >"$config_filename"
  634.  
  635.                         return 0
  636.                         ;;
  637.                 # fixme: Implement nfs://-URLs
  638.                 *)
  639.                         print -u2 -f $"%s: Unknown URL scheme %q\n" "$0" "${c.nfs_server.scheme}"
  640.                         return 2
  641.                         ;;
  642.         esac
  643.  
  644.         # notreached
  645. }
  646.  
  647.  
  648. function cmd_umount
  649. {
  650.         set -o nounset
  651.         nameref c=$1
  652.         integer retval
  653.         integer saved_optind_m1 # saved OPTIND-1
  654.  
  655.         typeset mydebug=false # fixme: should be "bool" for ksh93v
  656.         # fixme: Need better text layout for $ mount_sshnfs mount --man #
  657.         typeset -r mount_sshnfs_cmdumount_usage=$'+
  658.         [-?\n@(#)\$Id: mount_sshnfs umount (Roland Mainz) 2023-07-24 \$\n]
  659.         [-author?Roland Mainz <roland.mainz@nrubsig.org>]
  660.         [+NAME?mount_sshnfs umount - unmount NFSv4 filesystem mounted
  661.                 via mount_sshnfs mount]
  662.         [+DESCRIPTION?\bmount_sshnfs umount\b unmounts a NFSv4
  663.                 filesystem previously mounted via mount_sshnfs mount.]
  664.  
  665.         mountpoint
  666.        
  667.         [+SEE ALSO?\bksh93\b(1),\bssh\b(1),\bmount.nfs\b(8),\bnfs\b(5)]
  668.         '
  669.  
  670.         # remove subcmd name (in this case 'umount')
  671.         unset c.args[0]
  672.  
  673.         #
  674.         # Expand MOUNT_SSHNFS_CMDUMOUNT_OPTIONS before arguments given to
  675.         # mount_sshnfs.ksh.
  676.         # By default we use IFS=$' \t\n' for argument splitting
  677.         #
  678.         c.args=( ${MOUNT_SSHNFS_CMDUMOUNT_OPTIONS-} "${c.args[@]}" )
  679.  
  680.         #
  681.         # Argument parsing
  682.         #
  683.         while getopts -a "${progname} umount" "${mount_sshnfs_cmdumount_usage}" OPT "${c.args[@]}" ; do
  684.                 case "${OPT}" in
  685.                         *)
  686.                                 usage "${progname} umount" "${mount_sshnfs_cmdumount_usage}"
  687.                                 return $?
  688.                                 ;;
  689.                 esac
  690.         done
  691.  
  692.         (( saved_optind_m1=OPTIND-1 ))
  693.  
  694.         # remove options we just parsed from c.args
  695.         for ((i=0 ; i < saved_optind_m1 ; i++)) ; do
  696.                 unset c.args[$i]
  697.         done
  698.  
  699.  
  700.         #
  701.         # Get remaining arguments
  702.         #
  703.         c.mountpoint="${c.args[saved_optind_m1+0]-}"
  704.  
  705.         #
  706.         # Read configuration file for this mountpoint
  707.         #
  708.         typeset config_filename
  709.         mountpoint2configfilename config_filename "${c.mountpoint}"
  710.  
  711.         if [[ ! -f "${config_filename}" ]] ; then
  712.                 print -u2 -f $"%s: Config file %q for mount point %q not found.\n" \
  713.                         "$0" \
  714.                         "$config_filename" \
  715.                         "${c.mountpoint}"
  716.                 return 1
  717.         fi
  718.  
  719.         compound mnt_config
  720.         read -C mnt_config <"${config_filename}" || return 1
  721.  
  722.         ${mydebug} && print -v mnt_config
  723.  
  724.         #
  725.         # Do the unmount
  726.         #
  727.         if (( c.is_ccygwin == 1 )) ; then
  728.                 # unmount the NFS filesystem
  729.                 "${c.msnfsv41_nfsmountcmd}" -d "${mnt_config.windows_drive_letter}"
  730.                 (( retval=$? ))
  731.  
  732.                 # remove the Cygwin bind mount
  733.                 (( retval == 0 )) && umount "${c.mountpoint}"
  734.         else
  735.                 umount "${c.mountpoint}"
  736.                 (( retval=$? ))
  737.         fi
  738.  
  739.         if (( retval != 0 )) ; then
  740.                 return $retval
  741.         fi
  742.  
  743.         #
  744.         # Quit ssh port forwarding process
  745.         #
  746.         ssh \
  747.                 -S "${mnt_config.ssh_control_socket_name}" \
  748.                 -O 'exit' \
  749.                 "${mnt_config.nfsserver_ssh_login_name}@${mnt_config.nfsserver_host}"
  750.  
  751.         rm -f "${config_filename}"
  752.         return 0
  753. }
  754.  
  755.  
  756. function main
  757. {
  758.         set -o nounset
  759.  
  760.         # fixme: Need better text layout for $ mount_sshnfs --man #
  761.         typeset -r mount_sshnfs_usage=$'+
  762.         [-?\n@(#)\$Id: mount_sshnfs (Roland Mainz) 2023-07-24 \$\n]
  763.         [-author?Roland Mainz <roland.mainz@nrubsig.org>]
  764.         [+NAME?mount_sshnfs - mount/umount NFSv4 filesystem via ssh
  765.                 tunnel]
  766.         [+DESCRIPTION?\bmount_sshnfs\b mounts/unmounts a NFSv4
  767.                 filesystem via ssh tunnel.]
  768.         [D:debug?Enable debugging.]
  769.  
  770.         mount [options]
  771.         umount [options]
  772.         status [options]
  773.         restart_forwarding [options]
  774.  
  775.         [+EXAMPLES]{
  776.                 [+?Example 1:][+?Mount&&unmount /export/home/rmainz on NFS server "/export/home/rmainz"]{
  777. [+\n# mkdir -p /foobarmnt
  778. # ksh mount_sshnfs.ksh mount ssh+nfs:://rmainz@derfwpc5131/export/home/rmainz /foobarmnt
  779. # mount_sshnfs.ksh umount /foobarmnt
  780. ]
  781. }
  782.                 [+?Example 2:][+?Mount&&unmount /export/home/rmainz on NFS server "/export/home/rmainz" via SSH jumphost rmainz@10.49.20.131]{
  783. [+\n# mkdir -p /foobarmnt
  784. # ksh mount_sshnfs.ksh mount -o ro,mount_sshnfs_jumphost=rmainz@10.49.20.131 ssh+nfs:://rmainz@derfwpc5131/export/home/rmainz /foobarmnt
  785. # mount_sshnfs.ksh umount /foobarmnt
  786. ]
  787.                 }
  788.         }
  789.         [+SEE ALSO?\bksh93\b(1),\bssh\b(1),\bmount.nfs\b(8),\bnfs\b(5)]
  790.         '
  791.  
  792.         compound c
  793.         typeset -a c.args
  794.         integer saved_optind_m1 # saved OPTIND-1
  795.        
  796.         if [[ "${ uname -o ;}" == 'Cygwin' ]] ; then
  797.                 integer c.is_ccygwin=1
  798.                
  799.                 # only for testing!!
  800.                 PATH+=':/cygdrive/c/Users/roland_mainz/Downloads/ms-nfs41-client-x64/ms-nfs41-client-x64/'
  801.  
  802.                 typeset c.msnfsv41_nfsmountcmd="$(which 'nfs_mount.exe')"
  803.                
  804.                 if [[ ! -x "${c.msnfsv41_nfsmountcmd}" ]] ; then
  805.                         print -u2 -f $"%s: Cannot find MS-NFSV41 nfs_mount.exe command\n" "$0"
  806.                         return 1
  807.                 fi
  808.         else
  809.                 integer c.is_ccygwin=0
  810.         fi
  811.  
  812.         # Cygwin does not set logname
  813.         [[ ! -v LOGNAME ]] && export LOGNAME="$(logname)"
  814.  
  815.         #
  816.         # Expand MOUNT_SSHNFS_OPTIONS before arguments given to
  817.         # mount_sshnfs.ksh.
  818.         # By default we use IFS=$' \t\n' for argument splitting
  819.         #
  820.         c.args=( ${MOUNT_SSHNFS_OPTIONS-} "$@" )
  821.  
  822.         #
  823.         # Argument parsing
  824.         #
  825.         while getopts -a "${progname}" "${mount_sshnfs_usage}" OPT "${c.args[@]}" ; do
  826.                 case "${OPT}" in
  827.                         'D')
  828.                                 # fixme: Implement debugging option
  829.                                 ;;
  830.                         *)
  831.                                 usage "${progname}" "${mount_sshnfs_usage}"
  832.                                 return $?
  833.                                 ;;
  834.                 esac
  835.         done
  836.  
  837.         (( saved_optind_m1=OPTIND-1 ))
  838.  
  839.         # remove options we just parsed from c.args
  840.         for ((i=0 ; i < saved_optind_m1 ; i++)) ; do
  841.                 unset c.args[$i]
  842.         done
  843.  
  844.         #
  845.         # c.args mighth be a sparse array (e.g. "([1]=aaa [2]=bbb [4]=ccc)")
  846.         # right now after we removed processed options/arguments.
  847.         # For easier processing below we "reflow" the array back to a
  848.         # normal linear layout (e.g. ([0]=aaa [1]=bbb [2]=ccc)
  849.         #
  850.         c.args=( "${c.args[@]}" )
  851.  
  852.         #
  853.         # Subcommand dispatcher
  854.         #
  855.         case "${c.args[0]-}" in
  856.                 'mount')
  857.                         cmd_mount c
  858.                         return $?
  859.                         ;;
  860.                 'umount')
  861.                         cmd_umount c
  862.                         return $?
  863.                         ;;
  864.                 'status' | 'restart_forwarding')
  865.                         print -u2 -f $"%s: not implemented yet\n" "$0"
  866.                         return 2
  867.                         ;;
  868.                 *)
  869.                         print -u2 -f $"%s: Unknown command %q\n" \
  870.                                 "$0" "${c.args[0]-}"
  871.                         usage "${progname}" "${mount_sshnfs_usage}"
  872.                         return 1
  873.                         ;;
  874.         esac
  875.  
  876.         # notreached
  877. }
  878.  
  879.  
  880. #
  881. # main
  882. #
  883. builtin cat
  884. builtin mkdir
  885. builtin basename
  886.  
  887. typeset progname="${ basename "${0}" ; }"
  888.  
  889. main "$@"
  890. exit $?
  891.  
  892. # EOF.

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