Starting with a back-story: I wanted[1] to write:

$ nbdkit --tls=require --tls-psk=$dir/tests/keys.psk null --run \
 'nbdsh -u "nbds://alice@localhost/?tls-psk-file='"$dir"'/tests/keys.psk" \
   -c "h.get_size()"'

but it fails with the latest libnbd release, due to:

nbdsh: unable to connect to uri 
'nbds://alice@localhost/?tls-psk-file=/home/eblake/libnbd/tests/keys.psk': 
nbd_connect_uri: local file access (tls-psk-file) is not allowed, call 
nbd_set_uri_allow_local_file to enable this: Operation not permitted

But without this patch, this obvious attempt at a fix doesn't work:

$ nbdkit --tls=require --tls-psk=$dir/tests/keys.psk null --run \
 'nbdsh -c "h.set_uri_allow_local_file(True)" \
   -u "nbds://alice@localhost/?tls-psk-file='"$dir"'/tests/keys.psk" \
   -c "h.get_size()"'

because nbdsh was performing h.connect_uri() prior to running any of
the -c strings (seen more obviously two patches ago when refactoring
shortcuts to reuse the -c framework), where the attempt to inject -c
to obey the error message is processed too late, even though it
occurred earlier in the command line.  The immediate workaround is
thus:

$ nbdkit --tls=require --tls-psk=$dir/tests/keys.psk null --run \
 'nbdsh -c "h.set_uri_allow_local_file(True)" \
   -c 
"h.connect_uri(\"nbds://alice@localhost/?tls-psk-file='"$dir"'/tests/keys.psk\")"
 \
   -c "h.get_size()"'

which gets awkward because of the multiple layers of quoting required
(--run, -c, and Python each strip a layer).  But we can do better.  We
can create args.command in command-line order by introducing the use
of a custom Action subclass of argparse.  test-context.sh has to be
updated to match our saner processing order.  nbdkit's testsuite still
passes even when using this update to nbdsh, so I don't think programs
in the wild were relying on insane command-line rearranging.

[1] Actually, I wanted to use -U- and write -u "$uri" instead of -u
"nbds+unix...", but that involves teaching nbdkit how to display a
more-useful URI when TLS is in use.  Not this patch's problem.
---
 python/nbdsh.py    | 60 ++++++++++++++++++++++++++++++----------------
 sh/test-context.sh | 26 ++++++++++----------
 2 files changed, 51 insertions(+), 35 deletions(-)

diff --git a/python/nbdsh.py b/python/nbdsh.py
index f23d8bfb..370f3fd2 100644
--- a/python/nbdsh.py
+++ b/python/nbdsh.py
@@ -33,11 +33,37 @@ def shell():
     epilog = '''Please read the nbdsh(1) manual page for full usage.'''
     parser = argparse.ArgumentParser(prog='nbdsh', description=description,
                                      epilog=epilog)
+
+    # We want to treat some options as shortcuts for longer '-c code'
+    # snippets while preserving relative ordering of the overall command
+    # line; use of the shortcuts does not prevent interactive mode.
+    class ShortcutAction(argparse.Action):
+        def __init__(self, option_strings, dest, nargs=None, const=None,
+                     default=argparse.SUPPRESS, **kwargs):
+            if nargs not in [0, 1]:
+                raise ValueError("nargs must be 0 or 1")
+            if const is None:
+                raise ValueError("missing const string for use as shortcut")
+            super().__init__(option_strings, dest, nargs=nargs, const=const,
+                             default=default, **kwargs)
+
+        def __call__(self, parser, namespace, values, option_string=None):
+            setattr(namespace, 'shortcuts',
+                    getattr(namespace, 'shortcuts') + 1)
+            commands = getattr(namespace, 'command')[:]
+            if self.nargs == 0:
+                commands.append(self.const)
+            else:
+                commands.append(self.const % values[0])
+            setattr(namespace, 'command', commands)
+
+    parser.set_defaults(shortcuts=0)
     short_options = []
     long_options = []
-    shortcuts = 0

-    parser.add_argument('--base-allocation', action='store_true',
+    parser.add_argument('--base-allocation', action=ShortcutAction, nargs=0,
+                        const='h.add_meta_context' +
+                        '(nbd.CONTEXT_BASE_ALLOCATION)',
                         help='request the "base:allocation" meta context')
     long_options.append("--base-allocation")

@@ -51,15 +77,20 @@ def shell():
                         help="do not create the implicit handle 'h'")
     short_options.append("-n")

