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