xref: /freebsd-src/sys/contrib/openzfs/module/os/linux/spl/spl-procfs-list.c (revision aca928a50a42f00f344df934005b09dbcb4e2f77)
1eda14cbcSMatt Macy /*
2eda14cbcSMatt Macy  * CDDL HEADER START
3eda14cbcSMatt Macy  *
4eda14cbcSMatt Macy  * The contents of this file are subject to the terms of the
5eda14cbcSMatt Macy  * Common Development and Distribution License (the "License").
6eda14cbcSMatt Macy  * You may not use this file except in compliance with the License.
7eda14cbcSMatt Macy  *
8eda14cbcSMatt Macy  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9271171e0SMartin Matuska  * or https://opensource.org/licenses/CDDL-1.0.
10eda14cbcSMatt Macy  * See the License for the specific language governing permissions
11eda14cbcSMatt Macy  * and limitations under the License.
12eda14cbcSMatt Macy  *
13eda14cbcSMatt Macy  * When distributing Covered Code, include this CDDL HEADER in each
14eda14cbcSMatt Macy  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15eda14cbcSMatt Macy  * If applicable, add the following below this CDDL HEADER, with the
16eda14cbcSMatt Macy  * fields enclosed by brackets "[]" replaced with your own identifying
17eda14cbcSMatt Macy  * information: Portions Copyright [yyyy] [name of copyright owner]
18eda14cbcSMatt Macy  *
19eda14cbcSMatt Macy  * CDDL HEADER END
20eda14cbcSMatt Macy  */
21eda14cbcSMatt Macy /*
22eda14cbcSMatt Macy  * Copyright (c) 2018 by Delphix. All rights reserved.
23eda14cbcSMatt Macy  */
24eda14cbcSMatt Macy 
25eda14cbcSMatt Macy #include <sys/list.h>
26eda14cbcSMatt Macy #include <sys/procfs_list.h>
27eda14cbcSMatt Macy #include <linux/proc_fs.h>
28dbd5678dSMartin Matuska #include <sys/mutex.h>
29eda14cbcSMatt Macy 
30eda14cbcSMatt Macy /*
31eda14cbcSMatt Macy  * A procfs_list is a wrapper around a linked list which implements the seq_file
32eda14cbcSMatt Macy  * interface, allowing the contents of the list to be exposed through procfs.
33eda14cbcSMatt Macy  * The kernel already has some utilities to help implement the seq_file
34eda14cbcSMatt Macy  * interface for linked lists (seq_list_*), but they aren't appropriate for use
35eda14cbcSMatt Macy  * with lists that have many entries, because seq_list_start walks the list at
36eda14cbcSMatt Macy  * the start of each read syscall to find where it left off, so reading a file
37eda14cbcSMatt Macy  * ends up being quadratic in the number of entries in the list.
38eda14cbcSMatt Macy  *
39eda14cbcSMatt Macy  * This implementation avoids this penalty by maintaining a separate cursor into
40eda14cbcSMatt Macy  * the list per instance of the file that is open. It also maintains some extra
41eda14cbcSMatt Macy  * information in each node of the list to prevent reads of entries that have
42eda14cbcSMatt Macy  * been dropped from the list.
43eda14cbcSMatt Macy  *
44eda14cbcSMatt Macy  * Callers should only add elements to the list using procfs_list_add, which
45eda14cbcSMatt Macy  * adds an element to the tail of the list. Other operations can be performed
46eda14cbcSMatt Macy  * directly on the wrapped list using the normal list manipulation functions,
47eda14cbcSMatt Macy  * but elements should only be removed from the head of the list.
48eda14cbcSMatt Macy  */
49eda14cbcSMatt Macy 
50eda14cbcSMatt Macy #define	NODE_ID(procfs_list, obj) \
51eda14cbcSMatt Macy 		(((procfs_list_node_t *)(((char *)obj) + \
52eda14cbcSMatt Macy 		(procfs_list)->pl_node_offset))->pln_id)
53eda14cbcSMatt Macy 
54eda14cbcSMatt Macy typedef struct procfs_list_cursor {
55eda14cbcSMatt Macy 	procfs_list_t	*procfs_list;	/* List into which this cursor points */
56eda14cbcSMatt Macy 	void		*cached_node;	/* Most recently accessed node */
57eda14cbcSMatt Macy 	loff_t		cached_pos;	/* Position of cached_node */
58eda14cbcSMatt Macy } procfs_list_cursor_t;
59eda14cbcSMatt Macy 
60eda14cbcSMatt Macy static int
procfs_list_seq_show(struct seq_file * f,void * p)61eda14cbcSMatt Macy procfs_list_seq_show(struct seq_file *f, void *p)
62eda14cbcSMatt Macy {
63eda14cbcSMatt Macy 	procfs_list_cursor_t *cursor = f->private;
64eda14cbcSMatt Macy 	procfs_list_t *procfs_list = cursor->procfs_list;
65eda14cbcSMatt Macy 
66eda14cbcSMatt Macy 	ASSERT(MUTEX_HELD(&procfs_list->pl_lock));
67eda14cbcSMatt Macy 	if (p == SEQ_START_TOKEN) {
68eda14cbcSMatt Macy 		if (procfs_list->pl_show_header != NULL)
69eda14cbcSMatt Macy 			return (procfs_list->pl_show_header(f));
70eda14cbcSMatt Macy 		else
71eda14cbcSMatt Macy 			return (0);
72eda14cbcSMatt Macy 	}
73eda14cbcSMatt Macy 	return (procfs_list->pl_show(f, p));
74eda14cbcSMatt Macy }
75eda14cbcSMatt Macy 
76eda14cbcSMatt Macy static void *
procfs_list_next_node(procfs_list_cursor_t * cursor,loff_t * pos)77eda14cbcSMatt Macy procfs_list_next_node(procfs_list_cursor_t *cursor, loff_t *pos)
78eda14cbcSMatt Macy {
79eda14cbcSMatt Macy 	void *next_node;
80eda14cbcSMatt Macy 	procfs_list_t *procfs_list = cursor->procfs_list;
81eda14cbcSMatt Macy 
82eda14cbcSMatt Macy 	if (cursor->cached_node == SEQ_START_TOKEN)
83eda14cbcSMatt Macy 		next_node = list_head(&procfs_list->pl_list);
84eda14cbcSMatt Macy 	else
85eda14cbcSMatt Macy 		next_node = list_next(&procfs_list->pl_list,
86eda14cbcSMatt Macy 		    cursor->cached_node);
87eda14cbcSMatt Macy 
88eda14cbcSMatt Macy 	if (next_node != NULL) {
89eda14cbcSMatt Macy 		cursor->cached_node = next_node;
90eda14cbcSMatt Macy 		cursor->cached_pos = NODE_ID(procfs_list, cursor->cached_node);
91eda14cbcSMatt Macy 		*pos = cursor->cached_pos;
92c40487d4SMatt Macy 	} else {
93c40487d4SMatt Macy 		/*
94c40487d4SMatt Macy 		 * seq_read() expects ->next() to update the position even
95c40487d4SMatt Macy 		 * when there are no more entries. Advance the position to
96c40487d4SMatt Macy 		 * prevent a warning from being logged.
97c40487d4SMatt Macy 		 */
98c40487d4SMatt Macy 		cursor->cached_node = NULL;
99c40487d4SMatt Macy 		cursor->cached_pos++;
100c40487d4SMatt Macy 		*pos = cursor->cached_pos;
101eda14cbcSMatt Macy 	}
102c40487d4SMatt Macy 
103eda14cbcSMatt Macy 	return (next_node);
104eda14cbcSMatt Macy }
105eda14cbcSMatt Macy 
106eda14cbcSMatt Macy static void *
procfs_list_seq_start(struct seq_file * f,loff_t * pos)107eda14cbcSMatt Macy procfs_list_seq_start(struct seq_file *f, loff_t *pos)
108eda14cbcSMatt Macy {
109eda14cbcSMatt Macy 	procfs_list_cursor_t *cursor = f->private;
110eda14cbcSMatt Macy 	procfs_list_t *procfs_list = cursor->procfs_list;
111eda14cbcSMatt Macy 
112eda14cbcSMatt Macy 	mutex_enter(&procfs_list->pl_lock);
113eda14cbcSMatt Macy 
114eda14cbcSMatt Macy 	if (*pos == 0) {
115eda14cbcSMatt Macy 		cursor->cached_node = SEQ_START_TOKEN;
116eda14cbcSMatt Macy 		cursor->cached_pos = 0;
117eda14cbcSMatt Macy 		return (SEQ_START_TOKEN);
118c40487d4SMatt Macy 	} else if (cursor->cached_node == NULL) {
119c40487d4SMatt Macy 		return (NULL);
120eda14cbcSMatt Macy 	}
121eda14cbcSMatt Macy 
122eda14cbcSMatt Macy 	/*
123eda14cbcSMatt Macy 	 * Check if our cached pointer has become stale, which happens if the
124eda14cbcSMatt Macy 	 * the message where we left off has been dropped from the list since
125eda14cbcSMatt Macy 	 * the last read syscall completed.
126eda14cbcSMatt Macy 	 */
127eda14cbcSMatt Macy 	void *oldest_node = list_head(&procfs_list->pl_list);
128eda14cbcSMatt Macy 	if (cursor->cached_node != SEQ_START_TOKEN && (oldest_node == NULL ||
129eda14cbcSMatt Macy 	    NODE_ID(procfs_list, oldest_node) > cursor->cached_pos))
130eda14cbcSMatt Macy 		return (ERR_PTR(-EIO));
131eda14cbcSMatt Macy 
132eda14cbcSMatt Macy 	/*
133eda14cbcSMatt Macy 	 * If it isn't starting from the beginning of the file, the seq_file
134eda14cbcSMatt Macy 	 * code will either pick up at the same position it visited last or the
135eda14cbcSMatt Macy 	 * following one.
136eda14cbcSMatt Macy 	 */
137eda14cbcSMatt Macy 	if (*pos == cursor->cached_pos) {
138eda14cbcSMatt Macy 		return (cursor->cached_node);
139eda14cbcSMatt Macy 	} else {
140eda14cbcSMatt Macy 		ASSERT3U(*pos, ==, cursor->cached_pos + 1);
141eda14cbcSMatt Macy 		return (procfs_list_next_node(cursor, pos));
142eda14cbcSMatt Macy 	}
143eda14cbcSMatt Macy }
144eda14cbcSMatt Macy 
145eda14cbcSMatt Macy static void *
procfs_list_seq_next(struct seq_file * f,void * p,loff_t * pos)146eda14cbcSMatt Macy procfs_list_seq_next(struct seq_file *f, void *p, loff_t *pos)
147eda14cbcSMatt Macy {
148eda14cbcSMatt Macy 	procfs_list_cursor_t *cursor = f->private;
149eda14cbcSMatt Macy 	ASSERT(MUTEX_HELD(&cursor->procfs_list->pl_lock));
150eda14cbcSMatt Macy 	return (procfs_list_next_node(cursor, pos));
151eda14cbcSMatt Macy }
152eda14cbcSMatt Macy 
153eda14cbcSMatt Macy static void
procfs_list_seq_stop(struct seq_file * f,void * p)154eda14cbcSMatt Macy procfs_list_seq_stop(struct seq_file *f, void *p)
155eda14cbcSMatt Macy {
156eda14cbcSMatt Macy 	procfs_list_cursor_t *cursor = f->private;
157eda14cbcSMatt Macy 	procfs_list_t *procfs_list = cursor->procfs_list;
158eda14cbcSMatt Macy 	mutex_exit(&procfs_list->pl_lock);
159eda14cbcSMatt Macy }
160eda14cbcSMatt Macy 
161e92ffd9bSMartin Matuska static const struct seq_operations procfs_list_seq_ops = {
162eda14cbcSMatt Macy 	.show  = procfs_list_seq_show,
163eda14cbcSMatt Macy 	.start = procfs_list_seq_start,
164eda14cbcSMatt Macy 	.next  = procfs_list_seq_next,
165eda14cbcSMatt Macy 	.stop  = procfs_list_seq_stop,
166eda14cbcSMatt Macy };
167eda14cbcSMatt Macy 
168eda14cbcSMatt Macy static int
procfs_list_open(struct inode * inode,struct file * filp)169eda14cbcSMatt Macy procfs_list_open(struct inode *inode, struct file *filp)
170eda14cbcSMatt Macy {
171eda14cbcSMatt Macy 	int rc = seq_open_private(filp, &procfs_list_seq_ops,
172eda14cbcSMatt Macy 	    sizeof (procfs_list_cursor_t));
173eda14cbcSMatt Macy 	if (rc != 0)
174eda14cbcSMatt Macy 		return (rc);
175eda14cbcSMatt Macy 
176eda14cbcSMatt Macy 	struct seq_file *f = filp->private_data;
177eda14cbcSMatt Macy 	procfs_list_cursor_t *cursor = f->private;
178c03c5b1cSMartin Matuska 	cursor->procfs_list = SPL_PDE_DATA(inode);
179eda14cbcSMatt Macy 	cursor->cached_node = NULL;
180eda14cbcSMatt Macy 	cursor->cached_pos = 0;
181eda14cbcSMatt Macy 
182eda14cbcSMatt Macy 	return (0);
183eda14cbcSMatt Macy }
184eda14cbcSMatt Macy 
185eda14cbcSMatt Macy static ssize_t
procfs_list_write(struct file * filp,const char __user * buf,size_t len,loff_t * ppos)186eda14cbcSMatt Macy procfs_list_write(struct file *filp, const char __user *buf, size_t len,
187eda14cbcSMatt Macy     loff_t *ppos)
188eda14cbcSMatt Macy {
189eda14cbcSMatt Macy 	struct seq_file *f = filp->private_data;
190eda14cbcSMatt Macy 	procfs_list_cursor_t *cursor = f->private;
191eda14cbcSMatt Macy 	procfs_list_t *procfs_list = cursor->procfs_list;
192eda14cbcSMatt Macy 	int rc;
193eda14cbcSMatt Macy 
194eda14cbcSMatt Macy 	if (procfs_list->pl_clear != NULL &&
195eda14cbcSMatt Macy 	    (rc = procfs_list->pl_clear(procfs_list)) != 0)
196eda14cbcSMatt Macy 		return (-rc);
197eda14cbcSMatt Macy 	return (len);
198eda14cbcSMatt Macy }
199eda14cbcSMatt Macy 
200eda14cbcSMatt Macy static const kstat_proc_op_t procfs_list_operations = {
201eda14cbcSMatt Macy #ifdef HAVE_PROC_OPS_STRUCT
202eda14cbcSMatt Macy 	.proc_open	= procfs_list_open,
203eda14cbcSMatt Macy 	.proc_write	= procfs_list_write,
204eda14cbcSMatt Macy 	.proc_read	= seq_read,
205eda14cbcSMatt Macy 	.proc_lseek	= seq_lseek,
206eda14cbcSMatt Macy 	.proc_release	= seq_release_private,
207eda14cbcSMatt Macy #else
208eda14cbcSMatt Macy 	.open		= procfs_list_open,
209eda14cbcSMatt Macy 	.write		= procfs_list_write,
210eda14cbcSMatt Macy 	.read		= seq_read,
211eda14cbcSMatt Macy 	.llseek		= seq_lseek,
212eda14cbcSMatt Macy 	.release	= seq_release_private,
213eda14cbcSMatt Macy #endif
214eda14cbcSMatt Macy };
215eda14cbcSMatt Macy 
216eda14cbcSMatt Macy /*
217eda14cbcSMatt Macy  * Initialize a procfs_list and create a file for it in the proc filesystem
218eda14cbcSMatt Macy  * under the kstat namespace.
219eda14cbcSMatt Macy  */
220eda14cbcSMatt Macy void
procfs_list_install(const char * module,const char * submodule,const char * name,mode_t mode,procfs_list_t * procfs_list,int (* show)(struct seq_file * f,void * p),int (* show_header)(struct seq_file * f),int (* clear)(procfs_list_t * procfs_list),size_t procfs_list_node_off)221eda14cbcSMatt Macy procfs_list_install(const char *module,
222c40487d4SMatt Macy     const char *submodule,
223eda14cbcSMatt Macy     const char *name,
224eda14cbcSMatt Macy     mode_t mode,
225eda14cbcSMatt Macy     procfs_list_t *procfs_list,
226eda14cbcSMatt Macy     int (*show)(struct seq_file *f, void *p),
227eda14cbcSMatt Macy     int (*show_header)(struct seq_file *f),
228eda14cbcSMatt Macy     int (*clear)(procfs_list_t *procfs_list),
229eda14cbcSMatt Macy     size_t procfs_list_node_off)
230eda14cbcSMatt Macy {
231c40487d4SMatt Macy 	char *modulestr;
232c40487d4SMatt Macy 
233c40487d4SMatt Macy 	if (submodule != NULL)
234c40487d4SMatt Macy 		modulestr = kmem_asprintf("%s/%s", module, submodule);
235c40487d4SMatt Macy 	else
236c40487d4SMatt Macy 		modulestr = kmem_asprintf("%s", module);
237*aca928a5SMartin Matuska 	mutex_init(&procfs_list->pl_lock, NULL, MUTEX_NOLOCKDEP, NULL);
238eda14cbcSMatt Macy 	list_create(&procfs_list->pl_list,
239eda14cbcSMatt Macy 	    procfs_list_node_off + sizeof (procfs_list_node_t),
240eda14cbcSMatt Macy 	    procfs_list_node_off + offsetof(procfs_list_node_t, pln_link));
241eda14cbcSMatt Macy 	procfs_list->pl_next_id = 1; /* Save id 0 for SEQ_START_TOKEN */
242eda14cbcSMatt Macy 	procfs_list->pl_show = show;
243eda14cbcSMatt Macy 	procfs_list->pl_show_header = show_header;
244eda14cbcSMatt Macy 	procfs_list->pl_clear = clear;
245eda14cbcSMatt Macy 	procfs_list->pl_node_offset = procfs_list_node_off;
246eda14cbcSMatt Macy 
247c40487d4SMatt Macy 	kstat_proc_entry_init(&procfs_list->pl_kstat_entry, modulestr, name);
248eda14cbcSMatt Macy 	kstat_proc_entry_install(&procfs_list->pl_kstat_entry, mode,
249eda14cbcSMatt Macy 	    &procfs_list_operations, procfs_list);
250c40487d4SMatt Macy 	kmem_strfree(modulestr);
251eda14cbcSMatt Macy }
252eda14cbcSMatt Macy EXPORT_SYMBOL(procfs_list_install);
253eda14cbcSMatt Macy 
254eda14cbcSMatt Macy /* Remove the proc filesystem file corresponding to the given list */
255eda14cbcSMatt Macy void
procfs_list_uninstall(procfs_list_t * procfs_list)256eda14cbcSMatt Macy procfs_list_uninstall(procfs_list_t *procfs_list)
257eda14cbcSMatt Macy {
258eda14cbcSMatt Macy 	kstat_proc_entry_delete(&procfs_list->pl_kstat_entry);
259eda14cbcSMatt Macy }
260eda14cbcSMatt Macy EXPORT_SYMBOL(procfs_list_uninstall);
261eda14cbcSMatt Macy 
262eda14cbcSMatt Macy void
procfs_list_destroy(procfs_list_t * procfs_list)263eda14cbcSMatt Macy procfs_list_destroy(procfs_list_t *procfs_list)
264eda14cbcSMatt Macy {
265eda14cbcSMatt Macy 	ASSERT(list_is_empty(&procfs_list->pl_list));
266eda14cbcSMatt Macy 	list_destroy(&procfs_list->pl_list);
267eda14cbcSMatt Macy 	mutex_destroy(&procfs_list->pl_lock);
268eda14cbcSMatt Macy }
269eda14cbcSMatt Macy EXPORT_SYMBOL(procfs_list_destroy);
270eda14cbcSMatt Macy 
271eda14cbcSMatt Macy /*
272eda14cbcSMatt Macy  * Add a new node to the tail of the list. While the standard list manipulation
273eda14cbcSMatt Macy  * functions can be use for all other operation, adding elements to the list
274eda14cbcSMatt Macy  * should only be done using this helper so that the id of the new node is set
275eda14cbcSMatt Macy  * correctly.
276eda14cbcSMatt Macy  */
277eda14cbcSMatt Macy void
procfs_list_add(procfs_list_t * procfs_list,void * p)278eda14cbcSMatt Macy procfs_list_add(procfs_list_t *procfs_list, void *p)
279eda14cbcSMatt Macy {
280eda14cbcSMatt Macy 	ASSERT(MUTEX_HELD(&procfs_list->pl_lock));
281eda14cbcSMatt Macy 	NODE_ID(procfs_list, p) = procfs_list->pl_next_id++;
282eda14cbcSMatt Macy 	list_insert_tail(&procfs_list->pl_list, p);
283eda14cbcSMatt Macy }
284eda14cbcSMatt Macy EXPORT_SYMBOL(procfs_list_add);
285