mirror_zfs/module/spl/spl-proc.c
Brian Behlendorf 055ffd98cf Autoconf --enable-debug-* cleanup
Cleanup the --enable-debug-* configure options, this has been pending
for quite some time and I am glad I finally got to it.  To summerize:

1) All SPL_AC_DEBUG_* macros were updated to be a more autoconf
friendly.  This mainly involved shift to the GNU approved usage of
AC_ARG_ENABLE and ensuring AS_IF is used rather than directly using
an if [ test ] construct.

2) --enable-debug-kmem=yes by default.  This simply enabled keeping
a running tally of total memory allocated and freed and reporting a
memory leak if there was one at module unload.  Additionally, it
ensure /proc/spl/kmem/slab will exist by default which is handy.
The overhead is low for this and it should not impact performance.

3) --enable-debug-kmem-tracking=no by default.  This option was added
to provide a configure option to enable to detailed memory allocation
tracking.  This support was always there but you had to know where to
turn it on.  By default this support is disabled because it is known
to badly hurt performence, however it is invaluable when chasing a
memory leak.

4) --enable-debug-kstat removed.  After further reflection I can't see
why you would ever really want to turn this support off.  It is now
always on which had the nice side effect of simplifying the proc handling
code in spl-proc.c.  We can now always assume the top level directory
will be there.

5) --enable-debug-callb removed.  This never really did anything, it was
put in provisionally because it might have been needed.  It turns out
it was not so I am just removing it to prevent confusion.
2009-10-30 13:58:51 -07:00

1090 lines
32 KiB
C

