diff mbox series

[RFC,1/6] utils/python_api: add scripting interface for Qemu with python lib

Message ID 20190807071445.4109-2-bala24@linux.ibm.com
State New
Headers show
Series Enhancing Qemu MMIO emulation with scripting interface | expand

Commit Message

Balamuruhan S Aug. 7, 2019, 7:14 a.m. UTC
Adds scripting interface with python library to call functions in
python modules from Qemu that can be used to feed input externally
and without recompiling Qemu that can be used for early development,
testing and can be extended to abstract some of Qemu code out to a
python script to ease maintenance.

Signed-off-by: Balamuruhan S <bala24@linux.ibm.com>
---
 configure                   |  10 +++++
 include/sysemu/python_api.h |  30 +++++++++++++
 util/Makefile.objs          |   1 +
 util/python_api.c           | 100 ++++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 141 insertions(+)
 create mode 100644 include/sysemu/python_api.h
 create mode 100644 util/python_api.c

Comments

Philippe Mathieu-Daudé Aug. 7, 2019, 10:20 a.m. UTC | #1
Hi,

On 8/7/19 9:14 AM, Balamuruhan S wrote:
> Adds scripting interface with python library to call functions in
> python modules from Qemu that can be used to feed input externally
> and without recompiling Qemu that can be used for early development,
> testing and can be extended to abstract some of Qemu code out to a
> python script to ease maintenance.
> 
> Signed-off-by: Balamuruhan S <bala24@linux.ibm.com>
> ---
>  configure                   |  10 +++++
>  include/sysemu/python_api.h |  30 +++++++++++++
>  util/Makefile.objs          |   1 +
>  util/python_api.c           | 100 ++++++++++++++++++++++++++++++++++++++++++++
>  4 files changed, 141 insertions(+)
>  create mode 100644 include/sysemu/python_api.h
>  create mode 100644 util/python_api.c
> 
> diff --git a/configure b/configure
> index 714e7fb6a1..fddddcc879 100755
> --- a/configure
> +++ b/configure
> @@ -1866,6 +1866,11 @@ fi
>  # Preserve python version since some functionality is dependent on it
>  python_version=$($python -V 2>&1 | sed -e 's/Python\ //')
>  
> +# Python config to be used for CFLAGS and LDFLAGS
> +if ! [ -z "$python" ]; then
> +    python_config="$python-config"
> +fi
> +
>  # Suppress writing compiled files
>  python="$python -B"
>  
> @@ -6304,6 +6309,11 @@ echo_version() {
>      fi
>  }
>  
> +if ! [ -z "$python_config" ]; then
> +    QEMU_CFLAGS="$QEMU_CFLAGS $($python_config --includes)"
> +    QEMU_LDFLAGS="$QEMU_LDFLAGS $($python_config --ldflags)"
> +fi
> +
>  # prepend pixman and ftd flags after all config tests are done
>  QEMU_CFLAGS="$pixman_cflags $fdt_cflags $QEMU_CFLAGS"
>  QEMU_LDFLAGS="$fdt_ldflags $QEMU_LDFLAGS"
> diff --git a/include/sysemu/python_api.h b/include/sysemu/python_api.h
> new file mode 100644
> index 0000000000..ff02d58377
> --- /dev/null
> +++ b/include/sysemu/python_api.h
> @@ -0,0 +1,30 @@
> +#ifndef _PPC_PNV_PYTHON_H
> +#define _PPC_PNV_PYTHON_H
> +
> +#include <stdbool.h>
> +#include <Python.h>
> +
> +extern PyObject *python_callback(const char *abs_module_path, const char *mod,
> +                                 const char *func, char *args[],
> +                                 const int nargs);
> +
> +extern uint64_t python_callback_int(const char *abs_module_path,
> +                                    const char *mod,
> +                                    const char *func, char *args[],
> +                                    const int nargs);
> +
> +extern char *python_callback_str(const char *abs_module_path, const char *mod,
> +                                 const char *func, char *args[],
> +                                 const int nargs);
> +
> +extern bool python_callback_bool(const char *abs_module_path, const char *mod,
> +                                 const char *func, char *args[],
> +                                 const int nargs);
> +
> +extern void python_args_init_cast_int(char *args[], int arg, int pos);
> +
> +extern void python_args_init_cast_long(char *args[], uint64_t arg, int pos);
> +
> +extern void python_args_clean(char *args[], int nargs);
> +
> +#endif
> diff --git a/util/Makefile.objs b/util/Makefile.objs
> index 41bf59d127..05851c94a7 100644
> --- a/util/Makefile.objs
> +++ b/util/Makefile.objs
> @@ -50,6 +50,7 @@ util-obj-y += range.o
>  util-obj-y += stats64.o
>  util-obj-y += systemd.o
>  util-obj-y += iova-tree.o
> +util-obj-y += python_api.o

