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; 92 } 93 return (next_node); 94 } 95 96 static void * 97 procfs_list_seq_start(struct seq_file *f, loff_t *pos) 98 { 99 procfs_list_cursor_t *cursor = f->private; 100 procfs_list_t *procfs_list = cursor->procfs_list; 101 102 mutex_enter(&procfs_list->pl_lock); 103 104 if (*pos == 0) { 105 cursor->cached_node = SEQ_START_TOKEN; 106 cursor->cached_pos = 0; 107 return (SEQ_START_TOKEN); 108 } 109 110 /* 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. 114 */ 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)); 119 120 /* 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 123 * following one. 124 */ 125 if (*pos == cursor->cached_pos) { 126 return (cursor->cached_node); 127 } else { 128 ASSERT3U(*pos, ==, cursor->cached_pos + 1); 129 return (procfs_list_next_node(cursor, pos)); 130 } 131 } 132 133 static void * 134 procfs_list_seq_next(struct seq_file *f, void *p, loff_t *pos) 135 { 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)); 139 } 140 141 static void 142 procfs_list_seq_stop(struct seq_file *f, void *p) 143 { 144 procfs_list_cursor_t *cursor = f->private; 145 procfs_list_t *procfs_list = cursor->procfs_list; 146 mutex_exit(&procfs_list->pl_lock); 147 } 148 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, 154 }; 155 156 static int 157 procfs_list_open(struct inode *inode, struct file *filp) 158 { 159 int rc = seq_open_private(filp, &procfs_list_seq_ops, 160 sizeof (procfs_list_cursor_t)); 161 if (rc != 0) 162 return (rc); 163 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; 169 170 return (0); 171 } 172 173 static ssize_t 174 procfs_list_write(struct file *filp, const char __user *buf, size_t len, 175 loff_t *ppos) 176 { 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; 180 int rc; 181 182 if (procfs_list->pl_clear != NULL && 183 (rc = procfs_list->pl_clear(procfs_list)) != 0) 184 return (-rc); 185 return (len); 186 } 187 188 static const kstat_proc_op_t procfs_list_operations = { 189 #ifdef HAVE_PROC_OPS_STRUCT 190 .proc_open = procfs_list_open, 191 .proc_write = procfs_list_write, 192 .proc_read = seq_read, 193 .proc_lseek = seq_lseek, 194 .proc_release = seq_release_private, 195 #else 196 .open = procfs_list_open, 197 .write = procfs_list_write, 198 .read = seq_read, 199 .llseek = seq_lseek, 200 .release = seq_release_private, 201 #endif 202 }; 203 204 /* 205 * Initialize a procfs_list and create a file for it in the proc filesystem 206 * under the kstat namespace. 207 */ 208 void 209 procfs_list_install(const char *module, 210 const char *name, 211 mode_t mode, 212 procfs_list_t *procfs_list, 213 int (*show)(struct seq_file *f, void *p), 214 int (*show_header)(struct seq_file *f), 215 int (*clear)(procfs_list_t *procfs_list), 216 size_t procfs_list_node_off) 217 { 218 mutex_init(&procfs_list->pl_lock, NULL, MUTEX_DEFAULT, NULL); 219 list_create(&procfs_list->pl_list, 220 procfs_list_node_off + sizeof (procfs_list_node_t), 221 procfs_list_node_off + offsetof(procfs_list_node_t, pln_link)); 222 procfs_list->pl_next_id = 1; /* Save id 0 for SEQ_START_TOKEN */ 223 procfs_list->pl_show = show; 224 procfs_list->pl_show_header = show_header; 225 procfs_list->pl_clear = clear; 226 procfs_list->pl_node_offset = procfs_list_node_off; 227 228 kstat_proc_entry_init(&procfs_list->pl_kstat_entry, module, name); 229 kstat_proc_entry_install(&procfs_list->pl_kstat_entry, mode, 230 &procfs_list_operations, procfs_list); 231 } 232 EXPORT_SYMBOL(procfs_list_install); 233 234 /* Remove the proc filesystem file corresponding to the given list */ 235 void 236 procfs_list_uninstall(procfs_list_t *procfs_list) 237 { 238 kstat_proc_entry_delete(&procfs_list->pl_kstat_entry); 239 } 240 EXPORT_SYMBOL(procfs_list_uninstall); 241 242 void 243 procfs_list_destroy(procfs_list_t *procfs_list) 244 { 245 ASSERT(list_is_empty(&procfs_list->pl_list)); 246 list_destroy(&procfs_list->pl_list); 247 mutex_destroy(&procfs_list->pl_lock); 248 } 249 EXPORT_SYMBOL(procfs_list_destroy); 250 251 /* 252 * Add a new node to the tail of the list. While the standard list manipulation 253 * functions can be use for all other operation, adding elements to the list 254 * should only be done using this helper so that the id of the new node is set 255 * correctly. 256 */ 257 void 258 procfs_list_add(procfs_list_t *procfs_list, void *p) 259 { 260 ASSERT(MUTEX_HELD(&procfs_list->pl_lock)); 261 NODE_ID(procfs_list, p) = procfs_list->pl_next_id++; 262 list_insert_tail(&procfs_list->pl_list, p); 263 } 264 EXPORT_SYMBOL(procfs_list_add); 265