-    parser.add_argument('--opt-mode', action='store_true',
+    parser.add_argument('--opt-mode', action=ShortcutAction, nargs=0,
+                        const='h.set_opt_mode(True)',
                         help='request opt mode during connection')
     long_options.append("--opt-mode")

-    parser.add_argument('-u', '--uri', help="connect to NBD URI")
+    parser.add_argument('-u', '--uri', action=ShortcutAction, nargs=1,
+                        const='h.connect_uri("%s")',
+                        help="connect to NBD URI")
     short_options.append("-u")
     long_options.append("--uri")
     # For back-compat, provide --connect as an undocumented synonym to --uri
-    parser.add_argument('--connect', dest='uri', help=argparse.SUPPRESS)
+    parser.add_argument('--connect', action=ShortcutAction, nargs=1,
+                        const='h.connect_uri("%s")',
+                        dest='uri', help=argparse.SUPPRESS)

     parser.add_argument('-v', '--verbose', action='store_true',
                         help="enable verbose debugging")
@@ -80,9 +111,7 @@ def shell():
     args = parser.parse_args()

     # It's an error if -n is passed with certain other options.
-    if args.n and (args.base_allocation or
-                   args.opt_mode or
-                   args.uri is not None):
+    if args.n and args.shortcuts > 0:
         print("error: -n option cannot be used with " +
               "--base-allocation, --opt-mode or --uri",
               file=sys.stderr)
@@ -111,18 +140,7 @@ def shell():
         h.set_handle_name("nbdsh")
         cmds = args.command

-        # Set other attributes in the handle.
-        if args.uri is not None:
-            cmds.insert(0, 'h.connect_uri("%s")' % args.uri)
-            shortcuts += 1
-        if args.base_allocation:
-            cmds.insert(0, 'h.add_meta_context(nbd.CONTEXT_BASE_ALLOCATION)')
-            shortcuts += 1
-        if args.opt_mode:
-            cmds.insert(0, 'h.set_opt_mode(True)')
-            shortcuts += 1
-
-    # Run all -c snippets, including any shortcuts we just added
+    # Run all -c snippets, including shortcuts
     # https://stackoverflow.com/a/11754346
     d = dict(locals(), **globals())
     try:
@@ -140,7 +158,7 @@ def shell():
         sys.exit(1)

     # If there are no explicit -c or --command parameters, go interactive.
-    if len(args.command) - shortcuts == 0:
+    if len(args.command) - args.shortcuts == 0:
         sys.ps1 = "nbd> "
         if args.n:
             h = None
diff --git a/sh/test-context.sh b/sh/test-context.sh
index 168fe487..8f759075 100755
--- a/sh/test-context.sh
+++ b/sh/test-context.sh
@@ -31,15 +31,13 @@ if test "x$output" != xFalse; then
     fail=1
 fi

-# Use of -c to request context is too late with -u
-output=$(nbdkit -U - null --run 'nbdsh -c "
-try:
-    h.add_meta_context(nbd.CONTEXT_BASE_ALLOCATION)
-    assert False
-except nbd.Error:
-    print(\"okay\")
-" -u "nbd+unix://?socket=$unixsocket"')
-if test "x$output" != xokay; then
+# Can also use manual -c to request context before -u
+output=$(nbdkit -U - null --run 'nbdsh \
+-c "h.add_meta_context(nbd.CONTEXT_BASE_ALLOCATION)" \
+-u "nbd+unix://?socket=$unixsocket" \
+-c "print(h.can_meta_context(nbd.CONTEXT_BASE_ALLOCATION))"
+')
+if test "x$output" != xTrue; then
     echo "$0: unexpected output: $output"
     fail=1
 fi
@@ -53,9 +51,9 @@ if test "x$output" != xTrue; then
     fail=1
 fi

-# Again, but with --b after -u, and with abbreviated option names
+# Again, but with abbreviated option names
 output=$(nbdkit -U - null --run 'nbdsh \
-    -u "nbd+unix://?socket=$unixsocket" --b \
+    --b -u "nbd+unix://?socket=$unixsocket" \
     -c "print(h.can_meta_context(nbd.CONTEXT_BASE_ALLOCATION))"')
 if test "x$output" != xTrue; then
     echo "$0: unexpected output: $output"
@@ -65,7 +63,7 @@ fi
 if [[ $(nbdkit --help) =~ --no-sr ]]; then
     # meta context depends on server cooperation
     output=$(nbdkit -U - --no-sr null --run 'nbdsh \
-      -u "nbd+unix://?socket=$unixsocket" --base-allocation \
+      --base-allocation -u "nbd+unix://?socket=$unixsocket" \
       -c "print(h.can_meta_context(nbd.CONTEXT_BASE_ALLOCATION))"')
     if test "x$output" != xFalse; then
         echo "$0: unexpected output: $output"
@@ -77,7 +75,7 @@ fi

 # Test interaction with opt mode
 output=$(nbdkit -U - null --run 'nbdsh \
-    -u "nbd+unix://?socket=$unixsocket" --opt-mode --base-allocation \
+    --opt-mode --base-allocation -u "nbd+unix://?socket=$unixsocket" \
     -c "
 try:
     h.can_meta_context(nbd.CONTEXT_BASE_ALLOCATION)
@@ -94,7 +92,7 @@ fi

 # And with --opt-mode, we can get away without --base-allocation
 output=$(nbdkit -U - null --run 'nbdsh \
-    -u "nbd+unix://?socket=$unixsocket" --opt-mode \
+    --opt-mode -u "nbd+unix://?socket=$unixsocket" \
     -c "h.add_meta_context(nbd.CONTEXT_BASE_ALLOCATION)" \
     -c "h.opt_go()" \
     -c "print(h.can_meta_context(nbd.CONTEXT_BASE_ALLOCATION))"')
-- 
2.37.3

_______________________________________________
Libguestfs mailing list
Libguestfs@redhat.com
https://listman.redhat.com/mailman/listinfo/libguestfs

Reply via email to