This is probably conditional to having python-dev (or whatever distribs
call the package) installed.

>  util-obj-$(CONFIG_INOTIFY1) += filemonitor-inotify.o
>  util-obj-$(CONFIG_LINUX) += vfio-helpers.o
>  util-obj-$(CONFIG_POSIX) += drm.o
> diff --git a/util/python_api.c b/util/python_api.c
> new file mode 100644
> index 0000000000..854187e00f
> --- /dev/null
> +++ b/util/python_api.c
> @@ -0,0 +1,100 @@
> +#include "sysemu/python_api.h"
> +#include "qemu/osdep.h"
> +
> +PyObject *python_callback(const char *abs_module_path, const char *mod,
> +                          const char *func, char *args[], const int nargs)
> +{
> +    PyObject *mod_name, *module, *mod_ref, *function, *arguments;
> +    PyObject *result = 0;
> +    PyObject *value = NULL;
> +
> +    /* Set PYTHONPATH to absolute module path directory */
> +    if (!abs_module_path)
> +        abs_module_path = ".";
> +    setenv("PYTHONPATH", abs_module_path, 1);
> +
> +    /* Initialize the Python Interpreter */
> +    Py_Initialize();
> +    mod_name = PyUnicode_FromString(mod);
> +    /* Import module object */
> +    module = PyImport_Import(mod_name);
> +    if (!module) {
> +        PyErr_Print();
> +        fprintf(stderr, "Failed to load \"%s\"\n", mod);
> +        exit(EXIT_FAILURE);
> +    }
> +    mod_ref = PyModule_GetDict(module);
> +    function = PyDict_GetItemString(mod_ref, func);
> +    if (function && PyCallable_Check(function)) {
> +        arguments = PyTuple_New(nargs);
> +        for (int i = 0; i < nargs; i++) {
> +            value = PyUnicode_FromString(args[i]);
> +            if (!value) {
> +                Py_DECREF(arguments);
> +                Py_DECREF(module);
> +                fprintf(stderr, "Cannot convert argument\n");
> +                exit(EXIT_FAILURE);
> +            }
> +            PyTuple_SetItem(arguments, i, value);
> +        }
> +        PyErr_Print();
> +        result = PyObject_CallObject(function, arguments);
> +        PyErr_Print();
> +    }
> +    else {
> +        if (PyErr_Occurred())
> +            PyErr_Print();
> +        fprintf(stderr, "Cannot find function \"%s\"\n", func);
> +        exit(EXIT_FAILURE);
> +    }
> +    /* Clean up */
> +    Py_DECREF(value);
> +    Py_DECREF(module);
> +    Py_DECREF(mod_name);
> +    /* Finish the Python Interpreter */
> +    Py_Finalize();
> +    return result;
> +}
> +
> +uint64_t python_callback_int(const char *abs_module_path, const char *mod,
> +                             const char *func, char *args[], const int nargs)
> +{
> +    PyObject *result;
> +    result = python_callback(abs_module_path, mod, func, args, nargs);
> +    return PyLong_AsLong(result);
> +}
> +
> +char *python_callback_str(const char *abs_module_path, const char *mod,
> +                          const char *func, char *args[], const int nargs)
> +{
> +    PyObject *result;
> +    result = python_callback(abs_module_path, mod, func, args, nargs);
> +    return PyUnicode_AsUTF8(result);
> +}
> +
> +bool python_callback_bool(const char *abs_module_path, const char *mod,
> +                          const char *func, char *args[], const int nargs)
> +{
> +    PyObject *result;
> +    result = python_callback(abs_module_path, mod, func, args, nargs);
> +    return (result == Py_True);
> +}
> +
> +void python_args_init_cast_int(char *args[], int arg, int pos)
> +{
> +    args[pos]= malloc(sizeof(int));
> +    sprintf(args[pos], "%d", arg);
> +}
> +
> +void python_args_init_cast_long(char *args[], uint64_t arg, int pos)
> +{
> +    args[pos]= g_malloc(sizeof(uint64_t) * 2);
> +    sprintf(args[pos], "%lx", arg);
> +}
> +
> +void python_args_clean(char *args[], int nargs)
> +{
> +    for (int i = 0; i < nargs; i++) {
> +        g_free(args[i]);
> +    }
> +}
> 

