diff --git a/SOURCES/0009-python-Try-harder-to-print-the-full-traceback-on-err.patch b/SOURCES/0009-python-Try-harder-to-print-the-full-traceback-on-err.patch new file mode 100644 index 0000000..f9ee353 --- /dev/null +++ b/SOURCES/0009-python-Try-harder-to-print-the-full-traceback-on-err.patch @@ -0,0 +1,229 @@ +From 107a0d0ca210858d35bbda10e699274b12efa978 Mon Sep 17 00:00:00 2001 +From: "Richard W.M. Jones" +Date: Wed, 8 Aug 2018 13:50:23 +0100 +Subject: [PATCH] python: Try harder to print the full traceback on error. +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +The tracebacks are compressed into a single line because we're using +PyObject_Str, but they are just about usable if not very readable. +For example you would see an error like this: + +nbdkit: error: ./python-exception.py: config_complete: error: ['Traceback (most recent call last):\n', ' File "./python-exception.py", line 54, in config_complete\n raise_error1()\n', ' File "./python-exception.py", line 48, in raise_error1\n raise_error2()\n', ' File "./python-exception.py", line 45, in raise_error2\n raise RuntimeError("this is the test string")\n', 'RuntimeError: this is the test string\n'] + +which can be read by manually unfolding the exception in an editor as: + +nbdkit: error: ./python-exception.py: config_complete: error: +Traceback (most recent call last): + File "./python-exception.py", line 54, in config_complete + raise_error1() + File "./python-exception.py", line 48, in raise_error1 + raise_error2() + File "./python-exception.py", line 45, in raise_error2 + raise RuntimeError("this is the test string") +RuntimeError: this is the test string + +This also fixes the Python exception test: + +(1) It originally was not testing anything. Adding ‘set -e’ fixes +that. + +(2) The valgrind test is always broken because of Python itself. +Skip the test under valgrind. + +(3) This now tests both simple exceptions and full tracebacks. + +Tested with Python 2.7.15 & 3.6.6. + +(cherry picked from commit 72c0d64a47db642cafa89884f2ee554bd0b8e822) +--- + plugins/python/python.c | 93 +++++++++++++++++++++++++++++++++++------- + tests/python-exception.py | 20 ++++++++- + tests/test-python-exception.sh | 20 ++++++++- + 3 files changed, 117 insertions(+), 16 deletions(-) + +diff --git a/plugins/python/python.c b/plugins/python/python.c +index 7eb91d7..ef1a2cf 100644 +--- a/plugins/python/python.c ++++ b/plugins/python/python.c +@@ -129,27 +129,92 @@ python_to_string (PyObject *str) + return NULL; + } + ++/* This is the fallback in case we cannot get the full traceback. */ ++static void ++print_python_error (const char *callback, PyObject *error) ++{ ++ PyObject *error_str; ++ char *error_cstr = NULL; ++ ++ error_str = PyObject_Str (error); ++ error_cstr = python_to_string (error_str); ++ nbdkit_error ("%s: %s: error: %s", ++ script, callback, ++ error_cstr ? error_cstr : ""); ++ Py_DECREF (error_str); ++ free (error_cstr); ++} ++ ++/* Convert the Python traceback to a string and call nbdkit_error. ++ * https://stackoverflow.com/a/15907460/7126113 ++ */ ++static int ++print_python_traceback (const char *callback, ++ PyObject *type, PyObject *error, PyObject *traceback) ++{ ++ PyObject *module_name, *traceback_module, *format_exception_fn, *rv, ++ *traceback_str; ++ char *traceback_cstr; ++ ++#ifdef HAVE_PYSTRING_FROMSTRING ++ module_name = PyString_FromString ("traceback"); ++#else ++ module_name = PyUnicode_FromString ("traceback"); ++#endif ++ traceback_module = PyImport_Import (module_name); ++ Py_DECREF (module_name); ++ ++ /* couldn't 'import traceback' */ ++ if (traceback_module == NULL) ++ return -1; ++ ++ format_exception_fn = PyObject_GetAttrString (traceback_module, ++ "format_exception"); ++ if (format_exception_fn == NULL) ++ return -1; ++ if (!PyCallable_Check (format_exception_fn)) ++ return -1; ++ ++ rv = PyObject_CallFunctionObjArgs (format_exception_fn, ++ type, error, traceback, NULL); ++ traceback_str = PyObject_Str (rv); ++ Py_DECREF (rv); ++ traceback_cstr = python_to_string (traceback_str); ++ if (traceback_cstr == NULL) { ++ Py_DECREF (traceback_str); ++ return -1; ++ } ++ ++ nbdkit_error ("%s: %s: error: %s", ++ script, callback, ++ traceback_cstr); ++ Py_DECREF (traceback_str); ++ free (traceback_cstr); ++ ++ /* This means we succeeded in calling nbdkit_error. */ ++ return 0; ++} ++ + static int + check_python_failure (const char *callback) + { + if (PyErr_Occurred ()) { +- PyObject *type, *error, *traceback, *error_str; +- char *error_cstr; ++ PyObject *type, *error, *traceback; + +- /* Convert the Python exception to a string. +- * https://stackoverflow.com/a/1418703 +- * But forget about the traceback, it's very hard to print. +- * https://stackoverflow.com/q/1796510 +- */ + PyErr_Fetch (&type, &error, &traceback); + PyErr_NormalizeException (&type, &error, &traceback); +- error_str = PyObject_Str (error); +- error_cstr = python_to_string (error_str); +- nbdkit_error ("%s: %s: error: %s", +- script, callback, +- error_cstr ? error_cstr : ""); +- Py_DECREF (error_str); +- free (error_cstr); ++ ++ /* Try to print the full traceback. */ ++ if (print_python_traceback (callback, type, error, traceback) == -1) { ++ /* Couldn't do that, so fall back to converting the Python error ++ * to a string. ++ */ ++ print_python_error (callback, error); ++ } ++ ++ /* In all cases this returns -1 to indicate that a Python error ++ * occurred. ++ */ + return -1; + } + return 0; +diff --git a/tests/python-exception.py b/tests/python-exception.py +index 1debf51..739057f 100644 +--- a/tests/python-exception.py ++++ b/tests/python-exception.py +@@ -32,10 +32,28 @@ + + # A dummy python plugin which just raises an exception in config_complete. + ++test = "simple" + +-def config_complete(): ++def config(k, v): ++ global test ++ if k == "test": ++ test = v ++ else: ++ raise RuntimeError("unknown config parameter") ++ ++def raise_error2(): + raise RuntimeError("this is the test string") + ++def raise_error1(): ++ raise_error2() ++ ++def config_complete(): ++ if test == "simple": ++ raise RuntimeError("this is the test string") ++ elif test == "traceback": ++ raise_error1() ++ else: ++ raise RuntimeError("unknown test") + + def open(readonly): + return 1 +diff --git a/tests/test-python-exception.sh b/tests/test-python-exception.sh +index 83999af..fd94827 100755 +--- a/tests/test-python-exception.sh ++++ b/tests/test-python-exception.sh +@@ -31,12 +31,30 @@ + # OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + # SUCH DAMAGE. + ++set -e ++set -x ++ ++# Python language leaks like a sieve as well as a lot of worrying ++# "Conditional jump or move depends on uninitialised value(s)". ++if test -n "$NBDKIT_VALGRIND"; then ++ echo "$0: skipping Python test under valgrind." ++ exit 77 ++fi ++ + output=test-python-exception.out + + rm -f $output + +-nbdkit -f -v python ./python-exception.py > $output 2>&1 ||: ++nbdkit -f -v python ./python-exception.py test=simple > $output 2>&1 ||: ++cat $output + + grep 'this is the test string' $output + ++nbdkit -f -v python ./python-exception.py test=traceback > $output 2>&1 ||: ++cat $output ++ ++grep 'raise_error1' $output ++grep 'raise_error2' $output ++grep 'this is the test string' $output ++ + rm $output +-- +1.8.3.1 + diff --git a/SPECS/nbdkit.spec b/SPECS/nbdkit.spec index db19967..3966fbd 100644 --- a/SPECS/nbdkit.spec +++ b/SPECS/nbdkit.spec @@ -27,7 +27,7 @@ Name: nbdkit Version: 1.2.6 -Release: 1%{?dist} +Release: 1%{?dist}.2 Summary: NBD server License: BSD @@ -50,6 +50,7 @@ Patch5: 0005-vddk-Improve-error-message-if-the-proprietary-librar.patch Patch6: 0006-vddk-If-relative-libdir-parameter-is-passed-make-it-.patch Patch7: 0007-vddk-Two-more-static-dlsym-variables.patch Patch8: 0008-vddk-Add-a-very-simple-test.patch +Patch9: 0009-python-Try-harder-to-print-the-full-traceback-on-err.patch %if 0%{patches_touch_autotools} BuildRequires: autoconf, automake, libtool @@ -272,7 +273,7 @@ copy="$(mktemp -d)" cp -a . "$copy" mv "$copy" python3 -%configure --disable-static --with-tls-priority=@NBDKIT,SYSTEM \ +%configure --disable-static --with-tls-priority=NORMAL \ --disable-perl \ --disable-ocaml \ --disable-ruby \ @@ -408,6 +409,13 @@ make check -j1 || { %changelog +* Thu Nov 08 2018 Richard W.M. Jones - 1.2.6-1.el7_6.2 +- Enhanced Python error reporting. + resolves: rhbz#1613946 + +* Fri Oct 12 2018 Pino Toscano - 1.2.6-1.el7_6.1 +- Fix TLS priority. (RHBZ#1632220) + * Wed Aug 1 2018 Richard W.M. Jones - 1.2.6-1 - New stable version 1.2.6.