/*****************************************************************************\ * 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 . * UCRL-CODE-235197 * * This file is part of the SPL, Solaris Porting Layer. * For details, see . * * 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 . ***************************************************************************** * Solaris Porting Layer (SPL) Proc Implementation. \*****************************************************************************/ #include #include #include #include #include #include #include #ifdef SS_DEBUG_SUBSYS #undef SS_DEBUG_SUBSYS #endif #define SS_DEBUG_SUBSYS SS_PROC #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 #ifdef DEBUG_KMEM static unsigned long table_min = 0; static unsigned long table_max = ~0; #endif static struct ctl_table_header *spl_header = NULL; static struct proc_dir_entry *proc_spl = NULL; #ifdef DEBUG_KMEM static struct proc_dir_entry *proc_spl_kmem = NULL; static struct proc_dir_entry *proc_spl_kmem_slab = NULL; #endif /* DEBUG_KMEM */ 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_LOG SPL_PROC_HANDLER(proc_dobitmasks) { unsigned long *mask = table->data; int is_subsys = (mask == &spl_debug_subsys) ? 1 : 0; int is_printk = (mask == &spl_debug_printk) ? 1 : 0; int size = 512, rc; char *str; SENTRY; str = kmem_alloc(size, KM_SLEEP); if (str == NULL) SRETURN(-ENOMEM); if (write) { rc = proc_copyin_string(str, size, buffer, *lenp); if (rc < 0) SRETURN(rc); rc = spl_debug_str2mask(mask, str, is_subsys); /* Always print BUG/ASSERT to console, so keep this mask */ if (is_printk) *mask |= SD_EMERG; *ppos += *lenp; } else { rc = spl_debug_mask2str(str, size, *mask, is_subsys); if (*ppos >= rc) rc = 0; else rc = proc_copyout_string(buffer, *lenp, str + *ppos, "\n"); if (rc >= 0) { *lenp = rc; *ppos += rc; } } kmem_free(str, size); SRETURN(rc); } SPL_PROC_HANDLER(proc_debug_mb) { char str[32]; int rc, len; SENTRY; if (write) { rc = proc_copyin_string(str, sizeof(str), buffer, *lenp); if (rc < 0) SRETURN(rc); rc = spl_debug_set_mb(simple_strtoul(str, NULL, 0)); *ppos += *lenp; } else { len = snprintf(str, sizeof(str), "%d", spl_debug_get_mb()); if (*ppos >= len) rc = 0; else rc = proc_copyout_string(buffer,*lenp,str+*ppos,"\n"); if (rc >= 0) { *lenp = rc; *ppos += rc; } } SRETURN(rc); } SPL_PROC_HANDLER(proc_dump_kernel) { SENTRY; if (write) { spl_debug_dumplog(0); *ppos += *lenp; } else { *lenp = 0; } SRETURN(0); } SPL_PROC_HANDLER(proc_force_bug) { SENTRY; if (write) PANIC("Crashing due to forced panic\n"); else *lenp = 0; SRETURN(0); } SPL_PROC_HANDLER(proc_console_max_delay_cs) { int rc, max_delay_cs; spl_ctl_table dummy = *table; long d; SENTRY; dummy.data = &max_delay_cs; dummy.proc_handler = &proc_dointvec; if (write) { max_delay_cs = 0; rc = spl_proc_dointvec(&dummy,write,filp,buffer,lenp,ppos); if (rc < 0) SRETURN(rc); if (max_delay_cs <= 0) SRETURN(-EINVAL); d = (max_delay_cs * HZ) / 100; if (d == 0 || d < spl_console_min_delay) SRETURN(-EINVAL); spl_console_max_delay = d; } else { max_delay_cs = (spl_console_max_delay * 100) / HZ; rc = spl_proc_dointvec(&dummy,write,filp,buffer,lenp,ppos); } SRETURN(rc); } SPL_PROC_HANDLER(proc_console_min_delay_cs) { int rc, min_delay_cs; spl_ctl_table dummy = *table; long d; SENTRY; dummy.data = &min_delay_cs; dummy.proc_handler = &proc_dointvec; if (write) { min_delay_cs = 0; rc = spl_proc_dointvec(&dummy,write,filp,buffer,lenp,ppos); if (rc < 0) SRETURN(rc); if (min_delay_cs <= 0) SRETURN(-EINVAL); d = (min_delay_cs * HZ) / 100; if (d == 0 || d > spl_console_max_delay) SRETURN(-EINVAL); spl_console_min_delay = d; } else { min_delay_cs = (spl_console_min_delay * 100) / HZ; rc = spl_proc_dointvec(&dummy,write,filp,buffer,lenp,ppos); } SRETURN(rc); } SPL_PROC_HANDLER(proc_console_backoff) { int rc, backoff; spl_ctl_table dummy = *table; SENTRY; dummy.data = &backoff; dummy.proc_handler = &proc_dointvec; if (write) { backoff = 0; rc = spl_proc_dointvec(&dummy,write,filp,buffer,lenp,ppos); if (rc < 0) SRETURN(rc); if (backoff <= 0) SRETURN(-EINVAL); spl_console_backoff = backoff; } else { backoff = spl_console_backoff; rc = spl_proc_dointvec(&dummy,write,filp,buffer,lenp,ppos); } SRETURN(rc); } #endif /* DEBUG_LOG */ #ifdef DEBUG_KMEM SPL_PROC_HANDLER(proc_domemused) { int rc = 0; unsigned long min = 0, max = ~0, val; spl_ctl_table dummy = *table; SENTRY; 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 = spl_proc_doulongvec_minmax(&dummy, write, filp, buffer, lenp, ppos); } SRETURN(rc); } SPL_PROC_HANDLER(proc_doslab) { int rc = 0; unsigned long min = 0, max = ~0, val = 0, mask; spl_ctl_table dummy = *table; spl_kmem_cache_t *skc; SENTRY; 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 = spl_proc_doulongvec_minmax(&dummy, write, filp, buffer, lenp, ppos); } SRETURN(rc); } #endif /* DEBUG_KMEM */ SPL_PROC_HANDLER(proc_dohostid) { int len, rc = 0; char *end, str[32]; SENTRY; if (write) { /* We can't use spl_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) SRETURN(rc); spl_hostid = simple_strtoul(str, &end, 16); if (str == end) SRETURN(-EINVAL); } else { len = snprintf(str, sizeof(str), "%lx", spl_hostid); if (*ppos >= len) rc = 0; else rc = proc_copyout_string(buffer,*lenp,str+*ppos,"\n"); if (rc >= 0) { *lenp = rc; *ppos += rc; } } SRETURN(rc); } #ifndef HAVE_KALLSYMS_LOOKUP_NAME SPL_PROC_HANDLER(proc_dokallsyms_lookup_name) { int len, rc = 0; char *end, str[32]; SENTRY; if (write) { /* This may only be set once at module load time */ if (spl_kallsyms_lookup_name_fn != SYMBOL_POISON) SRETURN(-EEXIST); /* We can't use spl_proc_doulongvec_minmax() in the write * case here because the address 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) SRETURN(rc); spl_kallsyms_lookup_name_fn = (kallsyms_lookup_name_t)simple_strtoul(str, &end, 16); wake_up(&spl_kallsyms_lookup_name_waitq); if (str == end) SRETURN(-EINVAL); *ppos += *lenp; } else { len = snprintf(str, sizeof(str), "%lx", (unsigned long)spl_kallsyms_lookup_name_fn); if (*ppos >= len) rc = 0; else rc = proc_copyout_string(buffer,*lenp,str+*ppos,"\n"); if (rc >= 0) { *lenp = rc; *ppos += rc; } } SRETURN(rc); } #endif /* HAVE_KALLSYMS_LOOKUP_NAME */ SPL_PROC_HANDLER(proc_doavailrmem) { int len, rc = 0; char str[32]; SENTRY; if (write) { *ppos += *lenp; } else { len = snprintf(str, sizeof(str), "%lu", (unsigned long)availrmem); if (*ppos >= len) rc = 0; else rc = proc_copyout_string(buffer,*lenp,str+*ppos,"\n"); if (rc >= 0) { *lenp = rc; *ppos += rc; } } SRETURN(rc); } SPL_PROC_HANDLER(proc_dofreemem) { int len, rc = 0; char str[32]; SENTRY; if (write) { *ppos += *lenp; } else { len = snprintf(str, sizeof(str), "%lu", (unsigned long)freemem); if (*ppos >= len) rc = 0; else rc = proc_copyout_string(buffer,*lenp,str+*ppos,"\n"); if (rc >= 0) { *lenp = rc; *ppos += rc; } } SRETURN(rc); } #ifdef DEBUG_KMEM 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); /* * Backed by Linux slab see /proc/slabinfo. */ if (skc->skc_flags & KMC_SLAB) 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; SENTRY; 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) SRETURN(NULL); } SRETURN(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; SENTRY; ++*pos; SRETURN((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 struct file_operations proc_slab_operations = { .open = proc_slab_open, .read = seq_read, .llseek = seq_lseek, .release = seq_release, }; #endif /* DEBUG_KMEM */ #ifdef DEBUG_LOG static struct ctl_table spl_debug_table[] = { { .procname = "subsystem", .data = &spl_debug_subsys, .maxlen = sizeof(unsigned long), .mode = 0644, .proc_handler = &proc_dobitmasks }, { .procname = "mask", .data = &spl_debug_mask, .maxlen = sizeof(unsigned long), .mode = 0644, .proc_handler = &proc_dobitmasks }, { .procname = "printk", .data = &spl_debug_printk, .maxlen = sizeof(unsigned long), .mode = 0644, .proc_handler = &proc_dobitmasks }, { .procname = "mb", .mode = 0644, .proc_handler = &proc_debug_mb, }, { .procname = "binary", .data = &spl_debug_binary, .maxlen = sizeof(int), .mode = 0644, .proc_handler = &proc_dointvec, }, { .procname = "catastrophe", .data = &spl_debug_catastrophe, .maxlen = sizeof(int), .mode = 0444, .proc_handler = &proc_dointvec, }, { .procname = "panic_on_bug", .data = &spl_debug_panic_on_bug, .maxlen = sizeof(int), .mode = 0644, .proc_handler = &proc_dointvec }, { .procname = "path", .data = spl_debug_file_path, .maxlen = sizeof(spl_debug_file_path), .mode = 0644, .proc_handler = &proc_dostring, }, { .procname = "dump", .mode = 0200, .proc_handler = &proc_dump_kernel, }, { .procname = "force_bug", .mode = 0200, .proc_handler = &proc_force_bug, }, { .procname = "console_ratelimit", .data = &spl_console_ratelimit, .maxlen = sizeof(int), .mode = 0644, .proc_handler = &proc_dointvec, }, { .procname = "console_max_delay_centisecs", .maxlen = sizeof(int), .mode = 0644, .proc_handler = &proc_console_max_delay_cs, }, { .procname = "console_min_delay_centisecs", .maxlen = sizeof(int), .mode = 0644, .proc_handler = &proc_console_min_delay_cs, }, { .procname = "console_backoff", .maxlen = sizeof(int), .mode = 0644, .proc_handler = &proc_console_backoff, }, { .procname = "stack_max", .data = &spl_debug_stack, .maxlen = sizeof(int), .mode = 0444, .proc_handler = &proc_dointvec, }, {0}, }; #endif /* DEBUG_LOG */ static struct ctl_table spl_vm_table[] = { { .procname = "minfree", .data = &minfree, .maxlen = sizeof(int), .mode = 0644, .proc_handler = &proc_dointvec, }, { .procname = "desfree", .data = &desfree, .maxlen = sizeof(int), .mode = 0644, .proc_handler = &proc_dointvec, }, { .procname = "lotsfree", .data = &lotsfree, .maxlen = sizeof(int), .mode = 0644, .proc_handler = &proc_dointvec, }, { .procname = "needfree", .data = &needfree, .maxlen = sizeof(int), .mode = 0444, .proc_handler = &proc_dointvec, }, { .procname = "swapfs_minfree", .data = &swapfs_minfree, .maxlen = sizeof(int), .mode = 0644, .proc_handler = &proc_dointvec, }, { .procname = "swapfs_reserve", .data = &swapfs_reserve, .maxlen = sizeof(int), .mode = 0644, .proc_handler = &proc_dointvec, }, { .procname = "availrmem", .mode = 0444, .proc_handler = &proc_doavailrmem, }, { .procname = "freemem", .data = (void *)2, .maxlen = sizeof(int), .mode = 0444, .proc_handler = &proc_dofreemem, }, { .procname = "physmem", .data = &physmem, .maxlen = sizeof(int), .mode = 0444, .proc_handler = &proc_dointvec, }, {0}, }; #ifdef DEBUG_KMEM static struct ctl_table spl_kmem_table[] = { { .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, }, { .procname = "vmem_used", .data = &vmem_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 = "vmem_max", .data = &vmem_alloc_max, .maxlen = sizeof(unsigned long), .extra1 = &table_min, .extra2 = &table_max, .mode = 0444, .proc_handler = &proc_doulongvec_minmax, }, { .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, }, {0}, }; #endif /* DEBUG_KMEM */ static struct ctl_table spl_kstat_table[] = { {0}, }; static struct ctl_table spl_table[] = { /* NB No .strategy entries have been provided since * sysctl(8) prefers to go via /proc for portability. */ { .procname = "version", .data = spl_version, .maxlen = sizeof(spl_version), .mode = 0444, .proc_handler = &proc_dostring, }, { .procname = "hostid", .data = &spl_hostid, .maxlen = sizeof(unsigned long), .mode = 0644, .proc_handler = &proc_dohostid, }, #ifndef HAVE_KALLSYMS_LOOKUP_NAME { .procname = "kallsyms_lookup_name", .data = &spl_kallsyms_lookup_name_fn, .maxlen = sizeof(unsigned long), .mode = 0644, .proc_handler = &proc_dokallsyms_lookup_name, }, #endif #ifdef DEBUG_LOG { .procname = "debug", .mode = 0555, .child = spl_debug_table, }, #endif { .procname = "vm", .mode = 0555, .child = spl_vm_table, }, #ifdef DEBUG_KMEM { .procname = "kmem", .mode = 0555, .child = spl_kmem_table, }, #endif { .procname = "kstat", .mode = 0555, .child = spl_kstat_table, }, { 0 }, }; static struct ctl_table spl_dir[] = { { .procname = "spl", .mode = 0555, .child = spl_table, }, { 0 } }; static struct ctl_table spl_root[] = { { #ifdef HAVE_CTL_NAME .ctl_name = CTL_KERN, #endif .procname = "kernel", .mode = 0555, .child = spl_dir, }, { 0 } }; int spl_proc_init(void) { int rc = 0; SENTRY; spl_header = register_sysctl_table(spl_root); if (spl_header == NULL) SRETURN(-EUNATCH); proc_spl = proc_mkdir("spl", NULL); if (proc_spl == NULL) SGOTO(out, rc = -EUNATCH); #ifdef DEBUG_KMEM proc_spl_kmem = proc_mkdir("kmem", proc_spl); if (proc_spl_kmem == NULL) SGOTO(out, rc = -EUNATCH); proc_spl_kmem_slab = proc_create_data("slab", 0444, proc_spl_kmem, &proc_slab_operations, NULL); if (proc_spl_kmem_slab == NULL) SGOTO(out, rc = -EUNATCH); #endif /* DEBUG_KMEM */ proc_spl_kstat = proc_mkdir("kstat", proc_spl); if (proc_spl_kstat == NULL) SGOTO(out, rc = -EUNATCH); out: if (rc) { remove_proc_entry("kstat", proc_spl); #ifdef DEBUG_KMEM remove_proc_entry("slab", proc_spl_kmem); remove_proc_entry("kmem", proc_spl); #endif remove_proc_entry("spl", NULL); unregister_sysctl_table(spl_header); } SRETURN(rc); } void spl_proc_fini(void) { SENTRY; remove_proc_entry("kstat", proc_spl); #ifdef DEBUG_KMEM remove_proc_entry("slab", proc_spl_kmem); remove_proc_entry("kmem", proc_spl); #endif remove_proc_entry("spl", NULL); ASSERT(spl_header != NULL); unregister_sysctl_table(spl_header); SEXIT; }