xref: /freebsd/sys/contrib/openzfs/contrib/pyzfs/libzfs_core/_nvlist.py (revision 61145dc2b94f12f6a47344fb9aac702321880e43)
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