From: Michael Tremer Date: Wed, 2 Aug 2023 15:08:15 +0000 (+0000) Subject: _pakfire: util: Refactor PyObject_AsFileHandle using fmemcookie X-Git-Tag: 0.9.29~80 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=3dca9fd03fe30f9610613d83e28d51fa2c4fee86;p=pakfire.git _pakfire: util: Refactor PyObject_AsFileHandle using fmemcookie Python is not very good at handling file objects, so we wrap them into this little tool. Signed-off-by: Michael Tremer --- diff --git a/src/_pakfire/util.c b/src/_pakfire/util.c index 3fda33ed4..fb2b1f133 100644 --- a/src/_pakfire/util.c +++ b/src/_pakfire/util.c @@ -112,21 +112,109 @@ PyObject* PyDateTime_FromTime_t(const time_t* t) { ); } -FILE* PyObject_AsFileHandle(PyObject* object, const char* mode) { - // Get a file descriptor - int fd = PyObject_AsFileDescriptor(object); - if (fd < 0) - return NULL; +static ssize_t PyObject_AsFileHandle_read(void* cookie, char* buffer, size_t size) { + PyObject* object = (PyObject*)cookie; + PyObject* pybuffer = NULL; + const char* bytes = NULL; + Py_ssize_t bytes_read = -1; + + // Make sure we acquired the GIL + PyGILState_STATE state = PyGILState_Ensure(); + + // Read data from the object + pybuffer = PyObject_CallMethod(object, "read", "i", size); + if (!pybuffer) + goto ERROR; + + // Handle strings + if (PyUnicode_Check(pybuffer)) { + bytes = PyUnicode_AsUTF8AndSize(pybuffer, &bytes_read); + if (!bytes) + goto ERROR; - // Duplicate the file handle - fd = dup(fd); + // Handle bytes + } else if (PyBytes_Check(pybuffer)) { + bytes = PyBytes_AsString(pybuffer); + if (!bytes) + goto ERROR; + + bytes_read = PyBytes_Size(pybuffer); + + // Fail on anything else + } else { + PyErr_SetString(PyExc_TypeError, "read() returned an unexpected data type\n"); + goto ERROR; + } - // Convert to file handle - FILE* f = fdopen(fd, mode); - if (!f) { - PyErr_SetFromErrno(PyExc_OSError); + // Copy the data into the target buffer + memcpy(buffer, bytes, bytes_read); + +ERROR: + Py_DECREF(pybuffer); + + // Release the GIL + PyGILState_Release(state); + + return bytes_read; +} + +static ssize_t PyObject_AsFileHandle_write(void* cookie, const char* buffer, size_t size) { + PyObject* object = (PyObject*)cookie; + ssize_t r = 0; + + // Make sure we acquired the GIL + PyGILState_STATE state = PyGILState_Ensure(); + + PyObject* pybuffer = PyBytes_FromStringAndSize(buffer, size); + if (!pybuffer) + goto ERROR; + + PyObject* result = PyObject_CallMethod(object, "write", "b", pybuffer); + if (!result) + goto ERROR; + + // It looks like we successfully wrote the data + r = size; + +ERROR: + Py_XDECREF(pybuffer); + + // Release the GIL + PyGILState_Release(state); + + return r; +} + +static int PyObject_AsFileHandle_close(void* cookie) { + PyObject* object = (PyObject*)cookie; + + // Make sure we acquired the GIL + PyGILState_STATE state = PyGILState_Ensure(); + + // Decrement the reference counter for the object + Py_DECREF(object); + + // Release the GIL + PyGILState_Release(state); + + return 0; +} + +static cookie_io_functions_t PyFileHandleMethods = { + .read = PyObject_AsFileHandle_read, + .write = PyObject_AsFileHandle_write, + .close = PyObject_AsFileHandle_close, +}; + +FILE* PyObject_AsFileHandle(PyObject* object, const char* mode) { + // We only support reading files + if (!mode || *mode != 'r') { + errno = ENOTSUP; return NULL; } - return f; + // Increment the reference counter for the object + Py_INCREF(object); + + return fopencookie(object, mode, PyFileHandleMethods); }