2 # Copyright 2015 ClusterHQ
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
8 # http://www.apache.org/licenses/LICENSE-2.0
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.
18 nvlist_in and nvlist_out provide support for converting between
19 a dictionary on the Python side and an nvlist_t on the C side
20 with the automatic memory management for C memory allocations.
22 nvlist_in takes a dictionary and produces a CData object corresponding
23 to a C nvlist_t pointer suitable for passing as an input parameter.
24 The nvlist_t is populated based on the dictionary.
26 nvlist_out takes a dictionary and produces a CData object corresponding
27 to a C nvlist_t pointer to pointer suitable for passing as an output parameter.
28 Upon exit from a with-block the dictionary is populated based on the nvlist_t.
30 The dictionary must follow a certain format to be convertible
31 to the nvlist_t. The dictionary produced from the nvlist_t
32 will follow the same format.
35 - keys are always byte strings
36 - a value can be None in which case it represents boolean truth by its mere
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,
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
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
50 from __future__
import absolute_import
, division
, print_function
53 from collections
import namedtuple
54 from contextlib
import contextmanager
55 from .bindings
import libnvpair
56 from .ctypes
import _type_to_suffix
64 This function converts a python dictionary to a C nvlist_t
65 and provides automatic memory management for the latter.
67 :param dict props: the dictionary to be converted.
68 :return: an FFI CData object representing the nvlist_t pointer.
71 nvlistp
= _ffi
.new("nvlist_t **")
72 res
= _lib
.nvlist_alloc(nvlistp
, 1, 0) # UNIQUE_NAME == 1
74 raise MemoryError('nvlist_alloc failed')
75 nvlist
= _ffi
.gc(nvlistp
[0], _lib
.nvlist_free
)
76 _dict_to_nvlist(props
, nvlist
)
81 def nvlist_out(props
):
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.
91 :param dict props: the dictionary to be populated with data from the
93 :return: an FFI CData object representing the pointer to nvlist_t pointer.
96 nvlistp
= _ffi
.new("nvlist_t **")
97 nvlistp
[0] = _ffi
.NULL
# to be sure
100 # clear old entries, if any
102 _nvlist_to_dict(nvlistp
[0], props
)
104 if nvlistp
[0] != _ffi
.NULL
:
105 _lib
.nvlist_free(nvlistp
[0])
106 nvlistp
[0] = _ffi
.NULL
109 def packed_nvlist_out(packed_nvlist
, packed_size
):
111 This function converts a packed C nvlist_t to a python dictionary and
112 provides automatic memory management for the former.
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.
120 with
nvlist_out(props
) as nvp
:
121 ret
= _lib
.nvlist_unpack(packed_nvlist
, packed_size
, nvp
, 0)
123 raise MemoryError('nvlist_unpack failed')
127 _TypeInfo
= namedtuple('_TypeInfo', ['suffix', 'ctype', 'is_array', 'convert'])
130 def _type_info(typeid
):
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
161 # only integer properties need to be here
162 _prop_name_to_type_str
= {
163 b
"rewind-request": "uint32",
165 b
"N_MORE_ERRORS": "int32",
166 b
"pool_context": "int32",
170 def _nvlist_add_array(nvlist
, key
, array
):
172 return isinstance(x
, numbers
.Integral
) and not isinstance(x
, bool)
176 is_integer
= _is_integer(specimen
)
177 specimen_ctype
= None
178 if isinstance(specimen
, _ffi
.CData
):
179 specimen_ctype
= _ffi
.typeof(specimen
)
181 for element
in array
[1:]:
182 if is_integer
and _is_integer(element
):
184 elif type(element
) is not type(specimen
):
185 raise TypeError('Array has elements of different types: ' +
186 type(specimen
).__name
__ +
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
+
195 _ffi
.typeof(element
).cname
)
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
201 for dictionary
in array
:
202 nvlistp
= _ffi
.new('nvlist_t **')
203 res
= _lib
.nvlist_alloc(nvlistp
, 1, 0) # UNIQUE_NAME == 1
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
):
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
))
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
))
227 raise TypeError('Unsupported value type ' + type(specimen
).__name
__)
229 raise MemoryError('nvlist_add failed, err = %d' % ret
)
232 def _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)
243 valptr
= _ffi
.new(typeinfo
.ctype
)
244 lenptr
= _ffi
.new("uint_t *")
245 ret
= cfunc(pair
, valptr
, lenptr
)
247 raise RuntimeError('nvpair_value failed')
248 length
= int(lenptr
[0])
250 for i
in range(length
):
251 val
.append(typeinfo
.convert(valptr
[0][i
]))
253 if typeid
== _lib
.DATA_TYPE_BOOLEAN
:
254 val
= None # XXX or should it be True ?
256 valptr
= _ffi
.new(typeinfo
.ctype
)
257 ret
= cfunc(pair
, valptr
)
259 raise RuntimeError('nvpair_value failed')
260 val
= typeinfo
.convert(valptr
[0])
262 pair
= _lib
.nvlist_next_nvpair(nvlist
, pair
)
266 def _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
__)
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
)
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
)
290 raise TypeError('Unsupported value type ' + type(v
).__name
__)
292 raise MemoryError('nvlist_add failed')
295 # vim: softtabstop=4 tabstop=4 expandtab shiftwidth=4