]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - sys/contrib/openzfs/module/os/linux/spl/spl-procfs-list.c
OpenZFS: MFV 2.0-rc3-gfc5966
[FreeBSD/FreeBSD.git] / sys / contrib / openzfs / module / os / linux / spl / spl-procfs-list.c
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         } else {
93                 /*
94                  * seq_read() expects ->next() to update the position even
95                  * when there are no more entries. Advance the position to
96                  * prevent a warning from being logged.
97                  */
98                 cursor->cached_node = NULL;
99                 cursor->cached_pos++;
100                 *pos = cursor->cached_pos;
101         }
102
103         return (next_node);
104 }
105
106 static void *
107 procfs_list_seq_start(struct seq_file *f, loff_t *pos)
108 {
109         procfs_list_cursor_t *cursor = f->private;
110         procfs_list_t *procfs_list = cursor->procfs_list;
111
112         mutex_enter(&procfs_list->pl_lock);
113
114         if (*pos == 0) {
115                 cursor->cached_node = SEQ_START_TOKEN;
116                 cursor->cached_pos = 0;
117                 return (SEQ_START_TOKEN);
118         } else if (cursor->cached_node == NULL) {
119                 return (NULL);
120         }
121
122         /*
123          * Check if our cached pointer has become stale, which happens if the
124          * the message where we left off has been dropped from the list since
125          * the last read syscall completed.
126          */
127         void *oldest_node = list_head(&procfs_list->pl_list);
128         if (cursor->cached_node != SEQ_START_TOKEN && (oldest_node == NULL ||
129             NODE_ID(procfs_list, oldest_node) > cursor->cached_pos))
130                 return (ERR_PTR(-EIO));
131
132         /*
133          * If it isn't starting from the beginning of the file, the seq_file
134          * code will either pick up at the same position it visited last or the
135          * following one.
136          */
137         if (*pos == cursor->cached_pos) {
138                 return (cursor->cached_node);
139         } else {
140                 ASSERT3U(*pos, ==, cursor->cached_pos + 1);
141                 return (procfs_list_next_node(cursor, pos));
142         }
143 }
144
145 static void *
146 procfs_list_seq_next(struct seq_file *f, void *p, loff_t *pos)
147 {
148         procfs_list_cursor_t *cursor = f->private;
149         ASSERT(MUTEX_HELD(&cursor->procfs_list->pl_lock));
150         return (procfs_list_next_node(cursor, pos));
151 }
152
153 static void
154 procfs_list_seq_stop(struct seq_file *f, void *p)
155 {
156         procfs_list_cursor_t *cursor = f->private;
157         procfs_list_t *procfs_list = cursor->procfs_list;
158         mutex_exit(&procfs_list->pl_lock);
159 }
160
161 static struct seq_operations procfs_list_seq_ops = {
162         .show  = procfs_list_seq_show,
163         .start = procfs_list_seq_start,
164         .next  = procfs_list_seq_next,
165         .stop  = procfs_list_seq_stop,
166 };
167
168 static int
169 procfs_list_open(struct inode *inode, struct file *filp)
170 {
171         int rc = seq_open_private(filp, &procfs_list_seq_ops,
172             sizeof (procfs_list_cursor_t));
173         if (rc != 0)
174                 return (rc);
175
176         struct seq_file *f = filp->private_data;
177         procfs_list_cursor_t *cursor = f->private;
178         cursor->procfs_list = PDE_DATA(inode);
179         cursor->cached_node = NULL;
180         cursor->cached_pos = 0;
181
182         return (0);
183 }
184
185 static ssize_t
186 procfs_list_write(struct file *filp, const char __user *buf, size_t len,
187     loff_t *ppos)
188 {
189         struct seq_file *f = filp->private_data;
190         procfs_list_cursor_t *cursor = f->private;
191         procfs_list_t *procfs_list = cursor->procfs_list;
192         int rc;
193
194         if (procfs_list->pl_clear != NULL &&
195             (rc = procfs_list->pl_clear(procfs_list)) != 0)
196                 return (-rc);
197         return (len);
198 }
199
200 static const kstat_proc_op_t procfs_list_operations = {
201 #ifdef HAVE_PROC_OPS_STRUCT
202         .proc_open      = procfs_list_open,
203         .proc_write     = procfs_list_write,
204         .proc_read      = seq_read,
205         .proc_lseek     = seq_lseek,
206         .proc_release   = seq_release_private,
207 #else
208         .open           = procfs_list_open,
209         .write          = procfs_list_write,
210         .read           = seq_read,
211         .llseek         = seq_lseek,
212         .release        = seq_release_private,
213 #endif
214 };
215
216 /*
217  * Initialize a procfs_list and create a file for it in the proc filesystem
218  * under the kstat namespace.
219  */
220 void
221 procfs_list_install(const char *module,
222     const char *submodule,
223     const char *name,
224     mode_t mode,
225     procfs_list_t *procfs_list,
226     int (*show)(struct seq_file *f, void *p),
227     int (*show_header)(struct seq_file *f),
228     int (*clear)(procfs_list_t *procfs_list),
229     size_t procfs_list_node_off)
230 {
231         char *modulestr;
232
233         if (submodule != NULL)
234                 modulestr = kmem_asprintf("%s/%s", module, submodule);
235         else
236                 modulestr = kmem_asprintf("%s", module);
237         mutex_init(&procfs_list->pl_lock, NULL, MUTEX_DEFAULT, NULL);
238         list_create(&procfs_list->pl_list,
239             procfs_list_node_off + sizeof (procfs_list_node_t),
240             procfs_list_node_off + offsetof(procfs_list_node_t, pln_link));
241         procfs_list->pl_next_id = 1; /* Save id 0 for SEQ_START_TOKEN */
242         procfs_list->pl_show = show;
243         procfs_list->pl_show_header = show_header;
244         procfs_list->pl_clear = clear;
245         procfs_list->pl_node_offset = procfs_list_node_off;
246
247         kstat_proc_entry_init(&procfs_list->pl_kstat_entry, modulestr, name);
248         kstat_proc_entry_install(&procfs_list->pl_kstat_entry, mode,
249             &procfs_list_operations, procfs_list);
250         kmem_strfree(modulestr);
251 }
252 EXPORT_SYMBOL(procfs_list_install);
253
254 /* Remove the proc filesystem file corresponding to the given list */
255 void
256 procfs_list_uninstall(procfs_list_t *procfs_list)
257 {
258         kstat_proc_entry_delete(&procfs_list->pl_kstat_entry);
259 }
260 EXPORT_SYMBOL(procfs_list_uninstall);
261
262 void
263 procfs_list_destroy(procfs_list_t *procfs_list)
264 {
265         ASSERT(list_is_empty(&procfs_list->pl_list));
266         list_destroy(&procfs_list->pl_list);
267         mutex_destroy(&procfs_list->pl_lock);
268 }
269 EXPORT_SYMBOL(procfs_list_destroy);
270
271 /*
272  * Add a new node to the tail of the list. While the standard list manipulation
273  * functions can be use for all other operation, adding elements to the list
274  * should only be done using this helper so that the id of the new node is set
275  * correctly.
276  */
277 void
278 procfs_list_add(procfs_list_t *procfs_list, void *p)
279 {
280         ASSERT(MUTEX_HELD(&procfs_list->pl_lock));
281         NODE_ID(procfs_list, p) = procfs_list->pl_next_id++;
282         list_insert_tail(&procfs_list->pl_list, p);
283 }
284 EXPORT_SYMBOL(procfs_list_add);