| # |
| # Copyright 2015 ClusterHQ |
| # |
| # Licensed under the Apache License, Version 2.0 (the "License"); |
| # you may not use this file except in compliance with the License. |
| # You may obtain a copy of the License at |
| # |
| # http://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, software |
| # distributed under the License is distributed on an "AS IS" BASIS, |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| # See the License for the specific language governing permissions and |
| # limitations under the License. |
| # |
| |
| """ |
| nvlist_in and nvlist_out provide support for converting between |
| a dictionary on the Python side and an nvlist_t on the C side |
| with the automatic memory management for C memory allocations. |
| |
| nvlist_in takes a dictionary and produces a CData object corresponding |
| to a C nvlist_t pointer suitable for passing as an input parameter. |
| The nvlist_t is populated based on the dictionary. |
| |
| nvlist_out takes a dictionary and produces a CData object corresponding |
| to a C nvlist_t pointer to pointer suitable for passing as an output parameter. |
| Upon exit from a with-block the dictionary is populated based on the nvlist_t. |
| |
| The dictionary must follow a certain format to be convertible |
| to the nvlist_t. The dictionary produced from the nvlist_t |
| will follow the same format. |
| |
| Format: |
| - keys are always byte strings |
| - a value can be None in which case it represents boolean truth by its mere |
| presence |
| - a value can be a bool |
| - a value can be a byte string |
| - a value can be an integer |
| - a value can be a CFFI CData object representing one of the following C types: |
| int8_t, uint8_t, int16_t, uint16_t, int32_t, uint32_t, int64_t, uint64_t, |
| boolean_t, uchar_t |
| - a value can be a dictionary that recursively adheres to this format |
| - a value can be a list of bools, byte strings, integers or CData objects of |
| types specified above |
| - a value can be a list of dictionaries that adhere to this format |
| - all elements of a list value must be of the same type |
| """ |
| from __future__ import absolute_import, division, print_function |
| |
| import numbers |
| from collections import namedtuple |
| from contextlib import contextmanager |
| from .bindings import libnvpair |
| from .ctypes import _type_to_suffix |
| |
| _ffi = libnvpair.ffi |
| _lib = libnvpair.lib |
| |
| |
| def nvlist_in(props): |
| """ |
| This function converts a python dictionary to a C nvlist_t |
| and provides automatic memory management for the latter. |
| |
| :param dict props: the dictionary to be converted. |
| :return: an FFI CData object representing the nvlist_t pointer. |
| :rtype: CData |
| """ |
| nvlistp = _ffi.new("nvlist_t **") |
| res = _lib.nvlist_alloc(nvlistp, 1, 0) # UNIQUE_NAME == 1 |
| if res != 0: |
| raise MemoryError('nvlist_alloc failed') |
| nvlist = _ffi.gc(nvlistp[0], _lib.nvlist_free) |
| _dict_to_nvlist(props, nvlist) |
| return nvlist |
| |
| |
| @contextmanager |
| def nvlist_out(props): |
| """ |
| A context manager that allocates a pointer to a C nvlist_t and yields |
| a CData object representing a pointer to the pointer via 'as' target. |
| The caller can pass that pointer to a pointer to a C function that |
| creates a new nvlist_t object. |
| The context manager takes care of memory management for the nvlist_t |
| and also populates the 'props' dictionary with data from the nvlist_t |
| upon leaving the 'with' block. |
| |
| :param dict props: the dictionary to be populated with data from the |
| nvlist. |
| :return: an FFI CData object representing the pointer to nvlist_t pointer. |
| :rtype: CData |
| """ |
| nvlistp = _ffi.new("nvlist_t **") |
| nvlistp[0] = _ffi.NULL # to be sure |
| try: |
| yield nvlistp |
| # clear old entries, if any |
| props.clear() |
| _nvlist_to_dict(nvlistp[0], props) |
| finally: |
| if nvlistp[0] != _ffi.NULL: |
| _lib.nvlist_free(nvlistp[0]) |
| nvlistp[0] = _ffi.NULL |
| |
| |
| def packed_nvlist_out(packed_nvlist, packed_size): |
| """ |
| This function converts a packed C nvlist_t to a python dictionary and |
| provides automatic memory management for the former. |
| |
| :param bytes packed_nvlist: packed nvlist_t. |
| :param int packed_size: nvlist_t packed size. |
| :return: an `dict` of values representing the data contained by nvlist_t. |
| :rtype: dict |
| """ |
| props = {} |
| with nvlist_out(props) as nvp: |
| ret = _lib.nvlist_unpack(packed_nvlist, packed_size, nvp, 0) |
| if ret != 0: |
| raise MemoryError('nvlist_unpack failed') |
| return props |
| |
| |
| _TypeInfo = namedtuple('_TypeInfo', ['suffix', 'ctype', 'is_array', 'convert']) |
| |
| |
| def _type_info(typeid): |
| return { |
| _lib.DATA_TYPE_BOOLEAN: _TypeInfo(None, None, None, None), |
| _lib.DATA_TYPE_BOOLEAN_VALUE: _TypeInfo("boolean_value", "boolean_t *", False, bool), # noqa: E501 |
| _lib.DATA_TYPE_BYTE: _TypeInfo("byte", "uchar_t *", False, int), # noqa: E501 |
| _lib.DATA_TYPE_INT8: _TypeInfo("int8", "int8_t *", False, int), # noqa: E501 |
| _lib.DATA_TYPE_UINT8: _TypeInfo("uint8", "uint8_t *", False, int), # noqa: E501 |
| _lib.DATA_TYPE_INT16: _TypeInfo("int16", "int16_t *", False, int), # noqa: E501 |
| _lib.DATA_TYPE_UINT16: _TypeInfo("uint16", "uint16_t *", False, int), # noqa: E501 |
| _lib.DATA_TYPE_INT32: _TypeInfo("int32", "int32_t *", False, int), # noqa: E501 |
| _lib.DATA_TYPE_UINT32: _TypeInfo("uint32", "uint32_t *", False, int), # noqa: E501 |
| _lib.DATA_TYPE_INT64: _TypeInfo("int64", "int64_t *", False, int), # noqa: E501 |
| _lib.DATA_TYPE_UINT64: _TypeInfo("uint64", "uint64_t *", False, int), # noqa: E501 |
| _lib.DATA_TYPE_STRING: _TypeInfo("string", "char **", False, _ffi.string), # noqa: E501 |
| _lib.DATA_TYPE_NVLIST: _TypeInfo("nvlist", "nvlist_t **", False, lambda x: _nvlist_to_dict(x, {})), # noqa: E501 |
| _lib.DATA_TYPE_BOOLEAN_ARRAY: _TypeInfo("boolean_array", "boolean_t **", True, bool), # noqa: E501 |
| # XXX use bytearray ? |
| _lib.DATA_TYPE_BYTE_ARRAY: _TypeInfo("byte_array", "uchar_t **", True, int), # noqa: E501 |
| _lib.DATA_TYPE_INT8_ARRAY: _TypeInfo("int8_array", "int8_t **", True, int), # noqa: E501 |
| _lib.DATA_TYPE_UINT8_ARRAY: _TypeInfo("uint8_array", "uint8_t **", True, int), # noqa: E501 |
| _lib.DATA_TYPE_INT16_ARRAY: _TypeInfo("int16_array", "int16_t **", True, int), # noqa: E501 |
| _lib.DATA_TYPE_UINT16_ARRAY: _TypeInfo("uint16_array", "uint16_t **", True, int), # noqa: E501 |
| _lib.DATA_TYPE_INT32_ARRAY: _TypeInfo("int32_array", "int32_t **", True, int), # noqa: E501 |
| _lib.DATA_TYPE_UINT32_ARRAY: _TypeInfo("uint32_array", "uint32_t **", True, int), # noqa: E501 |
| _lib.DATA_TYPE_INT64_ARRAY: _TypeInfo("int64_array", "int64_t **", True, int), # noqa: E501 |
| _lib.DATA_TYPE_UINT64_ARRAY: _TypeInfo("uint64_array", "uint64_t **", True, int), # noqa: E501 |
| _lib.DATA_TYPE_STRING_ARRAY: _TypeInfo("string_array", "char ***", True, _ffi.string), # noqa: E501 |
| _lib.DATA_TYPE_NVLIST_ARRAY: _TypeInfo("nvlist_array", "nvlist_t ***", True, lambda x: _nvlist_to_dict(x, {})), # noqa: E501 |
| }[typeid] |
| |
| |
| # only integer properties need to be here |
| _prop_name_to_type_str = { |
| b"rewind-request": "uint32", |
| b"type": "uint32", |
| b"N_MORE_ERRORS": "int32", |
| b"pool_context": "int32", |
| } |
| |
| |
| def _nvlist_add_array(nvlist, key, array): |
| def _is_integer(x): |
| return isinstance(x, numbers.Integral) and not isinstance(x, bool) |
| |
| ret = 0 |
| specimen = array[0] |
| is_integer = _is_integer(specimen) |
| specimen_ctype = None |
| if isinstance(specimen, _ffi.CData): |
| specimen_ctype = _ffi.typeof(specimen) |
| |
| for element in array[1:]: |
| if is_integer and _is_integer(element): |
| pass |
| elif type(element) is not type(specimen): |
| raise TypeError('Array has elements of different types: ' + |
| type(specimen).__name__ + |
| ' and ' + |
| type(element).__name__) |
| elif specimen_ctype is not None: |
| ctype = _ffi.typeof(element) |
| if ctype is not specimen_ctype: |
| raise TypeError('Array has elements of different C types: ' + |
| _ffi.typeof(specimen).cname + |
| ' and ' + |
| _ffi.typeof(element).cname) |
| |
| if isinstance(specimen, dict): |
| # NB: can't use automatic memory management via nvlist_in() here, |
| # we have a loop, but 'with' would require recursion |
| c_array = [] |
| for dictionary in array: |
| nvlistp = _ffi.new('nvlist_t **') |
| res = _lib.nvlist_alloc(nvlistp, 1, 0) # UNIQUE_NAME == 1 |
| if res != 0: |
| raise MemoryError('nvlist_alloc failed') |
| nested_nvlist = _ffi.gc(nvlistp[0], _lib.nvlist_free) |
| _dict_to_nvlist(dictionary, nested_nvlist) |
| c_array.append(nested_nvlist) |
| ret = _lib.nvlist_add_nvlist_array(nvlist, key, c_array, len(c_array)) |
| elif isinstance(specimen, bytes): |
| c_array = [] |
| for string in array: |
| c_array.append(_ffi.new('char[]', string)) |
| ret = _lib.nvlist_add_string_array(nvlist, key, c_array, len(c_array)) |
| elif isinstance(specimen, bool): |
| ret = _lib.nvlist_add_boolean_array(nvlist, key, array, len(array)) |
| elif isinstance(specimen, numbers.Integral): |
| suffix = _prop_name_to_type_str.get(key, "uint64") |
| cfunc = getattr(_lib, "nvlist_add_%s_array" % (suffix,)) |
| ret = cfunc(nvlist, key, array, len(array)) |
| elif isinstance( |
| specimen, _ffi.CData) and _ffi.typeof(specimen) in _type_to_suffix: |
| suffix = _type_to_suffix[_ffi.typeof(specimen)][True] |
| cfunc = getattr(_lib, "nvlist_add_%s_array" % (suffix,)) |
| ret = cfunc(nvlist, key, array, len(array)) |
| else: |
| raise TypeError('Unsupported value type ' + type(specimen).__name__) |
| if ret != 0: |
| raise MemoryError('nvlist_add failed, err = %d' % ret) |
| |
| |
| def _nvlist_to_dict(nvlist, props): |
| pair = _lib.nvlist_next_nvpair(nvlist, _ffi.NULL) |
| while pair != _ffi.NULL: |
| name = _ffi.string(_lib.nvpair_name(pair)) |
| typeid = int(_lib.nvpair_type(pair)) |
| typeinfo = _type_info(typeid) |
| is_array = bool(_lib.nvpair_type_is_array(pair)) |
| cfunc = getattr(_lib, "nvpair_value_%s" % (typeinfo.suffix,), None) |
| val = None |
| ret = 0 |
| if is_array: |
| valptr = _ffi.new(typeinfo.ctype) |
| lenptr = _ffi.new("uint_t *") |
| ret = cfunc(pair, valptr, lenptr) |
| if ret != 0: |
| raise RuntimeError('nvpair_value failed') |
| length = int(lenptr[0]) |
| val = [] |
| for i in range(length): |
| val.append(typeinfo.convert(valptr[0][i])) |
| else: |
| if typeid == _lib.DATA_TYPE_BOOLEAN: |
| val = None # XXX or should it be True ? |
| else: |
| valptr = _ffi.new(typeinfo.ctype) |
| ret = cfunc(pair, valptr) |
| if ret != 0: |
| raise RuntimeError('nvpair_value failed') |
| val = typeinfo.convert(valptr[0]) |
| props[name] = val |
| pair = _lib.nvlist_next_nvpair(nvlist, pair) |
| return props |
| |
| |
| def _dict_to_nvlist(props, nvlist): |
| for k, v in props.items(): |
| if not isinstance(k, bytes): |
| raise TypeError('Unsupported key type ' + type(k).__name__) |
| ret = 0 |
| if isinstance(v, dict): |
| ret = _lib.nvlist_add_nvlist(nvlist, k, nvlist_in(v)) |
| elif isinstance(v, list): |
| _nvlist_add_array(nvlist, k, v) |
| elif isinstance(v, bytes): |
| ret = _lib.nvlist_add_string(nvlist, k, v) |
| elif isinstance(v, bool): |
| ret = _lib.nvlist_add_boolean_value(nvlist, k, v) |
| elif v is None: |
| ret = _lib.nvlist_add_boolean(nvlist, k) |
| elif isinstance(v, numbers.Integral): |
| suffix = _prop_name_to_type_str.get(k, "uint64") |
| cfunc = getattr(_lib, "nvlist_add_%s" % (suffix,)) |
| ret = cfunc(nvlist, k, v) |
| elif isinstance(v, _ffi.CData) and _ffi.typeof(v) in _type_to_suffix: |
| suffix = _type_to_suffix[_ffi.typeof(v)][False] |
| cfunc = getattr(_lib, "nvlist_add_%s" % (suffix,)) |
| ret = cfunc(nvlist, k, v) |
| else: |
| raise TypeError('Unsupported value type ' + type(v).__name__) |
| if ret != 0: |
| raise MemoryError('nvlist_add failed') |
| |
| |
| # vim: softtabstop=4 tabstop=4 expandtab shiftwidth=4 |