Wondering about security, is this feature safe to enable in production
environment? It seems to bypass all the hard effort to harden QEMU security.
Stefan Hajnoczi Aug. 8, 2019, 10:09 a.m. UTC | #2
On Wed, Aug 07, 2019 at 12:44:40PM +0530, Balamuruhan S wrote:
> +void python_args_init_cast_int(char *args[], int arg, int pos)
> +{
> +    args[pos]= malloc(sizeof(int));
> +    sprintf(args[pos], "%d", arg);
> +}

This is broken.  args[pos] is a (possibly NULL) pointer to 4 bytes.
sprintf() will buffer overflow if arg has more than 3 digits.

A correct way to do this is:

  args[pos] = g_strdup_printf("%d", arg);

> +void python_args_init_cast_long(char *args[], uint64_t arg, int pos)
> +{
> +    args[pos]= g_malloc(sizeof(uint64_t) * 2);
> +    sprintf(args[pos], "%lx", arg);
> +}

Same issue.

> +void python_args_clean(char *args[], int nargs)
> +{
> +    for (int i = 0; i < nargs; i++) {
> +        g_free(args[i]);
> +    }
> +}

Mixing malloc() and g_free() is unsafe.  If you switch to
g_strdup_printf() then g_free() is correct.
Stefan Hajnoczi Aug. 8, 2019, 10:10 a.m. UTC | #3
On Wed, Aug 07, 2019 at 12:20:47PM +0200, Philippe Mathieu-Daudé wrote:
> > +void python_args_clean(char *args[], int nargs)
> > +{
> > +    for (int i = 0; i < nargs; i++) {
> > +        g_free(args[i]);
> > +    }
> > +}
> > 
> 
> Wondering about security, is this feature safe to enable in production
> environment? It seems to bypass all the hard effort to harden QEMU security.

This seems like a feature that distros would not enable.  Only users
building QEMU from source could enable it.

Stefan
Philippe Mathieu-Daudé Aug. 8, 2019, 10:33 a.m. UTC | #4
On 8/8/19 12:10 PM, Stefan Hajnoczi wrote:
> On Wed, Aug 07, 2019 at 12:20:47PM +0200, Philippe Mathieu-Daudé wrote:
>>> +void python_args_clean(char *args[], int nargs)
>>> +{
>>> +    for (int i = 0; i < nargs; i++) {
>>> +        g_free(args[i]);
>>> +    }
>>> +}
>>>
>>
>> Wondering about security, is this feature safe to enable in production
>> environment? It seems to bypass all the hard effort to harden QEMU security.
> 
> This seems like a feature that distros would not enable.  Only users
> building QEMU from source could enable it.

Good. What about throwing big ./configure warning like the unsupported
cpu/os ones? Better safe than sorry :)
Daniel P. Berrangé Aug. 8, 2019, 10:49 a.m. UTC | #5
On Wed, Aug 07, 2019 at 12:44:40PM +0530, Balamuruhan S wrote:
> Adds scripting interface with python library to call functions in
> python modules from Qemu that can be used to feed input externally
> and without recompiling Qemu that can be used for early development,
> testing and can be extended to abstract some of Qemu code out to a
> python script to ease maintenance.

