Blob Blame History Raw
From 5fe01bc3b8932b5dffa88578663727c3df694bc4 Mon Sep 17 00:00:00 2001
From: Tomas Hozza <thozza@redhat.com>
Date: Tue, 14 Apr 2015 14:01:02 +0200
Subject: [PATCH 4/4] Resolve Python 3 incompatibilities in libunbound and
 pythonmod wrappers

Signed-off-by: Tomas Hozza <thozza@redhat.com>

Fix issue with Python 3 mapping of FILE* using file_py3.i from ldns.
---
 Makefile.in                    |   3 +-
 configure.ac                   |   2 +
 libunbound/python/file_py3.i   | 155 +++++++++++++++++++++++++++++++++++++++++
 libunbound/python/libunbound.i |   4 ++
 pythonmod/interface.i          |  28 ++++----
 pythonmod/pythonmod.c          |   8 ++-
 6 files changed, 184 insertions(+), 16 deletions(-)
 create mode 100644 libunbound/python/file_py3.i

diff --git a/Makefile.in b/Makefile.in
index 94b126d..a84b303 100644
--- a/Makefile.in
+++ b/Makefile.in
@@ -25,6 +25,7 @@ DNSTAP_SRC=@DNSTAP_SRC@
 DNSTAP_OBJ=@DNSTAP_OBJ@
 WITH_PYTHONMODULE=@WITH_PYTHONMODULE@
 WITH_PYUNBOUND=@WITH_PYUNBOUND@
+PY_MAJOR_VERSION=@PY_MAJOR_VERSION@
 PYTHON_SITE_PKG=@PYTHON_SITE_PKG@
 PYTHONMOD_INSTALL=@PYTHONMOD_INSTALL@
 PYTHONMOD_UNINSTALL=@PYTHONMOD_UNINSTALL@
@@ -393,7 +394,7 @@ libunbound_wrap.lo libunbound_wrap.o: libunbound/python/libunbound_wrap.c \
 	unbound.h
 libunbound/python/libunbound_wrap.c:	$(srcdir)/libunbound/python/libunbound.i unbound.h
 	@-if test ! -d libunbound/python; then $(INSTALL) -d libunbound/python; fi
-	$(SWIG) -python -o $@ $(CPPFLAGS) $(srcdir)/libunbound/python/libunbound.i
+	$(SWIG) -python -o $@ $(CPPFLAGS) -DPY_MAJOR_VERSION=$(PY_MAJOR_VERSION) $(srcdir)/libunbound/python/libunbound.i
 
 # Pyunbound python unbound wrapper
 _unbound.la:	libunbound_wrap.lo libunbound.la
diff --git a/configure.ac b/configure.ac
index 6028138..de809af 100644
--- a/configure.ac
+++ b/configure.ac
@@ -475,6 +475,8 @@ if test x_$ub_test_python != x_no; then
 		AC_ERROR([Python version >= 2.4.0 is required])
 	fi
 
+      PY_MAJOR_VERSION="`$PYTHON -c "import sys; print(sys.version_info.major)"`"
+      AC_SUBST(PY_MAJOR_VERSION)
       # Have Python
       AC_DEFINE(HAVE_PYTHON,1,[Define if you have Python libraries and header files.])
       LIBS="$PYTHON_LDFLAGS $LIBS"
