cregit-Linux how code gets into the kernel

Release 4.10 fs/proc/proc_sysctl.c

Directory: fs/proc
/*
 * /proc/sys support
 */
#include <linux/init.h>
#include <linux/sysctl.h>
#include <linux/poll.h>
#include <linux/proc_fs.h>
#include <linux/printk.h>
#include <linux/security.h>
#include <linux/sched.h>
#include <linux/namei.h>
#include <linux/mm.h>
#include <linux/module.h>
#include "internal.h"


static const struct dentry_operations proc_sys_dentry_operations;

static const struct file_operations proc_sys_file_operations;

static const struct inode_operations proc_sys_inode_operations;

static const struct file_operations proc_sys_dir_file_operations;

static const struct inode_operations proc_sys_dir_operations;

/* Support for permanently empty directories */


struct ctl_table sysctl_mount_point[] = {
	{ }
};


static bool is_empty_dir(struct ctl_table_header *head) { return head->ctl_table[0].child == sysctl_mount_point; }

Contributors

PersonTokensPropCommitsCommitProp
eric w. biedermaneric w. biederman23100.00%1100.00%
Total23100.00%1100.00%


static void set_empty_dir(struct ctl_dir *dir) { dir->header.ctl_table[0].child = sysctl_mount_point; }

Contributors

PersonTokensPropCommitsCommitProp
eric w. biedermaneric w. biederman24100.00%1100.00%
Total24100.00%1100.00%


static void clear_empty_dir(struct ctl_dir *dir) { dir->header.ctl_table[0].child = NULL; }

Contributors

PersonTokensPropCommitsCommitProp
eric w. biedermaneric w. biederman24100.00%1100.00%
Total24100.00%1100.00%


void proc_sys_poll_notify(struct ctl_table_poll *poll) { if (!poll) return; atomic_inc(&poll->event); wake_up_interruptible(&poll->wait); }

Contributors

PersonTokensPropCommitsCommitProp
lucas de marchilucas de marchi32100.00%1100.00%
Total32100.00%1100.00%

static struct ctl_table root_table[] = { { .procname = "", .mode = S_IFDIR|S_IRUGO|S_IXUGO, }, { } }; static struct ctl_table_root sysctl_table_root = { .default_set.dir.header = { {{.count = 1, .nreg = 1, .ctl_table = root_table }}, .ctl_table_arg = root_table, .root = &sysctl_table_root, .set = &sysctl_table_root.default_set, }, }; static DEFINE_SPINLOCK(sysctl_lock); static void drop_sysctl_table(struct ctl_table_header *header); static int sysctl_follow_link(struct ctl_table_header **phead, struct ctl_table **pentry); static int insert_links(struct ctl_table_header *head); static void put_links(struct ctl_table_header *header);
static void sysctl_print_dir(struct ctl_dir *dir) { if (dir->header.parent) sysctl_print_dir(dir->header.parent); pr_cont("%s/", dir->header.ctl_table[0].procname); }

Contributors

PersonTokensPropCommitsCommitProp
eric w. biedermaneric w. biederman4193.18%240.00%
al viroal viro12.27%120.00%
christoph hellwigchristoph hellwig12.27%120.00%
andrew mortonandrew morton12.27%120.00%
Total44100.00%5100.00%


static int namecmp(const char *name1, int len1, const char *name2, int len2) { int minlen; int cmp; minlen = len1; if (minlen > len2) minlen = len2; cmp = memcmp(name1, name2, minlen); if (cmp == 0) cmp = len1 - len2; return cmp; }

Contributors

PersonTokensPropCommitsCommitProp
eric w. biedermaneric w. biederman6392.65%266.67%
al viroal viro57.35%133.33%
Total68100.00%3100.00%

/* Called under sysctl_lock */
static struct ctl_table *find_entry(struct ctl_table_header **phead, struct ctl_dir *dir, const char *name, int namelen) { struct ctl_table_header *head; struct ctl_table *entry; struct rb_node *node = dir->root.rb_node; while (node) { struct ctl_node *ctl_node; const char *procname; int cmp; ctl_node = rb_entry(node, struct ctl_node, node); head = ctl_node->header; entry = &head->ctl_table[ctl_node - head->node]; procname = entry->procname; cmp = namecmp(name, namelen, procname, strlen(procname)); if (cmp < 0) node = node->rb_left; else if (cmp > 0) node = node->rb_right; else { *phead = head; return entry; } } return NULL; }

