Blob Blame History Raw
From e701c277cdb07d849a9f7b67aabfc4ff391f8970 Mon Sep 17 00:00:00 2001
From: Chris Lumens <clumens@redhat.com>
Date: Tue, 13 Jan 2015 11:13:17 -0500
Subject: [PATCH 4/7] Add new functions to extend exception handling.

We've been avoiding this for an awful long time, but there is now a need for
users of pyparted to be able to specify what answers should be given to parted
exceptions that ask a question.  Specifically, we need to be able to ask the
user whether to proceed with a disk containing a corrupted GPT disk label or not.

This adds a function to register a callback that can prompt the user (among other
possible actions) and a function to clear this callback to restore default
behavior.
---
 src/_pedmodule.c | 135 ++++++++++++++++++++++++++++++++++++++++++++++++++-----
 1 file changed, 125 insertions(+), 10 deletions(-)

diff --git a/src/_pedmodule.c b/src/_pedmodule.c
index a1c8947..7f02193 100644
--- a/src/_pedmodule.c
+++ b/src/_pedmodule.c
@@ -5,7 +5,7 @@
  * Python module that implements the libparted functionality via Python
  * classes and other high level language features.
  *
- * Copyright (C) 2007, 2008 Red Hat, Inc.
+ * Copyright (C) 2007-2015 Red Hat, Inc.
  *
  * This copyrighted material is made available to anyone wishing to use,
  * modify, copy, or redistribute it subject to the terms and conditions of
@@ -44,6 +44,8 @@
 char *partedExnMessage = NULL;
 unsigned int partedExnRaised = 0;
 
+PyObject *exn_handler = NULL;
+
 /* Docs strings are broken out of the module structure here to be at least a
  * little bit readable.
  */
@@ -190,6 +192,25 @@ PyDoc_STRVAR(unit_get_by_name_doc,
 "Returns a Unit given its textual representation.  Returns one of the\n"
 "UNIT_* constants.");
 