diff --git a/libunbound/python/file_py3.i b/libunbound/python/file_py3.i
new file mode 100644
index 0000000..5d8b5a2
--- /dev/null
+++ b/libunbound/python/file_py3.i
@@ -0,0 +1,155 @@
+/*
+ * file_py3.i: Typemaps for FILE* for Python 3
+ *
+ * Copyright (c) 2011, Karel Slany (karel.slany AT nic.cz)
+ * All rights reserved.
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * 
+ *     * Redistributions of source code must retain the above copyright notice,
+ *       this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above copyright
+ *       notice, this list of conditions and the following disclaimer in the
+ *       documentation and/or other materials provided with the distribution.
+ *     * Neither the name of the organization nor the names of its
+ *       contributors may be used to endorse or promote products derived from this
+ *       software without specific prior written permission.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+%{
+#include <unistd.h>
+#include <fcntl.h>
+%}
+
+%types(FILE *);
+
+//#define SWIG_FILE3_DEBUG
+
+/* converts basic file descriptor flags onto a string */
+%fragment("fdfl_to_str", "header") {
+const char *
+fdfl_to_str(int fdfl) {
+
+  static const char * const file_mode[] = {"w+", "w", "r"};
+
+  if (fdfl & O_RDWR) {
+    return file_mode[0];
+  } else if (fdfl & O_WRONLY) {
+    return file_mode[1];
+  } else {
+    return file_mode[2];
+  }
+}
+}
+
+%fragment("is_obj_file", "header") {
+int
+is_obj_file(PyObject *obj) {
+  int fd, fdfl;
+  if (!PyLong_Check(obj) &&                                /* is not an integer */
+      PyObject_HasAttrString(obj, "fileno") &&             /* has fileno method */
+      (PyObject_CallMethod(obj, "flush", NULL) != NULL) && /* flush() succeeded */
+      ((fd = PyObject_AsFileDescriptor(obj)) != -1) &&     /* got file descriptor */
+      ((fdfl = fcntl(fd, F_GETFL)) != -1)                  /* got descriptor flags */
+    ) {
+    return 1;
+  }
+  else {
+    return 0;
+  }
+}
+}
+
+%fragment("obj_to_file","header", fragment="fdfl_to_str,is_obj_file") {
+FILE *
+obj_to_file(PyObject *obj) {
+  int fd, fdfl;
+  FILE *fp;
+  if (is_obj_file(obj)) {
+    fd = PyObject_AsFileDescriptor(obj);
+    fdfl = fcntl(fd, F_GETFL);
+    fp = fdopen(dup(fd), fdfl_to_str(fdfl)); /* the FILE* must be flushed
+                                                and closed after being used */
+#ifdef SWIG_FILE3_DEBUG
+    fprintf(stderr, "opening fd %d (fl %d \"%s\") as FILE %p\n",
+            fd, fdfl, fdfl_to_str(fdfl), (void *)fp);
+#endif
+    return fp;
+  }
+  return NULL;
+}
+}
+
+/* returns -1 if error occurred */
+/* caused magic SWIG Syntax errors when was commented out */
+#if 0
+%fragment("dispose_file", "header") {
+int
+dispose_file(FILE **fp) {
+#ifdef SWIG_FILE3_DEBUG
+  fprintf(stderr, "flushing FILE %p\n", (void *)fp);
+#endif
+  if (*fp == NULL) {
+    return 0;
+  }
+  if ((fflush(*fp) == 0) &&  /* flush file */
+      (fclose(*fp) == 0)) {  /* close file */
+    *fp = NULL;
+    return 0;
+  }
+  return -1;
+}
+}
+#endif
+
+%typemap(arginit, noblock = 1) FILE* {
+  $1 = NULL;
+}
+
+/*
+ * added due to ub_ctx_debugout since since it is overloaded:
+ * takes void* and FILE*. In reality only FILE* but the wrapper
+ * and the function is declared in such way.
+ */
+%typemap(typecheck, noblock = 1, fragment = "is_obj_file", precedence = SWIG_TYPECHECK_POINTER) FILE* {
+  $1 = is_obj_file($input);
+}
+
+%typemap(check, noblock = 1) FILE* {
+  if ($1 == NULL) {
+    /* The generated wrapper function raises TypeError on mismatching types. */
+    SWIG_exception_fail(SWIG_TypeError, "in method '" "$symname" "', argument "
+                        "$argnum"" of type '" "$type""'");
+  }
+}
+
+%typemap(in, noblock = 1, fragment = "obj_to_file") FILE* {
+  $1 = obj_to_file($input);
+}
+
+/*
+ * Commented out due the way how ub_ctx_debugout() uses the parameter.
+ * This typemap would cause the FILE* to be closed after return from
+ * the function. This caused Python interpreter to crash, since the
+ * function just stores the FILE* internally in ctx and use it for
+ * logging. So we'll leave the closing of the file on the OS.
+ */
+/*%typemap(freearg, noblock = 1, fragment = "dispose_file") FILE* {
+  if (dispose_file(&$1) == -1) {
+    SWIG_exception_fail(SWIG_IOError, "closing file in method '" "$symname" "', argument "
+                        "$argnum"" of type '" "$type""'");
+  }
+}*/
diff --git a/libunbound/python/libunbound.i b/libunbound/python/libunbound.i
index 1bef79f..3c0e45b 100644
--- a/libunbound/python/libunbound.i
+++ b/libunbound/python/libunbound.i
@@ -60,7 +60,11 @@
 %}
 
 //%include "doc.i"
+#if PY_MAJOR_VERSION >= 3
+%include "file_py3.i" // python 3 FILE *
+#else
 %include "file.i"
+#endif
 
 %feature("docstring") strerror "Convert error value to a human readable string."
 
