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