Contributors

PersonTokensPropCommitsCommitProp
eric w. biedermaneric w. biederman15999.38%480.00%
al viroal viro10.62%120.00%
Total160100.00%5100.00%


static int insert_entry(struct ctl_table_header *head, struct ctl_table *entry) { struct rb_node *node = &head->node[entry - head->ctl_table].node; struct rb_node **p = &head->parent->root.rb_node; struct rb_node *parent = NULL; const char *name = entry->procname; int namelen = strlen(name); while (*p) { struct ctl_table_header *parent_head; struct ctl_table *parent_entry; struct ctl_node *parent_node; const char *parent_name; int cmp; parent = *p; parent_node = rb_entry(parent, struct ctl_node, node); parent_head = parent_node->header; parent_entry = &parent_head->ctl_table[parent_node - parent_head->node]; parent_name = parent_entry->procname; cmp = namecmp(name, namelen, parent_name, strlen(parent_name)); if (cmp < 0) p = &(*p)->rb_left; else if (cmp > 0) p = &(*p)->rb_right; else { pr_err("sysctl duplicate entry: "); sysctl_print_dir(head->parent); pr_cont("/%s\n", entry->procname); return -EEXIST; } } rb_link_node(node, parent, p); rb_insert_color(node, &head->parent->root); return 0; }

Contributors

PersonTokensPropCommitsCommitProp
eric w. biedermaneric w. biederman19980.24%342.86%
al viroal viro3313.31%114.29%
michel lespinassemichel lespinasse124.84%114.29%
nick pigginnick piggin20.81%114.29%
andrew mortonandrew morton20.81%114.29%
Total248100.00%7100.00%


static void erase_entry(struct ctl_table_header *head, struct ctl_table *entry) { struct rb_node *node = &head->node[entry - head->ctl_table].node; rb_erase(node, &head->parent->root); }

Contributors

PersonTokensPropCommitsCommitProp
eric w. biedermaneric w. biederman4493.62%266.67%
al viroal viro36.38%133.33%
Total47100.00%3100.00%


static void init_header(struct ctl_table_header *head, struct ctl_table_root *root, struct ctl_table_set *set, struct ctl_node *node, struct ctl_table *table) { head->ctl_table = table; head->ctl_table_arg = table; head->used = 0; head->count = 1; head->nreg = 1; head->unregistering = NULL; head->root = root; head->set = set; head->parent = NULL; head->node = node; if (node) { struct ctl_table *entry; for (entry = table; entry->procname; entry++, node++) node->header = head; } }

Contributors

PersonTokensPropCommitsCommitProp
eric w. biedermaneric w. biederman11693.55%466.67%
al viroal viro54.03%116.67%
pavel emelianovpavel emelianov32.42%116.67%
Total124100.00%6100.00%


static void erase_header(struct ctl_table_header *head) { struct ctl_table *entry; for (entry = head->ctl_table; entry->procname; entry++) erase_entry(head, entry); }

Contributors

PersonTokensPropCommitsCommitProp
eric w. biedermaneric w. biederman3694.74%375.00%
pavel emelianovpavel emelianov25.26%125.00%
Total38100.00%4100.00%


static int insert_header(struct ctl_dir *dir, struct ctl_table_header *header) { struct ctl_table *entry; int err; /* Is this a permanently empty directory? */ if (is_empty_dir(&dir->header)) return -EROFS; /* Am I creating a permanently empty directory? */ if (header->ctl_table == sysctl_mount_point) { if (!RB_EMPTY_ROOT(&dir->root)) return -EINVAL; set_empty_dir(dir); } dir->header.nreg++; header->parent = dir; err = insert_links(header); if (err) goto fail_links; for (entry = header->ctl_table; entry->procname; entry++) { err = insert_entry(header, entry); if (err) goto fail; } return 0; fail: erase_header(header); put_links(header); fail_links: if (header->ctl_table == sysctl_mount_point) clear_empty_dir(dir); header->parent = NULL; drop_sysctl_table(&dir->header); return err; }

Contributors

PersonTokensPropCommitsCommitProp
eric w. biedermaneric w. biederman15185.31%571.43%
lucas de marchilucas de marchi2011.30%114.29%
pavel emelianovpavel emelianov63.39%114.29%
Total177100.00%7100.00%

