patch-1.3.57 linux/kernel/sysctl.c
Next file: linux/mm/Makefile
Previous file: linux/kernel/sys.c
Back to the patch index
Back to the overall index
- Lines: 670
- Date:
Wed Jan 10 09:27:40 1996
- Orig file:
v1.3.56/linux/kernel/sysctl.c
- Orig date:
Thu Jan 1 02:00:00 1970
diff -u --recursive --new-file v1.3.56/linux/kernel/sysctl.c linux/kernel/sysctl.c
@@ -0,0 +1,669 @@
+/*
+ * sysctl.c: General linux system control interface
+ *
+ * Begun 24 March 1995, Stephen Tweedie
+ * Added /proc support, Dec 1995
+ */
+
+#include <linux/autoconf.h>
+#include <linux/sched.h>
+#include <linux/mm.h>
+#include <linux/sysctl.h>
+#include <linux/swapctl.h>
+#include <linux/proc_fs.h>
+#include <linux/malloc.h>
+#include <linux/stat.h>
+#include <linux/ctype.h>
+#include <asm/bitops.h>
+#include <asm/segment.h>
+
+#include <linux/utsname.h>
+#include <linux/swapctl.h>
+
+static ctl_table root_table[];
+static struct ctl_table_header root_table_header =
+ {root_table, DNODE_SINGLE(&root_table_header)};
+
+static int parse_table(int *, int, void *, size_t *, void *, size_t,
+ ctl_table *, void **);
+
+static ctl_table kern_table[];
+static ctl_table vm_table[];
+
+/* /proc declarations: */
+
+#ifdef CONFIG_PROC_FS
+
+static int proc_readsys(struct inode * inode, struct file * file,
+ char * buf, int count);
+static int proc_writesys(struct inode * inode, struct file * file,
+ const char * buf, int count);
+static int proc_sys_permission(struct inode *, int);
+
+struct file_operations proc_sys_file_operations =
+{
+ NULL, /* lseek */
+ proc_readsys, /* read */
+ proc_writesys, /* write */
+ NULL, /* readdir */
+ NULL, /* select */
+ NULL, /* ioctl */
+ NULL, /* mmap */
+ NULL, /* no special open code */
+ NULL, /* no special release code */
+ NULL /* can't fsync */
+};
+
+struct inode_operations proc_sys_inode_operations =
+{
+ &proc_sys_file_operations,
+ NULL, /* create */
+ NULL, /* lookup */
+ NULL, /* link */
+ NULL, /* unlink */
+ NULL, /* symlink */
+ NULL, /* mkdir */
+ NULL, /* rmdir */
+ NULL, /* mknod */
+ NULL, /* rename */
+ NULL, /* readlink */
+ NULL, /* follow_link */
+ NULL, /* readpage */
+ NULL, /* writepage */
+ NULL, /* bmap */
+ NULL, /* truncate */
+ proc_sys_permission
+};
+
+extern struct proc_dir_entry proc_sys_root;
+
+static void register_proc_table(ctl_table *, struct proc_dir_entry *);
+static void unregister_proc_table(ctl_table *, struct proc_dir_entry *);
+#endif
+
+/* The default sysctl tables: */
+
+static ctl_table root_table[] = {
+ {CTL_KERN, "kernel", NULL, 0, 0555, kern_table},
+ {CTL_VM, "vm", NULL, 0, 0555, vm_table},
+ {0}
+};
+
+static ctl_table kern_table[] = {
+ {KERN_OSTYPE, "ostype", system_utsname.sysname, 64,
+ 0444, NULL, &proc_dostring, &sysctl_string},
+ {KERN_OSRELEASE, "osrelease", system_utsname.release, 64,
+ 0444, NULL, &proc_dostring, &sysctl_string},
+ {KERN_VERSION, "version", system_utsname.version, 64,
+ 0444, NULL, &proc_dostring, &sysctl_string},
+ {KERN_NODENAME, "hostname", system_utsname.nodename, 64,
+ 0644, NULL, &proc_dostring, &sysctl_string},
+ {KERN_DOMAINNAME, "domainname", system_utsname.domainname, 64,
+ 0644, NULL, &proc_dostring, &sysctl_string},
+ {KERN_NRINODE, "inode-nr", &nr_inodes, 2*sizeof(int),
+ 0444, NULL, &proc_dointvec},
+ {KERN_MAXINODE, "inode-max", &max_inodes, sizeof(int),
+ 0644, NULL, &proc_dointvec},
+ {KERN_NRFILE, "file-nr", &nr_files, sizeof(int),
+ 0444, NULL, &proc_dointvec},
+ {KERN_MAXFILE, "file-max", &max_files, sizeof(int),
+ 0644, NULL, &proc_dointvec},
+ {0}
+};
+
+static ctl_table vm_table[] = {
+ {VM_SWAPCTL, "swapctl",
+ &swap_control, sizeof(swap_control_t), 0600, NULL, &proc_dointvec},
+ {VM_KSWAPD, "kswapd",
+ &kswapd_ctl, sizeof(kswapd_ctl), 0600, NULL, &proc_dointvec},
+ {0}
+};
+
+void sysctl_init(void)
+{
+#ifdef CONFIG_PROC_FS
+ register_proc_table(root_table, &proc_sys_root);
+#endif
+}
+
+
+int do_sysctl (int *name, int nlen,
+ void *oldval, size_t *oldlenp,
+ void *newval, size_t newlen)
+{
+ int error;
+ struct ctl_table_header *tmp;
+ void *context;
+
+ if (nlen == 0 || nlen >= CTL_MAXNAME)
+ return -ENOTDIR;
+
+ error = verify_area(VERIFY_READ,name,nlen*sizeof(int));
+ if (error) return error;
+ if (oldval) {
+ if (!oldlenp)
+ return -EFAULT;
+ error = verify_area(VERIFY_WRITE,oldlenp,sizeof(size_t));
+ if (error) return error;
+ error = verify_area(VERIFY_WRITE,oldval,get_user(oldlenp));
+ if (error) return error;
+ }
+ if (newval) {
+ error = verify_area(VERIFY_READ,newval,newlen);
+ if (error) return error;
+ }
+ tmp = &root_table_header;
+ do {
+ context = 0;
+ error = parse_table(name, nlen, oldval, oldlenp,
+ newval, newlen, root_table, &context);
+ if (context)
+ kfree(context);
+ if (error != ENOTDIR)
+ return error;
+ tmp = tmp->DLIST_NEXT(ctl_entry);
+ } while (tmp != &root_table_header);
+ return -ENOTDIR;
+}
+
+extern asmlinkage int sys_sysctl(struct __sysctl_args *args)
+{
+ struct __sysctl_args tmp;
+ int error;
+ error = verify_area(VERIFY_READ, args, sizeof(*args));
+ if (error)
+ return error;
+ memcpy_fromfs(&tmp, args, sizeof(tmp));
+ return do_sysctl(tmp.name, tmp.nlen, tmp.oldval, tmp.oldlenp,
+ tmp.newval, tmp.newlen);
+}
+
+/* Like in_group_p, but testing against egid, not fsgid */
+static int in_egroup_p(gid_t grp)
+{
+ int i;
+
+ if (grp == current->euid)
+ return 1;
+
+ for (i = 0; i < NGROUPS; i++) {
+ if (current->groups[i] == NOGROUP)
+ break;
+ if (current->groups[i] == grp)
+ return 1;
+ }
+ return 0;
+}
+/* ctl_perm does NOT grant the superuser all rights automatically, because
+ some sysctl variables are readonly even to root. */
+static int test_perm(int mode, int op)
+{
+ if (!current->euid)
+ mode >>= 6;
+ else if (in_egroup_p(0))
+ mode >>= 3;
+ if ((mode & op & 0007) == op)
+ return 0;
+ return -EACCES;
+}
+static inline int ctl_perm(ctl_table *table, int op)
+{
+ return test_perm(table->mode, op);
+}
+
+static int parse_table(int *name, int nlen,
+ void *oldval, size_t *oldlenp,
+ void *newval, size_t newlen,
+ ctl_table *table, void **context)
+{
+ int error;
+repeat:
+ if (!nlen)
+ return -ENOTDIR;
+
+ for ( ; table->ctl_name; table++) {
+ if (get_user(name) == table->ctl_name ||
+ table->ctl_name == CTL_ANY) {
+ if (table->child) {
+ if (ctl_perm(table, 001))
+ return -EPERM;
+ if (table->strategy) {
+ error = table->strategy(
+ table, name, nlen,
+ oldval, oldlenp,
+ newval, newlen, context);
+ if (error)
+ return error;
+ }
+ name++;
+ nlen--;
+ table = table->child;
+ goto repeat;
+ }
+ error = do_sysctl_strategy(table, name, nlen,
+ oldval, oldlenp,
+ newval, newlen, context);
+ return error;
+ }
+ };
+ return -ENOTDIR;
+}
+
+/* Perform the actual read/write of a sysctl table entry. */
+int do_sysctl_strategy (ctl_table *table,
+ int *name, int nlen,
+ void *oldval, size_t *oldlenp,
+ void *newval, size_t newlen, void **context)
+{
+ int op = 0, rc, len;
+
+ if (oldval)
+ op |= 004;
+ if (newval)
+ op |= 002;
+ if (ctl_perm(table, op))
+ return -EPERM;
+
+ if (table->strategy) {
+ rc = table->strategy(table, name, nlen, oldval, oldlenp,
+ newval, newlen, context);
+ if (rc < 0)
+ return rc;
+ if (rc > 0)
+ return 0;
+ }
+
+ /* If there is no strategy routine, or if the strategy returns
+ * zero, proceed with automatic r/w */
+ if (table->data && table->maxlen) {
+ if (oldval && oldlenp && get_user(oldlenp)) {
+ len = get_user(oldlenp);
+ if (len > table->maxlen)
+ len = table->maxlen;
+ memcpy_tofs(oldval, table->data, len);
+ put_user(len, oldlenp);
+ }
+ if (newval && newlen) {
+ len = newlen;
+ if (len > table->maxlen)
+ len = table->maxlen;
+ memcpy_fromfs(table->data, newval, len);
+ }
+ }
+ return 0;
+}
+
+
+struct ctl_table_header *register_sysctl_table(ctl_table * table,
+ int insert_at_head)
+{
+ struct ctl_table_header *tmp;
+ tmp = kmalloc(sizeof(*tmp), GFP_KERNEL);
+ if (!tmp)
+ return 0;
+ *tmp = ((struct ctl_table_header) {table, DNODE_NULL});
+ if (insert_at_head)
+ DLIST_INSERT_AFTER(&root_table_header, tmp, ctl_entry);
+ else
+ DLIST_INSERT_BEFORE(&root_table_header, tmp, ctl_entry);
+#ifdef CONFIG_PROC_FS
+ register_proc_table(table, &proc_sys_root);
+#endif
+ return tmp;
+}
+
+void unregister_sysctl_table(struct ctl_table_header * table)
+{
+ DLIST_DELETE(table, ctl_entry);
+#ifdef CONFIG_PROC_FS
+ unregister_proc_table(table->ctl_table, &proc_sys_root);
+#endif
+}
+
+/*
+ * /proc/sys support
+ */
+
+#ifdef CONFIG_PROC_FS
+
+/* Scan the sysctl entries in table and add them all into /proc */
+static void register_proc_table(ctl_table * table, struct proc_dir_entry *root)
+{
+ struct proc_dir_entry *de;
+
+ for (; table->ctl_name; table++) {
+ /* Can't do anything without a proc name. */
+ if (!table->procname)
+ continue;
+ /* Maybe we can't do anything with it... */
+ if (!table->proc_handler &&
+ !table->child)
+ continue;
+
+ de = kmalloc(sizeof(*de), GFP_KERNEL);
+ if (!de) continue;
+ de->namelen = strlen(table->procname);
+ de->name = table->procname;
+ de->mode = table->mode;
+ de->nlink = 1;
+ de->uid = 0;
+ de->gid = 0;
+ de->size = 0;
+ de->get_info = 0; /* For internal use if we want it */
+ de->fill_inode = 0; /* To override struct inode fields */
+ de->next = de->subdir = 0;
+ de->data = (void *) table;
+ /* Is it a file? */
+ if (table->proc_handler) {
+ de->ops = &proc_sys_inode_operations;
+ de->mode |= S_IFREG;
+ }
+ /* Otherwise it's a subdir */
+ else {
+ de->ops = &proc_dir_inode_operations;
+ de->nlink++;
+ de->mode |= S_IFDIR;
+ }
+ table->de = de;
+ proc_register_dynamic(root, de);
+ if (de->mode & S_IFDIR )
+ register_proc_table(table->child, de);
+ }
+}
+
+static void unregister_proc_table(ctl_table * table, struct proc_dir_entry *root)
+{
+ struct proc_dir_entry *de;
+ for (; table->ctl_name; table++) {
+ if (!(de = table->de))
+ continue;
+ if (de->mode & S_IFDIR) {
+ if (!table->child) {
+ printk (KERN_ALERT "Help - malformed sysctl tree on free\n");
+ continue;
+ }
+ unregister_proc_table(table->child, de);
+ }
+ proc_unregister(root, de->low_ino);
+ kfree(de);
+ }
+}
+
+
+static int do_rw_proc(int write, struct inode * inode, struct file * file,
+ char * buf, int count)
+{
+ int error, op;
+ struct proc_dir_entry *de;
+ struct ctl_table *table;
+ size_t res;
+
+ error = verify_area(write ? VERIFY_READ : VERIFY_WRITE, buf, count);
+ if (error)
+ return error;
+
+ de = (struct proc_dir_entry*) inode->u.generic_ip;
+ if (!de || !de->data)
+ return -ENOTDIR;
+ table = (struct ctl_table *) de->data;
+ if (!table || !table->proc_handler)
+ return -ENOTDIR;
+ op = (write ? 002 : 004);
+ if (ctl_perm(table, op))
+ return -EPERM;
+
+ res = count;
+ error = (*table->proc_handler) (table, write, file, buf, &res);
+ if (error)
+ return error;
+ return res;
+}
+
+static int proc_readsys(struct inode * inode, struct file * file,
+ char * buf, int count)
+{
+ return do_rw_proc(0, inode, file, buf, count);
+}
+
+static int proc_writesys(struct inode * inode, struct file * file,
+ const char * buf, int count)
+{
+ return do_rw_proc(1, inode, file, (char *) buf, count);
+}
+
+static int proc_sys_permission(struct inode *inode, int op)
+{
+ return test_perm(inode->i_mode, op);
+}
+
+int proc_dostring(ctl_table *table, int write, struct file *filp,
+ void *buffer, size_t *lenp)
+{
+ int len;
+ char *p, c;
+
+ if (!table->data || !table->maxlen || !*lenp ||
+ (filp->f_pos && !write)) {
+ *lenp = 0;
+ return 0;
+ }
+
+ if (write) {
+ len = 0;
+ p = buffer;
+ while (len < *lenp &&
+ (c = get_user(p++)) != 0 && c != '\n')
+ len++;
+ if (len >= table->maxlen)
+ len = table->maxlen-1;
+ memcpy_fromfs(table->data, buffer, len);
+ ((char *) table->data)[len] = 0;
+ filp->f_pos += *lenp;
+ } else {
+ len = strlen(table->data) + 1;
+ if (len > table->maxlen)
+ len = table->maxlen;
+ if (len > *lenp)
+ len = *lenp;
+ if (len) {
+ memcpy_tofs(buffer, table->data, len-1);
+ put_user(0, ((char *) buffer) + len - 1);
+ }
+ if (len < *lenp) {
+ put_user('\n', ((char *) buffer) + len);
+ len++;
+ }
+ *lenp = len;
+ filp->f_pos += len;
+ }
+ return 0;
+}
+
+int proc_dointvec(ctl_table *table, int write, struct file *filp,
+ void *buffer, size_t *lenp)
+{
+ int *i, vleft, first=1, len, left, neg, val;
+ #define TMPBUFLEN 20
+ char buf[TMPBUFLEN], *p;
+
+ if (!table->data || !table->maxlen || !*lenp ||
+ (filp->f_pos && !write)) {
+ *lenp = 0;
+ return 0;
+ }
+
+ i = (int *) table->data;
+ vleft = table->maxlen / sizeof(int);
+ left = *lenp;
+
+ for (; left && vleft--; i++, first=0) {
+ if (write) {
+ while (left && isspace(get_user((char *) buffer)))
+ left--, ((char *) buffer)++;
+ if (!left)
+ break;
+ neg = 0;
+ len = left;
+ if (len > TMPBUFLEN-1)
+ len = TMPBUFLEN-1;
+ memcpy_fromfs(buf, buffer, len);
+ buf[len] = 0;
+ p = buf;
+ if (*p == '-' && left > 1) {
+ neg = 1;
+ left--, p++;
+ }
+ if (*p < '0' || *p > '9')
+ break;
+ val = simple_strtoul(p, &p, 0);
+ len = p-buf;
+ if ((len < left) && *p && !isspace(*p))
+ break;
+ if (neg)
+ val = -val;
+ buffer += len;
+ left -= len;
+ *i = val;
+ } else {
+ p = buf;
+ if (!first)
+ *p++ = '\t';
+ sprintf(p, "%d", *i);
+ len = strlen(buf);
+ if (len > left)
+ len = left;
+ memcpy_tofs(buffer, buf, len);
+ left -= len;
+ buffer += len;
+ }
+ }
+
+ if (!write && !first && left) {
+ put_user('\n', (char *) buffer);
+ left--, buffer++;
+ }
+ if (write) {
+ p = (char *) buffer;
+ while (left && isspace(get_user(p++)))
+ left--;
+ }
+ if (write && first)
+ return -EINVAL;
+ *lenp -= left;
+ filp->f_pos += *lenp;
+ return 0;
+}
+
+#else /* CONFIG_PROC_FS */
+
+int proc_dostring(ctl_table *table, int write, struct file *filp,
+ void *buffer, size_t *lenp)
+{
+ return -ENOSYS;
+}
+
+int proc_dointvec(ctl_table *table, int write, struct file *filp,
+ void *buffer, size_t *lenp)
+{
+ return -ENOSYS;
+}
+
+#endif /* CONFIG_PROC_FS */
+
+
+/*
+ * General sysctl support routines
+ */
+
+/* The generic string strategy routine: */
+int sysctl_string(ctl_table *table, int *name, int nlen,
+ void *oldval, size_t *oldlenp,
+ void *newval, size_t newlen, void **context)
+{
+ int l, len;
+
+ if (!table->data || !table->maxlen)
+ return -ENOTDIR;
+
+ if (oldval && oldlenp && get_user(oldlenp)) {
+ len = get_user(oldlenp);
+ l = strlen(table->data);
+ if (len > l) len = l;
+ if (len >= table->maxlen)
+ len = table->maxlen;
+ memcpy_tofs(oldval, table->data, len);
+ put_user(0, ((char *) oldval) + len);
+ put_user(len, oldlenp);
+ }
+ if (newval && newlen) {
+ len = newlen;
+ if (len > table->maxlen)
+ len = table->maxlen;
+ memcpy_fromfs(table->data, newval, len);
+ if (len == table->maxlen)
+ len--;
+ ((char *) table->data)[len] = 0;
+ }
+ return 0;
+}
+
+int do_string (
+ void *oldval, size_t *oldlenp, void *newval, size_t newlen,
+ int rdwr, char *data, size_t max)
+{
+ int l = strlen(data) + 1;
+ if (newval && !rdwr)
+ return -EPERM;
+ if (newval && newlen >= max)
+ return -EINVAL;
+ if (oldval) {
+ if (l > get_user(oldlenp))
+ return -ENOMEM;
+ put_user(l, oldlenp);
+ memcpy_tofs(oldval, data, l);
+ }
+ if (newval) {
+ memcpy_fromfs(data, newval, newlen);
+ data[newlen] = 0;
+ }
+ return 0;
+}
+
+int do_int (
+ void *oldval, size_t *oldlenp, void *newval, size_t newlen,
+ int rdwr, int *data)
+{
+ if (newval && !rdwr)
+ return -EPERM;
+ if (newval && newlen != sizeof(int))
+ return -EINVAL;
+ if (oldval) {
+ if (get_user(oldlenp) < sizeof(int))
+ return -ENOMEM;
+ put_user(sizeof(int), oldlenp);
+ memcpy_tofs(oldval, data, sizeof(int));
+ }
+ if (newval)
+ memcpy_fromfs(data, newval, sizeof(int));
+ return 0;
+}
+
+int do_struct (
+ void *oldval, size_t *oldlenp, void *newval, size_t newlen,
+ int rdwr, void *data, size_t len)
+{
+ if (newval && !rdwr)
+ return -EPERM;
+ if (newval && newlen != len)
+ return -EINVAL;
+ if (oldval) {
+ if (get_user(oldlenp) < len)
+ return -ENOMEM;
+ put_user(len, oldlenp);
+ memcpy_tofs(oldval, data, len);
+ }
+ if (newval)
+ memcpy_fromfs(data, newval, len);
+ return 0;
+}
+
FUNET's LINUX-ADM group, linux-adm@nic.funet.fi
TCL-scripts by Sam Shen, slshen@lbl.gov
with Sam's (original) version of this