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.
8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9 * or https://opensource.org/licenses/CDDL-1.0.
10 * See the License for the specific language governing permissions
11 * and limitations under the License.
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]
22 * Copyright (c) 2018 by Delphix. All rights reserved.
26 #include <sys/procfs_list.h>
27 #include <linux/proc_fs.h>
28 #include <sys/mutex.h>
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.
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.
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.
50 #define NODE_ID(procfs_list, obj) \
51 (((procfs_list_node_t *)(((char *)obj) + \
52 (procfs_list)->pl_node_offset))->pln_id)
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
;
61 procfs_list_seq_show(struct seq_file
*f
, void *p
)
63 procfs_list_cursor_t
*cursor
= f
->private;
64 procfs_list_t
*procfs_list
= cursor
->procfs_list
;
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
));
73 return (procfs_list
->pl_show(f
, p
));
77 procfs_list_next_node(procfs_list_cursor_t
*cursor
, loff_t
*pos
)
80 procfs_list_t
*procfs_list
= cursor
->procfs_list
;
82 if (cursor
->cached_node
== SEQ_START_TOKEN
)
83 next_node
= list_head(&procfs_list
->pl_list
);
85 next_node
= list_next(&procfs_list
->pl_list
,
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
;
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.
98 cursor
->cached_node
= NULL
;
100 *pos
= cursor
->cached_pos
;
107 procfs_list_seq_start(struct seq_file
*f
, loff_t
*pos
)
109 procfs_list_cursor_t
*cursor
= f
->private;
110 procfs_list_t
*procfs_list
= cursor
->procfs_list
;
112 mutex_enter(&procfs_list
->pl_lock
);
115 cursor
->cached_node
= SEQ_START_TOKEN
;
116 cursor
->cached_pos
= 0;
117 return (SEQ_START_TOKEN
);
118 } else if (cursor
->cached_node
== NULL
) {
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.
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
));
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
137 if (*pos
== cursor
->cached_pos
) {
138 return (cursor
->cached_node
);
140 ASSERT3U(*pos
, ==, cursor
->cached_pos
+ 1);
141 return (procfs_list_next_node(cursor
, pos
));
146 procfs_list_seq_next(struct seq_file
*f
, void *p
, loff_t
*pos
)
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
));
154 procfs_list_seq_stop(struct seq_file
*f
, void *p
)
156 procfs_list_cursor_t
*cursor
= f
->private;
157 procfs_list_t
*procfs_list
= cursor
->procfs_list
;
158 mutex_exit(&procfs_list
->pl_lock
);
161 static const 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
,
169 procfs_list_open(struct inode
*inode
, struct file
*filp
)
171 int rc
= seq_open_private(filp
, &procfs_list_seq_ops
,
172 sizeof (procfs_list_cursor_t
));
176 struct seq_file
*f
= filp
->private_data
;
177 procfs_list_cursor_t
*cursor
= f
->private;
178 cursor
->procfs_list
= SPL_PDE_DATA(inode
);
179 cursor
->cached_node
= NULL
;
180 cursor
->cached_pos
= 0;
186 procfs_list_write(struct file
*filp
, const char __user
*buf
, size_t len
,
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
;
194 if (procfs_list
->pl_clear
!= NULL
&&
195 (rc
= procfs_list
->pl_clear(procfs_list
)) != 0)
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
,
208 .open
= procfs_list_open
,
209 .write
= procfs_list_write
,
212 .release
= seq_release_private
,
217 * Initialize a procfs_list and create a file for it in the proc filesystem
218 * under the kstat namespace.
221 procfs_list_install(const char *module
,
222 const char *submodule
,
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
)
233 if (submodule
!= NULL
)
234 modulestr
= kmem_asprintf("%s/%s", module
, submodule
);
236 modulestr
= kmem_asprintf("%s", module
);
237 mutex_init(&procfs_list
->pl_lock
, NULL
, MUTEX_NOLOCKDEP
, 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
;
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
);
252 EXPORT_SYMBOL(procfs_list_install
);
254 /* Remove the proc filesystem file corresponding to the given list */
256 procfs_list_uninstall(procfs_list_t
*procfs_list
)
258 kstat_proc_entry_delete(&procfs_list
->pl_kstat_entry
);
260 EXPORT_SYMBOL(procfs_list_uninstall
);
263 procfs_list_destroy(procfs_list_t
*procfs_list
)
265 ASSERT(list_is_empty(&procfs_list
->pl_list
));
266 list_destroy(&procfs_list
->pl_list
);
267 mutex_destroy(&procfs_list
->pl_lock
);
269 EXPORT_SYMBOL(procfs_list_destroy
);
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
278 procfs_list_add(procfs_list_t
*procfs_list
, void *p
)
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
);
284 EXPORT_SYMBOL(procfs_list_add
);