/* called under sysctl_lock */
static int use_table(struct ctl_table_header *p) { if (unlikely(p->unregistering)) return 0; p->used++; return 1; }

Contributors

PersonTokensPropCommitsCommitProp
eric w. biedermaneric w. biederman1961.29%240.00%
lucas de marchilucas de marchi929.03%120.00%
al viroal viro26.45%120.00%
miklos szeredimiklos szeredi13.23%120.00%
Total31100.00%5100.00%

/* called under sysctl_lock */
static void unuse_table(struct ctl_table_header *p) { if (!--p->used) if (unlikely(p->unregistering)) complete(p->unregistering); }

Contributors

PersonTokensPropCommitsCommitProp
eric w. biedermaneric w. biederman1645.71%250.00%
lucas de marchilucas de marchi1542.86%125.00%
al viroal viro411.43%125.00%
Total35100.00%4100.00%

/* called under sysctl_lock, will reacquire if has to wait */
static void start_unregistering(struct ctl_table_header *p) { /* * if p->used is 0, nobody will ever touch that entry again; * we'll eliminate all paths to it before dropping sysctl_lock */ if (unlikely(p->used)) { struct completion wait; init_completion(&wait); p->unregistering = &wait; spin_unlock(&sysctl_lock); wait_for_completion(&wait); spin_lock(&sysctl_lock); } else { /* anything non-NULL; we'll never dereference it */ p->unregistering = ERR_PTR(-EINVAL); } /* * do not remove from the list until nobody holds it; walking the * list in do_sysctl() relies on that. */ erase_header(p); }

Contributors

PersonTokensPropCommitsCommitProp
eric w. biedermaneric w. biederman5975.64%350.00%
lucas de marchilucas de marchi1417.95%116.67%
al viroal viro45.13%116.67%
adrian bunkadrian bunk11.28%116.67%
Total78100.00%6100.00%


static void sysctl_head_get(struct ctl_table_header *head) { spin_lock(&sysctl_lock); head->count++; spin_unlock(&sysctl_lock); }

Contributors

PersonTokensPropCommitsCommitProp
eric w. biedermaneric w. biederman2692.86%266.67%
al viroal viro27.14%133.33%
Total28100.00%3100.00%


void sysctl_head_put(struct ctl_table_header *head) { spin_lock(&sysctl_lock); if (!--head->count) kfree_rcu(head, rcu); spin_unlock(&sysctl_lock); }

Contributors

PersonTokensPropCommitsCommitProp
eric w. biedermaneric w. biederman3183.78%266.67%
al viroal viro616.22%133.33%
Total37100.00%3100.00%


static struct ctl_table_header *sysctl_head_grab(struct ctl_table_header *head) { BUG_ON(!head); spin_lock(&sysctl_lock); if (!use_table(head)) head = ERR_PTR(-ENOENT); spin_unlock(&sysctl_lock); return head; }

Contributors

PersonTokensPropCommitsCommitProp
eric w. biedermaneric w. biederman4284.00%250.00%
al viroal viro510.00%125.00%
prasad joshiprasad joshi36.00%125.00%
Total50100.00%4100.00%


static void sysctl_head_finish(struct ctl_table_header *head) { if (!head) return; spin_lock(&sysctl_lock); unuse_table(head); spin_unlock(&sysctl_lock); }

Contributors

PersonTokensPropCommitsCommitProp
eric w. biedermaneric w. biederman2573.53%250.00%
al viroal viro720.59%125.00%
nick pigginnick piggin25.88%125.00%
Total34100.00%4100.00%


static struct ctl_table_set * lookup_header_set(struct ctl_table_root *root) { struct ctl_table_set *set = &root->default_set; if (root->lookup) set = root->lookup(root); return set; }

Contributors

PersonTokensPropCommitsCommitProp
eric w. biedermaneric w. biederman3995.12%266.67%
al viroal viro24.88%133.33%
Total41100.00%3100.00%


static struct ctl_table *lookup_entry(struct ctl_table_header **phead, struct ctl_dir *dir, const char *name, int namelen) { struct ctl_table_header *head; struct ctl_table *entry; spin_lock(&sysctl_lock); entry = find_entry(&head, dir, name, namelen); if (entry && use_table(head)) *phead = head; else entry = NULL; spin_unlock(&sysctl_lock); return entry; }

Contributors

