diff --git a/sdb/command.py b/sdb/command.py index 670b603..a8e7dd6 100644 --- a/sdb/command.py +++ b/sdb/command.py @@ -368,7 +368,7 @@ def _call(self, objs: Iterable[drgn.Object]) -> Iterable[drgn.Object]: err_msg = f"invalid memory access: {str(err)}" else: err_msg = "invalid memory access while handling object " - err_msg += "at address {hex(obj.address_of_().value_())}" + err_msg += f"at address {hex(obj.address_of_().value_())}" cmd_err = CommandError(self.name, err_msg) print(cmd_err.text) if result is not None: diff --git a/sdb/commands/nvpair/__init__.py b/sdb/commands/nvpair/__init__.py new file mode 100644 index 0000000..59acfe3 --- /dev/null +++ b/sdb/commands/nvpair/__init__.py @@ -0,0 +1,26 @@ +# +# Copyright 2019 Delphix +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +# pylint: disable=missing-docstring + +import glob +import importlib +import os + +for path in glob.glob("{}/*.py".format(os.path.dirname(__file__))): + if path != __file__: + module = os.path.splitext(os.path.basename(path))[0] + importlib.import_module("sdb.commands.nvpair.{}".format(module)) diff --git a/sdb/commands/nvpair/internal/__init__.py b/sdb/commands/nvpair/internal/__init__.py new file mode 100644 index 0000000..328731a --- /dev/null +++ b/sdb/commands/nvpair/internal/__init__.py @@ -0,0 +1,27 @@ +# +# Copyright 2019 Delphix +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +# pylint: disable=missing-docstring + +import glob +import importlib +import os + +for path in glob.glob("{}/*.py".format(os.path.dirname(__file__))): + if path != __file__: + module = os.path.splitext(os.path.basename(path))[0] + importlib.import_module( + "sdb.commands.nvpair.internal.{}".format(module)) diff --git a/sdb/commands/nvpair/nvpair.py b/sdb/commands/nvpair/nvpair.py new file mode 100644 index 0000000..26b82f6 --- /dev/null +++ b/sdb/commands/nvpair/nvpair.py @@ -0,0 +1,210 @@ +# +# Copyright 2020 Datto, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +# pylint: disable=missing-docstring + +import os +import drgn +import sdb + + +class Nvpair: + # Member data and methods to print the value of the + # various nvpair_t types + def __init__(self, nvp: drgn.Object, nvl: drgn.Object) -> None: + self.addr = nvp.address_ + self.nvp = nvp + self.nvl = nvl + self.nvp_size_ = sdb.type_canonicalize_size("nvpair_t") + self.nvl_size = sdb.type_canonicalize_size("nvlist_t") + self.name_size = int(nvp.nvp_name_sz) + self.data_ptr = self.addr + ((self.nvp_size_ + self.name_size + 7) & ~7) + + def get_type(self, strip: bool = False) -> str: + # N.B. data_type_t enum is not zero indexed it starts + # with -1 so we add one to get the correct index + fields = sdb.get_type("data_type_t").type.enumerators + enum_string: str = fields[self.nvp.nvp_type + 1].name + if strip: + prefix = os.path.commonprefix([f[0] for f in fields]) + return enum_string[prefix.rfind("_") + 1:] + return enum_string + + def get_name(self) -> drgn.Object: + # name is located after the nvpair_t struct + ptr = self.addr + self.nvp_size_ + return sdb.create_object("char *", ptr).string_().decode("utf-8") + + # works for all signed and unsigned int, long, long long + @staticmethod + def get_value_int(type_: str, loc: int) -> str: + sz = sdb.create_object('size_t', sdb.type_canonicalize_size(type_)) + # value is located after the name + contents = sdb.get_prog().read(loc, sz) + value = drgn.Object.from_bytes_(sdb.get_prog(), type_, contents) + return str(value.value_()) + + # iterate the array, obtain and print nvpair values + def get_array_values(self, type_: str) -> None: + indent = " " + count = self.nvp.nvp_value_elem + type_ = type_.replace("_array", "") + if "boolean" in type_: + type_ = "uint32_t" + val = "" + # skip n 64 bit pointers for string and nvlist array type + offset = count * 8 + for x in range(count): + if x == 0: + print("values=") + if "nvlist" in type_: + self.nvl.set_indent(self.nvl.indent + 4) + self.nvl.print_one( + sdb.create_object("nvlist_t *", self.data_ptr + offset)) + self.nvl.set_indent(self.nvl.indent - 4) + offset += self.nvl_size + elif "int" in type_: + sz = sdb.create_object('size_t', + sdb.type_canonicalize_size(type_)) + val = self.get_value_int(type_, self.data_ptr + (sz * x)) + print(f"{indent}{val}") + elif "byte" in type_: + val = self.get_value_int("unsigned char", self.data_ptr + x) + print(f"{indent}{hex(int(val))}") + elif "string" in type_: + offset += len(val) + val = sdb.create_object("char *", self.data_ptr + + offset).string_().decode("utf-8") + print(f"{indent}{val}") + offset += 1 # null terminator + + # obtain and print the nvpair value + def get_value(self) -> None: + type_ = (self.get_type(True) + "_t").lower() + if "array" in type_: + self.get_array_values(type_) + return + if "nvlist" in type_: + print("values=") + self.nvl.set_indent(self.nvl.indent + 4) + self.nvl.print_one(sdb.create_object("nvlist_t *", self.data_ptr)) + self.nvl.set_indent(self.nvl.indent - 4) + return + print("value=", end='') + if "boolean_value" in type_: + type_ = "uint32_t" + elif "boolean" in type_: # DATA_TYPE_BOOLEAN has name and no value + print("") + elif "hrtime" in type_: + type_ = "int64_t" + if "int" in type_: + print(self.get_value_int(type_, self.data_ptr)) + if "string" in type_: + print( + sdb.create_object("char *", + self.data_ptr).string_().decode("utf-8")) + if "byte" in type_: + print(hex(int(self.get_value_int("unsigned char", self.data_ptr)))) + return + + +class Nvlist(sdb.SingleInputCommand): + """ + Print nvlist_t + + DESCRIPTION + Print all the nvpair_t in the passed nvlist_t. Handle basic types, + array types, and nvlist_t types. Type double is omitted as it is + not used in zfs. + + EXAMPLE + Print nvlist_t of snapshot holds from the nvlist_t * pointer address: + + sdb> echo 0xffff970d0ff681e0 | nvlist + name=monday-1 type=DATA_TYPE_UINT64 value=1633989858 + name=monday-2 type=DATA_TYPE_UINT64 value=1633989863 + """ + + names = ["nvlist"] + input_type = "nvlist_t *" + output_type = "nvlist_t *" + indent = 0 + + def set_indent(self, indent: int) -> None: + self.indent = indent + + # nvlist iteration methods + @staticmethod + def nvlist_contains_nvp(nvl: drgn.Object, nvp: drgn.Object) -> int: + priv = drgn.cast("nvpriv_t *", nvl.nvl_priv) + if nvp.address_ == 0: + return 0 + + curr = priv.nvp_list + while not sdb.is_null(curr): + if curr.nvi_nvp.address_ == nvp.address_: + return 1 + # pylint: disable=protected-access + curr = curr._nvi_un._nvi._nvi_next + + return 0 + + @staticmethod + def nvlist_first_nvpair(nvl: drgn.Object) -> drgn.Object: + if sdb.is_null(nvl) or sdb.is_null(nvl.nvl_priv): + return None + priv = drgn.cast("nvpriv_t *", nvl.nvl_priv) + if sdb.is_null(priv.nvp_list): + return None + return priv.nvp_list.nvi_nvp + + @staticmethod + def nvlist_next_nvpair(nvl: drgn.Object, nvp: drgn.Object) -> drgn.Object: + if sdb.is_null(nvl) or sdb.is_null(nvl.nvl_priv): + return None + + priv = drgn.cast("nvpriv_t *", nvl.nvl_priv) + + curr_addr = nvp.address_ - drgn.offsetof(sdb.get_type("i_nvp_t"), + "nvi_nvp") + curr = sdb.create_object("i_nvp_t *", curr_addr) + + if priv.nvp_curr == curr or Nvlist.nvlist_contains_nvp(nvl, nvp): + # pylint: disable=protected-access + curr = curr._nvi_un._nvi._nvi_next + else: + curr = drgn.NULL(sdb.get_prog(), "i_nvp_t *") + + if not sdb.is_null(curr): + return curr.nvi_nvp + + return None + + # print one nvlist_t + def print_one(self, nvl: drgn.Object) -> None: + pair = self.nvlist_first_nvpair(nvl) + while pair is not None: + nvobj = Nvpair(pair, self) + print(f"{' '*self.indent}", end='') + print(f"name={nvobj.get_name()} ", end='') + print(f"type={nvobj.get_type()} ", end='') + # value will be printed in get_value function + nvobj.get_value() + pair = self.nvlist_next_nvpair(nvl, pair) + + def _call_one(self, obj: drgn.Object) -> None: + self.print_one(obj) + print("----") diff --git a/setup.py b/setup.py index adf4600..0d75696 100755 --- a/setup.py +++ b/setup.py @@ -12,6 +12,8 @@ "sdb.commands.internal", "sdb.commands.linux", "sdb.commands.linux.internal", + "sdb.commands.nvpair", + "sdb.commands.nvpair.internal", "sdb.commands.spl", "sdb.commands.spl.internal", "sdb.commands.zfs", diff --git a/tests/integration/data/regression_output/zfs/spa | member spa_load_info | nvlist b/tests/integration/data/regression_output/zfs/spa | member spa_load_info | nvlist new file mode 100644 index 0000000..ba19bb4 --- /dev/null +++ b/tests/integration/data/regression_output/zfs/spa | member spa_load_info | nvlist @@ -0,0 +1,42 @@ +name=enabled_feat type=DATA_TYPE_NVLIST values= + name=org.zfsonlinux:large_dnode type=DATA_TYPE_UINT64 value=0 + name=com.delphix:redaction_bookmarks type=DATA_TYPE_UINT64 value=0 + name=com.delphix:extensible_dataset type=DATA_TYPE_UINT64 value=1 + name=com.delphix:bookmark_written type=DATA_TYPE_UINT64 value=0 + name=com.delphix:hole_birth type=DATA_TYPE_UINT64 value=1 + name=org.illumos:skein type=DATA_TYPE_UINT64 value=0 + name=org.illumos:sha512 type=DATA_TYPE_UINT64 value=0 + name=org.illumos:lz4_compress type=DATA_TYPE_UINT64 value=1 + name=com.delphix:redacted_datasets type=DATA_TYPE_UINT64 value=0 + name=com.datto:bookmark_v2 type=DATA_TYPE_UINT64 value=0 + name=com.delphix:embedded_data type=DATA_TYPE_UINT64 value=1 + name=com.delphix:device_removal type=DATA_TYPE_UINT64 value=0 + name=org.open-zfs:large_blocks type=DATA_TYPE_UINT64 value=0 + name=com.joyent:multi_vdev_crash_dump type=DATA_TYPE_UINT64 value=0 + name=com.datto:encryption type=DATA_TYPE_UINT64 value=0 + name=org.illumos:edonr type=DATA_TYPE_UINT64 value=0 + name=com.delphix:spacemap_v2 type=DATA_TYPE_UINT64 value=1 + name=com.joyent:filesystem_limits type=DATA_TYPE_UINT64 value=0 + name=com.datto:resilver_defer type=DATA_TYPE_UINT64 value=0 + name=com.delphix:spacemap_histogram type=DATA_TYPE_UINT64 value=24 + name=com.delphix:async_destroy type=DATA_TYPE_UINT64 value=0 + name=org.zfsonlinux:userobj_accounting type=DATA_TYPE_UINT64 value=1 + name=com.delphix:zpool_checkpoint type=DATA_TYPE_UINT64 value=0 + name=com.delphix:obsolete_counts type=DATA_TYPE_UINT64 value=0 + name=com.delphix:empty_bpobj type=DATA_TYPE_UINT64 value=0 + name=com.delphix:bookmarks type=DATA_TYPE_UINT64 value=0 + name=com.delphix:enabled_txg type=DATA_TYPE_UINT64 value=26 + name=com.delphix:log_spacemap type=DATA_TYPE_UINT64 value=1 + name=org.zfsonlinux:allocation_classes type=DATA_TYPE_UINT64 value=0 + name=org.zfsonlinux:project_quota type=DATA_TYPE_UINT64 value=1 + name=com.delphix:livelist type=DATA_TYPE_UINT64 value=0 +name=can_rdonly type=DATA_TYPE_BOOLEAN value= +name=rewind_txg_ts type=DATA_TYPE_UINT64 value=1575588901 +name=seconds_of_rewind type=DATA_TYPE_INT64 value=-1575588901 +name=verify_data_errors type=DATA_TYPE_UINT64 value=0 +---- +---- +name=rewind_txg_ts type=DATA_TYPE_UINT64 value=1575589714 +name=seconds_of_rewind type=DATA_TYPE_INT64 value=-1575589714 +name=verify_data_errors type=DATA_TYPE_UINT64 value=3 +---- diff --git a/tests/integration/test_zfs_generic.py b/tests/integration/test_zfs_generic.py index 812c2ae..4ae4a51 100644 --- a/tests/integration/test_zfs_generic.py +++ b/tests/integration/test_zfs_generic.py @@ -34,7 +34,7 @@ "dbuf | dbuf -l 1", 'dbuf | dbuf -l 1 | head | dbuf', - # spa + vdev + metaslab + # spa + vdev + metaslab + nvlist "spa", "spa -H", "spa -v", @@ -51,6 +51,7 @@ "spa | vdev | metaslab -w", "spa | vdev | metaslab | member ms_allocatable | range_tree", "spa | vdev | metaslab | member ms_allocatable.rt_root | zfs_btree", + "spa | member spa_load_info | nvlist", # zfs_dbgmsg "zfs_dbgmsg",