diff --git a/pythonmod/interface.i b/pythonmod/interface.i
index cfaabb3..4b20c6e 100644
--- a/pythonmod/interface.i
+++ b/pythonmod/interface.i
@@ -37,7 +37,7 @@
 %include "stdint.i" // uint_16_t can be known type now
 
 %inline %{
-   //converts [len][data][len][data][0] string to a List of labels (PyStrings)
+   //converts [len][data][len][data][0] string to a List of labels (PyBytes)
    PyObject* GetNameAsLabelList(const char* name, int len) {
      PyObject* list;
      int cnt=0, i;
@@ -79,8 +79,8 @@ struct query_info {
 %inline %{
    enum enum_rr_class  { 
       RR_CLASS_IN = 1,
-      RR_CLASS_CH	= 3,
-      RR_CLASS_HS	= 4,
+      RR_CLASS_CH = 3,
+      RR_CLASS_HS = 4,
       RR_CLASS_NONE = 254,
       RR_CLASS_ANY = 255,
    };
@@ -164,7 +164,7 @@ struct query_info {
        char buf[LDNS_MAX_DOMAINLEN+1];
        buf[0] = '\0';
        dname_str((uint8_t*)dname, buf);
-       return PyString_FromString(buf);
+       return PyBytes_FromString(buf);
    }
 %}
 
@@ -440,7 +440,7 @@ struct comm_reply {
      reply_addr2str(reply, dest, 64);
      if (dest[0] == 0)
         return Py_None;
-     return PyString_FromString(dest);
+     return PyBytes_FromString(dest);
   }
 
   PyObject* _comm_reply_family_get(struct comm_reply* reply) {
@@ -448,9 +448,9 @@ struct comm_reply {
         int af = (int)((struct sockaddr_in*) &(reply->addr))->sin_family;
 
         switch(af) {
-           case AF_INET: return PyString_FromString("ip4");
-           case AF_INET6: return PyString_FromString("ip6"); 
-           case AF_UNIX: return PyString_FromString("unix");
+           case AF_INET: return PyBytes_FromString("ip4");
+           case AF_INET6: return PyBytes_FromString("ip6");
+           case AF_UNIX: return PyBytes_FromString("unix");
         }
 
         return Py_None;
@@ -711,13 +711,13 @@ struct delegpt {
 
 %inline %{
    PyObject* _get_dp_dname(struct delegpt* dp) {
-      return PyString_FromStringAndSize((char*)dp->name, dp->namelen);
+      return PyBytes_FromStringAndSize((char*)dp->name, dp->namelen);
    } 
    PyObject* _get_dp_dname_components(struct delegpt* dp) {
       return GetNameAsLabelList((char*)dp->name, dp->namelen);
    }
    PyObject* _get_dpns_dname(struct delegpt_ns* dpns) {
-      return PyString_FromStringAndSize((char*)dpns->name, dpns->namelen);
+      return PyBytes_FromStringAndSize((char*)dpns->name, dpns->namelen);
    }
    PyObject* _get_dpns_dname_components(struct delegpt_ns* dpns) {
       return GetNameAsLabelList((char*)dpns->name, dpns->namelen);
@@ -728,7 +728,7 @@ struct delegpt {
      delegpt_addr_addr2str(target, dest, 64);
      if (dest[0] == 0)
         return Py_None;
-     return PyString_FromString(dest);
+     return PyBytes_FromString(dest);
   }
 
 %}
@@ -842,7 +842,7 @@ int checkList(PyObject *l)
        for (i=0; i < PyList_Size(l); i++) 
        {
            item = PyList_GetItem(l, i);
-           if (!PyString_Check(item))
+           if (!PyBytes_Check(item))
               return 0;
        }
        return 1;
@@ -864,12 +864,12 @@ int pushRRList(sldns_buffer* qb, PyObject *l, uint32_t default_ttl, int qsec,
 
         len = sldns_buffer_remaining(qb);
         if(qsec) {
-                if(sldns_str2wire_rr_question_buf(PyString_AsString(item),
+                if(sldns_str2wire_rr_question_buf(PyBytes_AsString(item),
                         sldns_buffer_current(qb), &len, NULL, NULL, 0, NULL, 0)
                         != 0)
                         return 0;
         } else {
-                if(sldns_str2wire_rr_buf(PyString_AsString(item),
+                if(sldns_str2wire_rr_buf(PyBytes_AsString(item),
                         sldns_buffer_current(qb), &len, NULL, default_ttl,
                         NULL, 0, NULL, 0) != 0)
                         return 0;
diff --git a/pythonmod/pythonmod.c b/pythonmod/pythonmod.c
index 190d41a..48dbc01 100644
--- a/pythonmod/pythonmod.c
+++ b/pythonmod/pythonmod.c
@@ -133,7 +133,13 @@ int pythonmod_init(struct module_env* env, int id)
    /* Initialize Python libraries */
    if (!Py_IsInitialized()) 
    {
-      Py_SetProgramName("unbound");
+#if PY_MAJOR_VERSION >= 3
+      wchar_t progname[8];
+      mbstowcs(progname, "unbound", 8);
+#else
+      char *progname = "unbound";
+#endif
+      Py_SetProgramName(progname);
       Py_NoSiteFlag = 1;
       Py_Initialize();
       PyEval_InitThreads();
-- 
2.1.0