diff --git a/src/borg/_item.c b/src/borg/_item.c new file mode 100644 index 000000000..c5c78c407 --- /dev/null +++ b/src/borg/_item.c @@ -0,0 +1,41 @@ +#include "Python.h" + +/* + * This is not quite as dark magic as it looks. We just convert the address of (pointer to) + * a PyObject into a bytes object in _wrap_object, and convert these bytes back to the + * pointer to the original object. + * + * This mainly looks a bit confusing due to our mental special-casing of "char*" from other + * pointers. + * + * The big upside to this is that this neither does *any* serialization (beyond creating tiny + * bytes objects as "stand-ins"), nor has to copy the entire object that's passed around. + */ + +static PyObject * +_object_to_optr(PyObject *obj) +{ + /* + * Create a temporary reference to the object being passed around so it does not vanish. + * Note that we never decref this one in _unwrap_object, since we just transfer that reference + * there, i.e. there is an elided "Py_INCREF(x); Py_DECREF(x)". + * Since the reference is transferred, calls to _wrap_object and _unwrap_object must be symmetric. + */ + Py_INCREF(obj); + return PyBytes_FromStringAndSize((const char*) &obj, sizeof(void*)); +} + +static PyObject * +_optr_to_object(PyObject *bytes) +{ + if(!PyBytes_Check(bytes)) { + PyErr_SetString(PyExc_TypeError, "Cannot unwrap non-bytes object"); + return NULL; + } + if(PyBytes_Size(bytes) != sizeof(void*)) { + PyErr_SetString(PyExc_TypeError, "Invalid length of bytes object"); + return NULL; + } + PyObject *object = * (PyObject **) PyBytes_AsString(bytes); + return object; +} diff --git a/src/borg/helpers.py b/src/borg/helpers.py index 89246b921..2094c8652 100644 --- a/src/borg/helpers.py +++ b/src/borg/helpers.py @@ -141,7 +141,7 @@ def check_extension_modules(): raise ExtensionModuleError if platform.API_VERSION != platform.OS_API_VERSION != '1.1_01': raise ExtensionModuleError - if item.API_VERSION != '1.1_02': + if item.API_VERSION != '1.1_03': raise ExtensionModuleError diff --git a/src/borg/item.pyx b/src/borg/item.pyx index 91fe57ee1..f8b67ddd7 100644 --- a/src/borg/item.pyx +++ b/src/borg/item.pyx @@ -6,7 +6,12 @@ from .helpers import safe_encode, safe_decode from .helpers import bigint_to_int, int_to_bigint from .helpers import StableDict -API_VERSION = '1.1_02' +cdef extern from "_item.c": + object _object_to_optr(object obj) + object _optr_to_object(object bytes) + + +API_VERSION = '1.1_03' class PropDict: @@ -227,6 +232,26 @@ class Item(PropDict): setattr(self, attr, size) return size + def to_optr(self): + """ + Return an "object pointer" (optr), an opaque bag of bytes. + The return value is effectively a reference to this object + that can be passed exactly once to Item.from_optr to get this + object back. + + to_optr/from_optr must be used symmetrically, + don't call from_optr multiple times. + + This object can't be deallocated after a call to to_optr() + until from_optr() is called. + """ + return _object_to_optr(self) + + @classmethod + def from_optr(self, optr): + return _optr_to_object(optr) + + class EncryptedKey(PropDict): """ diff --git a/src/borg/testsuite/item.py b/src/borg/testsuite/item.py index f9d72f87c..aa40cc066 100644 --- a/src/borg/testsuite/item.py +++ b/src/borg/testsuite/item.py @@ -164,3 +164,8 @@ def test_item_file_size(): def test_item_file_size_no_chunks(): item = Item(mode=0o100666) assert item.get_size() == 0 + + +def test_item_optr(): + item = Item() + assert Item.from_optr(item.to_optr()) is item