]>
Commit | Line | Data |
---|---|---|
d1261452 JG |
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; | |
266d1121 BB |
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; | |
d1261452 | 101 | } |
266d1121 | 102 | |
d1261452 JG |
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); | |
266d1121 BB |
118 | } else if (cursor->cached_node == NULL) { |
119 | return (NULL); | |
d1261452 JG |
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 | ||
18168da7 | 161 | static const struct seq_operations procfs_list_seq_ops = { |
d1261452 JG |
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 | ||
0dd73648 BB |
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 | |
d1261452 JG |
208 | .open = procfs_list_open, |
209 | .write = procfs_list_write, | |
210 | .read = seq_read, | |
211 | .llseek = seq_lseek, | |
212 | .release = seq_release_private, | |
0dd73648 | 213 | #endif |
d1261452 JG |
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, | |
7b8363d7 | 222 | const char *submodule, |
d1261452 | 223 | const char *name, |
a887d653 | 224 | mode_t mode, |
d1261452 JG |
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 | { | |
7b8363d7 MM |
231 | char *modulestr; |
232 | ||
233 | if (submodule != NULL) | |
234 | modulestr = kmem_asprintf("%s/%s", module, submodule); | |
235 | else | |
236 | modulestr = kmem_asprintf("%s", module); | |
d1261452 JG |
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 | ||
7b8363d7 | 247 | kstat_proc_entry_init(&procfs_list->pl_kstat_entry, modulestr, name); |
a887d653 | 248 | kstat_proc_entry_install(&procfs_list->pl_kstat_entry, mode, |
d1261452 | 249 | &procfs_list_operations, procfs_list); |
7b8363d7 | 250 | kmem_strfree(modulestr); |
d1261452 JG |
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); |