I admit the use case is interesting, but this is opening a can of
worms...

Historically the project has held the view that we do not wish
to have an mechanism to support loading out of tree code into the
QEMU process. Much previously talk was around dlopen'd C plugins,
but dynanically loaded Python plugins are doing the same thing
at a conceptual level.

We didn't wish to expose internals of QEMU in a plugin API to
avoid having any kind of API promise across releases.

There was also the question of licensing with plugins opening
the door for people to extend QEMU with non-free/closed source
functionality.

While this series only uses the plugin for one fairly obscure
device, once a python plugin feature is intergrated in QEMU
there will inevitably be requests to use it in further areas
of QEMU.

IOW, acceptance of this patch is a significant question for
the project, and a broader discussion point, than just this
PPC feature patch series.

> Signed-off-by: Balamuruhan S <bala24@linux.ibm.com>
> ---
>  configure                   |  10 +++++
>  include/sysemu/python_api.h |  30 +++++++++++++
>  util/Makefile.objs          |   1 +
>  util/python_api.c           | 100 ++++++++++++++++++++++++++++++++++++++++++++
>  4 files changed, 141 insertions(+)
>  create mode 100644 include/sysemu/python_api.h
>  create mode 100644 util/python_api.c

Regards,
Daniel
Daniel P. Berrangé Aug. 8, 2019, 10:53 a.m. UTC | #6
On Thu, Aug 08, 2019 at 11:10:13AM +0100, Stefan Hajnoczi wrote:
> On Wed, Aug 07, 2019 at 12:20:47PM +0200, Philippe Mathieu-Daudé wrote:
> > > +void python_args_clean(char *args[], int nargs)
> > > +{
> > > +    for (int i = 0; i < nargs; i++) {
> > > +        g_free(args[i]);
> > > +    }
> > > +}
> > > 
> > 
> > Wondering about security, is this feature safe to enable in production
> > environment? It seems to bypass all the hard effort to harden QEMU security.
> 
> This seems like a feature that distros would not enable.  Only users
> building QEMU from source could enable it.

Well that's true when this scripting is only used from one obscure ppc
device. Once merged though, its inevitable that people will want to
extend scripting to more & more parts of QEMU code. This is a big can
of worms...

Regards,
Daniel
Philippe Mathieu-Daudé Aug. 8, 2019, 12:45 p.m. UTC | #7
On 8/8/19 12:49 PM, Daniel P. Berrangé wrote:
> On Wed, Aug 07, 2019 at 12:44:40PM +0530, Balamuruhan S wrote:
>> Adds scripting interface with python library to call functions in
>> python modules from Qemu that can be used to feed input externally
>> and without recompiling Qemu that can be used for early development,
>> testing and can be extended to abstract some of Qemu code out to a
>> python script to ease maintenance.
> 
> I admit the use case is interesting, but this is opening a can of
> worms...
> 
> Historically the project has held the view that we do not wish
> to have an mechanism to support loading out of tree code into the
> QEMU process. Much previously talk was around dlopen'd C plugins,
> but dynanically loaded Python plugins are doing the same thing
> at a conceptual level.
> 
> We didn't wish to expose internals of QEMU in a plugin API to
> avoid having any kind of API promise across releases.
> 
> There was also the question of licensing with plugins opening
> the door for people to extend QEMU with non-free/closed source
> functionality.
> 
> While this series only uses the plugin for one fairly obscure
> device, once a python plugin feature is intergrated in QEMU
> there will inevitably be requests to use it in further areas
> of QEMU.
> 
> IOW, acceptance of this patch is a significant question for
> the project, and a broader discussion point, than just this
> PPC feature patch series.

Since performance is not an issue, we can use a QMP-PyMMIO bridge.
Most of the functions required are already exposed, Damien completed the
missing ones in his 'FAULT INJECTION FRAMEWORK' series:
https://lists.gnu.org/archive/html/qemu-devel/2019-06/msg06230.html