+PyDoc_STRVAR(register_exn_handler_doc,
+"register_exn_handler(function)\n\n"
+"When parted raises an exception, the function registered here will be called\n"
+"to help determine what action to take.  This does not bypass the exception\n"
+"handler pyparted uses.  Instead, it can be used to pop up a dialog to ask the\n"
+"user which course of action to take, or to provide a different default answer,\n"
+"or other actions.\n\n"
+"The given function must accept as arguments:  (1) an integer corresponding to\n"
+"one of the EXCEPTION_TYPE_* constants; (2) an integer corresponding to one of the\n"
+"EXCEPTION_OPT_* constants; and (3) a string that is the problem encountered by\n"
+"parted.  This string will already be translated.  The given function must return\n"
+"one of the EXCEPTION_RESOLVE_* constants instructing parted how to proceed.");
+
+PyDoc_STRVAR(clear_exn_handler_doc,
+"clear_exn_handler()\n\n"
+"Clear any previously added exception handling function.  This means the\n"
+"default behavior for all parted exceptions will be used, so only safe\n"
+"answers to any questions parted asks will be automatically provided.");
+
 PyDoc_STRVAR(_ped_doc,
 "This module implements an interface to libparted.\n\n"
 "pyparted provides two API layers:  a lower level that exposes the complete\n"
@@ -204,6 +225,25 @@ PyDoc_STRVAR(_ped_doc,
 "For complete documentation, refer to the docs strings for each _ped\n"
 "method, exception class, and subclass.");
 
+PyObject *py_ped_register_exn_handler(PyObject *s, PyObject *args) {
+    PyObject *fn = NULL;
+
+    if (!PyArg_ParseTuple(args, "O", &fn)) {
+        return NULL;
+    }
+
+    Py_DECREF(exn_handler);
+    exn_handler = fn;
+
+    Py_RETURN_TRUE;
+}
+
+PyObject *py_ped_clear_exn_handler(PyObject *s, PyObject *args) {
+    exn_handler = Py_None;
+    Py_INCREF(exn_handler);
+    Py_RETURN_TRUE;
+}
+
 /* all of the methods for the _ped module */
 static struct PyMethodDef PyPedModuleMethods[] = {
     {"libparted_version", (PyCFunction) py_libparted_get_version, METH_VARARGS,
@@ -211,6 +251,11 @@ static struct PyMethodDef PyPedModuleMethods[] = {
     {"pyparted_version", (PyCFunction) py_pyparted_version, METH_VARARGS,
                          pyparted_version_doc},
 
+    {"register_exn_handler", (PyCFunction) py_ped_register_exn_handler, METH_VARARGS,
+                             register_exn_handler_doc},
+    {"clear_exn_handler", (PyCFunction) py_ped_clear_exn_handler, METH_VARARGS,
+                          clear_exn_handler_doc},
+
     /* pyconstraint.c */
     {"constraint_new_from_min_max", (PyCFunction) py_ped_constraint_new_from_min_max,
                                     METH_VARARGS, constraint_new_from_min_max_doc},
@@ -337,6 +382,10 @@ PyObject *py_pyparted_version(PyObject *s, PyObject *args) {
  * main motivation for this function is that methods in our parted module need
  * to be able to raise specific, helpful exceptions instead of something
  * generic.
+ *
+ * It is also possible for callers to specify a function to help in deciding
+ * what to do with parted exceptions.  See the docs for the
+ * py_ped_register_exn_handler function.
  */
 static PedExceptionOption partedExnHandler(PedException *e) {
     switch (e->type) {
@@ -350,13 +399,29 @@ static PedExceptionOption partedExnHandler(PedException *e) {
 
                 if (partedExnMessage == NULL)
                     PyErr_NoMemory();
-
-                /*
-                 * return 'no' for yes/no question exceptions in libparted,
-                 * prevent any potential disk destruction and pass up an
-                 * exception to our caller
-                 */
-                return PED_EXCEPTION_NO;
+                else if (exn_handler && PyCallable_Check(exn_handler)) {
+                    PyObject *args, *retval;
+
+                    args = PyTuple_New(3);
+                    PyTuple_SetItem(args, 0, PyInt_FromLong(e->type));
+                    PyTuple_SetItem(args, 1, PyInt_FromLong(e->options));
+                    PyTuple_SetItem(args, 2, PyUnicode_FromString(e->message));
+
+                    retval = PyObject_CallObject(exn_handler, NULL);
+                    Py_DECREF(args);
+                    if (retval != NULL && (PyLong_AsLong(retval) == PED_EXCEPTION_UNHANDLED || (PyLong_AsLong(retval) & e->options) > 0))
+                        return PyLong_AsLong(retval);
+                    else
+                        /* Call failed, use the default value. */
+                        return PED_EXCEPTION_NO;
+                }
+                else {
+                    /* If no exception handling function was registered to
+                     * tell us what to do, return "no" for any yes/no
+                     * questions to prevent any potential disk destruction.
+                     */
+                    return PED_EXCEPTION_NO;
+                }
             } else {
                 partedExnRaised = 0;
                 return PED_EXCEPTION_IGNORE;
@@ -372,8 +437,29 @@ static PedExceptionOption partedExnHandler(PedException *e) {
 
             if (partedExnMessage == NULL)
                 PyErr_NoMemory();
-
-            return PED_EXCEPTION_CANCEL;
+            else if (exn_handler && PyCallable_Check(exn_handler)) {
+                PyObject *args, *retval;
+
+                args = PyTuple_New(3);
+                PyTuple_SetItem(args, 0, PyInt_FromLong(e->type));
+                PyTuple_SetItem(args, 1, PyInt_FromLong(e->options));
+                PyTuple_SetItem(args, 2, PyUnicode_FromString(e->message));
+
+                retval = PyObject_CallObject(exn_handler, NULL);
+                Py_DECREF(args);
+                if (retval != NULL && (PyLong_AsLong(retval) == PED_EXCEPTION_UNHANDLED || (PyLong_AsLong(retval) & e->options) > 0))
+                    return PyLong_AsLong(retval);
+                else
+                    /* Call failed, use the default value. */
+                    return PED_EXCEPTION_CANCEL;
+            }
+            else {
+                /* If no exception handling function was registered to tell us
+                 * what to do, return "cancel" for any questions to prevent
+                 * any potential disk destruction.
+                 */
+                return PED_EXCEPTION_CANCEL;
+            }
 
         /* Raise exceptions for internal parted bugs immediately. */
         case PED_EXCEPTION_BUG:
@@ -607,6 +693,35 @@ PyMODINIT_FUNC init_ped(void) {
     Py_INCREF(UnknownTypeException);
     PyModule_AddObject(m, "UnknownTypeException", UnknownTypeException);
 
+    /* Exception type constants. */
+    PyModule_AddIntConstant(m, "EXCEPTION_TYPE_INFORMATION", PED_EXCEPTION_INFORMATION);
+    PyModule_AddIntConstant(m, "EXCEPTION_TYPE_WARNING", PED_EXCEPTION_WARNING);
+    PyModule_AddIntConstant(m, "EXCEPTION_TYPE_ERROR", PED_EXCEPTION_ERROR);
+    PyModule_AddIntConstant(m, "EXCEPTION_TYPE_FATAL", PED_EXCEPTION_FATAL);
+    PyModule_AddIntConstant(m, "EXCEPTION_TYPE_BUG", PED_EXCEPTION_BUG);
+    PyModule_AddIntConstant(m, "EXCEPTION_TYPE_NO_FEATURE", PED_EXCEPTION_NO_FEATURE);
+
+    /* Exception resolution constants. */
+    PyModule_AddIntConstant(m, "EXCEPTION_RESOLVE_UNHANDLED", PED_EXCEPTION_UNHANDLED);
+    PyModule_AddIntConstant(m, "EXCEPTION_RESOLVE_FIX", PED_EXCEPTION_FIX);
+    PyModule_AddIntConstant(m, "EXCEPTION_RESOLVE_YES", PED_EXCEPTION_YES);
+    PyModule_AddIntConstant(m, "EXCEPTION_RESOLVE_NO", PED_EXCEPTION_NO);
+    PyModule_AddIntConstant(m, "EXCEPTION_RESOLVE_OK", PED_EXCEPTION_OK);
+    PyModule_AddIntConstant(m, "EXCEPTION_RESOLVE_RETRY", PED_EXCEPTION_RETRY);
+    PyModule_AddIntConstant(m, "EXCEPTION_RESOLVE_IGNORE", PED_EXCEPTION_IGNORE);
+    PyModule_AddIntConstant(m, "EXCEPTION_RESOLVE_CANCEL", PED_EXCEPTION_CANCEL);
+
+    /* Exception option constants. */
+    PyModule_AddIntConstant(m, "EXCEPTION_OPT_OK_CANCEL", PED_EXCEPTION_OK_CANCEL);
+    PyModule_AddIntConstant(m, "EXCEPTION_OPT_YES_NO", PED_EXCEPTION_YES_NO);
+    PyModule_AddIntConstant(m, "EXCEPTION_OPT_YES_NO_CANCEL", PED_EXCEPTION_YES_NO_CANCEL);
+    PyModule_AddIntConstant(m, "EXCEPTION_OPT_IGNORE_CANCEL", PED_EXCEPTION_IGNORE_CANCEL);
+    PyModule_AddIntConstant(m, "EXCEPTION_OPT_RETRY_CANCEL", PED_EXCEPTION_RETRY_CANCEL);
+    PyModule_AddIntConstant(m, "EXCEPTION_OPT_RETRY_IGNORE_CANCEL", PED_EXCEPTION_RETRY_IGNORE_CANCEL);
+
+    exn_handler = Py_None;
+    Py_INCREF(exn_handler);
+
     /* Set up our libparted exception handler. */
     ped_exception_set_handler(partedExnHandler);
 }
-- 
2.4.0