PersonTokensPropCommitsCommitProp
eric w. biedermaneric w. biederman7284.71%375.00%
al viroal viro1315.29%125.00%
Total85100.00%4100.00%


static struct ctl_node *first_usable_entry(struct rb_node *node) { struct ctl_node *ctl_node; for (;node; node = rb_next(node)) { ctl_node = rb_entry(node, struct ctl_node, node); if (use_table(ctl_node->header)) return ctl_node; } return NULL; }

Contributors

PersonTokensPropCommitsCommitProp
eric w. biedermaneric w. biederman3661.02%583.33%
al viroal viro2338.98%116.67%
Total59100.00%6100.00%


static void first_entry(struct ctl_dir *dir, struct ctl_table_header **phead, struct ctl_table **pentry) { struct ctl_table_header *head = NULL; struct ctl_table *entry = NULL; struct ctl_node *ctl_node; spin_lock(&sysctl_lock); ctl_node = first_usable_entry(rb_first(&dir->root)); spin_unlock(&sysctl_lock); if (ctl_node) { head = ctl_node->header; entry = &head->ctl_table[ctl_node - head->node]; } *phead = head; *pentry = entry; }

Contributors

PersonTokensPropCommitsCommitProp
eric w. biedermaneric w. biederman9895.15%583.33%
al viroal viro54.85%116.67%
Total103100.00%6100.00%


static void next_entry(struct ctl_table_header **phead, struct ctl_table **pentry) { struct ctl_table_header *head = *phead; struct ctl_table *entry = *pentry; struct ctl_node *ctl_node = &head->node[entry - head->ctl_table]; spin_lock(&sysctl_lock); unuse_table(head); ctl_node = first_usable_entry(rb_next(&ctl_node->node)); spin_unlock(&sysctl_lock); head = NULL; if (ctl_node) { head = ctl_node->header; entry = &head->ctl_table[ctl_node - head->node]; } *phead = head; *pentry = entry; }

Contributors

PersonTokensPropCommitsCommitProp
eric w. biedermaneric w. biederman10183.47%571.43%
al viroal viro1915.70%114.29%
pavel emelianovpavel emelianov10.83%114.29%
Total121100.00%7100.00%


void register_sysctl_root(struct ctl_table_root *root) { }

Contributors

PersonTokensPropCommitsCommitProp
eric w. biedermaneric w. biederman555.56%266.67%
pavel emelianovpavel emelianov444.44%133.33%
Total9100.00%3100.00%