Maybe we simply need a clearer (better documented) QMP 'MMIO' API?
David Gibson Aug. 9, 2019, 4:39 a.m. UTC | #8
On Thu, Aug 08, 2019 at 02:45:02PM +0200, Philippe Mathieu-Daudé wrote:
> On 8/8/19 12:49 PM, Daniel P. Berrangé wrote:
> > On Wed, Aug 07, 2019 at 12:44:40PM +0530, Balamuruhan S wrote:
> >> Adds scripting interface with python library to call functions in
> >> python modules from Qemu that can be used to feed input externally
> >> and without recompiling Qemu that can be used for early development,
> >> testing and can be extended to abstract some of Qemu code out to a
> >> python script to ease maintenance.
> > 
> > I admit the use case is interesting, but this is opening a can of
> > worms...
> > 
> > Historically the project has held the view that we do not wish
> > to have an mechanism to support loading out of tree code into the
> > QEMU process. Much previously talk was around dlopen'd C plugins,
> > but dynanically loaded Python plugins are doing the same thing
> > at a conceptual level.
> > 
> > We didn't wish to expose internals of QEMU in a plugin API to
> > avoid having any kind of API promise across releases.
> > 
> > There was also the question of licensing with plugins opening
> > the door for people to extend QEMU with non-free/closed source
> > functionality.
> > 
> > While this series only uses the plugin for one fairly obscure
> > device, once a python plugin feature is intergrated in QEMU
> > there will inevitably be requests to use it in further areas
> > of QEMU.
> > 
> > IOW, acceptance of this patch is a significant question for
> > the project, and a broader discussion point, than just this
> > PPC feature patch series.
> 
> Since performance is not an issue, we can use a QMP-PyMMIO bridge.
> Most of the functions required are already exposed, Damien completed the
> missing ones in his 'FAULT INJECTION FRAMEWORK' series:
> https://lists.gnu.org/archive/html/qemu-devel/2019-06/msg06230.html
> 
> Maybe we simply need a clearer (better documented) QMP 'MMIO' API?

I tend to agree.  If performance is not a consideration to the point
that we can use an embedded Python interpreter, it should also not be
an issue to the point that we can move the processing to an entirely
different process with a vetted protocol in between them (QMP or an
extension thereof being the obvious choice).  That seems safer than
embeddeding arbitrary scripting right into the MMIO paths.
Stefan Hajnoczi Aug. 9, 2019, 8:46 a.m. UTC | #9
On Thu, Aug 08, 2019 at 11:53:07AM +0100, Daniel P. Berrangé wrote:
> On Thu, Aug 08, 2019 at 11:10:13AM +0100, Stefan Hajnoczi wrote:
> > On Wed, Aug 07, 2019 at 12:20:47PM +0200, Philippe Mathieu-Daudé wrote:
> > > > +void python_args_clean(char *args[], int nargs)
> > > > +{
> > > > +    for (int i = 0; i < nargs; i++) {
> > > > +        g_free(args[i]);
> > > > +    }
> > > > +}
> > > > 
> > > 
> > > Wondering about security, is this feature safe to enable in production
> > > environment? It seems to bypass all the hard effort to harden QEMU security.
> > 
> > This seems like a feature that distros would not enable.  Only users
> > building QEMU from source could enable it.
> 
> Well that's true when this scripting is only used from one obscure ppc
> device. Once merged though, its inevitable that people will want to
> extend scripting to more & more parts of QEMU code. This is a big can
> of worms...

When it gets used in new contexts it will be necessary to address
problems or accept that it is unsuitable for those use cases.  Starting
simple and dealing with challenges as and when necessary seems okay to
me.

I think we should give features a chance in QEMU if there is a
maintainer to support them.  I don't want to use this feature myself and
I see lots of issues with it for my use cases, but if it is compiled out
and doesn't place many requirements on code that does not use it, let's
give it a chance.

My main concern is licensing.  I think the QEMU Python API should be GPL
licensed because these scripts are executing as part of the QEMU
process.

