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 https://opensource.org/licenses/CDDL-1.0. 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/procfs_list.h> 27 #include <linux/proc_fs.h> 28 #include <sys/mutex.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 } else { 93 /* 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. 97 */ 98 cursor->cached_node = NULL; 99 cursor->cached_pos++; 100 *pos = cursor->cached_pos; 101 } 102 103 return (next_node); 104 } 105 106 static void * 107 procfs_list_seq_start(struct seq_file *f, loff_t *pos) 108 { 109 procfs_list_cursor_t *cursor = f->private; 110 procfs_list_t *procfs_list = cursor->procfs_list; 111 112 mutex_enter(&procfs_list->pl_lock); 113 114 if (*pos == 0) { 115 cursor->cached_node = SEQ_START_TOKEN; 116 cursor->cached_pos = 0; 117 return (SEQ_START_TOKEN); 118 } else if (cursor->cached_node == NULL) { 119 return (NULL); 120 } 121 122 /* 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. 126 */ 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)); 131 132 /* 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 135 * following one. 136 */ 137 if (*pos == cursor->cached_pos) { 138 return (cursor->cached_node); 139 } else { 140 ASSERT3U(*pos, ==, cursor->cached_pos + 1); 141 return (procfs_list_next_node(cursor, pos)); 142 } 143 } 144 145 static void * 146 procfs_list_seq_next(struct seq_file *f, void *p, loff_t *pos) 147 { 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)); 151 } 152 153 static void 154 procfs_list_seq_stop(struct seq_file *f, void *p) 155 { 156 procfs_list_cursor_t *cursor = f->private; 157 procfs_list_t *procfs_list = cursor->procfs_list; 158 mutex_exit(&procfs_list->pl_lock); 159 } 160 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, 166 }; 167 168 static int 169 procfs_list_open(struct inode *inode, struct file *filp) 170 { 171 int rc = seq_open_private(filp, &procfs_list_seq_ops, 172 sizeof (procfs_list_cursor_t)); 173 if (rc != 0) 174 return (rc); 175 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; 181 182 return (0); 183 } 184 185 static ssize_t 186 procfs_list_write(struct file *filp, const char __user *buf, size_t len, 187 loff_t *ppos) 188 { 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; 192 int rc; 193 194 if (procfs_list->pl_clear != NULL && 195 (rc = procfs_list->pl_clear(procfs_list)) != 0) 196 return (-rc); 197 return (len); 198 } 199 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, 207 #else 208 .open = procfs_list_open, 209 .write = procfs_list_write, 210 .read = seq_read, 211 .llseek = seq_lseek, 212 .release = seq_release_private, 213 #endif 214 }; 215 216 /* 217 * Initialize a procfs_list and create a file for it in the proc filesystem 218 * under the kstat namespace. 219 */ 220 void 221 procfs_list_install(const char *module, 222 const char *submodule, 223 const char *name, 224 mode_t mode, 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) 230 { 231 char *modulestr; 232 233 if (submodule != NULL) 234 modulestr = kmem_asprintf("%s/%s", module, submodule); 235 else 236 modulestr = kmem_asprintf("%s", module); 237 mutex_init(&procfs_list->pl_lock, NULL, MUTEX_DEFAULT, 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; 246 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); 251 } 252 EXPORT_SYMBOL(procfs_list_install); 253 254 /* Remove the proc filesystem file corresponding to the given list */ 255 void 256 procfs_list_uninstall(procfs_list_t *procfs_list) 257 { 258 kstat_proc_entry_delete(&procfs_list->pl_kstat_entry); 259 } 260 EXPORT_SYMBOL(procfs_list_uninstall); 261 262 void 263 procfs_list_destroy(procfs_list_t *procfs_list) 264 { 265 ASSERT(list_is_empty(&procfs_list->pl_list)); 266 list_destroy(&procfs_list->pl_list); 267 mutex_destroy(&procfs_list->pl_lock); 268 } 269 EXPORT_SYMBOL(procfs_list_destroy); 270 271 /* 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 275 * correctly. 276 */ 277 void 278 procfs_list_add(procfs_list_t *procfs_list, void *p) 279 { 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); 283 } 284 EXPORT_SYMBOL(procfs_list_add); 285