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 http://www.opensolaris.org/os/licensing.
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/mutex.h>
27 #include <sys/procfs_list.h>
28 #include <linux/proc_fs.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
;
97 procfs_list_seq_start(struct seq_file
*f
, loff_t
*pos
)
99 procfs_list_cursor_t
*cursor
= f
->private;
100 procfs_list_t
*procfs_list
= cursor
->procfs_list
;
102 mutex_enter(&procfs_list
->pl_lock
);
105 cursor
->cached_node
= SEQ_START_TOKEN
;
106 cursor
->cached_pos
= 0;
107 return (SEQ_START_TOKEN
);
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.
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
));
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
125 if (*pos
== cursor
->cached_pos
) {
126 return (cursor
->cached_node
);
128 ASSERT3U(*pos
, ==, cursor
->cached_pos
+ 1);
129 return (procfs_list_next_node(cursor
, pos
));
134 procfs_list_seq_next(struct seq_file
*f
, void *p
, loff_t
*pos
)
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
));
142 procfs_list_seq_stop(struct seq_file
*f
, void *p
)
144 procfs_list_cursor_t
*cursor
= f
->private;
145 procfs_list_t
*procfs_list
= cursor
->procfs_list
;
146 mutex_exit(&procfs_list
->pl_lock
);
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
,
157 procfs_list_open(struct inode
*inode
, struct file
*filp
)
159 int rc
= seq_open_private(filp
, &procfs_list_seq_ops
,
160 sizeof (procfs_list_cursor_t
));
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;
174 procfs_list_write(struct file
*filp
, const char __user
*buf
, size_t len
,
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
;
182 if (procfs_list
->pl_clear
!= NULL
&&
183 (rc
= procfs_list
->pl_clear(procfs_list
)) != 0)
188 static struct file_operations procfs_list_operations
= {
189 .owner
= THIS_MODULE
,
190 .open
= procfs_list_open
,
191 .write
= procfs_list_write
,
194 .release
= seq_release_private
,
198 * Initialize a procfs_list and create a file for it in the proc filesystem
199 * under the kstat namespace.
202 procfs_list_install(const char *module
,
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
)
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
;
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
);
224 EXPORT_SYMBOL(procfs_list_install
);
226 /* Remove the proc filesystem file corresponding to the given list */
228 procfs_list_uninstall(procfs_list_t
*procfs_list
)
230 kstat_proc_entry_delete(&procfs_list
->pl_kstat_entry
);
232 EXPORT_SYMBOL(procfs_list_uninstall
);
235 procfs_list_destroy(procfs_list_t
*procfs_list
)
237 ASSERT(list_is_empty(&procfs_list
->pl_list
));
238 list_destroy(&procfs_list
->pl_list
);
239 mutex_destroy(&procfs_list
->pl_lock
);
241 EXPORT_SYMBOL(procfs_list_destroy
);
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
250 procfs_list_add(procfs_list_t
*procfs_list
, void *p
)
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
);
256 EXPORT_SYMBOL(procfs_list_add
);