]> git.ipfire.org Git - pakfire.git/commitdiff
_pakfire: util: Refactor PyObject_AsFileHandle using fmemcookie
authorMichael Tremer <michael.tremer@ipfire.org>
Wed, 2 Aug 2023 15:08:15 +0000 (15:08 +0000)
committerMichael Tremer <michael.tremer@ipfire.org>
Wed, 2 Aug 2023 15:08:15 +0000 (15:08 +0000)
Python is not very good at handling file objects, so we wrap them into
this little tool.

Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
src/_pakfire/util.c

index 3fda33ed4e0d7f1da17f56a51edd3cad457a4402..fb2b1f133a3a826d5217220e12b097c3cd9b0194 100644 (file)
@@ -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);
 }