| /* |
| * Copyright (C) 2007-2010 Lawrence Livermore National Security, LLC. |
| * Copyright (C) 2007 The Regents of the University of California. |
| * Produced at Lawrence Livermore National Laboratory (cf, DISCLAIMER). |
| * Written by Brian Behlendorf <behlendorf1@llnl.gov>. |
| * UCRL-CODE-235197 |
| * |
| * This file is part of the SPL, Solaris Porting Layer. |
| * For details, see <http://zfsonlinux.org/>. |
| * |
| * The SPL is free software; you can redistribute it and/or modify it |
| * under the terms of the GNU General Public License as published by the |
| * Free Software Foundation; either version 2 of the License, or (at your |
| * option) any later version. |
| * |
| * The SPL is distributed in the hope that it will be useful, but WITHOUT |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
| * for more details. |
| * |
| * You should have received a copy of the GNU General Public License along |
| * with the SPL. If not, see <http://www.gnu.org/licenses/>. |
| * |
| * Solaris Porting Layer (SPL) Proc Implementation. |
| */ |
| |
| #include <sys/systeminfo.h> |
| #include <sys/kstat.h> |
| #include <sys/kmem.h> |
| #include <sys/kmem_cache.h> |
| #include <sys/vmem.h> |
| #include <sys/taskq.h> |
| #include <sys/proc.h> |
| #include <linux/ctype.h> |
| #include <linux/kmod.h> |
| #include <linux/seq_file.h> |
| #include <linux/uaccess.h> |
| #include <linux/version.h> |
| |
| #if defined(CONSTIFY_PLUGIN) && LINUX_VERSION_CODE >= KERNEL_VERSION(3, 8, 0) |
| typedef struct ctl_table __no_const spl_ctl_table; |
| #else |
| typedef struct ctl_table spl_ctl_table; |
| #endif |
| |
| static unsigned long table_min = 0; |
| static unsigned long table_max = ~0; |
| |
| static struct ctl_table_header *spl_header = NULL; |
| static struct proc_dir_entry *proc_spl = NULL; |
| static struct proc_dir_entry *proc_spl_kmem = NULL; |
| static struct proc_dir_entry *proc_spl_kmem_slab = NULL; |
| static struct proc_dir_entry *proc_spl_taskq_all = NULL; |
| static struct proc_dir_entry *proc_spl_taskq = NULL; |
| struct proc_dir_entry *proc_spl_kstat = NULL; |
| |
| static int |
| proc_copyin_string(char *kbuffer, int kbuffer_size, const char *ubuffer, |
| int ubuffer_size) |
| { |
| int size; |
| |
| if (ubuffer_size > kbuffer_size) |
| return (-EOVERFLOW); |
| |
| if (copy_from_user((void *)kbuffer, (void *)ubuffer, ubuffer_size)) |
| return (-EFAULT); |
| |
| /* strip trailing whitespace */ |
| size = strnlen(kbuffer, ubuffer_size); |
| while (size-- >= 0) |
| if (!isspace(kbuffer[size])) |
| break; |
| |
| /* empty string */ |
| if (size < 0) |
| return (-EINVAL); |
| |
| /* no space to terminate */ |
| if (size == kbuffer_size) |
| return (-EOVERFLOW); |
| |
| kbuffer[size + 1] = 0; |
| return (0); |
| } |
| |
| static int |
| proc_copyout_string(char *ubuffer, int ubuffer_size, const char *kbuffer, |
| char *append) |
| { |
| /* |
| * NB if 'append' != NULL, it's a single character to append to the |
| * copied out string - usually "\n", for /proc entries and |
| * (i.e. a terminating zero byte) for sysctl entries |
| */ |
| int size = MIN(strlen(kbuffer), ubuffer_size); |
| |
| if (copy_to_user(ubuffer, kbuffer, size)) |
| return (-EFAULT); |
| |
| if (append != NULL && size < ubuffer_size) { |
| if (copy_to_user(ubuffer + size, append, 1)) |
| return (-EFAULT); |
| |
| size++; |
| } |
| |
| return (size); |
| } |
| |
| #ifdef DEBUG_KMEM |
| static int |
| proc_domemused(struct ctl_table *table, int write, |
| void __user *buffer, size_t *lenp, loff_t *ppos) |
| { |
| int rc = 0; |
| unsigned long min = 0, max = ~0, val; |
| spl_ctl_table dummy = *table; |
| |
| dummy.data = &val; |
| dummy.proc_handler = &proc_dointvec; |
| dummy.extra1 = &min; |
| dummy.extra2 = &max; |
| |
| if (write) { |
| *ppos += *lenp; |
| } else { |
| #ifdef HAVE_ATOMIC64_T |
| val = atomic64_read((atomic64_t *)table->data); |
| #else |
| val = atomic_read((atomic_t *)table->data); |
| #endif /* HAVE_ATOMIC64_T */ |
| rc = proc_doulongvec_minmax(&dummy, write, buffer, lenp, ppos); |
| } |
| |
| return (rc); |
| } |
| #endif /* DEBUG_KMEM */ |
| |
| static int |
| proc_doslab(struct ctl_table *table, int write, |
| void __user *buffer, size_t *lenp, loff_t *ppos) |
| { |
| int rc = 0; |
| unsigned long min = 0, max = ~0, val = 0, mask; |
| spl_ctl_table dummy = *table; |
| spl_kmem_cache_t *skc = NULL; |
| |
| dummy.data = &val; |
| dummy.proc_handler = &proc_dointvec; |
| dummy.extra1 = &min; |
| dummy.extra2 = &max; |
| |
| if (write) { |
| *ppos += *lenp; |
| } else { |
| down_read(&spl_kmem_cache_sem); |
| mask = (unsigned long)table->data; |
| |
| list_for_each_entry(skc, &spl_kmem_cache_list, skc_list) { |
| |
| /* Only use slabs of the correct kmem/vmem type */ |
| if (!(skc->skc_flags & mask)) |
| continue; |
| |
| /* Sum the specified field for selected slabs */ |
| switch (mask & (KMC_TOTAL | KMC_ALLOC | KMC_MAX)) { |
| case KMC_TOTAL: |
| val += skc->skc_slab_size * skc->skc_slab_total; |
| break; |
| case KMC_ALLOC: |
| val += skc->skc_obj_size * skc->skc_obj_alloc; |
| break; |
| case KMC_MAX: |
| val += skc->skc_obj_size * skc->skc_obj_max; |
| break; |
| } |
| } |
| |
| up_read(&spl_kmem_cache_sem); |
| rc = proc_doulongvec_minmax(&dummy, write, buffer, lenp, ppos); |
| } |
| |
| return (rc); |
| } |
| |
| static int |
| proc_dohostid(struct ctl_table *table, int write, |
| void __user *buffer, size_t *lenp, loff_t *ppos) |
| { |
| int len, rc = 0; |
| char *end, str[32]; |
| |
| if (write) { |
| /* |
| * We can't use proc_doulongvec_minmax() in the write |
| * case here because hostid while a hex value has no |
| * leading 0x which confuses the helper function. |
| */ |
| rc = proc_copyin_string(str, sizeof (str), buffer, *lenp); |
| if (rc < 0) |
| return (rc); |
| |
| spl_hostid = simple_strtoul(str, &end, 16); |
| if (str == end) |
| return (-EINVAL); |
| |
| } else { |
| len = snprintf(str, sizeof (str), "%lx", |
| (unsigned long) zone_get_hostid(NULL)); |
| if (*ppos >= len) |
| rc = 0; |
| else |
| rc = proc_copyout_string(buffer, |
| *lenp, str + *ppos, "\n"); |
| |
| if (rc >= 0) { |
| *lenp = rc; |
| *ppos += rc; |
| } |
| } |
| |
| return (rc); |
| } |
| |
| static void |
| taskq_seq_show_headers(struct seq_file *f) |
| { |
| seq_printf(f, "%-25s %5s %5s %5s %5s %5s %5s %12s %5s %10s\n", |
| "taskq", "act", "nthr", "spwn", "maxt", "pri", |
| "mina", "maxa", "cura", "flags"); |
| } |
| |
| /* indices into the lheads array below */ |
| #define LHEAD_PEND 0 |
| #define LHEAD_PRIO 1 |
| #define LHEAD_DELAY 2 |
| #define LHEAD_WAIT 3 |
| #define LHEAD_ACTIVE 4 |
| #define LHEAD_SIZE 5 |
| |
| /* BEGIN CSTYLED */ |
| static unsigned int spl_max_show_tasks = 512; |
| module_param(spl_max_show_tasks, uint, 0644); |
| MODULE_PARM_DESC(spl_max_show_tasks, "Max number of tasks shown in taskq proc"); |
| /* END CSTYLED */ |
| |
| static int |
| taskq_seq_show_impl(struct seq_file *f, void *p, boolean_t allflag) |
| { |
| taskq_t *tq = p; |
| taskq_thread_t *tqt = NULL; |
| spl_wait_queue_entry_t *wq; |
| struct task_struct *tsk; |
| taskq_ent_t *tqe; |
| char name[100]; |
| struct list_head *lheads[LHEAD_SIZE], *lh; |
| static char *list_names[LHEAD_SIZE] = |
| {"pend", "prio", "delay", "wait", "active" }; |
| int i, j, have_lheads = 0; |
| unsigned long wflags, flags; |
| |
| spin_lock_irqsave_nested(&tq->tq_lock, flags, tq->tq_lock_class); |
| spin_lock_irqsave(&tq->tq_wait_waitq.lock, wflags); |
| |
| /* get the various lists and check whether they're empty */ |
| lheads[LHEAD_PEND] = &tq->tq_pend_list; |
| lheads[LHEAD_PRIO] = &tq->tq_prio_list; |
| lheads[LHEAD_DELAY] = &tq->tq_delay_list; |
| #ifdef HAVE_WAIT_QUEUE_HEAD_ENTRY |
| lheads[LHEAD_WAIT] = &tq->tq_wait_waitq.head; |
| #else |
| lheads[LHEAD_WAIT] = &tq->tq_wait_waitq.task_list; |
| #endif |
| lheads[LHEAD_ACTIVE] = &tq->tq_active_list; |
| |
| for (i = 0; i < LHEAD_SIZE; ++i) { |
| if (list_empty(lheads[i])) |
| lheads[i] = NULL; |
| else |
| ++have_lheads; |
| } |
| |
| /* early return in non-"all" mode if lists are all empty */ |
| if (!allflag && !have_lheads) { |
| spin_unlock_irqrestore(&tq->tq_wait_waitq.lock, wflags); |
| spin_unlock_irqrestore(&tq->tq_lock, flags); |
| return (0); |
| } |
| |
| /* unlock the waitq quickly */ |
| if (!lheads[LHEAD_WAIT]) |
| spin_unlock_irqrestore(&tq->tq_wait_waitq.lock, wflags); |
| |
| /* show the base taskq contents */ |
| snprintf(name, sizeof (name), "%s/%d", tq->tq_name, tq->tq_instance); |
| seq_printf(f, "%-25s ", name); |
| seq_printf(f, "%5d %5d %5d %5d %5d %5d %12d %5d %10x\n", |
| tq->tq_nactive, tq->tq_nthreads, tq->tq_nspawn, |
| tq->tq_maxthreads, tq->tq_pri, tq->tq_minalloc, tq->tq_maxalloc, |
| tq->tq_nalloc, tq->tq_flags); |
| |
| /* show the active list */ |
| if (lheads[LHEAD_ACTIVE]) { |
| j = 0; |
| list_for_each_entry(tqt, &tq->tq_active_list, tqt_active_list) { |
| if (j == 0) |
| seq_printf(f, "\t%s:", |
| list_names[LHEAD_ACTIVE]); |
| else if (j == 2) { |
| seq_printf(f, "\n\t "); |
| j = 0; |
| } |
| seq_printf(f, " [%d]%pf(%ps)", |
| tqt->tqt_thread->pid, |
| tqt->tqt_task->tqent_func, |
| tqt->tqt_task->tqent_arg); |
| ++j; |
| } |
| seq_printf(f, "\n"); |
| } |
| |
| for (i = LHEAD_PEND; i <= LHEAD_WAIT; ++i) |
| if (lheads[i]) { |
| j = 0; |
| list_for_each(lh, lheads[i]) { |
| if (spl_max_show_tasks != 0 && |
| j >= spl_max_show_tasks) { |
| seq_printf(f, "\n\t(truncated)"); |
| break; |
| } |
| /* show the wait waitq list */ |
| if (i == LHEAD_WAIT) { |
| #ifdef HAVE_WAIT_QUEUE_HEAD_ENTRY |
| wq = list_entry(lh, |
| spl_wait_queue_entry_t, entry); |
| #else |
| wq = list_entry(lh, |
| spl_wait_queue_entry_t, task_list); |
| #endif |
| if (j == 0) |
| seq_printf(f, "\t%s:", |
| list_names[i]); |
| else if (j % 8 == 0) |
| seq_printf(f, "\n\t "); |
| |
| tsk = wq->private; |
| seq_printf(f, " %d", tsk->pid); |
| /* pend, prio and delay lists */ |
| } else { |
| tqe = list_entry(lh, taskq_ent_t, |
| tqent_list); |
| if (j == 0) |
| seq_printf(f, "\t%s:", |
| list_names[i]); |
| else if (j % 2 == 0) |
| seq_printf(f, "\n\t "); |
| |
| seq_printf(f, " %pf(%ps)", |
| tqe->tqent_func, |
| tqe->tqent_arg); |
| } |
| ++j; |
| } |
| seq_printf(f, "\n"); |
| } |
| if (lheads[LHEAD_WAIT]) |
| spin_unlock_irqrestore(&tq->tq_wait_waitq.lock, wflags); |
| spin_unlock_irqrestore(&tq->tq_lock, flags); |
| |
| return (0); |
| } |
| |
| static int |
| taskq_all_seq_show(struct seq_file *f, void *p) |
| { |
| return (taskq_seq_show_impl(f, p, B_TRUE)); |
| } |
| |
| static int |
| taskq_seq_show(struct seq_file *f, void *p) |
| { |
| return (taskq_seq_show_impl(f, p, B_FALSE)); |
| } |
| |
| static void * |
| taskq_seq_start(struct seq_file *f, loff_t *pos) |
| { |
| struct list_head *p; |
| loff_t n = *pos; |
| |
| down_read(&tq_list_sem); |
| if (!n) |
| taskq_seq_show_headers(f); |
| |
| p = tq_list.next; |
| while (n--) { |
| p = p->next; |
| if (p == &tq_list) |
| return (NULL); |
| } |
| |
| return (list_entry(p, taskq_t, tq_taskqs)); |
| } |
| |
| static void * |
| taskq_seq_next(struct seq_file *f, void *p, loff_t *pos) |
| { |
| taskq_t *tq = p; |
| |
| ++*pos; |
| return ((tq->tq_taskqs.next == &tq_list) ? |
| NULL : list_entry(tq->tq_taskqs.next, taskq_t, tq_taskqs)); |
| } |
| |
| static void |
| slab_seq_show_headers(struct seq_file *f) |
| { |
| seq_printf(f, |
| "--------------------- cache ----------" |
| "--------------------------------------------- " |
| "----- slab ------ " |
| "---- object ----- " |
| "--- emergency ---\n"); |
| seq_printf(f, |
| "name " |
| " flags size alloc slabsize objsize " |
| "total alloc max " |
| "total alloc max " |
| "dlock alloc max\n"); |
| } |
| |
| static int |
| slab_seq_show(struct seq_file *f, void *p) |
| { |
| spl_kmem_cache_t *skc = p; |
| |
| ASSERT(skc->skc_magic == SKC_MAGIC); |
| |
| if (skc->skc_flags & KMC_SLAB) { |
| /* |
| * This cache is backed by a generic Linux kmem cache which |
| * has its own accounting. For these caches we only track |
| * the number of active allocated objects that exist within |
| * the underlying Linux slabs. For the overall statistics of |
| * the underlying Linux cache please refer to /proc/slabinfo. |
| */ |
| spin_lock(&skc->skc_lock); |
| seq_printf(f, "%-36s ", skc->skc_name); |
| seq_printf(f, "0x%05lx %9s %9lu %8s %8u " |
| "%5s %5s %5s %5s %5lu %5s %5s %5s %5s\n", |
| (long unsigned)skc->skc_flags, |
| "-", |
| (long unsigned)(skc->skc_obj_size * skc->skc_obj_alloc), |
| "-", |
| (unsigned)skc->skc_obj_size, |
| "-", "-", "-", "-", |
| (long unsigned)skc->skc_obj_alloc, |
| "-", "-", "-", "-"); |
| spin_unlock(&skc->skc_lock); |
| return (0); |
| } |
| |
| spin_lock(&skc->skc_lock); |
| seq_printf(f, "%-36s ", skc->skc_name); |
| seq_printf(f, "0x%05lx %9lu %9lu %8u %8u " |
| "%5lu %5lu %5lu %5lu %5lu %5lu %5lu %5lu %5lu\n", |
| (long unsigned)skc->skc_flags, |
| (long unsigned)(skc->skc_slab_size * skc->skc_slab_total), |
| (long unsigned)(skc->skc_obj_size * skc->skc_obj_alloc), |
| (unsigned)skc->skc_slab_size, |
| (unsigned)skc->skc_obj_size, |
| (long unsigned)skc->skc_slab_total, |
| (long unsigned)skc->skc_slab_alloc, |
| (long unsigned)skc->skc_slab_max, |
| (long unsigned)skc->skc_obj_total, |
| (long unsigned)skc->skc_obj_alloc, |
| (long unsigned)skc->skc_obj_max, |
| (long unsigned)skc->skc_obj_deadlock, |
| (long unsigned)skc->skc_obj_emergency, |
| (long unsigned)skc->skc_obj_emergency_max); |
| spin_unlock(&skc->skc_lock); |
| return (0); |
| } |
| |
| static void * |
| slab_seq_start(struct seq_file *f, loff_t *pos) |
| { |
| struct list_head *p; |
| loff_t n = *pos; |
| |
| down_read(&spl_kmem_cache_sem); |
| if (!n) |
| slab_seq_show_headers(f); |
| |
| p = spl_kmem_cache_list.next; |
| while (n--) { |
| p = p->next; |
| if (p == &spl_kmem_cache_list) |
| return (NULL); |
| } |
| |
| return (list_entry(p, spl_kmem_cache_t, skc_list)); |
| } |
| |
| static void * |
| slab_seq_next(struct seq_file *f, void *p, loff_t *pos) |
| { |
| spl_kmem_cache_t *skc = p; |
| |
| ++*pos; |
| return ((skc->skc_list.next == &spl_kmem_cache_list) ? |
| NULL : list_entry(skc->skc_list.next, spl_kmem_cache_t, skc_list)); |
| } |
| |
| static void |
| slab_seq_stop(struct seq_file *f, void *v) |
| { |
| up_read(&spl_kmem_cache_sem); |
| } |
| |
| static struct seq_operations slab_seq_ops = { |
| .show = slab_seq_show, |
| .start = slab_seq_start, |
| .next = slab_seq_next, |
| .stop = slab_seq_stop, |
| }; |
| |
| static int |
| proc_slab_open(struct inode *inode, struct file *filp) |
| { |
| return (seq_open(filp, &slab_seq_ops)); |
| } |
| |
| static const kstat_proc_op_t proc_slab_operations = { |
| #ifdef HAVE_PROC_OPS_STRUCT |
| .proc_open = proc_slab_open, |
| .proc_read = seq_read, |
| .proc_lseek = seq_lseek, |
| .proc_release = seq_release, |
| #else |
| .open = proc_slab_open, |
| .read = seq_read, |
| .llseek = seq_lseek, |
| .release = seq_release, |
| #endif |
| }; |
| |
| static void |
| taskq_seq_stop(struct seq_file *f, void *v) |
| { |
| up_read(&tq_list_sem); |
| } |
| |
| static struct seq_operations taskq_all_seq_ops = { |
| .show = taskq_all_seq_show, |
| .start = taskq_seq_start, |
| .next = taskq_seq_next, |
| .stop = taskq_seq_stop, |
| }; |
| |
| static struct seq_operations taskq_seq_ops = { |
| .show = taskq_seq_show, |
| .start = taskq_seq_start, |
| .next = taskq_seq_next, |
| .stop = taskq_seq_stop, |
| }; |
| |
| static int |
| proc_taskq_all_open(struct inode *inode, struct file *filp) |
| { |
| return (seq_open(filp, &taskq_all_seq_ops)); |
| } |
| |
| static int |
| proc_taskq_open(struct inode *inode, struct file *filp) |
| { |
| return (seq_open(filp, &taskq_seq_ops)); |
| } |
| |
| static const kstat_proc_op_t proc_taskq_all_operations = { |
| #ifdef HAVE_PROC_OPS_STRUCT |
| .proc_open = proc_taskq_all_open, |
| .proc_read = seq_read, |
| .proc_lseek = seq_lseek, |
| .proc_release = seq_release, |
| #else |
| .open = proc_taskq_all_open, |
| .read = seq_read, |
| .llseek = seq_lseek, |
| .release = seq_release, |
| #endif |
| }; |
| |
| static const kstat_proc_op_t proc_taskq_operations = { |
| #ifdef HAVE_PROC_OPS_STRUCT |
| .proc_open = proc_taskq_open, |
| .proc_read = seq_read, |
| .proc_lseek = seq_lseek, |
| .proc_release = seq_release, |
| #else |
| .open = proc_taskq_open, |
| .read = seq_read, |
| .llseek = seq_lseek, |
| .release = seq_release, |
| #endif |
| }; |
| |
| static struct ctl_table spl_kmem_table[] = { |
| #ifdef DEBUG_KMEM |
| { |
| .procname = "kmem_used", |
| .data = &kmem_alloc_used, |
| #ifdef HAVE_ATOMIC64_T |
| .maxlen = sizeof (atomic64_t), |
| #else |
| .maxlen = sizeof (atomic_t), |
| #endif /* HAVE_ATOMIC64_T */ |
| .mode = 0444, |
| .proc_handler = &proc_domemused, |
| }, |
| { |
| .procname = "kmem_max", |
| .data = &kmem_alloc_max, |
| .maxlen = sizeof (unsigned long), |
| .extra1 = &table_min, |
| .extra2 = &table_max, |
| .mode = 0444, |
| .proc_handler = &proc_doulongvec_minmax, |
| }, |
| #endif /* DEBUG_KMEM */ |
| { |
| .procname = "slab_kmem_total", |
| .data = (void *)(KMC_KMEM | KMC_TOTAL), |
| .maxlen = sizeof (unsigned long), |
| .extra1 = &table_min, |
| .extra2 = &table_max, |
| .mode = 0444, |
| .proc_handler = &proc_doslab, |
| }, |
| { |
| .procname = "slab_kmem_alloc", |
| .data = (void *)(KMC_KMEM | KMC_ALLOC), |
| .maxlen = sizeof (unsigned long), |
| .extra1 = &table_min, |
| .extra2 = &table_max, |
| .mode = 0444, |
| .proc_handler = &proc_doslab, |
| }, |
| { |
| .procname = "slab_kmem_max", |
| .data = (void *)(KMC_KMEM | KMC_MAX), |
| .maxlen = sizeof (unsigned long), |
| .extra1 = &table_min, |
| .extra2 = &table_max, |
| .mode = 0444, |
| .proc_handler = &proc_doslab, |
| }, |
| { |
| .procname = "slab_vmem_total", |
| .data = (void *)(KMC_VMEM | KMC_TOTAL), |
| .maxlen = sizeof (unsigned long), |
| .extra1 = &table_min, |
| .extra2 = &table_max, |
| .mode = 0444, |
| .proc_handler = &proc_doslab, |
| }, |
| { |
| .procname = "slab_vmem_alloc", |
| .data = (void *)(KMC_VMEM | KMC_ALLOC), |
| .maxlen = sizeof (unsigned long), |
| .extra1 = &table_min, |
| .extra2 = &table_max, |
| .mode = 0444, |
| .proc_handler = &proc_doslab, |
| }, |
| { |
| .procname = "slab_vmem_max", |
| .data = (void *)(KMC_VMEM | KMC_MAX), |
| .maxlen = sizeof (unsigned long), |
| .extra1 = &table_min, |
| .extra2 = &table_max, |
| .mode = 0444, |
| .proc_handler = &proc_doslab, |
| }, |
| {}, |
| }; |
| |
| static struct ctl_table spl_kstat_table[] = { |
| {}, |
| }; |
| |
| static struct ctl_table spl_table[] = { |
| /* |
| * NB No .strategy entries have been provided since |
| * sysctl(8) prefers to go via /proc for portability. |
| */ |
| { |
| .procname = "gitrev", |
| .data = spl_gitrev, |
| .maxlen = sizeof (spl_gitrev), |
| .mode = 0444, |
| .proc_handler = &proc_dostring, |
| }, |
| { |
| .procname = "hostid", |
| .data = &spl_hostid, |
| .maxlen = sizeof (unsigned long), |
| .mode = 0644, |
| .proc_handler = &proc_dohostid, |
| }, |
| { |
| .procname = "kmem", |
| .mode = 0555, |
| .child = spl_kmem_table, |
| }, |
| { |
| .procname = "kstat", |
| .mode = 0555, |
| .child = spl_kstat_table, |
| }, |
| {}, |
| }; |
| |
| static struct ctl_table spl_dir[] = { |
| { |
| .procname = "spl", |
| .mode = 0555, |
| .child = spl_table, |
| }, |
| {} |
| }; |
| |
| static struct ctl_table spl_root[] = { |
| { |
| #ifdef HAVE_CTL_NAME |
| .ctl_name = CTL_KERN, |
| #endif |
| .procname = "kernel", |
| .mode = 0555, |
| .child = spl_dir, |
| }, |
| {} |
| }; |
| |
| int |
| spl_proc_init(void) |
| { |
| int rc = 0; |
| |
| spl_header = register_sysctl_table(spl_root); |
| if (spl_header == NULL) |
| return (-EUNATCH); |
| |
| proc_spl = proc_mkdir("spl", NULL); |
| if (proc_spl == NULL) { |
| rc = -EUNATCH; |
| goto out; |
| } |
| |
| proc_spl_taskq_all = proc_create_data("taskq-all", 0444, proc_spl, |
| &proc_taskq_all_operations, NULL); |
| if (proc_spl_taskq_all == NULL) { |
| rc = -EUNATCH; |
| goto out; |
| } |
| |
| proc_spl_taskq = proc_create_data("taskq", 0444, proc_spl, |
| &proc_taskq_operations, NULL); |
| if (proc_spl_taskq == NULL) { |
| rc = -EUNATCH; |
| goto out; |
| } |
| |
| proc_spl_kmem = proc_mkdir("kmem", proc_spl); |
| if (proc_spl_kmem == NULL) { |
| rc = -EUNATCH; |
| goto out; |
| } |
| |
| proc_spl_kmem_slab = proc_create_data("slab", 0444, proc_spl_kmem, |
| &proc_slab_operations, NULL); |
| if (proc_spl_kmem_slab == NULL) { |
| rc = -EUNATCH; |
| goto out; |
| } |
| |
| proc_spl_kstat = proc_mkdir("kstat", proc_spl); |
| if (proc_spl_kstat == NULL) { |
| rc = -EUNATCH; |
| goto out; |
| } |
| out: |
| if (rc) { |
| remove_proc_entry("kstat", proc_spl); |
| remove_proc_entry("slab", proc_spl_kmem); |
| remove_proc_entry("kmem", proc_spl); |
| remove_proc_entry("taskq-all", proc_spl); |
| remove_proc_entry("taskq", proc_spl); |
| remove_proc_entry("spl", NULL); |
| unregister_sysctl_table(spl_header); |
| } |
| |
| return (rc); |
| } |
| |
| void |
| spl_proc_fini(void) |
| { |
| remove_proc_entry("kstat", proc_spl); |
| remove_proc_entry("slab", proc_spl_kmem); |
| remove_proc_entry("kmem", proc_spl); |
| remove_proc_entry("taskq-all", proc_spl); |
| remove_proc_entry("taskq", proc_spl); |
| remove_proc_entry("spl", NULL); |
| |
| ASSERT(spl_header != NULL); |
| unregister_sysctl_table(spl_header); |
| } |