Skip to content

Commit fddf16c

Browse files
committed
Add nvlist command to sdb
Print all the nvpair_t in the passed nvlist_t. Handle basic types, array types, and nvlist_t types. Signed-off-by: Paul Zuchowski <[email protected]> Fixes #26
1 parent 3e6c69a commit fddf16c

File tree

7 files changed

+310
-3
lines changed

7 files changed

+310
-3
lines changed

sdb/command.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -367,8 +367,8 @@ def _call(self, objs: Iterable[drgn.Object]) -> Iterable[drgn.Object]:
367367
#
368368
err_msg = f"invalid memory access: {str(err)}"
369369
else:
370-
err_msg = "invalid memory access while handling object "
371-
err_msg += "at address {hex(obj.address_of_().value_())}"
370+
err_msg = "invalid memory access while handling object at address %s" % (
371+
hex(obj.address_of_().value_()))
372372
cmd_err = CommandError(self.name, err_msg)
373373
print(cmd_err.text)
374374
if result is not None:

sdb/commands/nvpair/__init__.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
#
2+
# Copyright 2019 Delphix
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+
# pylint: disable=missing-docstring
18+
19+
import glob
20+
import importlib
21+
import os
22+
23+
for path in glob.glob("{}/*.py".format(os.path.dirname(__file__))):
24+
if path != __file__:
25+
module = os.path.splitext(os.path.basename(path))[0]
26+
importlib.import_module("sdb.commands.nvpair.{}".format(module))
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
#
2+
# Copyright 2019 Delphix
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+
# pylint: disable=missing-docstring
18+
19+
import glob
20+
import importlib
21+
import os
22+
23+
for path in glob.glob("{}/*.py".format(os.path.dirname(__file__))):
24+
if path != __file__:
25+
module = os.path.splitext(os.path.basename(path))[0]
26+
importlib.import_module(
27+
"sdb.commands.nvpair.internal.{}".format(module))

sdb/commands/nvpair/nvpair.py

