Skip to content

Commit b44af37

Browse files
richl9biger410
authored andcommitted
targetcli: dump iscsi and vhost info as in targetcli
Orabug: 37301968 Signed-off-by: Richard Li <[email protected]>
1 parent feb76b5 commit b44af37

File tree

2 files changed

+299
-0
lines changed

2 files changed

+299
-0
lines changed

drgn_tools/targetcli.py

Lines changed: 292 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,292 @@
1+
# Copyright (c) 2025, Oracle and/or its affiliates.
2+
# Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/
3+
"""
4+
Helpers to retrieve iscsi target info and reconstruct targetcli structure on iscsi target server.
5+
6+
Configuration could be found under /sys/kernel/config/target/
7+
"""
8+
import argparse
9+
from typing import Iterable
10+
11+
from drgn import FaultError
12+
from drgn import Object
13+
from drgn import Program
14+
from drgn.helpers.linux.list import hlist_for_each_entry
15+
from drgn.helpers.linux.list import list_for_each_entry
16+
17+
from drgn_tools.corelens import CorelensModule
18+
from drgn_tools.module import ensure_debuginfo
19+
20+
######################################
21+
# iscsi
22+
######################################
23+
24+
25+
def for_each_iqn(prog: Program) -> Iterable[Object]:
26+
"""
27+
List tiqn from g_tiqn_list
28+
29+
:returns: Iterator of ``struct iscsi_tiqn *``
30+
"""
31+
tiqn_list = prog["g_tiqn_list"]
32+
33+
return list_for_each_entry(
34+
"struct iscsi_tiqn", tiqn_list.address_of_(), "tiqn_list"
35+
)
36+
37+
38+
def for_each_iscsi_tpg(tiqn: Object) -> Iterable[Object]:
39+
"""
40+
Get a list of tpg from tiqn
41+
42+
:param tiqn: ``struct iscsi_tiqn *``
43+
:returns: Iterator of ``struct iscsi_portal_group *``
44+
"""
45+
return list_for_each_entry(
46+
"struct iscsi_portal_group",
47+
tiqn.tiqn_tpg_list.address_of_(),
48+
"tpg_list",
49+
)
50+
51+
52+
def print_iscsi_info(prog) -> None:
53+
"""Dump iscsi section info"""
54+
55+
msg = ensure_debuginfo(prog, ["target_core_mod", "iscsi_target_mod"])
56+
if msg:
57+
print(msg)
58+
return
59+
60+
print("o- iscsi")
61+
indent = " "
62+
for tiqn in for_each_iqn(prog):
63+
print(
64+
"{}o- {} (struct iscsi_tiqn * {})".format(
65+
indent,
66+
get_tiqn_name(tiqn),
67+
hex(tiqn.value_()),
68+
)
69+
)
70+
71+
for tpg in for_each_iscsi_tpg(tiqn):
72+
se_tpg = tpg.tpg_se_tpg
73+
print(
74+
"{}o- {} (struct se_portal_group {})".format(
75+
indent * 2,
76+
get_tpg_name(se_tpg),
77+
hex(se_tpg.address_of_()),
78+
)
79+
)
80+
print(f"{indent * 3}o- acls")
81+
for acl in list_for_each_entry(
82+
"struct se_node_acl",
83+
se_tpg.acl_node_list.address_of_(),
84+
"acl_list",
85+
):
86+
print(
87+
"{}o- {} (struct se_node_acl * {})".format(
88+
indent * 4,
89+
get_acl_name(acl),
90+
hex(acl),
91+
)
92+
)
93+
print(f"{indent * 4}o- mapped_luns")
94+
for se_dev in hlist_for_each_entry(
95+
"struct se_dev_entry",
96+
acl.lun_entry_hlist.address_of_(),
97+
"link",
98+
):
99+
print_lun_info(se_dev.se_lun, nr_indent=5)
100+
101+
print(f"{indent * 3}o- luns")
102+
for lun in hlist_for_each_entry(
103+
"struct se_lun",
104+
se_tpg.tpg_lun_hlist.address_of_(),
105+
"link",
106+
):
107+
print_lun_info(lun, nr_indent=4)
108+
109+
110+
######################################
111+
# vhost
112+
######################################
113+
114+
115+
def for_each_vhost_tpg(prog) -> Iterable[Object]:
116+
"""
117+
List vhost_scsi_tpg from vhost_scsi_list
118+
119+
:returns: Iterator of ``struct vhost_scsi_tpg *``
120+
"""
121+
122+
vhost_scsi_list = prog["vhost_scsi_list"]
123+
124+
return list_for_each_entry(
125+
"struct vhost_scsi_tpg", vhost_scsi_list.address_of_(), "tv_tpg_list"
126+
)
127+
128+
129+
def print_vhost_info(prog) -> None:
130+
"""Dump vhost section info"""
131+
132+
msg = ensure_debuginfo(prog, ["vhost", "vhost_scsi", "target_core_mod"])
133+
if msg:
134+
print(msg)
135+
return
136+
137+
print("o- vhost")
138+
for tpg in for_each_vhost_tpg(prog):
139+
indent = " "
140+
se_tpg = tpg.se_tpg
141+
print(
142+
"{}o- {} (struct se_portal_group {}) ({})".format(
143+
indent,
144+
get_tpg_name(se_tpg),
145+
hex(se_tpg.address_of_()),
146+
get_ci_name_from_cg(tpg.tport.tport_wwn.wwn_group),
147+
)
148+
)
149+
150+
print(f"{indent * 2}o- acls")
151+
for acl in list_for_each_entry(
152+
"struct se_node_acl",
153+
se_tpg.acl_node_list.address_of_(),
154+
"acl_list",
155+
):
156+
acl_name = get_acl_name(acl)
157+
if not acl_name:
158+
continue
159+
160+
print(
161+
"{}o- {} (struct se_node_acl * {})".format(
162+
indent * 3,
163+
acl_name,
164+
hex(acl),
165+
)
166+
)
167+
168+
print(f"{indent * 3}o- mapped_luns")
169+
for se_dev in hlist_for_each_entry(
170+
"struct se_dev_entry",
171+
acl.lun_entry_hlist.address_of_(),
172+
"link",
173+
):
174+
print_lun_info(se_dev.se_lun, nr_indent=4)
175+
176+
print(f"{indent * 2}o- luns")
177+
for lun in hlist_for_each_entry(
178+
"struct se_lun", se_tpg.tpg_lun_hlist.address_of_(), "link"
179+
):
180+
print_lun_info(lun, nr_indent=3)
181+
182+
183+
def print_lun_info(lun: Object, nr_indent: int = 1) -> None:
184+
"""
185+
Dump lun info
186+
187+
:param lun: ``struct se_lun *``
188+
:param nr_indent: int indicating numbers of indentations
189+
"""
190+
print(
191+
"{}o- {} (struct se_lun * {}) \n{}o- BACKSTORE: {} \n{}o- DEVICE: {}".format(
192+
" " * nr_indent,
193+
get_lun_name(lun),
194+
hex(lun),
195+
" " * (nr_indent + 1),
196+
get_backstore_name_from_lun(lun),
197+
" " * (nr_indent + 1),
198+
get_device_path_from_lun(lun),
199+
)
200+
)
201+
202+
203+
def get_ci_name_from_cg(cg: Object) -> str:
204+
"""
205+
Get ci_name given a config group
206+
207+
:param cg: ``struct config_group *``
208+
:returns: str
209+
"""
210+
try:
211+
return cg.cg_item.ci_name.string_().decode()
212+
except FaultError:
213+
return ""
214+
215+
216+
def get_backstore_name_from_lun(lun: Object) -> str:
217+
"""
218+
Get backstore name given a lun
219+
220+
:param lun: ``struct se_lun *``
221+
:returns: str
222+
"""
223+
return get_ci_name_from_cg(lun.lun_se_dev.dev_group)
224+
225+
226+
def get_tpg_name(tpg: Object) -> str:
227+
"""
228+
Get tpg name
229+
230+
:param tpg: ``struct se_portal_group``
231+
:returns: str
232+
"""
233+
return get_ci_name_from_cg(tpg.tpg_group)
234+
235+
236+
def get_acl_name(acl: Object) -> str:
237+
"""
238+
Get acl name. If the name is empty, it could mean the acl is not configured or potentially corrupted.
239+
240+
:param acl: ``struct se_node_acl *``
241+
:returns: str
242+
"""
243+
return get_ci_name_from_cg(acl.acl_group)
244+
245+
246+
def get_tiqn_name(tiqn: Object) -> str:
247+
"""
248+
Get tiqn name
249+
250+
:param tiqn: ``struct iscsi_tiqn *``
251+
:returns: str
252+
"""
253+
return tiqn.tiqn.string_().decode()
254+
255+
256+
def get_lun_name(lun: Object) -> str:
257+
"""
258+
Get lun name
259+
260+
:param lun: ``struct se_lun *``
261+
:returns: str
262+
"""
263+
return get_ci_name_from_cg(lun.lun_group)
264+
265+
266+
def get_device_path_from_lun(lun: Object) -> str:
267+
"""
268+
Get block backend device path given lun
269+
270+
:param lun: ``struct se_lun *``
271+
:returns: str
272+
"""
273+
return lun.lun_se_dev.udev_path.string_().decode()
274+
275+
276+
def dump_targetcli(prog) -> None:
277+
"""Dump targetcli structure"""
278+
279+
print_iscsi_info(prog)
280+
print_vhost_info(prog)
281+
282+
283+
class TargetCli(CorelensModule):
284+
"""
285+
Dump targetcli structure on iscsi target
286+
"""
287+
288+
name = "targetcli"
289+
skip_unless_have_kmod = ["target_core_mod"]
290+
291+
def run(self, prog: Program, args: argparse.Namespace) -> None:
292+
dump_targetcli(prog)

tests/test_targetcli.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# Copyright (c) 2025, Oracle and/or its affiliates.
2+
# Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/
3+
from drgn_tools import targetcli
4+
5+
6+
def test_targetcli(prog):
7+
targetcli.dump_targetcli(prog)

0 commit comments

Comments
 (0)