Beyond that, let's see if people find this feature useful.  Maybe it
will die and be removed, maybe it will become popular and we'll have to
change our perspective :).

Stefan
Balamuruhan S Aug. 11, 2019, 6:39 a.m. UTC | #10
On 8/8/19 3:39 PM, Stefan Hajnoczi wrote:
> On Wed, Aug 07, 2019 at 12:44:40PM +0530, Balamuruhan S wrote:
>> +void python_args_init_cast_int(char *args[], int arg, int pos)
>> +{
>> +    args[pos]= malloc(sizeof(int));
>> +    sprintf(args[pos], "%d", arg);
>> +}
> This is broken.  args[pos] is a (possibly NULL) pointer to 4 bytes.
> sprintf() will buffer overflow if arg has more than 3 digits.
>
> A correct way to do this is:
>
>   args[pos] = g_strdup_printf("%d", arg);

Thanks for correcting it.

>
>> +void python_args_init_cast_long(char *args[], uint64_t arg, int pos)
>> +{
>> +    args[pos]= g_malloc(sizeof(uint64_t) * 2);
>> +    sprintf(args[pos], "%lx", arg);
>> +}
> Same issue.
>
>> +void python_args_clean(char *args[], int nargs)
>> +{
>> +    for (int i = 0; i < nargs; i++) {
>> +        g_free(args[i]);
>> +    }
>> +}
> Mixing malloc() and g_free() is unsafe.  If you switch to
> g_strdup_printf() then g_free() is correct.

sure, I will fix it.
Balamuruhan S Aug. 12, 2019, 4:45 a.m. UTC | #11
On 8/8/19 6:15 PM, Philippe Mathieu-Daudé wrote:
> On 8/8/19 12:49 PM, Daniel P. Berrangé wrote:
>> On Wed, Aug 07, 2019 at 12:44:40PM +0530, Balamuruhan S wrote:
>>> Adds scripting interface with python library to call functions in
>>> python modules from Qemu that can be used to feed input externally
>>> and without recompiling Qemu that can be used for early development,
>>> testing and can be extended to abstract some of Qemu code out to a
>>> python script to ease maintenance.
>> I admit the use case is interesting, but this is opening a can of
>> worms...
>>
>> Historically the project has held the view that we do not wish
>> to have an mechanism to support loading out of tree code into the
>> QEMU process. Much previously talk was around dlopen'd C plugins,
>> but dynanically loaded Python plugins are doing the same thing
>> at a conceptual level.
>>
>> We didn't wish to expose internals of QEMU in a plugin API to
>> avoid having any kind of API promise across releases.
>>
>> There was also the question of licensing with plugins opening
>> the door for people to extend QEMU with non-free/closed source
>> functionality.
>>
>> While this series only uses the plugin for one fairly obscure
>> device, once a python plugin feature is intergrated in QEMU
>> there will inevitably be requests to use it in further areas
>> of QEMU.
>>
>> IOW, acceptance of this patch is a significant question for
>> the project, and a broader discussion point, than just this
>> PPC feature patch series.
> Since performance is not an issue, we can use a QMP-PyMMIO bridge.
> Most of the functions required are already exposed, Damien completed the
> missing ones in his 'FAULT INJECTION FRAMEWORK' series:
> https://lists.gnu.org/archive/html/qemu-devel/2019-06/msg06230.html

will look at this approach and try using QMP-PyMMIO bridge.

Thank you all for review and suggestions.

