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