1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * debugfs interface for sunrpc |
4 | * |
5 | * (c) 2014 Jeff Layton <jlayton@primarydata.com> |
6 | */ |
7 | |
8 | #include <linux/debugfs.h> |
9 | #include <linux/sunrpc/sched.h> |
10 | #include <linux/sunrpc/clnt.h> |
11 | |
12 | #include "netns.h" |
13 | #include "fail.h" |
14 | |
15 | static struct dentry *topdir; |
16 | static struct dentry *rpc_clnt_dir; |
17 | static struct dentry *rpc_xprt_dir; |
18 | |
19 | static int |
20 | tasks_show(struct seq_file *f, void *v) |
21 | { |
22 | u32 xid = 0; |
23 | struct rpc_task *task = v; |
24 | struct rpc_clnt *clnt = task->tk_client; |
25 | const char *rpc_waitq = "none" ; |
26 | |
27 | if (RPC_IS_QUEUED(task)) |
28 | rpc_waitq = rpc_qname(q: task->tk_waitqueue); |
29 | |
30 | if (task->tk_rqstp) |
31 | xid = be32_to_cpu(task->tk_rqstp->rq_xid); |
32 | |
33 | seq_printf(m: f, fmt: "%5u %04x %6d 0x%x 0x%x %8ld %ps %sv%u %s a:%ps q:%s\n" , |
34 | task->tk_pid, task->tk_flags, task->tk_status, |
35 | clnt->cl_clid, xid, rpc_task_timeout(task), task->tk_ops, |
36 | clnt->cl_program->name, clnt->cl_vers, rpc_proc_name(task), |
37 | task->tk_action, rpc_waitq); |
38 | return 0; |
39 | } |
40 | |
41 | static void * |
42 | tasks_start(struct seq_file *f, loff_t *ppos) |
43 | __acquires(&clnt->cl_lock) |
44 | { |
45 | struct rpc_clnt *clnt = f->private; |
46 | loff_t pos = *ppos; |
47 | struct rpc_task *task; |
48 | |
49 | spin_lock(lock: &clnt->cl_lock); |
50 | list_for_each_entry(task, &clnt->cl_tasks, tk_task) |
51 | if (pos-- == 0) |
52 | return task; |
53 | return NULL; |
54 | } |
55 | |
56 | static void * |
57 | tasks_next(struct seq_file *f, void *v, loff_t *pos) |
58 | { |
59 | struct rpc_clnt *clnt = f->private; |
60 | struct rpc_task *task = v; |
61 | struct list_head *next = task->tk_task.next; |
62 | |
63 | ++*pos; |
64 | |
65 | /* If there's another task on list, return it */ |
66 | if (next == &clnt->cl_tasks) |
67 | return NULL; |
68 | return list_entry(next, struct rpc_task, tk_task); |
69 | } |
70 | |
71 | static void |
72 | tasks_stop(struct seq_file *f, void *v) |
73 | __releases(&clnt->cl_lock) |
74 | { |
75 | struct rpc_clnt *clnt = f->private; |
76 | spin_unlock(lock: &clnt->cl_lock); |
77 | } |
78 | |
79 | static const struct seq_operations tasks_seq_operations = { |
80 | .start = tasks_start, |
81 | .next = tasks_next, |
82 | .stop = tasks_stop, |
83 | .show = tasks_show, |
84 | }; |
85 | |
86 | static int tasks_open(struct inode *inode, struct file *filp) |
87 | { |
88 | int ret = seq_open(filp, &tasks_seq_operations); |
89 | if (!ret) { |
90 | struct seq_file *seq = filp->private_data; |
91 | struct rpc_clnt *clnt = seq->private = inode->i_private; |
92 | |
93 | if (!refcount_inc_not_zero(r: &clnt->cl_count)) { |
94 | seq_release(inode, filp); |
95 | ret = -EINVAL; |
96 | } |
97 | } |
98 | |
99 | return ret; |
100 | } |
101 | |
102 | static int |
103 | tasks_release(struct inode *inode, struct file *filp) |
104 | { |
105 | struct seq_file *seq = filp->private_data; |
106 | struct rpc_clnt *clnt = seq->private; |
107 | |
108 | rpc_release_client(clnt); |
109 | return seq_release(inode, filp); |
110 | } |
111 | |
112 | static const struct file_operations tasks_fops = { |
113 | .owner = THIS_MODULE, |
114 | .open = tasks_open, |
115 | .read = seq_read, |
116 | .llseek = seq_lseek, |
117 | .release = tasks_release, |
118 | }; |
119 | |
120 | static int do_xprt_debugfs(struct rpc_clnt *clnt, struct rpc_xprt *xprt, void *numv) |
121 | { |
122 | int len; |
123 | char name[24]; /* enough for "../../rpc_xprt/ + 8 hex digits + NULL */ |
124 | char link[9]; /* enough for 8 hex digits + NULL */ |
125 | int *nump = numv; |
126 | |
127 | if (IS_ERR_OR_NULL(ptr: xprt->debugfs)) |
128 | return 0; |
129 | len = snprintf(buf: name, size: sizeof(name), fmt: "../../rpc_xprt/%s" , |
130 | xprt->debugfs->d_name.name); |
131 | if (len >= sizeof(name)) |
132 | return -1; |
133 | if (*nump == 0) |
134 | strcpy(p: link, q: "xprt" ); |
135 | else { |
136 | len = snprintf(buf: link, size: sizeof(link), fmt: "xprt%d" , *nump); |
137 | if (len >= sizeof(link)) |
138 | return -1; |
139 | } |
140 | debugfs_create_symlink(name: link, parent: clnt->cl_debugfs, dest: name); |
141 | (*nump)++; |
142 | return 0; |
143 | } |
144 | |
145 | void |
146 | rpc_clnt_debugfs_register(struct rpc_clnt *clnt) |
147 | { |
148 | int len; |
149 | char name[9]; /* enough for 8 hex digits + NULL */ |
150 | int xprtnum = 0; |
151 | |
152 | len = snprintf(buf: name, size: sizeof(name), fmt: "%x" , clnt->cl_clid); |
153 | if (len >= sizeof(name)) |
154 | return; |
155 | |
156 | /* make the per-client dir */ |
157 | clnt->cl_debugfs = debugfs_create_dir(name, parent: rpc_clnt_dir); |
158 | |
159 | /* make tasks file */ |
160 | debugfs_create_file(name: "tasks" , S_IFREG | 0400, parent: clnt->cl_debugfs, data: clnt, |
161 | fops: &tasks_fops); |
162 | |
163 | rpc_clnt_iterate_for_each_xprt(clnt, fn: do_xprt_debugfs, data: &xprtnum); |
164 | } |
165 | |
166 | void |
167 | rpc_clnt_debugfs_unregister(struct rpc_clnt *clnt) |
168 | { |
169 | debugfs_remove_recursive(dentry: clnt->cl_debugfs); |
170 | clnt->cl_debugfs = NULL; |
171 | } |
172 | |
173 | static int |
174 | xprt_info_show(struct seq_file *f, void *v) |
175 | { |
176 | struct rpc_xprt *xprt = f->private; |
177 | |
178 | seq_printf(m: f, fmt: "netid: %s\n" , xprt->address_strings[RPC_DISPLAY_NETID]); |
179 | seq_printf(m: f, fmt: "addr: %s\n" , xprt->address_strings[RPC_DISPLAY_ADDR]); |
180 | seq_printf(m: f, fmt: "port: %s\n" , xprt->address_strings[RPC_DISPLAY_PORT]); |
181 | seq_printf(m: f, fmt: "state: 0x%lx\n" , xprt->state); |
182 | return 0; |
183 | } |
184 | |
185 | static int |
186 | xprt_info_open(struct inode *inode, struct file *filp) |
187 | { |
188 | int ret; |
189 | struct rpc_xprt *xprt = inode->i_private; |
190 | |
191 | ret = single_open(filp, xprt_info_show, xprt); |
192 | |
193 | if (!ret) { |
194 | if (!xprt_get(xprt)) { |
195 | single_release(inode, filp); |
196 | ret = -EINVAL; |
197 | } |
198 | } |
199 | return ret; |
200 | } |
201 | |
202 | static int |
203 | xprt_info_release(struct inode *inode, struct file *filp) |
204 | { |
205 | struct rpc_xprt *xprt = inode->i_private; |
206 | |
207 | xprt_put(xprt); |
208 | return single_release(inode, filp); |
209 | } |
210 | |
211 | static const struct file_operations xprt_info_fops = { |
212 | .owner = THIS_MODULE, |
213 | .open = xprt_info_open, |
214 | .read = seq_read, |
215 | .llseek = seq_lseek, |
216 | .release = xprt_info_release, |
217 | }; |
218 | |
219 | void |
220 | rpc_xprt_debugfs_register(struct rpc_xprt *xprt) |
221 | { |
222 | int len, id; |
223 | static atomic_t cur_id; |
224 | char name[9]; /* 8 hex digits + NULL term */ |
225 | |
226 | id = (unsigned int)atomic_inc_return(v: &cur_id); |
227 | |
228 | len = snprintf(buf: name, size: sizeof(name), fmt: "%x" , id); |
229 | if (len >= sizeof(name)) |
230 | return; |
231 | |
232 | /* make the per-client dir */ |
233 | xprt->debugfs = debugfs_create_dir(name, parent: rpc_xprt_dir); |
234 | |
235 | /* make tasks file */ |
236 | debugfs_create_file(name: "info" , S_IFREG | 0400, parent: xprt->debugfs, data: xprt, |
237 | fops: &xprt_info_fops); |
238 | } |
239 | |
240 | void |
241 | rpc_xprt_debugfs_unregister(struct rpc_xprt *xprt) |
242 | { |
243 | debugfs_remove_recursive(dentry: xprt->debugfs); |
244 | xprt->debugfs = NULL; |
245 | } |
246 | |
247 | #if IS_ENABLED(CONFIG_FAIL_SUNRPC) |
248 | struct fail_sunrpc_attr fail_sunrpc = { |
249 | .attr = FAULT_ATTR_INITIALIZER, |
250 | }; |
251 | EXPORT_SYMBOL_GPL(fail_sunrpc); |
252 | |
253 | static void fail_sunrpc_init(void) |
254 | { |
255 | struct dentry *dir; |
256 | |
257 | dir = fault_create_debugfs_attr(name: "fail_sunrpc" , NULL, |
258 | attr: &fail_sunrpc.attr); |
259 | |
260 | debugfs_create_bool(name: "ignore-client-disconnect" , S_IFREG | 0600, parent: dir, |
261 | value: &fail_sunrpc.ignore_client_disconnect); |
262 | |
263 | debugfs_create_bool(name: "ignore-server-disconnect" , S_IFREG | 0600, parent: dir, |
264 | value: &fail_sunrpc.ignore_server_disconnect); |
265 | |
266 | debugfs_create_bool(name: "ignore-cache-wait" , S_IFREG | 0600, parent: dir, |
267 | value: &fail_sunrpc.ignore_cache_wait); |
268 | } |
269 | #else |
270 | static void fail_sunrpc_init(void) |
271 | { |
272 | } |
273 | #endif |
274 | |
275 | void __exit |
276 | sunrpc_debugfs_exit(void) |
277 | { |
278 | debugfs_remove_recursive(dentry: topdir); |
279 | topdir = NULL; |
280 | rpc_clnt_dir = NULL; |
281 | rpc_xprt_dir = NULL; |
282 | } |
283 | |
284 | void __init |
285 | sunrpc_debugfs_init(void) |
286 | { |
287 | topdir = debugfs_create_dir(name: "sunrpc" , NULL); |
288 | |
289 | rpc_clnt_dir = debugfs_create_dir(name: "rpc_clnt" , parent: topdir); |
290 | |
291 | rpc_xprt_dir = debugfs_create_dir(name: "rpc_xprt" , parent: topdir); |
292 | |
293 | fail_sunrpc_init(); |
294 | } |
295 | |