Lines changed: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
1+
#
2+
# Copyright 2020 Datto, Inc.
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+
# pylint: disable=missing-docstring
18+
19+
import os
20+
import drgn
21+
import sdb
22+
23+
24+
class Nvpair:
25+
# Member data and methods to print the value of the
26+
# various nvpair_t types
27+
def __init__(self, nvp: drgn.Object, nvl: drgn.Object) -> None:
28+
self.addr = nvp.address_
29+
self.nvp = nvp
30+
self.nvl = nvl
31+
self.nvp_size_ = sdb.type_canonicalize_size("nvpair_t")
32+
self.nvl_size = sdb.type_canonicalize_size("nvlist_t")
33+
self.name_size = int(nvp.nvp_name_sz)
34+
self.data_ptr = self.addr + ((self.nvp_size_ + self.name_size + 7) & ~7)
35+
36+
def get_type(self, strip: int = 0) -> str:
37+
# N.B. data_type_t enum is not zero indexed it starts
38+
# with -1 so we add one to get the correct index
39+
fields = sdb.get_type("data_type_t").type.enumerators
40+
enum_string: str = fields[self.nvp.nvp_type + 1].name
41+
if strip:
42+
prefix = os.path.commonprefix([f[0] for f in fields])
43+
return enum_string[prefix.rfind("_") + 1:]
44+
return enum_string
45+
46+
def get_name(self) -> drgn.Object:
47+
# name is located after the nvpair_t struct
48+
ptr = self.addr + self.nvp_size_
49+
return sdb.create_object("char *", ptr).string_().decode("utf-8")
50+
51+
# works for all signed and unsigned int, long, long long
52+
@staticmethod
53+
def get_value_int(type_: str, loc: int) -> str:
54+
sz = sdb.create_object('size_t', sdb.type_canonicalize_size(type_))
55+
# value is located after the name
56+
contents = sdb.get_prog().read(loc, sz)
57+
value = drgn.Object.from_bytes_(sdb.get_prog(), type_, contents)
58+
return str(value.value_())
59+
60+
# iterate the array, obtain and print nvpair values
61+
def get_array_values(self, type_: str) -> None:
62+
indent = " "
63+
count = self.nvp.nvp_value_elem
64+
type_ = type_.replace("_array", "")
65+
if "boolean" in type_:
66+
type_ = "uint32_t"
67+
val = ""
68+
# skip n 64 bit pointers for string and nvlist array type
69+
offset = count * 8
70+
for x in range(count):
71+
if x == 0:
72+
print("values=")
73+
if "nvlist" in type_:
74+
self.nvl.set_indent(self.nvl.indent + 4)
75+
self.nvl.print_one(
76+
sdb.create_object("nvlist_t *", self.data_ptr + offset))
77+
self.nvl.set_indent(self.nvl.indent - 4)
78+
offset += self.nvl_size
79+
elif "int" in type_:
80+
sz = sdb.create_object('size_t',
81+
sdb.type_canonicalize_size(type_))
82+
val = self.get_value_int(type_, self.data_ptr + (sz * x))
83+
print(f"{indent}{val}")
84+
elif "byte" in type_:
85+
val = self.get_value_int("unsigned char", self.data_ptr + x)
86+
print(f"{indent}{hex(int(val))}")
87+
elif "string" in type_:
88+
offset += len(val)
89+
val = sdb.create_object("char *", self.data_ptr +
90+
offset).string_().decode("utf-8")
91+
print(f"{indent}{val}")
92+
offset += 1 # null terminator
93+
94+
# obtain and print the nvpair value
95+
def get_value(self) -> None:
96+
type_ = (self.get_type(1) + "_t").lower()
97+
if "array" in type_:
98+
self.get_array_values(type_)
99+
return
100+
if "nvlist" in type_:
101+
print("values=")
102+
self.nvl.set_indent(self.nvl.indent + 4)
103+
self.nvl.print_one(sdb.create_object("nvlist_t *", self.data_ptr))
104+
self.nvl.set_indent(self.nvl.indent - 4)
105+
return
106+
print("value=", end='')
107+
if "boolean_value" in type_:
108+
type_ = "uint32_t"
109+
elif "boolean" in type_: # DATA_TYPE_BOOLEAN has name and no value
110+
print("")
111+
elif "hrtime" in type_:
112+
type_ = "int64_t"
113+
if "int" in type_:
114+
print(self.get_value_int(type_, self.data_ptr))
115+
if "string" in type_:
116+
print(
117+
sdb.create_object("char *",
118+
self.data_ptr).string_().decode("utf-8"))
119+
if "byte" in type_:
120+
print(hex(int(self.get_value_int("unsigned char", self.data_ptr))))
121+
return
122+
123+
124+
class Nvlist(sdb.SingleInputCommand):
125+
"""
126+
Print nvlist_t
127+
128+
DESCRIPTION
129+
Print all the nvpair_t in the passed nvlist_t. Handle basic types,
130+
array types, and nvlist_t types. Type double is omitted as it is
131+
not used in zfs.
132+
133+
EXAMPLE
134+
Print nvlist_t of snapshot holds from the nvlist_t * pointer address:
135+
136+
sdb> echo 0xffff970d0ff681e0 | nvlist
137+
name=monday-1 type=DATA_TYPE_UINT64 value=1633989858
138+
name=monday-2 type=DATA_TYPE_UINT64 value=1633989863
139+
"""
140+
141+
names = ["nvlist"]
142+
input_type = "nvlist_t *"
143+
output_type = "nvlist_t *"
144+
indent = 0
145+
146+
def set_indent(self, indent: int) -> None:
147+
self.indent = indent
148+
149+
# nvlist iteration methods
150+
@staticmethod
151+
def nvlist_contains_nvp(nvl: drgn.Object, nvp: drgn.Object) -> int:
152+
priv = drgn.cast("nvpriv_t *", nvl.nvl_priv)
153+
154+
if nvp.address_ == 0:
155+
return 0
156+
157+
curr = priv.nvp_list
158+
while not sdb.is_null(curr):
159+
if curr.nvi_nvp.address_ == nvp.address_:
160+
return 1
161+
# pylint: disable=protected-access
162+
curr = curr._nvi_un._nvi._nvi_next
163+
164+
return 0
165+
166+
@staticmethod
167+
def nvlist_first_nvpair(nvl: drgn.Object) -> drgn.Object:
168+
if sdb.is_null(nvl) or sdb.is_null(nvl.nvl_priv):
169+
return None
170+
priv = drgn.cast("nvpriv_t *", nvl.nvl_priv)
171+
return priv.nvp_list.nvi_nvp
172+
173+
@staticmethod
174+
def nvlist_next_nvpair(nvl: drgn.Object, nvp: drgn.Object) -> drgn.Object:
175+
if sdb.is_null(nvl) or sdb.is_null(nvl.nvl_priv):
176+
return None
177+
178+
priv = drgn.cast("nvpriv_t *", nvl.nvl_priv)
179+
180+
curr_addr = nvp.address_ - drgn.offsetof(sdb.get_type("i_nvp_t"),
181+
"nvi_nvp")
182+
curr = sdb.create_object("i_nvp_t *", curr_addr)
183+
184+
if priv.nvp_curr == curr or Nvlist.nvlist_contains_nvp(nvl, nvp):
185+
# pylint: disable=protected-access
186+
curr = curr._nvi_un._nvi._nvi_next
187+
else:
188+
curr = drgn.NULL(sdb.get_prog(), "i_nvp_t *")
189+
190+
if not sdb.is_null(curr):
191+
return curr.nvi_nvp
192+
193+
return None
194+
195+
# print one nvlist_t
196+
def print_one(self, nvl: drgn.Object) -> None:
197+
pair = self.nvlist_first_nvpair(nvl)
198+
while pair is not None:
199+
nvobj = Nvpair(pair, self)
200+
print(f"{' '*self.indent}", end='')
201+
print(f"name={nvobj.get_name()} ", end='')
202+
print(f"type={nvobj.get_type()} ", end='')
203+
# value will be printed in get_value function
204+
nvobj.get_value()
205+
pair = self.nvlist_next_nvpair(nvl, pair)
206+
207+
def _call_one(self, obj: drgn.Object) -> None:
208+
self.print_one(obj)
209+
print("----")

