]> git.proxmox.com Git - mirror_zfs.git/blob - module/spl/spl-procfs-list.c
Fixes for procfs files backed by linked lists
[mirror_zfs.git] / module / 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 }
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 struct file_operations procfs_list_operations = {
189 .owner = THIS_MODULE,
190 .open = procfs_list_open,
191 .write = procfs_list_write,
192 .read = seq_read,
193 .llseek = seq_lseek,
194 .release = seq_release_private,
195 };
196
197 /*
198 * Initialize a procfs_list and create a file for it in the proc filesystem
199 * under the kstat namespace.
200 */
201 void
202 procfs_list_install(const char *module,
203 const char *name,
204 procfs_list_t *procfs_list,
205 int (*show)(struct seq_file *f, void *p),
206 int (*show_header)(struct seq_file *f),
207 int (*clear)(procfs_list_t *procfs_list),
208 size_t procfs_list_node_off)
209 {
210 mutex_init(&procfs_list->pl_lock, NULL, MUTEX_DEFAULT, NULL);
211 list_create(&procfs_list->pl_list,
212 procfs_list_node_off + sizeof (procfs_list_node_t),
213 procfs_list_node_off + offsetof(procfs_list_node_t, pln_link));
214 procfs_list->pl_next_id = 1; /* Save id 0 for SEQ_START_TOKEN */
215 procfs_list->pl_show = show;
216 procfs_list->pl_show_header = show_header;
217 procfs_list->pl_clear = clear;
218 procfs_list->pl_node_offset = procfs_list_node_off;
219
220 kstat_proc_entry_init(&procfs_list->pl_kstat_entry, module, name);
221 kstat_proc_entry_install(&procfs_list->pl_kstat_entry,
222 &procfs_list_operations, procfs_list);
223 }
224 EXPORT_SYMBOL(procfs_list_install);
225
226 /* Remove the proc filesystem file corresponding to the given list */
227 void
228 procfs_list_uninstall(procfs_list_t *procfs_list)
229 {
230 kstat_proc_entry_delete(&procfs_list->pl_kstat_entry);
231 }
232 EXPORT_SYMBOL(procfs_list_uninstall);
233
234 void
235 procfs_list_destroy(procfs_list_t *procfs_list)
236 {
237 ASSERT(list_is_empty(&procfs_list->pl_list));
238 list_destroy(&procfs_list->pl_list);
239 mutex_destroy(&procfs_list->pl_lock);
240 }
241 EXPORT_SYMBOL(procfs_list_destroy);
242
243 /*
244 * Add a new node to the tail of the list. While the standard list manipulation
245 * functions can be use for all other operation, adding elements to the list
246 * should only be done using this helper so that the id of the new node is set
247 * correctly.
248 */
249 void
250 procfs_list_add(procfs_list_t *procfs_list, void *p)
251 {
252 ASSERT(MUTEX_HELD(&procfs_list->pl_lock));
253 NODE_ID(procfs_list, p) = procfs_list->pl_next_id++;
254 list_insert_tail(&procfs_list->pl_list, p);
255 }
256 EXPORT_SYMBOL(procfs_list_add);