>
> Maybe we simply need a clearer (better documented) QMP 'MMIO' API?
>
Balamuruhan S Aug. 12, 2019, 4:53 a.m. UTC | #12
On 8/9/19 2:16 PM, Stefan Hajnoczi wrote:
> On Thu, Aug 08, 2019 at 11:53:07AM +0100, Daniel P. Berrangé wrote:
>> On Thu, Aug 08, 2019 at 11:10:13AM +0100, Stefan Hajnoczi wrote:
>>> On Wed, Aug 07, 2019 at 12:20:47PM +0200, Philippe Mathieu-Daudé wrote:
>>>>> +void python_args_clean(char *args[], int nargs)
>>>>> +{
>>>>> +    for (int i = 0; i < nargs; i++) {
>>>>> +        g_free(args[i]);
>>>>> +    }
>>>>> +}
>>>>>
>>>> Wondering about security, is this feature safe to enable in production
>>>> environment? It seems to bypass all the hard effort to harden QEMU security.
>>> This seems like a feature that distros would not enable.  Only users
>>> building QEMU from source could enable it.
>> Well that's true when this scripting is only used from one obscure ppc
>> device. Once merged though, its inevitable that people will want to
>> extend scripting to more & more parts of QEMU code. This is a big can
>> of worms...
> When it gets used in new contexts it will be necessary to address
> problems or accept that it is unsuitable for those use cases.  Starting
> simple and dealing with challenges as and when necessary seems okay to
> me.
>
> I think we should give features a chance in QEMU if there is a
> maintainer to support them.  I don't want to use this feature myself and
> I see lots of issues with it for my use cases, but if it is compiled out
> and doesn't place many requirements on code that does not use it, let's
> give it a chance.
>
> My main concern is licensing.  I think the QEMU Python API should be GPL
> licensed because these scripts are executing as part of the QEMU
> process.
>
> Beyond that, let's see if people find this feature useful.  Maybe it
> will die and be removed, maybe it will become popular and we'll have to
> change our perspective :).

Thank you all for review and sharing the thoughts :) 

>
> Stefan
diff mbox series

Patch

diff --git a/configure b/configure
index 714e7fb6a1..fddddcc879 100755
--- a/configure
+++ b/configure
@@ -1866,6 +1866,11 @@  fi
 # Preserve python version since some functionality is dependent on it
 python_version=$($python -V 2>&1 | sed -e 's/Python\ //')
 
+# Python config to be used for CFLAGS and LDFLAGS
+if ! [ -z "$python" ]; then
+    python_config="$python-config"
+fi
+
 # Suppress writing compiled files
 python="$python -B"
 
@@ -6304,6 +6309,11 @@  echo_version() {
     fi
 }
 
+if ! [ -z "$python_config" ]; then
+    QEMU_CFLAGS="$QEMU_CFLAGS $($python_config --includes)"
+    QEMU_LDFLAGS="$QEMU_LDFLAGS $($python_config --ldflags)"
+fi
+
 # prepend pixman and ftd flags after all config tests are done
 QEMU_CFLAGS="$pixman_cflags $fdt_cflags $QEMU_CFLAGS"
 QEMU_LDFLAGS="$fdt_ldflags $QEMU_LDFLAGS"
diff --git a/include/sysemu/python_api.h b/include/sysemu/python_api.h
new file mode 100644
index 0000000000..ff02d58377
--- /dev/null
+++ b/include/sysemu/python_api.h
@@ -0,0 +1,30 @@ 
+#ifndef _PPC_PNV_PYTHON_H
+#define _PPC_PNV_PYTHON_H
+
+#include <stdbool.h>
+#include <Python.h>
+
+extern PyObject *python_callback(const char *abs_module_path, const char *mod,
+                                 const char *func, char *args[],
+                                 const int nargs);
+
+extern uint64_t python_callback_int(const char *abs_module_path,
+                                    const char *mod,
+                                    const char *func, char *args[],
+                                    const int nargs);
+
+extern char *python_callback_str(const char *abs_module_path, const char *mod,
+                                 const char *func, char *args[],
+                                 const int nargs);
+
+extern bool python_callback_bool(const char *abs_module_path, const char *mod,
+                                 const char *func, char *args[],
+                                 const int nargs);
+
+extern void python_args_init_cast_int(char *args[], int arg, int pos);
+
+extern void python_args_init_cast_long(char *args[], uint64_t arg, int pos);
+
+extern void python_args_clean(char *args[], int nargs);
+
+#endif
diff --git a/util/Makefile.objs b/util/Makefile.objs
index 41bf59d127..05851c94a7 100644
--- a/util/Makefile.objs
+++ b/util/Makefile.objs
@@ -50,6 +50,7 @@  util-obj-y += range.o
 util-obj-y += stats64.o
 util-obj-y += systemd.o
 util-obj-y += iova-tree.o
