1# 2# Copyright 2015 ClusterHQ 3# 4# Licensed under the Apache License, Version 2.0 (the "License"); 5# you may not use this file except in compliance with the License. 6# You may obtain a copy of the License at 7# 8# http://www.apache.org/licenses/LICENSE-2.0 9# 10# Unless required by applicable law or agreed to in writing, software 11# distributed under the License is distributed on an "AS IS" BASIS, 12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13# See the License for the specific language governing permissions and 14# limitations under the License. 15# 16 17""" 18nvlist_in and nvlist_out provide support for converting between 19a dictionary on the Python side and an nvlist_t on the C side 20with the automatic memory management for C memory allocations. 21 22nvlist_in takes a dictionary and produces a CData object corresponding 23to a C nvlist_t pointer suitable for passing as an input parameter. 24The nvlist_t is populated based on the dictionary. 25 26nvlist_out takes a dictionary and produces a CData object corresponding 27to a C nvlist_t pointer to pointer suitable for passing as an output parameter. 28Upon exit from a with-block the dictionary is populated based on the nvlist_t. 29 30The dictionary must follow a certain format to be convertible 31to the nvlist_t. The dictionary produced from the nvlist_t 32will follow the same format. 33 34Format: 35- keys are always byte strings 36- a value can be None in which case it represents boolean truth by its mere 37 presence 38- a value can be a bool 39- a value can be a byte string 40- a value can be an integer 41- a value can be a CFFI CData object representing one of the following C types: 42 int8_t, uint8_t, int16_t, uint16_t, int32_t, uint32_t, int64_t, uint64_t, 43 boolean_t, uchar_t 44- a value can be a dictionary that recursively adheres to this format 45- a value can be a list of bools, byte strings, integers or CData objects of 46 types specified above 47- a value can be a list of dictionaries that adhere to this format 48- all elements of a list value must be of the same type 49""" 50from __future__ import absolute_import, division, print_function 51 52import numbers 53from collections import namedtuple 54from contextlib import contextmanager 55from .bindings import libnvpair 56from .ctypes import _type_to_suffix 57 58_ffi = libnvpair.ffi 59_lib = libnvpair.lib 60 61 62def nvlist_in(props): 63 """ 64 This function converts a python dictionary to a C nvlist_t 65 and provides automatic memory management for the latter. 66 67 :param dict props: the dictionary to be converted. 68 :return: an FFI CData object representing the nvlist_t pointer. 69 :rtype: CData 70 """ 71 nvlistp = _ffi.new("nvlist_t **") 72 res = _lib.nvlist_alloc(nvlistp, 1, 0) # UNIQUE_NAME == 1 73 if res != 0: 74 raise MemoryError('nvlist_alloc failed') 75 nvlist = _ffi.gc(nvlistp[0], _lib.nvlist_free) 76 _dict_to_nvlist(props, nvlist) 77 return nvlist 78 79 80@contextmanager 81def nvlist_out(props): 82 """ 83 A context manager that allocates a pointer to a C nvlist_t and yields 84 a CData object representing a pointer to the pointer via 'as' target. 85 The caller can pass that pointer to a pointer to a C function that 86 creates a new nvlist_t object. 87 The context manager takes care of memory management for the nvlist_t 88 and also populates the 'props' dictionary with data from the nvlist_t 89 upon leaving the 'with' block. 90 91 :param dict props: the dictionary to be populated with data from the 92 nvlist. 93 :return: an FFI CData object representing the pointer to nvlist_t pointer. 94 :rtype: CData 95 """ 96 nvlistp = _ffi.new("nvlist_t **") 97 nvlistp[0] = _ffi.NULL # to be sure 98 try: 99 yield nvlistp 100 # clear old entries, if any 101 props.clear() 102 _nvlist_to_dict(nvlistp[0], props) 103 finally: 104 if nvlistp[0] != _ffi.NULL: 105 _lib.nvlist_free(nvlistp[0]) 106 nvlistp[0] = _ffi.NULL 107 108 109def packed_nvlist_out(packed_nvlist, packed_size): 110 """ 111 This function converts a packed C nvlist_t to a python dictionary and 112 provides automatic memory management for the former. 113 114 :param bytes packed_nvlist: packed nvlist_t. 115 :param int packed_size: nvlist_t packed size. 116 :return: an `dict` of values representing the data contained by nvlist_t. 117 :rtype: dict 118 """ 119 props = {} 120 with nvlist_out(props) as nvp: 121 ret = _lib.nvlist_unpack(packed_nvlist, packed_size, nvp, 0) 122 if ret != 0: 123 raise MemoryError('nvlist_unpack failed') 124 return props 125 126 127_TypeInfo = namedtuple('_TypeInfo', ['suffix', 'ctype', 'is_array', 'convert']) 128 129 130def _type_info(typeid): 131 return { 132 _lib.DATA_TYPE_BOOLEAN: _TypeInfo(None, None, None, None), 133 _lib.DATA_TYPE_BOOLEAN_VALUE: _TypeInfo("boolean_value", "boolean_t *", False, bool), # noqa: E501 134 _lib.DATA_TYPE_BYTE: _TypeInfo("byte", "uchar_t *", False, int), # noqa: E501 135 _lib.DATA_TYPE_INT8: _TypeInfo("int8", "int8_t *", False, int), # noqa: E501 136 _lib.DATA_TYPE_UINT8: _TypeInfo("uint8", "uint8_t *", False, int), # noqa: E501 137 _lib.DATA_TYPE_INT16: _TypeInfo("int16", "int16_t *", False, int), # noqa: E501 138 _lib.DATA_TYPE_UINT16: _TypeInfo("uint16", "uint16_t *", False, int), # noqa: E501 139 _lib.DATA_TYPE_INT32: _TypeInfo("int32", "int32_t *", False, int), # noqa: E501 140 _lib.DATA_TYPE_UINT32: _TypeInfo("uint32", "uint32_t *", False, int), # noqa: E501 141 _lib.DATA_TYPE_INT64: _TypeInfo("int64", "int64_t *", False, int), # noqa: E501 142 _lib.DATA_TYPE_UINT64: _TypeInfo("uint64", "uint64_t *", False, int), # noqa: E501 143 _lib.DATA_TYPE_STRING: _TypeInfo("string", "char **", False, _ffi.string), # noqa: E501 144 _lib.DATA_TYPE_NVLIST: _TypeInfo("nvlist", "nvlist_t **", False, lambda x: _nvlist_to_dict(x, {})), # noqa: E501 145 _lib.DATA_TYPE_BOOLEAN_ARRAY: _TypeInfo("boolean_array", "boolean_t **", True, bool), # noqa: E501 146 # XXX use bytearray ? 147 _lib.DATA_TYPE_BYTE_ARRAY: _TypeInfo("byte_array", "uchar_t **", True, int), # noqa: E501 148 _lib.DATA_TYPE_INT8_ARRAY: _TypeInfo("int8_array", "int8_t **", True, int), # noqa: E501 149 _lib.DATA_TYPE_UINT8_ARRAY: _TypeInfo("uint8_array", "uint8_t **", True, int), # noqa: E501 150 _lib.DATA_TYPE_INT16_ARRAY: _TypeInfo("int16_array", "int16_t **", True, int), # noqa: E501 151 _lib.DATA_TYPE_UINT16_ARRAY: _TypeInfo("uint16_array", "uint16_t **", True, int), # noqa: E501 152 _lib.DATA_TYPE_INT32_ARRAY: _TypeInfo("int32_array", "int32_t **", True, int), # noqa: E501 153 _lib.DATA_TYPE_UINT32_ARRAY: _TypeInfo("uint32_array", "uint32_t **", True, int), # noqa: E501 154 _lib.DATA_TYPE_INT64_ARRAY: _TypeInfo("int64_array", "int64_t **", True, int), # noqa: E501 155 _lib.DATA_TYPE_UINT64_ARRAY: _TypeInfo("uint64_array", "uint64_t **", True, int), # noqa: E501 156 _lib.DATA_TYPE_STRING_ARRAY: _TypeInfo("string_array", "char ***", True, _ffi.string), # noqa: E501 157 _lib.DATA_TYPE_NVLIST_ARRAY: _TypeInfo("nvlist_array", "nvlist_t ***", True, lambda x: _nvlist_to_dict(x, {})), # noqa: E501 158 }[typeid] 159 160 161# only integer properties need to be here 162_prop_name_to_type_str = { 163 b"rewind-request": "uint32", 164 b"type": "uint32", 165 b"N_MORE_ERRORS": "int32", 166 b"pool_context": "int32", 167} 168 169 170def _nvlist_add_array(nvlist, key, array): 171 def _is_integer(x): 172 return isinstance(x, numbers.Integral) and not isinstance(x, bool) 173 174 ret = 0 175 specimen = array[0] 176 is_integer = _is_integer(specimen) 177 specimen_ctype = None 178 if isinstance(specimen, _ffi.CData): 179 specimen_ctype = _ffi.typeof(specimen) 180 181 for element in array[1:]: 182 if is_integer and _is_integer(element): 183 pass 184 elif type(element) is not type(specimen): 185 raise TypeError('Array has elements of different types: ' + 186 type(specimen).__name__ + 187 ' and ' + 188 type(element).__name__) 189 elif specimen_ctype is not None: 190 ctype = _ffi.typeof(element) 191 if ctype is not specimen_ctype: 192 raise TypeError('Array has elements of different C types: ' + 193 _ffi.typeof(specimen).cname + 194 ' and ' + 195 _ffi.typeof(element).cname) 196 197 if isinstance(specimen, dict): 198 # NB: can't use automatic memory management via nvlist_in() here, 199 # we have a loop, but 'with' would require recursion 200 c_array = [] 201 for dictionary in array: 202 nvlistp = _ffi.new('nvlist_t **') 203 res = _lib.nvlist_alloc(nvlistp, 1, 0) # UNIQUE_NAME == 1 204 if res != 0: 205 raise MemoryError('nvlist_alloc failed') 206 nested_nvlist = _ffi.gc(nvlistp[0], _lib.nvlist_free) 207 _dict_to_nvlist(dictionary, nested_nvlist) 208 c_array.append(nested_nvlist) 209 ret = _lib.nvlist_add_nvlist_array(nvlist, key, c_array, len(c_array)) 210 elif isinstance(specimen, bytes): 211 c_array = [] 212 for string in array: 213 c_array.append(_ffi.new('char[]', string)) 214 ret = _lib.nvlist_add_string_array(nvlist, key, c_array, len(c_array)) 215 elif isinstance(specimen, bool): 216 ret = _lib.nvlist_add_boolean_array(nvlist, key, array, len(array)) 217 elif isinstance(specimen, numbers.Integral): 218 suffix = _prop_name_to_type_str.get(key, "uint64") 219 cfunc = getattr(_lib, "nvlist_add_%s_array" % (suffix,)) 220 ret = cfunc(nvlist, key, array, len(array)) 221 elif isinstance( 222 specimen, _ffi.CData) and _ffi.typeof(specimen) in _type_to_suffix: 223 suffix = _type_to_suffix[_ffi.typeof(specimen)][True] 224 cfunc = getattr(_lib, "nvlist_add_%s_array" % (suffix,)) 225 ret = cfunc(nvlist, key, array, len(array)) 226 else: 227 raise TypeError('Unsupported value type ' + type(specimen).__name__) 228 if ret != 0: 229 raise MemoryError('nvlist_add failed, err = %d' % ret) 230 231 232def _nvlist_to_dict(nvlist, props): 233 pair = _lib.nvlist_next_nvpair(nvlist, _ffi.NULL) 234 while pair != _ffi.NULL: 235 name = _ffi.string(_lib.nvpair_name(pair)) 236 typeid = int(_lib.nvpair_type(pair)) 237 typeinfo = _type_info(typeid) 238 is_array = bool(_lib.nvpair_type_is_array(pair)) 239 cfunc = getattr(_lib, "nvpair_value_%s" % (typeinfo.suffix,), None) 240 val = None 241 ret = 0 242 if is_array: 243 valptr = _ffi.new(typeinfo.ctype) 244 lenptr = _ffi.new("uint_t *") 245 ret = cfunc(pair, valptr, lenptr) 246 if ret != 0: 247 raise RuntimeError('nvpair_value failed') 248 length = int(lenptr[0]) 249 val = [] 250 for i in range(length): 251 val.append(typeinfo.convert(valptr[0][i])) 252 else: 253 if typeid == _lib.DATA_TYPE_BOOLEAN: 254 val = None # XXX or should it be True ? 255 else: 256 valptr = _ffi.new(typeinfo.ctype) 257 ret = cfunc(pair, valptr) 258 if ret != 0: 259 raise RuntimeError('nvpair_value failed') 260 val = typeinfo.convert(valptr[0]) 261 props[name] = val 262 pair = _lib.nvlist_next_nvpair(nvlist, pair) 263 return props 264 265 266def _dict_to_nvlist(props, nvlist): 267 for k, v in props.items(): 268 if not isinstance(k, bytes): 269 raise TypeError('Unsupported key type ' + type(k).__name__) 270 ret = 0 271 if isinstance(v, dict): 272 ret = _lib.nvlist_add_nvlist(nvlist, k, nvlist_in(v)) 273 elif isinstance(v, list): 274 _nvlist_add_array(nvlist, k, v) 275 elif isinstance(v, bytes): 276 ret = _lib.nvlist_add_string(nvlist, k, v) 277 elif isinstance(v, bool): 278 ret = _lib.nvlist_add_boolean_value(nvlist, k, v) 279 elif v is None: 280 ret = _lib.nvlist_add_boolean(nvlist, k) 281 elif isinstance(v, numbers.Integral): 282 suffix = _prop_name_to_type_str.get(k, "uint64") 283 cfunc = getattr(_lib, "nvlist_add_%s" % (suffix,)) 284 ret = cfunc(nvlist, k, v) 285 elif isinstance(v, _ffi.CData) and _ffi.typeof(v) in _type_to_suffix: 286 suffix = _type_to_suffix[_ffi.typeof(v)][False] 287 cfunc = getattr(_lib, "nvlist_add_%s" % (suffix,)) 288 ret = cfunc(nvlist, k, v) 289 else: 290 raise TypeError('Unsupported value type ' + type(v).__name__) 291 if ret != 0: 292 raise MemoryError('nvlist_add failed') 293 294 295# vim: softtabstop=4 tabstop=4 expandtab shiftwidth=4 296