/* * sysctl_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 (uid_eq(current_euid(), GLOBAL_ROOT_UID)) mode >>= 6; else if (in_egroup_p(GLOBAL_ROOT_GID)) mode >>= 3; if ((op & ~mode & (MAY_READ|MAY_WRITE|MAY_EXEC)) == 0) return 0; return -EACCES; }

Contributors

PersonTokensPropCommitsCommitProp
eric w. biedermaneric w. biederman5687.50%375.00%
lucas de marchilucas de marchi812.50%125.00%
Total64100.00%4100.00%


static int sysctl_perm(struct ctl_table_header *head, struct ctl_table *table, int op) { struct ctl_table_root *root = head->root; int mode; if (root->permissions) mode = root->permissions(head, table); else mode = table->mode; return test_perm(mode, op); }

Contributors

PersonTokensPropCommitsCommitProp
eric w. biedermaneric w. biederman4774.60%360.00%
lucas de marchilucas de marchi1422.22%120.00%
al viroal viro23.17%120.00%
Total63100.00%5100.00%


static struct inode *proc_sys_make_inode(struct super_block *sb, struct ctl_table_header *head, struct ctl_table *table) { struct ctl_table_root *root = head->root; struct inode *inode; struct proc_inode *ei; inode = new_inode(sb); if (!inode) goto out; inode->i_ino = get_next_ino(); sysctl_head_get(head); ei = PROC_I(inode); ei->sysctl = head; ei->sysctl_entry = table; inode->i_mtime = inode->i_atime = inode->i_ctime = current_time(inode); inode->i_mode = table->mode; if (!S_ISDIR(table->mode)) { inode->i_mode |= S_IFREG; inode->i_op = &proc_sys_inode_operations; inode->i_fop = &proc_sys_file_operations; } else { inode->i_mode |= S_IFDIR; inode->i_op = &proc_sys_dir_operations; inode->i_fop = &proc_sys_dir_file_operations; if (is_empty_dir(head)) make_empty_dir_inode(inode); } if (root->set_ownership) root->set_ownership(head, table, &inode->i_uid, &inode->i_gid); out: return inode; }

Contributors

PersonTokensPropCommitsCommitProp
eric w. biedermaneric w. biederman13765.24%444.44%
dmitry torokhovdmitry torokhov3416.19%111.11%
al viroal viro2210.48%222.22%
miklos szeredimiklos szeredi136.19%111.11%
deepa dinamanideepa dinamani41.90%111.11%
Total210100.00%9100.00%


static struct ctl_table_header *grab_header(struct inode *inode) { struct ctl_table_header *head = PROC_I(inode)->sysctl; if (!head) head = &sysctl_table_root.default_set.dir.header; return sysctl_head_grab(head); }

Contributors

PersonTokensPropCommitsCommitProp
eric w. biedermaneric w. biederman2859.57%571.43%
al viroal viro1225.53%114.29%
christoph hellwigchristoph hellwig714.89%114.29%
Total47100.00%7100.00%


static struct dentry *proc_sys_lookup(struct inode *dir, struct dentry *dentry, unsigned int flags) { struct ctl_table_header *head = grab_header(dir); struct ctl_table_header *h = NULL; const struct qstr *name = &dentry->d_name; struct ctl_table *p; struct inode *inode; struct dentry *err = ERR_PTR(-ENOENT); struct ctl_dir *ctl_dir; int ret; if (IS_ERR(head)) return ERR_CAST(head); ctl_dir = container_of(head, struct ctl_dir, header); p = lookup_entry(&h, ctl_dir, name->name, name->len); if (!p) goto out; if (S_ISLNK(p->mode)) { ret = sysctl_follow_link(&h, &p); err = ERR_PTR(ret); if (ret) goto out; } err = ERR_PTR(-ENOMEM); inode = proc_sys_make_inode(dir->i_sb, h ? h : head, p); if (!inode) goto out; err = NULL; d_set_d_op(dentry, &proc_sys_dentry_operations); d_add(dentry, inode); out: if (h) sysctl_head_finish(h); sysctl_head_finish(head); return err; }

Contributors

PersonTokensPropCommitsCommitProp
eric w. biedermaneric w. biederman18377.22%660.00%
al viroal viro4518.99%330.00%
francesco ruggerifrancesco ruggeri93.80%110.00%
Total237100.00%10100.00%


static ssize_t proc_sys_call_handler(struct file *filp, void __user *buf, size_t count, loff_t *ppos, int write) { struct inode *inode = file_inode(filp); struct ctl_table_header *head = grab_header(inode); struct ctl_table *table = PROC_I(inode)->sysctl_entry; ssize_t error; size_t res; if (IS_ERR(head)) return PTR_ERR(head); /* * At this point we know that the sysctl was not unregistered * and won't be until we finish. */ error = -EPERM; if (sysctl_perm(head, table, write ? MAY_WRITE : MAY_READ)) goto out; /* if that can happen at all, it should be -EINVAL, not -EISDIR */ error = -EINVAL; if (!table->proc_handler) goto out; /* careful: calling conventions are nasty here */ res = count; error = table->proc_handler(table, write, buf, &res, ppos); if (!error) error = res; out: sysctl_head_finish(head); return error; }

Contributors

PersonTokensPropCommitsCommitProp
eric w. biedermaneric w. biederman12779.87%233.33%
al viroal viro2616.35%350.00%
miklos szeredimiklos szeredi63.77%116.67%
Total159100.00%6100.00%


static ssize_t proc_sys_read(struct file *filp, char __user *buf, size_t count, loff_t *ppos) { return proc_sys_call_handler(filp, (void __user *)buf, count, ppos, 0); }

Contributors

PersonTokensPropCommitsCommitProp
eric w. biedermaneric w. biederman2661.90%250.00%
al viroal viro1023.81%125.00%
christoph hellwigchristoph hellwig614.29%125.00%
Total42100.00%4100.00%


static ssize_t proc_sys_write(struct file *filp, const char __user *buf, size_t count, loff_t *ppos) { return proc_sys_call_handler(filp, (void __user *)buf, count, ppos, 1); }

Contributors

PersonTokensPropCommitsCommitProp
eric w. biedermaneric w. biederman4195.35%266.67%
al viroal viro24.65%133.33%
Total43100.00%3100.00%


static int proc_sys_open(struct