setup.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
"sdb.commands.internal",
1313
"sdb.commands.linux",
1414
"sdb.commands.linux.internal",
15+
"sdb.commands.nvpair",
16+
"sdb.commands.nvpair.internal",
1517
"sdb.commands.spl",
1618
"sdb.commands.spl.internal",
1719
"sdb.commands.zfs",
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
name=enabled_feat type=DATA_TYPE_NVLIST values=
2+
name=org.zfsonlinux:large_dnode type=DATA_TYPE_UINT64 value=0
3+
name=com.delphix:redaction_bookmarks type=DATA_TYPE_UINT64 value=0
4+
name=com.delphix:extensible_dataset type=DATA_TYPE_UINT64 value=1
5+
name=com.delphix:bookmark_written type=DATA_TYPE_UINT64 value=0
6+
name=com.delphix:hole_birth type=DATA_TYPE_UINT64 value=1
7+
name=org.illumos:skein type=DATA_TYPE_UINT64 value=0
8+
name=org.illumos:sha512 type=DATA_TYPE_UINT64 value=0
9+
name=org.illumos:lz4_compress type=DATA_TYPE_UINT64 value=1
10+
name=com.delphix:redacted_datasets type=DATA_TYPE_UINT64 value=0
11+
name=com.datto:bookmark_v2 type=DATA_TYPE_UINT64 value=0
12+
name=com.delphix:embedded_data type=DATA_TYPE_UINT64 value=1
13+
name=com.delphix:device_removal type=DATA_TYPE_UINT64 value=0
14+
name=org.open-zfs:large_blocks type=DATA_TYPE_UINT64 value=0
15+
name=com.joyent:multi_vdev_crash_dump type=DATA_TYPE_UINT64 value=0
16+
name=com.datto:encryption type=DATA_TYPE_UINT64 value=0
17+
name=org.illumos:edonr type=DATA_TYPE_UINT64 value=0
18+
name=com.delphix:spacemap_v2 type=DATA_TYPE_UINT64 value=1
19+
name=com.joyent:filesystem_limits type=DATA_TYPE_UINT64 value=0
20+
name=com.datto:resilver_defer type=DATA_TYPE_UINT64 value=0
21+
name=com.delphix:spacemap_histogram type=DATA_TYPE_UINT64 value=24
22+
name=com.delphix:async_destroy type=DATA_TYPE_UINT64 value=0
23+
name=org.zfsonlinux:userobj_accounting type=DATA_TYPE_UINT64 value=1
24+
name=com.delphix:zpool_checkpoint type=DATA_TYPE_UINT64 value=0
25+
name=com.delphix:obsolete_counts type=DATA_TYPE_UINT64 value=0
26+
name=com.delphix:empty_bpobj type=DATA_TYPE_UINT64 value=0
27+
name=com.delphix:bookmarks type=DATA_TYPE_UINT64 value=0
28+
name=com.delphix:enabled_txg type=DATA_TYPE_UINT64 value=26
29+
name=com.delphix:log_spacemap type=DATA_TYPE_UINT64 value=1
30+
name=org.zfsonlinux:allocation_classes type=DATA_TYPE_UINT64 value=0
31+
name=org.zfsonlinux:project_quota type=DATA_TYPE_UINT64 value=1
32+
name=com.delphix:livelist type=DATA_TYPE_UINT64 value=0
33+
name=can_rdonly type=DATA_TYPE_BOOLEAN value=
34+
name=rewind_txg_ts type=DATA_TYPE_UINT64 value=1575588901
35+
name=seconds_of_rewind type=DATA_TYPE_INT64 value=-1575588901
36+
name=verify_data_errors type=DATA_TYPE_UINT64 value=0
37+
----
38+
sdb: nvlist: invalid memory access while handling object at address 0xffffa089413b8138
39+
name=rewind_txg_ts type=DATA_TYPE_UINT64 value=1575589714
40+
name=seconds_of_rewind type=DATA_TYPE_INT64 value=-1575589714
41+
name=verify_data_errors type=DATA_TYPE_UINT64 value=3
42+
----

tests/integration/test_zfs_generic.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@
3434
"dbuf | dbuf -l 1",
3535
'dbuf | dbuf -l 1 | head | dbuf',
3636

37-
# spa + vdev + metaslab
37+
# spa + vdev + metaslab + nvlist
3838
"spa",
3939
"spa -H",
4040
"spa -v",
@@ -51,6 +51,7 @@
5151
"spa | vdev | metaslab -w",
5252
"spa | vdev | metaslab | member ms_allocatable | range_tree",
5353
"spa | vdev | metaslab | member ms_allocatable.rt_root | zfs_btree",
54+
"spa | member spa_load_info | nvlist",
5455

5556
# zfs_dbgmsg
5657
"zfs_dbgmsg",

0 commit comments

Comments
 (0)