]> granicus.if.org Git - zfs/blob - contrib/pyzfs/libzfs_core/_nvlist.py
pyzfs: python3 support (library 2/2)
[zfs] / contrib / pyzfs / libzfs_core / _nvlist.py
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 """
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.
21
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.
25
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.
29
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.
33
34 Format:
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 """
50 from __future__ import absolute_import, division, print_function
51
52 import numbers
53 from collections import namedtuple
54 from contextlib import contextmanager
55 from .bindings import libnvpair
56 from .ctypes import _type_to_suffix
57
58 _ffi = libnvpair.ffi
59 _lib = libnvpair.lib
60
61
62 def 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
81 def 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
109 def 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 containted 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
130 def _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
170 def _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
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)
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
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__)
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