/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright (c) 2018 by Delphix. All rights reserved. */ #include #include #include #include /* * A procfs_list is a wrapper around a linked list which implements the seq_file * interface, allowing the contents of the list to be exposed through procfs. * The kernel already has some utilities to help implement the seq_file * interface for linked lists (seq_list_*), but they aren't appropriate for use * with lists that have many entries, because seq_list_start walks the list at * the start of each read syscall to find where it left off, so reading a file * ends up being quadratic in the number of entries in the list. * * This implementation avoids this penalty by maintaining a separate cursor into * the list per instance of the file that is open. It also maintains some extra * information in each node of the list to prevent reads of entries that have * been dropped from the list. * * Callers should only add elements to the list using procfs_list_add, which * adds an element to the tail of the list. Other operations can be performed * directly on the wrapped list using the normal list manipulation functions, * but elements should only be removed from the head of the list. */ #define NODE_ID(procfs_list, obj) \ (((procfs_list_node_t *)(((char *)obj) + \ (procfs_list)->pl_node_offset))->pln_id) typedef struct procfs_list_cursor { procfs_list_t *procfs_list; /* List into which this cursor points */ void *cached_node; /* Most recently accessed node */ loff_t cached_pos; /* Position of cached_node */ } procfs_list_cursor_t; static int procfs_list_seq_show(struct seq_file *f, void *p) { procfs_list_cursor_t *cursor = f->private; procfs_list_t *procfs_list = cursor->procfs_list; ASSERT(MUTEX_HELD(&procfs_list->pl_lock)); if (p == SEQ_START_TOKEN) { if (procfs_list->pl_show_header != NULL) return (procfs_list->pl_show_header(f)); else return (0); } return (procfs_list->pl_show(f, p)); } static void * procfs_list_next_node(procfs_list_cursor_t *cursor, loff_t *pos) { void *next_node; procfs_list_t *procfs_list = cursor->procfs_list; if (cursor->cached_node == SEQ_START_TOKEN) next_node = list_head(&procfs_list->pl_list); else next_node = list_next(&procfs_list->pl_list, cursor->cached_node); if (next_node != NULL) { cursor->cached_node = next_node; cursor->cached_pos = NODE_ID(procfs_list, cursor->cached_node); *pos = cursor->cached_pos; } else { /* * seq_read() expects ->next() to update the position even * when there are no more entries. Advance the position to * prevent a warning from being logged. */ cursor->cached_node = NULL; cursor->cached_pos++; *pos = cursor->cached_pos; } return (next_node); } static void * procfs_list_seq_start(struct seq_file *f, loff_t *pos) { procfs_list_cursor_t *cursor = f->private; procfs_list_t *procfs_list = cursor->procfs_list; mutex_enter(&procfs_list->pl_lock); if (*pos == 0) { cursor->cached_node = SEQ_START_TOKEN; cursor->cached_pos = 0; return (SEQ_START_TOKEN); } else if (cursor->cached_node == NULL) { return (NULL); } /* * Check if our cached pointer has become stale, which happens if the * the message where we left off has been dropped from the list since * the last read syscall completed. */ void *oldest_node = list_head(&procfs_list->pl_list); if (cursor->cached_node != SEQ_START_TOKEN && (oldest_node == NULL || NODE_ID(procfs_list, oldest_node) > cursor->cached_pos)) return (ERR_PTR(-EIO)); /* * If it isn't starting from the beginning of the file, the seq_file * code will either pick up at the same position it visited last or the * following one. */ if (*pos == cursor->cached_pos) { return (cursor->cached_node); } else { ASSERT3U(*pos, ==, cursor->cached_pos + 1); return (procfs_list_next_node(cursor, pos)); } } static void * procfs_list_seq_next(struct seq_file *f, void *p, loff_t *pos) { procfs_list_cursor_t *cursor = f->private; ASSERT(MUTEX_HELD(&cursor->procfs_list->pl_lock)); return (procfs_list_next_node(cursor, pos)); } static void procfs_list_seq_stop(struct seq_file *f, void *p) { procfs_list_cursor_t *cursor = f->private; procfs_list_t *procfs_list = cursor->procfs_list; mutex_exit(&procfs_list->pl_lock); } static struct seq_operations procfs_list_seq_ops = { .show = procfs_list_seq_show, .start = procfs_list_seq_start, .next = procfs_list_seq_next, .stop = procfs_list_seq_stop, }; static int procfs_list_open(struct inode *inode, struct file *filp) { int rc = seq_open_private(filp, &procfs_list_seq_ops, sizeof (procfs_list_cursor_t)); if (rc != 0) return (rc); struct seq_file *f = filp->private_data; procfs_list_cursor_t *cursor = f->private; cursor->procfs_list = SPL_PDE_DATA(inode); cursor->cached_node = NULL; cursor->cached_pos = 0; return (0); } static ssize_t procfs_list_write(struct file *filp, const char __user *buf, size_t len, loff_t *ppos) { struct seq_file *f = filp->private_data; procfs_list_cursor_t *cursor = f->private; procfs_list_t *procfs_list = cursor->procfs_list; int rc; if (procfs_list->pl_clear != NULL && (rc = procfs_list->pl_clear(procfs_list)) != 0) return (-rc); return (len); } static const kstat_proc_op_t procfs_list_operations = { #ifdef HAVE_PROC_OPS_STRUCT .proc_open = procfs_list_open, .proc_write = procfs_list_write, .proc_read = seq_read, .proc_lseek = seq_lseek, .proc_release = seq_release_private, #else .open = procfs_list_open, .write = procfs_list_write, .read = seq_read, .llseek = seq_lseek, .release = seq_release_private, #endif }; /* * Initialize a procfs_list and create a file for it in the proc filesystem * under the kstat namespace. */ 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) { char *modulestr; if (submodule != NULL) modulestr = kmem_asprintf("%s/%s", module, submodule); else modulestr = kmem_asprintf("%s", module); mutex_init(&procfs_list->pl_lock, NULL, MUTEX_DEFAULT, NULL); list_create(&procfs_list->pl_list, procfs_list_node_off + sizeof (procfs_list_node_t), procfs_list_node_off + offsetof(procfs_list_node_t, pln_link)); procfs_list->pl_next_id = 1; /* Save id 0 for SEQ_START_TOKEN */ procfs_list->pl_show = show; procfs_list->pl_show_header = show_header; procfs_list->pl_clear = clear; procfs_list->pl_node_offset = procfs_list_node_off; kstat_proc_entry_init(&procfs_list->pl_kstat_entry, modulestr, name); kstat_proc_entry_install(&procfs_list->pl_kstat_entry, mode, &procfs_list_operations, procfs_list); kmem_strfree(modulestr); } EXPORT_SYMBOL(procfs_list_install); /* Remove the proc filesystem file corresponding to the given list */ void procfs_list_uninstall(procfs_list_t *procfs_list) { kstat_proc_entry_delete(&procfs_list->pl_kstat_entry); } EXPORT_SYMBOL(procfs_list_uninstall); void procfs_list_destroy(procfs_list_t *procfs_list) { ASSERT(list_is_empty(&procfs_list->pl_list)); list_destroy(&procfs_list->pl_list); mutex_destroy(&procfs_list->pl_lock); } EXPORT_SYMBOL(procfs_list_destroy); /* * Add a new node to the tail of the list. While the standard list manipulation * functions can be use for all other operation, adding elements to the list * should only be done using this helper so that the id of the new node is set * correctly. */ void procfs_list_add(procfs_list_t *procfs_list, void *p) { ASSERT(MUTEX_HELD(&procfs_list->pl_lock)); NODE_ID(procfs_list, p) = procfs_list->pl_next_id++; list_insert_tail(&procfs_list->pl_list, p); } EXPORT_SYMBOL(procfs_list_add);