+util-obj-y += python_api.o
 util-obj-$(CONFIG_INOTIFY1) += filemonitor-inotify.o
 util-obj-$(CONFIG_LINUX) += vfio-helpers.o
 util-obj-$(CONFIG_POSIX) += drm.o
diff --git a/util/python_api.c b/util/python_api.c
new file mode 100644
index 0000000000..854187e00f
--- /dev/null
+++ b/util/python_api.c
@@ -0,0 +1,100 @@ 
+#include "sysemu/python_api.h"
+#include "qemu/osdep.h"
+
+PyObject *python_callback(const char *abs_module_path, const char *mod,
+                          const char *func, char *args[], const int nargs)
+{
+    PyObject *mod_name, *module, *mod_ref, *function, *arguments;
+    PyObject *result = 0;
+    PyObject *value = NULL;
+
+    /* Set PYTHONPATH to absolute module path directory */
+    if (!abs_module_path)
+        abs_module_path = ".";
+    setenv("PYTHONPATH", abs_module_path, 1);
+
+    /* Initialize the Python Interpreter */
+    Py_Initialize();
+    mod_name = PyUnicode_FromString(mod);
+    /* Import module object */
+    module = PyImport_Import(mod_name);
+    if (!module) {
+        PyErr_Print();
+        fprintf(stderr, "Failed to load \"%s\"\n", mod);
+        exit(EXIT_FAILURE);
+    }
+    mod_ref = PyModule_GetDict(module);
+    function = PyDict_GetItemString(mod_ref, func);
+    if (function && PyCallable_Check(function)) {
+        arguments = PyTuple_New(nargs);
+        for (int i = 0; i < nargs; i++) {
+            value = PyUnicode_FromString(args[i]);
+            if (!value) {
+                Py_DECREF(arguments);
+                Py_DECREF(module);
+                fprintf(stderr, "Cannot convert argument\n");
+                exit(EXIT_FAILURE);
+            }
+            PyTuple_SetItem(arguments, i, value);
+        }
+        PyErr_Print();
+        result = PyObject_CallObject(function, arguments);
+        PyErr_Print();
+    }
+    else {
+        if (PyErr_Occurred())
+            PyErr_Print();
+        fprintf(stderr, "Cannot find function \"%s\"\n", func);
+        exit(EXIT_FAILURE);
+    }
+    /* Clean up */
+    Py_DECREF(value);
+    Py_DECREF(module);
+    Py_DECREF(mod_name);
+    /* Finish the Python Interpreter */
+    Py_Finalize();
+    return result;
+}
+
+uint64_t python_callback_int(const char *abs_module_path, const char *mod,
+                             const char *func, char *args[], const int nargs)
+{
+    PyObject *result;
+    result = python_callback(abs_module_path, mod, func, args, nargs);
+    return PyLong_AsLong(result);
+}
+
+char *python_callback_str(const char *abs_module_path, const char *mod,
+                          const char *func, char *args[], const int nargs)
+{
+    PyObject *result;
+    result = python_callback(abs_module_path, mod, func, args, nargs);
+    return PyUnicode_AsUTF8(result);
+}
+
+bool python_callback_bool(const char *abs_module_path, const char *mod,
+                          const char *func, char *args[], const int nargs)
+{
+    PyObject *result;
+    result = python_callback(abs_module_path, mod, func, args, nargs);
+    return (result == Py_True);
+}
+
+void python_args_init_cast_int(char *args[], int arg, int pos)
+{
+    args[pos]= malloc(sizeof(int));
+    sprintf(args[pos], "%d", arg);
+}
+
+void python_args_init_cast_long(char *args[], uint64_t arg, int pos)
+{
+    args[pos]= g_malloc(sizeof(uint64_t) * 2);
+    sprintf(args[pos], "%lx", arg);
+}
+
+void python_args_clean(char *args[], int nargs)
+{
+    for (int i = 0; i < nargs; i++) {
+        g_free(args[i]);
+    }
+}