xref: /freebsd/sys/contrib/openzfs/module/os/linux/spl/spl-procfs-list.c (revision 61145dc2b94f12f6a47344fb9aac702321880e43)
1 // SPDX-License-Identifier: CDDL-1.0
2 /*
3  * CDDL HEADER START
4  *
5  * The contents of this file are subject to the terms of the
6  * Common Development and Distribution License (the "License").
7  * You may not use this file except in compliance with the License.
8  *
9  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10  * or https://opensource.org/licenses/CDDL-1.0.
11  * See the License for the specific language governing permissions
12  * and limitations under the License.
13  *
14  * When distributing Covered Code, include this CDDL HEADER in each
15  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16  * If applicable, add the following below this CDDL HEADER, with the
17  * fields enclosed by brackets "[]" replaced with your own identifying
18  * information: Portions Copyright [yyyy] [name of copyright owner]
19  *
20  * CDDL HEADER END
21  */
22 /*
23  * Copyright (c) 2018 by Delphix. All rights reserved.
24  */
25 
26 #include <sys/list.h>
27 #include <sys/procfs_list.h>
28 #include <linux/proc_fs.h>
29 #include <sys/mutex.h>
30 
31 /*
32  * A procfs_list is a wrapper around a linked list which implements the seq_file
33  * interface, allowing the contents of the list to be exposed through procfs.
34  * The kernel already has some utilities to help implement the seq_file
35  * interface for linked lists (seq_list_*), but they aren't appropriate for use
36  * with lists that have many entries, because seq_list_start walks the list at
37  * the start of each read syscall to find where it left off, so reading a file
38  * ends up being quadratic in the number of entries in the list.
39  *
40  * This implementation avoids this penalty by maintaining a separate cursor into
41  * the list per instance of the file that is open. It also maintains some extra
42  * information in each node of the list to prevent reads of entries that have
43  * been dropped from the list.
44  *
45  * Callers should only add elements to the list using procfs_list_add, which
46  * adds an element to the tail of the list. Other operations can be performed
47  * directly on the wrapped list using the normal list manipulation functions,
48  * but elements should only be removed from the head of the list.
49  */
50 
51 #define	NODE_ID(procfs_list, obj) \
52 		(((procfs_list_node_t *)(((char *)obj) + \
53 		(procfs_list)->pl_node_offset))->pln_id)
54 
55 typedef struct procfs_list_cursor {
56 	procfs_list_t	*procfs_list;	/* List into which this cursor points */
57 	void		*cached_node;	/* Most recently accessed node */
58 	loff_t		cached_pos;	/* Position of cached_node */
59 } procfs_list_cursor_t;
60 
61 static int
procfs_list_seq_show(struct seq_file * f,void * p)62 procfs_list_seq_show(struct seq_file *f, void *p)
63 {
64 	procfs_list_cursor_t *cursor = f->private;
65 	procfs_list_t *procfs_list = cursor->procfs_list;
66 
67 	ASSERT(MUTEX_HELD(&procfs_list->pl_lock));
68 	if (p == SEQ_START_TOKEN) {
69 		if (procfs_list->pl_show_header != NULL)
70 			return (procfs_list->pl_show_header(f));
71 		else
72 			return (0);
73 	}
74 	return (procfs_list->pl_show(f, p));
75 }
76 
77 static void *
procfs_list_next_node(procfs_list_cursor_t * cursor,loff_t * pos)78 procfs_list_next_node(procfs_list_cursor_t *cursor, loff_t *pos)
79 {
80 	void *next_node;
81 	procfs_list_t *procfs_list = cursor->procfs_list;
82 
83 	if (cursor->cached_node == SEQ_START_TOKEN)
84 		next_node = list_head(&procfs_list->pl_list);
85 	else
86 		next_node = list_next(&procfs_list->pl_list,
87 		    cursor->cached_node);
88 
89 	if (next_node != NULL) {
90 		cursor->cached_node = next_node;
91 		cursor->cached_pos = NODE_ID(procfs_list, cursor->cached_node);
92 		*pos = cursor->cached_pos;
93 	} else {
94 		/*
95 		 * seq_read() expects ->next() to update the position even
96 		 * when there are no more entries. Advance the position to
97 		 * prevent a warning from being logged.
98 		 */
99 		cursor->cached_node = NULL;
100 		cursor->cached_pos++;
101 		*pos = cursor->cached_pos;
102 	}
103 
104 	return (next_node);
105 }
106 
107 static void *
procfs_list_seq_start(struct seq_file * f,loff_t * pos)108 procfs_list_seq_start(struct seq_file *f, loff_t *pos)
109 {
110 	procfs_list_cursor_t *cursor = f->private;
111 	procfs_list_t *procfs_list = cursor->procfs_list;
112 
113 	mutex_enter(&procfs_list->pl_lock);
114 
115 	if (*pos == 0) {
116 		cursor->cached_node = SEQ_START_TOKEN;
117 		cursor->cached_pos = 0;
118 		return (SEQ_START_TOKEN);
119 	} else if (cursor->cached_node == NULL) {
120 		return (NULL);
121 	}
122 
123 	/*
124 	 * Check if our cached pointer has become stale, which happens if the
125 	 * the message where we left off has been dropped from the list since
126 	 * the last read syscall completed.
127 	 */
128 	void *oldest_node = list_head(&procfs_list->pl_list);
129 	if (cursor->cached_node != SEQ_START_TOKEN && (oldest_node == NULL ||
130 	    NODE_ID(procfs_list, oldest_node) > cursor->cached_pos))
131 		return (ERR_PTR(-EIO));
132 
133 	/*
134 	 * If it isn't starting from the beginning of the file, the seq_file
135 	 * code will either pick up at the same position it visited last or the
136 	 * following one.
137 	 */
138 	if (*pos == cursor->cached_pos) {
139 		return (cursor->cached_node);
140 	} else {
141 		ASSERT3U(*pos, ==, cursor->cached_pos + 1);
142 		return (procfs_list_next_node(cursor, pos));
143 	}
144 }
145 
146 static void *
procfs_list_seq_next(struct seq_file * f,void * p,loff_t * pos)147 procfs_list_seq_next(struct seq_file *f, void *p, loff_t *pos)
148 {
149 	procfs_list_cursor_t *cursor = f->private;
150 	ASSERT(MUTEX_HELD(&cursor->procfs_list->pl_lock));
151 	return (procfs_list_next_node(cursor, pos));
152 }
153 
154 static void
procfs_list_seq_stop(struct seq_file * f,void * p)155 procfs_list_seq_stop(struct seq_file *f, void *p)
156 {
157 	procfs_list_cursor_t *cursor = f->private;
158 	procfs_list_t *procfs_list = cursor->procfs_list;
159 	mutex_exit(&procfs_list->pl_lock);
160 }
161 
162 static const struct seq_operations procfs_list_seq_ops = {
163 	.show  = procfs_list_seq_show,
164 	.start = procfs_list_seq_start,
165 	.next  = procfs_list_seq_next,
166 	.stop  = procfs_list_seq_stop,
167 };
168 
169 static int
procfs_list_open(struct inode * inode,struct file * filp)170 procfs_list_open(struct inode *inode, struct file *filp)
171 {
172 	int rc = seq_open_private(filp, &procfs_list_seq_ops,
173 	    sizeof (procfs_list_cursor_t));
174 	if (rc != 0)
175 		return (rc);
176 
177 	struct seq_file *f = filp->private_data;
178 	procfs_list_cursor_t *cursor = f->private;
179 	cursor->procfs_list = SPL_PDE_DATA(inode);
180 	cursor->cached_node = NULL;
181 	cursor->cached_pos = 0;
182 
183 	return (0);
184 }
185 
186 static ssize_t
procfs_list_write(struct file * filp,const char __user * buf,size_t len,loff_t * ppos)187 procfs_list_write(struct file *filp, const char __user *buf, size_t len,
188     loff_t *ppos)
189 {
190 	struct seq_file *f = filp->private_data;
191 	procfs_list_cursor_t *cursor = f->private;
192 	procfs_list_t *procfs_list = cursor->procfs_list;
193 	int rc;
194 
195 	if (procfs_list->pl_clear != NULL &&
196 	    (rc = procfs_list->pl_clear(procfs_list)) != 0)
197 		return (-rc);
198 	return (len);
199 }
200 
201 static const kstat_proc_op_t procfs_list_operations = {
202 #ifdef HAVE_PROC_OPS_STRUCT
203 	.proc_open	= procfs_list_open,
204 	.proc_write	= procfs_list_write,
205 	.proc_read	= seq_read,
206 	.proc_lseek	= seq_lseek,
207 	.proc_release	= seq_release_private,
208 #else
209 	.open		= procfs_list_open,
210 	.write		= procfs_list_write,
211 	.read		= seq_read,
212 	.llseek		= seq_lseek,
213 	.release	= seq_release_private,
214 #endif
215 };
216 
217 /*
218  * Initialize a procfs_list and create a file for it in the proc filesystem
219  * under the kstat namespace.
220  */
221 void
procfs_list_install(const char * module,const char * submodule,const char * name,mode_t mode,procfs_list_t * procfs_list,int (* show)(struct seq_file * f,void * p),int (* show_header)(struct seq_file * f),int (* clear)(procfs_list_t * procfs_list),size_t procfs_list_node_off)222 procfs_list_install(const char *module,
223     const char *submodule,
224     const char *name,
225     mode_t mode,
226     procfs_list_t *procfs_list,
227     int (*show)(struct seq_file *f, void *p),
228     int (*show_header)(struct seq_file *f),
229     int (*clear)(procfs_list_t *procfs_list),
230     size_t procfs_list_node_off)
231 {
232 	char *modulestr;
233 
234 	if (submodule != NULL)
235 		modulestr = kmem_asprintf("%s/%s", module, submodule);
236 	else
237 		modulestr = kmem_asprintf("%s", module);
238 	mutex_init(&procfs_list->pl_lock, NULL, MUTEX_NOLOCKDEP, NULL);
239 	list_create(&procfs_list->pl_list,
240 	    procfs_list_node_off + sizeof (procfs_list_node_t),
241 	    procfs_list_node_off + offsetof(procfs_list_node_t, pln_link));
242 	procfs_list->pl_next_id = 1; /* Save id 0 for SEQ_START_TOKEN */
243 	procfs_list->pl_show = show;
244 	procfs_list->pl_show_header = show_header;
245 	procfs_list->pl_clear = clear;
246 	procfs_list->pl_node_offset = procfs_list_node_off;
247 
248 	kstat_proc_entry_init(&procfs_list->pl_kstat_entry, modulestr, name);
249 	kstat_proc_entry_install(&procfs_list->pl_kstat_entry, mode,
250 	    &procfs_list_operations, procfs_list);
251 	kmem_strfree(modulestr);
252 }
253 EXPORT_SYMBOL(procfs_list_install);
254 
255 /* Remove the proc filesystem file corresponding to the given list */
256 void
procfs_list_uninstall(procfs_list_t * procfs_list)257 procfs_list_uninstall(procfs_list_t *procfs_list)
258 {
259 	kstat_proc_entry_delete(&procfs_list->pl_kstat_entry);
260 }
261 EXPORT_SYMBOL(procfs_list_uninstall);
262 
263 void
procfs_list_destroy(procfs_list_t * procfs_list)264 procfs_list_destroy(procfs_list_t *procfs_list)
265 {
266 	ASSERT(list_is_empty(&procfs_list->pl_list));
267 	list_destroy(&procfs_list->pl_list);
268 	mutex_destroy(&procfs_list->pl_lock);
269 }
270 EXPORT_SYMBOL(procfs_list_destroy);
271 
272 /*
273  * Add a new node to the tail of the list. While the standard list manipulation
274  * functions can be use for all other operation, adding elements to the list
275  * should only be done using this helper so that the id of the new node is set
276  * correctly.
277  */
278 void
procfs_list_add(procfs_list_t * procfs_list,void * p)279 procfs_list_add(procfs_list_t *procfs_list, void *p)
280 {
281 	ASSERT(MUTEX_HELD(&procfs_list->pl_lock));
282 	NODE_ID(procfs_list, p) = procfs_list->pl_next_id++;
283 	list_insert_tail(&procfs_list->pl_list, p);
284 }
285 EXPORT_SYMBOL(procfs_list_add);
286