/*
* This file is part of the SPL: Solaris Porting Layer.
*
* Copyright (c) 2008 Lawrence Livermore National Security, LLC.
* Produced at Lawrence Livermore National Laboratory
* Written by:
* Brian Behlendorf <behlendorf1@llnl.gov>,
* Herb Wartens <wartens2@llnl.gov>,
* Jim Garlick <garlick@llnl.gov>
* UCRL-CODE-235197
*
* This 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.
*
* This 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 this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include <sys/proc.h>
#ifdef DEBUG_SUBSYSTEM
#undef DEBUG_SUBSYSTEM
#endif
#define DEBUG_SUBSYSTEM S_PROC
#ifdef DEBUG_KMEM
static unsigned long table_min = 0;
static unsigned long table_max = ~0;
#endif
#ifdef CONFIG_SYSCTL
static struct ctl_table_header *spl_header = NULL;
#endif /* CONFIG_SYSCTL */
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;
#ifdef HAVE_CTL_UNNUMBERED
#define CTL_SPL CTL_UNNUMBERED
#define CTL_SPL_DEBUG CTL_UNNUMBERED
#define CTL_SPL_VM CTL_UNNUMBERED
#define CTL_SPL_MUTEX CTL_UNNUMBERED
#define CTL_SPL_KMEM CTL_UNNUMBERED
#define CTL_SPL_KSTAT CTL_UNNUMBERED
#define CTL_VERSION CTL_UNNUMBERED /* Version */
#define CTL_HOSTID CTL_UNNUMBERED /* Host id by /usr/bin/hostid */
#define CTL_HW_SERIAL CTL_UNNUMBERED /* HW serial number by hostid */
#define CTL_KALLSYMS CTL_UNNUMBERED /* kallsyms_lookup_name addr */
#define CTL_DEBUG_SUBSYS CTL_UNNUMBERED /* Debug subsystem */
#define CTL_DEBUG_MASK CTL_UNNUMBERED /* Debug mask */
#define CTL_DEBUG_PRINTK CTL_UNNUMBERED /* All messages to console */
#define CTL_DEBUG_MB CTL_UNNUMBERED /* Debug buffer size */
#define CTL_DEBUG_BINARY CTL_UNNUMBERED /* Binary data in buffer */
#define CTL_DEBUG_CATASTROPHE CTL_UNNUMBERED /* Set if BUG'd or panic'd */
#define CTL_DEBUG_PANIC_ON_BUG CTL_UNNUMBERED /* Should panic on BUG */
#define CTL_DEBUG_PATH CTL_UNNUMBERED /* Dump log location */
#define CTL_DEBUG_DUMP CTL_UNNUMBERED /* Dump debug buffer to file */
#define CTL_DEBUG_FORCE_BUG CTL_UNNUMBERED /* Hook to force a BUG */
#define CTL_DEBUG_STACK_SIZE CTL_UNNUMBERED /* Max observed stack size */
#define CTL_CONSOLE_RATELIMIT CTL_UNNUMBERED /* Ratelimit console messages */
#define CTL_CONSOLE_MAX_DELAY_CS CTL_UNNUMBERED /* Max delay skip messages */
#define CTL_CONSOLE_MIN_DELAY_CS CTL_UNNUMBERED /* Init delay skip messages */
#define CTL_CONSOLE_BACKOFF CTL_UNNUMBERED /* Delay increase factor */
#define CTL_VM_MINFREE CTL_UNNUMBERED /* Minimum free memory */
#define CTL_VM_DESFREE CTL_UNNUMBERED /* Desired free memory */
#define CTL_VM_LOTSFREE CTL_UNNUMBERED /* Lots of free memory */
#define CTL_VM_NEEDFREE CTL_UNNUMBERED /* Need free memory */
#define CTL_VM_SWAPFS_MINFREE CTL_UNNUMBERED /* Minimum swapfs memory */
#define CTL_VM_SWAPFS_RESERVE CTL_UNNUMBERED /* Reserved swapfs memory */
#define CTL_VM_AVAILRMEM CTL_UNNUMBERED /* Easily available memory */
#define CTL_VM_FREEMEM CTL_UNNUMBERED /* Free memory */
#define CTL_VM_PHYSMEM CTL_UNNUMBERED /* Total physical memory */
#ifdef DEBUG_KMEM
#define CTL_KMEM_KMEMUSED CTL_UNNUMBERED /* Alloc'd kmem bytes */
#define CTL_KMEM_KMEMMAX CTL_UNNUMBERED /* Max alloc'd by kmem bytes */
#define CTL_KMEM_VMEMUSED CTL_UNNUMBERED /* Alloc'd vmem bytes */
#define CTL_KMEM_VMEMMAX CTL_UNNUMBERED /* Max alloc'd by vmem bytes */
#define CTL_KMEM_ALLOC_FAILED CTL_UNNUMBERED /* Cache allocations failed */
#endif
#else /* HAVE_CTL_UNNUMBERED */
enum {
CTL_SPL = 0x87,
CTL_SPL_DEBUG = 0x88,
CTL_SPL_VM = 0x89,
CTL_SPL_MUTEX = 0x90,
CTL_SPL_KMEM = 0x91,
CTL_SPL_KSTAT = 0x92,
};
enum {
CTL_VERSION = 1, /* Version */
CTL_HOSTID, /* Host id reported by /usr/bin/hostid */
CTL_HW_SERIAL, /* Hardware serial number from hostid */
CTL_KALLSYMS, /* Address of kallsyms_lookup_name */
CTL_DEBUG_SUBSYS, /* Debug subsystem */
CTL_DEBUG_MASK, /* Debug mask */
CTL_DEBUG_PRINTK, /* Force all messages to console */
CTL_DEBUG_MB, /* Debug buffer size */
CTL_DEBUG_BINARY, /* Include binary data in buffer */
CTL_DEBUG_CATASTROPHE, /* Set if we have BUG'd or panic'd */
CTL_DEBUG_PANIC_ON_BUG, /* Set if we should panic on BUG */
CTL_DEBUG_PATH, /* Dump log location */
CTL_DEBUG_DUMP, /* Dump debug buffer to file */
CTL_DEBUG_FORCE_BUG, /* Hook to force a BUG */
CTL_DEBUG_STACK_SIZE, /* Max observed stack size */
CTL_CONSOLE_RATELIMIT, /* Ratelimit console messages */
CTL_CONSOLE_MAX_DELAY_CS, /* Max delay which we skip messages */
CTL_CONSOLE_MIN_DELAY_CS, /* Init delay which we skip messages */
CTL_CONSOLE_BACKOFF, /* Delay increase factor */
CTL_VM_MINFREE, /* Minimum free memory threshold */
CTL_VM_DESFREE, /* Desired free memory threshold */
CTL_VM_LOTSFREE, /* Lots of free memory threshold */
CTL_VM_NEEDFREE, /* Need free memory deficit */
CTL_VM_SWAPFS_MINFREE, /* Minimum swapfs memory */
CTL_VM_SWAPFS_RESERVE, /* Reserved swapfs memory */
CTL_VM_AVAILRMEM, /* Easily available memory */
CTL_VM_FREEMEM, /* Free memory */
CTL_VM_PHYSMEM, /* Total physical memory */
#ifdef DEBUG_KMEM
CTL_KMEM_KMEMUSED, /* Alloc'd kmem bytes */
CTL_KMEM_KMEMMAX, /* Max alloc'd by kmem bytes */
CTL_KMEM_VMEMUSED, /* Alloc'd vmem bytes */
CTL_KMEM_VMEMMAX, /* Max alloc'd by vmem bytes */
#endif
};
#endif /* HAVE_CTL_UNNUMBERED */
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;
}
static int
proc_dobitmasks(struct ctl_table *table, int write, struct file *filp,
void __user *buffer, size_t *lenp, loff_t *ppos)
{
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;
ENTRY;
str = kmem_alloc(size, KM_SLEEP);
if (str == NULL)
RETURN(-ENOMEM);
if (write) {
rc = proc_copyin_string(str, size, buffer, *lenp);
if (rc < 0)
RETURN(rc);
rc = spl_debug_str2mask(mask, str, is_subsys);
/* Always print BUG/ASSERT to console, so keep this mask */
if (is_printk)
*mask |= D_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);
RETURN(rc);
}
static int
proc_debug_mb(struct ctl_table *table, int write, struct file *filp,
void __user *buffer, size_t *lenp, loff_t *ppos)
{
char str[32];
int rc, len;
ENTRY;
if (write) {
rc = proc_copyin_string(str, sizeof(str), buffer, *lenp);
if (rc < 0)
RETURN(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;
}
}
RETURN(rc);
}
static int
proc_dump_kernel(struct ctl_table *table, int write, struct file *filp,
void __user *buffer, size_t *lenp, loff_t *ppos)
{
ENTRY;
if (write) {
spl_debug_dumplog(0);
*ppos += *lenp;
} else {
*lenp = 0;
}
RETURN(0);
}
static int
proc_force_bug(struct ctl_table *table, int write, struct file *filp,
void __user *buffer, size_t *lenp, loff_t *ppos)
{
ENTRY;
if (write) {
CERROR("Crashing due to forced SBUG\n");
SBUG();
/* Unreachable */
} else {
*lenp = 0;
}
RETURN(0);
}
static int
proc_console_max_delay_cs(struct ctl_table *table, int write, struct file *filp,
void __user *buffer, size_t *lenp, loff_t *ppos)
{
int rc, max_delay_cs;
struct ctl_table dummy = *table;
long d;
ENTRY;
dummy.data = &max_delay_cs;
dummy.proc_handler = &proc_dointvec;
if (write) {
max_delay_cs = 0;
rc = proc_dointvec(&dummy, write, filp, buffer, lenp, ppos);
if (rc < 0)
RETURN(rc);
if (max_delay_cs <= 0)
RETURN(-EINVAL);
d = (max_delay_cs * HZ) / 100;
if (d == 0 || d < spl_console_min_delay)
RETURN(-EINVAL);
spl_console_max_delay = d;
} else {
max_delay_cs = (spl_console_max_delay * 100) / HZ;
rc = proc_dointvec(&dummy, write, filp, buffer, lenp, ppos);
}
RETURN(rc);
}
static int
proc_console_min_delay_cs(struct ctl_table *table, int write, struct file *filp,
void __user *buffer, size_t *lenp, loff_t *ppos)
{
int rc, min_delay_cs;
struct ctl_table dummy = *table;
long d;
ENTRY;
dummy.data = &min_delay_cs;
dummy.proc_handler = &proc_dointvec;
if (write) {
min_delay_cs = 0;
rc = proc_dointvec(&dummy, write, filp, buffer, lenp, ppos);
if (rc < 0)
RETURN(rc);
if (min_delay_cs <= 0)
RETURN(-EINVAL);
d = (min_delay_cs * HZ) / 100;
if (d == 0 || d > spl_console_max_delay)
RETURN(-EINVAL);
spl_console_min_delay = d;
} else {
min_delay_cs = (spl_console_min_delay * 100) / HZ;
rc = proc_dointvec(&dummy, write, filp, buffer, lenp, ppos);
}
RETURN(rc);
}
static int
proc_console_backoff(struct ctl_table *table, int write, struct file *filp,
void __user *buffer, size_t *lenp, loff_t *ppos)
{
int rc, backoff;
struct ctl_table dummy = *table;
ENTRY;
dummy.data = &backoff;
dummy.proc_handler = &proc_dointvec;
if (write) {
backoff = 0;
rc = proc_dointvec(&dummy, write, filp, buffer, lenp, ppos);
if (rc < 0)
RETURN(rc);
if (backoff <= 0)
RETURN(-EINVAL);
spl_console_backoff = backoff;
} else {
backoff = spl_console_backoff;
rc = proc_dointvec(&dummy, write, filp, buffer, lenp, ppos);
}
RETURN(rc);
}
#ifdef DEBUG_KMEM
static int
proc_doatomic64(struct ctl_table *table, int write, struct file *filp,
void __user *buffer, size_t *lenp, loff_t *ppos)
{
int rc = 0;
unsigned long min = 0, max = ~0, val;
struct ctl_table dummy = *table;
ENTRY;
dummy.data = &val;
dummy.proc_handler = &proc_dointvec;
dummy.extra1 = &min;
dummy.extra2 = &max;
if (write) {
*ppos += *lenp;
} else {
val = atomic64_read((atomic64_t *)table->data);
rc = proc_doulongvec_minmax(&dummy, write, filp,
buffer, lenp, ppos);
}
RETURN(rc);
}
#endif /* DEBUG_KMEM */
static int
proc_dohostid(struct ctl_table *table, int write, struct file *filp,
void __user *buffer, size_t *lenp, loff_t *ppos)
{
int len, rc = 0;
int32_t val;
char *end, str[32];
ENTRY;
if (write) {
/* We can't use proc_doulongvec_minmax() in the write
* case hear 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);
val = simple_strtol(str, &end, 16);
if (str == end)
RETURN(-EINVAL);
spl_hostid = (long) val;
(void) snprintf(hw_serial, HW_HOSTID_LEN, "%u",
(val >= 0) ? val : -val);
hw_serial[HW_HOSTID_LEN - 1] = '\0';
*ppos += *lenp;
} 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;
}
}
RETURN(rc);
}
#ifndef HAVE_KALLSYMS_LOOKUP_NAME
static int
proc_dokallsyms_lookup_name(struct ctl_table *table, int write,
struct file *filp, void __user *buffer,
size_t *lenp, loff_t *ppos) {
int len, rc = 0;
char *end, str[32];
ENTRY;
if (write) {
/* This may only be set once at module load time */
if (spl_kallsyms_lookup_name_fn != SYMBOL_POISON)
RETURN(-EEXIST);
/* We can't use proc_doulongvec_minmax() in the write
* case hear 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)
RETURN(rc);
spl_kallsyms_lookup_name_fn =
(kallsyms_lookup_name_t)simple_strtoul(str, &end, 16);
if (str == end)
RETURN(-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;
}
}
RETURN(rc);
}
#endif /* HAVE_KALLSYMS_LOOKUP_NAME */
static int
proc_doavailrmem(struct ctl_table *table, int write, struct file *filp,
void __user *buffer, size_t *lenp, loff_t *ppos)
{
int len, rc = 0;
char str[32];
ENTRY;
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;
}
}
RETURN(rc);
}
static int
proc_dofreemem(struct ctl_table *table, int write, struct file *filp,
void __user *buffer, size_t *lenp, loff_t *ppos)
{
int len, rc = 0;
char str[32];
ENTRY;
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;
}
}
RETURN(rc);
}
#ifdef DEBUG_KMEM
static void
slab_seq_show_headers(struct seq_file *f)
{
seq_printf(f, "%-36s\n", "name");
}
static int
slab_seq_show(struct seq_file *f, void *p)
{
spl_kmem_cache_t *skc = p;
ASSERT(skc->skc_magic == SKC_MAGIC);
spin_lock(&skc->skc_lock);
seq_printf(f, "%-36s ", skc->skc_name);
seq_printf(f, "%u %u %u - %lu %lu %lu - %lu %lu %lu - %lu %lu %lu\n",
(unsigned)skc->skc_obj_size,
(unsigned)skc->skc_slab_objs,
(unsigned)skc->skc_slab_size,
(long unsigned)skc->skc_slab_fail,
(long unsigned)skc->skc_slab_create,
(long unsigned)skc->skc_slab_destroy,
(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);
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;
ENTRY;
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;
ENTRY;
++*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 struct file_operations proc_slab_operations = {
.open = proc_slab_open,
.read = seq_read,
.llseek = seq_lseek,
.release = seq_release,
};
#endif /* DEBUG_KMEM */
static struct ctl_table spl_debug_table[] = {
{
.ctl_name = CTL_DEBUG_SUBSYS,
.procname = "subsystem",
.data = &spl_debug_subsys,
.maxlen = sizeof(unsigned long),
.mode = 0644,
.proc_handler = &proc_dobitmasks
},
{
.ctl_name = CTL_DEBUG_MASK,
.procname = "mask",
.data = &spl_debug_mask,
.maxlen = sizeof(unsigned long),
.mode = 0644,
.proc_handler = &proc_dobitmasks
},
{
.ctl_name = CTL_DEBUG_PRINTK,
.procname = "printk",
.data = &spl_debug_printk,
.maxlen = sizeof(unsigned long),
.mode = 0644,
.proc_handler = &proc_dobitmasks
},
{
.ctl_name = CTL_DEBUG_MB,
.procname = "mb",
.mode = 0644,
.proc_handler = &proc_debug_mb,
},
{
.ctl_name = CTL_DEBUG_BINARY,
.procname = "binary",
.data = &spl_debug_binary,
.maxlen = sizeof(int),
.mode = 0644,
.proc_handler = &proc_dointvec,
},
{
.ctl_name = CTL_DEBUG_CATASTROPHE,
.procname = "catastrophe",
.data = &spl_debug_catastrophe,
.maxlen = sizeof(int),
.mode = 0444,
.proc_handler = &proc_dointvec,
},
{
.ctl_name = CTL_DEBUG_PANIC_ON_BUG,
.procname = "panic_on_bug",
.data = &spl_debug_panic_on_bug,
.maxlen = sizeof(int),
.mode = 0644,
.proc_handler = &proc_dointvec
},
{
.ctl_name = CTL_DEBUG_PATH,
.procname = "path",
.data = spl_debug_file_path,
.maxlen = sizeof(spl_debug_file_path),
.mode = 0644,
.proc_handler = &proc_dostring,
},
{
.ctl_name = CTL_DEBUG_DUMP,
.procname = "dump",
.mode = 0200,
.proc_handler = &proc_dump_kernel,
},
{ .ctl_name = CTL_DEBUG_FORCE_BUG,
.procname = "force_bug",
.mode = 0200,
.proc_handler = &proc_force_bug,
},
{
.ctl_name = CTL_CONSOLE_RATELIMIT,
.procname = "console_ratelimit",
.data = &spl_console_ratelimit,
.maxlen = sizeof(int),
.mode = 0644,
.proc_handler = &proc_dointvec,
},
{
.ctl_name = CTL_CONSOLE_MAX_DELAY_CS,
.procname = "console_max_delay_centisecs",
.maxlen = sizeof(int),
.mode = 0644,
.proc_handler = &proc_console_max_delay_cs,
},
{
.ctl_name = CTL_CONSOLE_MIN_DELAY_CS,
.procname = "console_min_delay_centisecs",
.maxlen = sizeof(int),
.mode = 0644,
.proc_handler = &proc_console_min_delay_cs,
},
{
.ctl_name = CTL_CONSOLE_BACKOFF,
.procname = "console_backoff",
.maxlen = sizeof(int),
.mode = 0644,
.proc_handler = &proc_console_backoff,
},
{
.ctl_name = CTL_DEBUG_STACK_SIZE,
.procname = "stack_max",
.data = &spl_debug_stack,
.maxlen = sizeof(int),
.mode = 0444,
.proc_handler = &proc_dointvec,
},
{0},
};
static struct ctl_table spl_vm_table[] = {
{
.ctl_name = CTL_VM_MINFREE,
.procname = "minfree",
.data = &minfree,
.maxlen = sizeof(int),
.mode = 0644,
.proc_handler = &proc_dointvec,
},
{
.ctl_name = CTL_VM_DESFREE,
.procname = "desfree",
.data = &desfree,
.maxlen = sizeof(int),
.mode = 0644,
.proc_handler = &proc_dointvec,
},
{
.ctl_name = CTL_VM_LOTSFREE,
.procname = "lotsfree",
.data = &lotsfree,
.maxlen = sizeof(int),
.mode = 0644,
.proc_handler = &proc_dointvec,
},
{
.ctl_name = CTL_VM_NEEDFREE,
.procname = "needfree",
.data = &needfree,
.maxlen = sizeof(int),
.mode = 0444,
.proc_handler = &proc_dointvec,
},
{
.ctl_name = CTL_VM_SWAPFS_MINFREE,
.procname = "swapfs_minfree",
.data = &swapfs_minfree,
.maxlen = sizeof(int),
.mode = 0644,
.proc_handler = &proc_dointvec,
},
{
.ctl_name = CTL_VM_SWAPFS_RESERVE,
.procname = "swapfs_reserve",
.data = &swapfs_reserve,
.maxlen = sizeof(int),
.mode = 0644,
.proc_handler = &proc_dointvec,
},
{
.ctl_name = CTL_VM_AVAILRMEM,
.procname = "availrmem",
.mode = 0444,
.proc_handler = &proc_doavailrmem,
},
{
.ctl_name = CTL_VM_FREEMEM,
.procname = "freemem",
.data = (void *)2,
.maxlen = sizeof(int),
.mode = 0444,
.proc_handler = &proc_dofreemem,
},
{
.ctl_name = CTL_VM_PHYSMEM,
.procname = "physmem",
.data = &physmem,
.maxlen = sizeof(int),
.mode = 0444,
.proc_handler = &proc_dointvec,
},
{0},
};
#ifdef DEBUG_KMEM
static struct ctl_table spl_kmem_table[] = {
{
.ctl_name = CTL_KMEM_KMEMUSED,
.procname = "kmem_used",
.data = &kmem_alloc_used,
.maxlen = sizeof(atomic64_t),
.mode = 0444,
.proc_handler = &proc_doatomic64,
},
{
.ctl_name = CTL_KMEM_KMEMMAX,
.procname = "kmem_max",
.data = &kmem_alloc_max,
.maxlen = sizeof(unsigned long),
.extra1 = &table_min,
.extra2 = &table_max,
.mode = 0444,
.proc_handler = &proc_doulongvec_minmax,
},
{
.ctl_name = CTL_KMEM_VMEMUSED,
.procname = "vmem_used",
.data = &vmem_alloc_used,
.maxlen = sizeof(atomic64_t),
.mode = 0444,
.proc_handler = &proc_doatomic64,
},
{
.ctl_name = CTL_KMEM_VMEMMAX,
.procname = "vmem_max",
.data = &vmem_alloc_max,
.maxlen = sizeof(unsigned long),
.extra1 = &table_min,
.extra2 = &table_max,
.mode = 0444,
.proc_handler = &proc_doulongvec_minmax,
},
{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.
*/
{
.ctl_name = CTL_VERSION,
.procname = "version",
.data = spl_version,
.maxlen = sizeof(spl_version),
.mode = 0444,
.proc_handler = &proc_dostring,
},
{
.ctl_name = CTL_HOSTID,
.procname = "hostid",
.data = &spl_hostid,
.maxlen = sizeof(unsigned long),
.mode = 0644,
.proc_handler = &proc_dohostid,
},
{
.ctl_name = CTL_HW_SERIAL,
.procname = "hw_serial",
.data = hw_serial,
.maxlen = sizeof(hw_serial),
.mode = 0444,
.proc_handler = &proc_dostring,
},
#ifndef HAVE_KALLSYMS_LOOKUP_NAME
{
.ctl_name = CTL_KALLSYMS,
.procname = "kallsyms_lookup_name",
.data = &spl_kallsyms_lookup_name_fn,
.maxlen = sizeof(unsigned long),
.mode = 0644,
.proc_handler = &proc_dokallsyms_lookup_name,
},
#endif
{
.ctl_name = CTL_SPL_DEBUG,
.procname = "debug",
.mode = 0555,
.child = spl_debug_table,
},
{
.ctl_name = CTL_SPL_VM,
.procname = "vm",
.mode = 0555,
.child = spl_vm_table,
},
#ifdef DEBUG_KMEM
{
.ctl_name = CTL_SPL_KMEM,
.procname = "kmem",
.mode = 0555,
.child = spl_kmem_table,
},
#endif
{
.ctl_name = CTL_SPL_KSTAT,
.procname = "kstat",
.mode = 0555,
.child = spl_kstat_table,
},
{ 0 },
};
static struct ctl_table spl_dir[] = {
{
.ctl_name = CTL_SPL,
.procname = "spl",
.mode = 0555,
.child = spl_table,
},
{ 0 }
};
static struct ctl_table spl_root[] = {
{
.ctl_name = CTL_KERN,
.procname = "kernel",
.mode = 0555,
.child = spl_dir,
},
{ 0 }
};
static int
proc_dir_entry_match(int len, const char *name, struct proc_dir_entry *de)
{
if (de->namelen != len)
return 0;
return !memcmp(name, de->name, len);
}
struct proc_dir_entry *
proc_dir_entry_find(struct proc_dir_entry *root, const char *str)
{
struct proc_dir_entry *de;
for (de = root->subdir; de; de = de->next)
if (proc_dir_entry_match(strlen(str), str, de))
return de;
return NULL;
}
int
proc_dir_entries(struct proc_dir_entry *root)
{
struct proc_dir_entry *de;
int i = 0;
for (de = root->subdir; de; de = de->next)
i++;
return i;
}
int
proc_init(void)
{
int rc = 0;
ENTRY;
#ifdef CONFIG_SYSCTL
spl_header = spl_register_sysctl_table(spl_root, 0);
if (spl_header == NULL)
RETURN(-EUNATCH);
#endif /* CONFIG_SYSCTL */
proc_spl = proc_mkdir("spl", NULL);
if (proc_spl == NULL)
GOTO(out, rc = -EUNATCH);
#ifdef DEBUG_KMEM
proc_spl_kmem = proc_mkdir("kmem", proc_spl);
if (proc_spl_kmem == NULL)
GOTO(out, rc = -EUNATCH);
proc_spl_kmem_slab = create_proc_entry("slab", 0444, proc_spl_kmem);
if (proc_spl_kmem_slab == NULL)
GOTO(out, rc = -EUNATCH);
proc_spl_kmem_slab->proc_fops = &proc_slab_operations;
#endif /* DEBUG_KMEM */
proc_spl_kstat = proc_mkdir("kstat", proc_spl);
if (proc_spl_kstat == NULL)
GOTO(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);
#ifdef CONFIG_SYSCTL
spl_unregister_sysctl_table(spl_header);
#endif /* CONFIG_SYSCTL */
}
RETURN(rc);
}
void
proc_fini(void)
{
ENTRY;
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);
#ifdef CONFIG_SYSCTL
ASSERT(spl_header != NULL);
spl_unregister_sysctl_table(spl_header);
#endif /* CONFIG_SYSCTL */
EXIT;
}