diff options
Diffstat (limited to 'datapath/brc_procfs.c')
-rw-r--r-- | datapath/brc_procfs.c | 185 |
1 files changed, 185 insertions, 0 deletions
diff --git a/datapath/brc_procfs.c b/datapath/brc_procfs.c new file mode 100644 index 000000000..733e9a94d --- /dev/null +++ b/datapath/brc_procfs.c @@ -0,0 +1,185 @@ +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/netdevice.h> +#include <linux/proc_fs.h> +#include <linux/seq_file.h> +#include <net/genetlink.h> +#include "openvswitch/brcompat-netlink.h" + +/* This code implements a Generic Netlink command BRC_GENL_C_SET_PROC that can + * be used to add, modify, and delete arbitrary files in selected + * subdirectories of /proc. It's a horrible kluge prompted by the need to + * simulate certain /proc/net/vlan and /proc/net/bonding files for software + * that wants to read them, and with any luck it will go away eventually. + * + * The implementation is a kluge too. In particular, we want to release the + * strings copied into the 'data' members of proc_dir_entry when the + * proc_dir_entry structures are freed, but there doesn't appear to be a way to + * hook that, so instead we have to rely on being the only entity modifying the + * directories in question. + */ + +static int brc_seq_show(struct seq_file *seq, void *unused) +{ + seq_puts(seq, seq->private); + return 0; +} + +static int brc_seq_open(struct inode *inode, struct file *file) +{ + return single_open(file, brc_seq_show, PDE(inode)->data); +} + +static struct file_operations brc_fops = { + .owner = THIS_MODULE, + .open = brc_seq_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static struct proc_dir_entry *proc_vlan_dir; +static struct proc_dir_entry *proc_bonding_dir; + +struct proc_dir_entry *brc_lookup_entry(struct proc_dir_entry *de, const char *name) +{ + int namelen = strlen(name); + for (de = de->subdir; de; de = de->next) { + if (de->namelen != namelen) + continue; + if (!memcmp(name, de->name, de->namelen)) + return de; + } + return NULL; +} + +static struct proc_dir_entry *brc_open_dir(const char *dir_name, + struct proc_dir_entry *parent, + struct proc_dir_entry **dirp) +{ + if (!*dirp) { + struct proc_dir_entry *dir; + if (brc_lookup_entry(parent, dir_name)) { + printk(KERN_WARNING "%s proc directory exists, can't " + "simulate--probably its real module is " + "loaded\n", dir_name); + return NULL; + } + dir = *dirp = proc_mkdir(dir_name, parent); + } + return *dirp; +} + +/* Maximum length of the BRC_GENL_A_PROC_DIR and BRC_GENL_A_PROC_NAME strings. + * If we could depend on supporting NLA_NUL_STRING and the .len member in + * Generic Netlink policy, then we could just put this in brc_genl_policy (and + * simplify brc_genl_set_proc() below too), but upstream 2.6.18 does not have + * either. */ +#define BRC_NAME_LEN_MAX 32 + +int brc_genl_set_proc(struct sk_buff *skb, struct genl_info *info) +{ + struct proc_dir_entry *dir, *entry; + const char *dir_name, *name; + char *data; + + if (!info->attrs[BRC_GENL_A_PROC_DIR] || + VERIFY_NUL_STRING(info->attrs[BRC_GENL_A_PROC_DIR]) || + !info->attrs[BRC_GENL_A_PROC_NAME] || + VERIFY_NUL_STRING(info->attrs[BRC_GENL_A_PROC_NAME]) || + (info->attrs[BRC_GENL_A_PROC_DATA] && + VERIFY_NUL_STRING(info->attrs[BRC_GENL_A_PROC_DATA]))) + return -EINVAL; + + dir_name = nla_data(info->attrs[BRC_GENL_A_PROC_DIR]); + name = nla_data(info->attrs[BRC_GENL_A_PROC_NAME]); + if (strlen(dir_name) > BRC_NAME_LEN_MAX || + strlen(name) > BRC_NAME_LEN_MAX) + return -EINVAL; + + if (!strcmp(dir_name, "net/vlan")) + dir = brc_open_dir("vlan", proc_net, &proc_vlan_dir); + else if (!strcmp(dir_name, "net/bonding")) + dir = brc_open_dir("bonding", proc_net, &proc_bonding_dir); + else + return -EINVAL; + if (!dir) { + /* Probably failed because the module that really implements + * the function in question is loaded and already owns the + * directory in question.*/ + return -EBUSY; + } + + entry = brc_lookup_entry(dir, name); + if (!info->attrs[BRC_GENL_A_PROC_DATA]) { + if (!entry) + return -ENOENT; + + data = entry->data; + remove_proc_entry(name, dir); + if (brc_lookup_entry(dir, name)) + return -EBUSY; /* Shouldn't happen */ + + kfree(data); + } else { + data = kstrdup(nla_data(info->attrs[BRC_GENL_A_PROC_DATA]), + GFP_KERNEL); + if (!data) + return -ENOMEM; + + if (entry) { + char *old_data = entry->data; + entry->data = data; + kfree(old_data); + return 0; + } + + entry = create_proc_entry(name, S_IFREG|S_IRUSR|S_IWUSR, dir); + if (!entry) { + kfree(data); + return -ENOBUFS; + } + entry->proc_fops = &brc_fops; + entry->data = data; + } + return 0; +} + +static void kill_proc_dir(const char *dir_name, + struct proc_dir_entry *parent, + struct proc_dir_entry *dir) +{ + if (!dir) + return; + for (;;) { + struct proc_dir_entry *e; + char *data; + char name[BRC_NAME_LEN_MAX + 1]; + + e = dir->subdir; + if (!e) + break; + + if (e->namelen >= sizeof name) { + /* Can't happen: we prevent adding names this long by + * limiting the BRC_GENL_A_PROC_NAME string to + * BRC_NAME_LEN_MAX bytes. */ + WARN_ON(1); + break; + } + strcpy(name, e->name); + + data = e->data; + e->data = NULL; + kfree(data); + + remove_proc_entry(name, dir); + } + remove_proc_entry(dir_name, parent); +} + +void brc_procfs_exit(void) +{ + kill_proc_dir("vlan", proc_net, proc_vlan_dir); + kill_proc_dir("bonding", proc_net, proc_bonding_dir); +} |