diff options
Diffstat (limited to 'drivers/connector')
| -rw-r--r-- | drivers/connector/Kconfig | 2 | ||||
| -rw-r--r-- | drivers/connector/cn_proc.c | 261 | ||||
| -rw-r--r-- | drivers/connector/cn_queue.c | 77 | ||||
| -rw-r--r-- | drivers/connector/connector.c | 344 |
4 files changed, 351 insertions, 333 deletions
diff --git a/drivers/connector/Kconfig b/drivers/connector/Kconfig index 100bfd42206..6e6730f9dfd 100644 --- a/drivers/connector/Kconfig +++ b/drivers/connector/Kconfig @@ -7,7 +7,7 @@ menuconfig CONNECTOR of the netlink socket protocol. Connector support can also be built as a module. If so, the module - will be called cn.ko. + will be called cn. if CONNECTOR diff --git a/drivers/connector/cn_proc.c b/drivers/connector/cn_proc.c index 5c9f67f98d1..ccdd4c7e748 100644 --- a/drivers/connector/cn_proc.c +++ b/drivers/connector/cn_proc.c @@ -27,12 +27,28 @@ #include <linux/ktime.h> #include <linux/init.h> #include <linux/connector.h> -#include <asm/atomic.h> -#include <asm/unaligned.h> +#include <linux/gfp.h> +#include <linux/ptrace.h> +#include <linux/atomic.h> +#include <linux/pid_namespace.h> #include <linux/cn_proc.h> -#define CN_PROC_MSG_SIZE (sizeof(struct cn_msg) + sizeof(struct proc_event)) +/* + * Size of a cn_msg followed by a proc_event structure. Since the + * sizeof struct cn_msg is a multiple of 4 bytes, but not 8 bytes, we + * add one 4-byte word to the size here, and then start the actual + * cn_msg structure 4 bytes into the stack buffer. The result is that + * the immediately following proc_event structure is aligned to 8 bytes. + */ +#define CN_PROC_MSG_SIZE (sizeof(struct cn_msg) + sizeof(struct proc_event) + 4) + +/* See comment above; we test our assumption about sizeof struct cn_msg here. */ +static inline struct cn_msg *buffer_to_cn_msg(__u8 *buffer) +{ + BUILD_BUG_ON(sizeof(struct cn_msg) != 20); + return (struct cn_msg *)(buffer + 4); +} static atomic_t proc_event_num_listeners = ATOMIC_INIT(0); static struct cb_id cn_proc_event_id = { CN_IDX_PROC, CN_VAL_PROC }; @@ -42,37 +58,44 @@ static DEFINE_PER_CPU(__u32, proc_event_counts) = { 0 }; static inline void get_seq(__u32 *ts, int *cpu) { - *ts = get_cpu_var(proc_event_counts)++; + preempt_disable(); + *ts = __this_cpu_inc_return(proc_event_counts) - 1; *cpu = smp_processor_id(); - put_cpu_var(proc_event_counts); + preempt_enable(); } void proc_fork_connector(struct task_struct *task) { struct cn_msg *msg; struct proc_event *ev; - __u8 buffer[CN_PROC_MSG_SIZE]; + __u8 buffer[CN_PROC_MSG_SIZE] __aligned(8); struct timespec ts; + struct task_struct *parent; if (atomic_read(&proc_event_num_listeners) < 1) return; - msg = (struct cn_msg*)buffer; - ev = (struct proc_event*)msg->data; + msg = buffer_to_cn_msg(buffer); + ev = (struct proc_event *)msg->data; + memset(&ev->event_data, 0, sizeof(ev->event_data)); get_seq(&msg->seq, &ev->cpu); ktime_get_ts(&ts); /* get high res monotonic timestamp */ - put_unaligned(timespec_to_ns(&ts), (__u64 *)&ev->timestamp_ns); + ev->timestamp_ns = timespec_to_ns(&ts); ev->what = PROC_EVENT_FORK; - ev->event_data.fork.parent_pid = task->real_parent->pid; - ev->event_data.fork.parent_tgid = task->real_parent->tgid; + rcu_read_lock(); + parent = rcu_dereference(task->real_parent); + ev->event_data.fork.parent_pid = parent->pid; + ev->event_data.fork.parent_tgid = parent->tgid; + rcu_read_unlock(); ev->event_data.fork.child_pid = task->pid; ev->event_data.fork.child_tgid = task->tgid; memcpy(&msg->id, &cn_proc_event_id, sizeof(msg->id)); msg->ack = 0; /* not used */ msg->len = sizeof(*ev); + msg->flags = 0; /* not used */ /* If cn_netlink_send() failed, the data is not sent */ - cn_netlink_send(msg, CN_IDX_PROC, GFP_KERNEL); + cn_netlink_send(msg, 0, CN_IDX_PROC, GFP_KERNEL); } void proc_exec_connector(struct task_struct *task) @@ -80,16 +103,17 @@ void proc_exec_connector(struct task_struct *task) struct cn_msg *msg; struct proc_event *ev; struct timespec ts; - __u8 buffer[CN_PROC_MSG_SIZE]; + __u8 buffer[CN_PROC_MSG_SIZE] __aligned(8); if (atomic_read(&proc_event_num_listeners) < 1) return; - msg = (struct cn_msg*)buffer; - ev = (struct proc_event*)msg->data; + msg = buffer_to_cn_msg(buffer); + ev = (struct proc_event *)msg->data; + memset(&ev->event_data, 0, sizeof(ev->event_data)); get_seq(&msg->seq, &ev->cpu); ktime_get_ts(&ts); /* get high res monotonic timestamp */ - put_unaligned(timespec_to_ns(&ts), (__u64 *)&ev->timestamp_ns); + ev->timestamp_ns = timespec_to_ns(&ts); ev->what = PROC_EVENT_EXEC; ev->event_data.exec.process_pid = task->pid; ev->event_data.exec.process_tgid = task->tgid; @@ -97,57 +121,184 @@ void proc_exec_connector(struct task_struct *task) memcpy(&msg->id, &cn_proc_event_id, sizeof(msg->id)); msg->ack = 0; /* not used */ msg->len = sizeof(*ev); - cn_netlink_send(msg, CN_IDX_PROC, GFP_KERNEL); + msg->flags = 0; /* not used */ + cn_netlink_send(msg, 0, CN_IDX_PROC, GFP_KERNEL); } void proc_id_connector(struct task_struct *task, int which_id) { struct cn_msg *msg; struct proc_event *ev; - __u8 buffer[CN_PROC_MSG_SIZE]; + __u8 buffer[CN_PROC_MSG_SIZE] __aligned(8); struct timespec ts; + const struct cred *cred; if (atomic_read(&proc_event_num_listeners) < 1) return; - msg = (struct cn_msg*)buffer; - ev = (struct proc_event*)msg->data; + msg = buffer_to_cn_msg(buffer); + ev = (struct proc_event *)msg->data; + memset(&ev->event_data, 0, sizeof(ev->event_data)); ev->what = which_id; ev->event_data.id.process_pid = task->pid; ev->event_data.id.process_tgid = task->tgid; + rcu_read_lock(); + cred = __task_cred(task); if (which_id == PROC_EVENT_UID) { - ev->event_data.id.r.ruid = task->uid; - ev->event_data.id.e.euid = task->euid; + ev->event_data.id.r.ruid = from_kuid_munged(&init_user_ns, cred->uid); + ev->event_data.id.e.euid = from_kuid_munged(&init_user_ns, cred->euid); } else if (which_id == PROC_EVENT_GID) { - ev->event_data.id.r.rgid = task->gid; - ev->event_data.id.e.egid = task->egid; + ev->event_data.id.r.rgid = from_kgid_munged(&init_user_ns, cred->gid); + ev->event_data.id.e.egid = from_kgid_munged(&init_user_ns, cred->egid); + } else { + rcu_read_unlock(); + return; + } + rcu_read_unlock(); + get_seq(&msg->seq, &ev->cpu); + ktime_get_ts(&ts); /* get high res monotonic timestamp */ + ev->timestamp_ns = timespec_to_ns(&ts); + + memcpy(&msg->id, &cn_proc_event_id, sizeof(msg->id)); + msg->ack = 0; /* not used */ + msg->len = sizeof(*ev); + msg->flags = 0; /* not used */ + cn_netlink_send(msg, 0, CN_IDX_PROC, GFP_KERNEL); +} + +void proc_sid_connector(struct task_struct *task) +{ + struct cn_msg *msg; + struct proc_event *ev; + struct timespec ts; + __u8 buffer[CN_PROC_MSG_SIZE] __aligned(8); + + if (atomic_read(&proc_event_num_listeners) < 1) + return; + + msg = buffer_to_cn_msg(buffer); + ev = (struct proc_event *)msg->data; + memset(&ev->event_data, 0, sizeof(ev->event_data)); + get_seq(&msg->seq, &ev->cpu); + ktime_get_ts(&ts); /* get high res monotonic timestamp */ + ev->timestamp_ns = timespec_to_ns(&ts); + ev->what = PROC_EVENT_SID; + ev->event_data.sid.process_pid = task->pid; + ev->event_data.sid.process_tgid = task->tgid; + + memcpy(&msg->id, &cn_proc_event_id, sizeof(msg->id)); + msg->ack = 0; /* not used */ + msg->len = sizeof(*ev); + msg->flags = 0; /* not used */ + cn_netlink_send(msg, 0, CN_IDX_PROC, GFP_KERNEL); +} + +void proc_ptrace_connector(struct task_struct *task, int ptrace_id) +{ + struct cn_msg *msg; + struct proc_event *ev; + struct timespec ts; + __u8 buffer[CN_PROC_MSG_SIZE] __aligned(8); + + if (atomic_read(&proc_event_num_listeners) < 1) + return; + + msg = buffer_to_cn_msg(buffer); + ev = (struct proc_event *)msg->data; + memset(&ev->event_data, 0, sizeof(ev->event_data)); + get_seq(&msg->seq, &ev->cpu); + ktime_get_ts(&ts); /* get high res monotonic timestamp */ + ev->timestamp_ns = timespec_to_ns(&ts); + ev->what = PROC_EVENT_PTRACE; + ev->event_data.ptrace.process_pid = task->pid; + ev->event_data.ptrace.process_tgid = task->tgid; + if (ptrace_id == PTRACE_ATTACH) { + ev->event_data.ptrace.tracer_pid = current->pid; + ev->event_data.ptrace.tracer_tgid = current->tgid; + } else if (ptrace_id == PTRACE_DETACH) { + ev->event_data.ptrace.tracer_pid = 0; + ev->event_data.ptrace.tracer_tgid = 0; } else - return; + return; + + memcpy(&msg->id, &cn_proc_event_id, sizeof(msg->id)); + msg->ack = 0; /* not used */ + msg->len = sizeof(*ev); + msg->flags = 0; /* not used */ + cn_netlink_send(msg, 0, CN_IDX_PROC, GFP_KERNEL); +} + +void proc_comm_connector(struct task_struct *task) +{ + struct cn_msg *msg; + struct proc_event *ev; + struct timespec ts; + __u8 buffer[CN_PROC_MSG_SIZE] __aligned(8); + + if (atomic_read(&proc_event_num_listeners) < 1) + return; + + msg = buffer_to_cn_msg(buffer); + ev = (struct proc_event *)msg->data; + memset(&ev->event_data, 0, sizeof(ev->event_data)); get_seq(&msg->seq, &ev->cpu); ktime_get_ts(&ts); /* get high res monotonic timestamp */ - put_unaligned(timespec_to_ns(&ts), (__u64 *)&ev->timestamp_ns); + ev->timestamp_ns = timespec_to_ns(&ts); + ev->what = PROC_EVENT_COMM; + ev->event_data.comm.process_pid = task->pid; + ev->event_data.comm.process_tgid = task->tgid; + get_task_comm(ev->event_data.comm.comm, task); memcpy(&msg->id, &cn_proc_event_id, sizeof(msg->id)); msg->ack = 0; /* not used */ msg->len = sizeof(*ev); - cn_netlink_send(msg, CN_IDX_PROC, GFP_KERNEL); + msg->flags = 0; /* not used */ + cn_netlink_send(msg, 0, CN_IDX_PROC, GFP_KERNEL); +} + +void proc_coredump_connector(struct task_struct *task) +{ + struct cn_msg *msg; + struct proc_event *ev; + __u8 buffer[CN_PROC_MSG_SIZE] __aligned(8); + struct timespec ts; + + if (atomic_read(&proc_event_num_listeners) < 1) + return; + + msg = buffer_to_cn_msg(buffer); + ev = (struct proc_event *)msg->data; + memset(&ev->event_data, 0, sizeof(ev->event_data)); + get_seq(&msg->seq, &ev->cpu); + ktime_get_ts(&ts); /* get high res monotonic timestamp */ + ev->timestamp_ns = timespec_to_ns(&ts); + ev->what = PROC_EVENT_COREDUMP; + ev->event_data.coredump.process_pid = task->pid; + ev->event_data.coredump.process_tgid = task->tgid; + + memcpy(&msg->id, &cn_proc_event_id, sizeof(msg->id)); + msg->ack = 0; /* not used */ + msg->len = sizeof(*ev); + msg->flags = 0; /* not used */ + cn_netlink_send(msg, 0, CN_IDX_PROC, GFP_KERNEL); } void proc_exit_connector(struct task_struct *task) { struct cn_msg *msg; struct proc_event *ev; - __u8 buffer[CN_PROC_MSG_SIZE]; + __u8 buffer[CN_PROC_MSG_SIZE] __aligned(8); struct timespec ts; if (atomic_read(&proc_event_num_listeners) < 1) return; - msg = (struct cn_msg*)buffer; - ev = (struct proc_event*)msg->data; + msg = buffer_to_cn_msg(buffer); + ev = (struct proc_event *)msg->data; + memset(&ev->event_data, 0, sizeof(ev->event_data)); get_seq(&msg->seq, &ev->cpu); ktime_get_ts(&ts); /* get high res monotonic timestamp */ - put_unaligned(timespec_to_ns(&ts), (__u64 *)&ev->timestamp_ns); + ev->timestamp_ns = timespec_to_ns(&ts); ev->what = PROC_EVENT_EXIT; ev->event_data.exit.process_pid = task->pid; ev->event_data.exit.process_tgid = task->tgid; @@ -157,7 +308,8 @@ void proc_exit_connector(struct task_struct *task) memcpy(&msg->id, &cn_proc_event_id, sizeof(msg->id)); msg->ack = 0; /* not used */ msg->len = sizeof(*ev); - cn_netlink_send(msg, CN_IDX_PROC, GFP_KERNEL); + msg->flags = 0; /* not used */ + cn_netlink_send(msg, 0, CN_IDX_PROC, GFP_KERNEL); } /* @@ -172,40 +324,57 @@ static void cn_proc_ack(int err, int rcvd_seq, int rcvd_ack) { struct cn_msg *msg; struct proc_event *ev; - __u8 buffer[CN_PROC_MSG_SIZE]; + __u8 buffer[CN_PROC_MSG_SIZE] __aligned(8); struct timespec ts; if (atomic_read(&proc_event_num_listeners) < 1) return; - msg = (struct cn_msg*)buffer; - ev = (struct proc_event*)msg->data; + msg = buffer_to_cn_msg(buffer); + ev = (struct proc_event *)msg->data; + memset(&ev->event_data, 0, sizeof(ev->event_data)); msg->seq = rcvd_seq; ktime_get_ts(&ts); /* get high res monotonic timestamp */ - put_unaligned(timespec_to_ns(&ts), (__u64 *)&ev->timestamp_ns); + ev->timestamp_ns = timespec_to_ns(&ts); ev->cpu = -1; ev->what = PROC_EVENT_NONE; ev->event_data.ack.err = err; memcpy(&msg->id, &cn_proc_event_id, sizeof(msg->id)); msg->ack = rcvd_ack + 1; msg->len = sizeof(*ev); - cn_netlink_send(msg, CN_IDX_PROC, GFP_KERNEL); + msg->flags = 0; /* not used */ + cn_netlink_send(msg, 0, CN_IDX_PROC, GFP_KERNEL); } /** * cn_proc_mcast_ctl * @data: message sent from userspace via the connector */ -static void cn_proc_mcast_ctl(void *data) +static void cn_proc_mcast_ctl(struct cn_msg *msg, + struct netlink_skb_parms *nsp) { - struct cn_msg *msg = data; enum proc_cn_mcast_op *mc_op = NULL; int err = 0; if (msg->len != sizeof(*mc_op)) return; - mc_op = (enum proc_cn_mcast_op*)msg->data; + /* + * Events are reported with respect to the initial pid + * and user namespaces so ignore requestors from + * other namespaces. + */ + if ((current_user_ns() != &init_user_ns) || + (task_active_pid_ns(current) != &init_pid_ns)) + return; + + /* Can only change if privileged. */ + if (!__netlink_ns_capable(nsp, &init_user_ns, CAP_NET_ADMIN)) { + err = EPERM; + goto out; + } + + mc_op = (enum proc_cn_mcast_op *)msg->data; switch (*mc_op) { case PROC_CN_MCAST_LISTEN: atomic_inc(&proc_event_num_listeners); @@ -217,6 +386,8 @@ static void cn_proc_mcast_ctl(void *data) err = EINVAL; break; } + +out: cn_proc_ack(err, msg->seq, msg->ack); } @@ -227,11 +398,11 @@ static void cn_proc_mcast_ctl(void *data) */ static int __init cn_proc_init(void) { - int err; - - if ((err = cn_add_callback(&cn_proc_event_id, "cn_proc", - &cn_proc_mcast_ctl))) { - printk(KERN_WARNING "cn_proc failed to register\n"); + int err = cn_add_callback(&cn_proc_event_id, + "cn_proc", + &cn_proc_mcast_ctl); + if (err) { + pr_warn("cn_proc failed to register\n"); return err; } return 0; diff --git a/drivers/connector/cn_queue.c b/drivers/connector/cn_queue.c index b6fe7e7a2c2..1f8bf054d11 100644 --- a/drivers/connector/cn_queue.c +++ b/drivers/connector/cn_queue.c @@ -1,9 +1,9 @@ /* - * cn_queue.c - * - * 2004-2005 Copyright (c) Evgeniy Polyakov <johnpol@2ka.mipt.ru> + * cn_queue.c + * + * 2004+ Copyright (c) Evgeniy Polyakov <zbr@ioremap.net> * All rights reserved. - * + * * This program 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 @@ -31,42 +31,37 @@ #include <linux/connector.h> #include <linux/delay.h> -void cn_queue_wrapper(struct work_struct *work) -{ - struct cn_callback_entry *cbq = - container_of(work, struct cn_callback_entry, work); - struct cn_callback_data *d = &cbq->data; - - d->callback(d->callback_priv); - - d->destruct_data(d->ddata); - d->ddata = NULL; - - kfree(d->free); -} - -static struct cn_callback_entry *cn_queue_alloc_callback_entry(char *name, struct cb_id *id, void (*callback)(void *)) +static struct cn_callback_entry * +cn_queue_alloc_callback_entry(struct cn_queue_dev *dev, const char *name, + struct cb_id *id, + void (*callback)(struct cn_msg *, + struct netlink_skb_parms *)) { struct cn_callback_entry *cbq; cbq = kzalloc(sizeof(*cbq), GFP_KERNEL); if (!cbq) { - printk(KERN_ERR "Failed to create new callback queue.\n"); + pr_err("Failed to create new callback queue.\n"); return NULL; } + atomic_set(&cbq->refcnt, 1); + + atomic_inc(&dev->refcnt); + cbq->pdev = dev; + snprintf(cbq->id.name, sizeof(cbq->id.name), "%s", name); memcpy(&cbq->id.id, id, sizeof(struct cb_id)); - cbq->data.callback = callback; - - INIT_WORK(&cbq->work, &cn_queue_wrapper); + cbq->callback = callback; return cbq; } -static void cn_queue_free_callback(struct cn_callback_entry *cbq) +void cn_queue_release_callback(struct cn_callback_entry *cbq) { - flush_workqueue(cbq->pdev->cn_queue); + if (!atomic_dec_and_test(&cbq->refcnt)) + return; + atomic_dec(&cbq->pdev->refcnt); kfree(cbq); } @@ -75,18 +70,18 @@ int cn_cb_equal(struct cb_id *i1, struct cb_id *i2) return ((i1->idx == i2->idx) && (i1->val == i2->val)); } -int cn_queue_add_callback(struct cn_queue_dev *dev, char *name, struct cb_id *id, void (*callback)(void *)) +int cn_queue_add_callback(struct cn_queue_dev *dev, const char *name, + struct cb_id *id, + void (*callback)(struct cn_msg *, + struct netlink_skb_parms *)) { struct cn_callback_entry *cbq, *__cbq; int found = 0; - cbq = cn_queue_alloc_callback_entry(name, id, callback); + cbq = cn_queue_alloc_callback_entry(dev, name, id, callback); if (!cbq) return -ENOMEM; - atomic_inc(&dev->refcnt); - cbq->pdev = dev; - spin_lock_bh(&dev->queue_lock); list_for_each_entry(__cbq, &dev->queue_list, callback_entry) { if (cn_cb_equal(&__cbq->id.id, id)) { @@ -99,8 +94,7 @@ int cn_queue_add_callback(struct cn_queue_dev *dev, char *name, struct cb_id *id spin_unlock_bh(&dev->queue_lock); if (found) { - cn_queue_free_callback(cbq); - atomic_dec(&dev->refcnt); + cn_queue_release_callback(cbq); return -EINVAL; } @@ -125,13 +119,11 @@ void cn_queue_del_callback(struct cn_queue_dev *dev, struct cb_id *id) } spin_unlock_bh(&dev->queue_lock); - if (found) { - cn_queue_free_callback(cbq); - atomic_dec(&dev->refcnt); - } + if (found) + cn_queue_release_callback(cbq); } -struct cn_queue_dev *cn_queue_alloc_dev(char *name, struct sock *nls) +struct cn_queue_dev *cn_queue_alloc_dev(const char *name, struct sock *nls) { struct cn_queue_dev *dev; @@ -146,12 +138,6 @@ struct cn_queue_dev *cn_queue_alloc_dev(char *name, struct sock *nls) dev->nls = nls; - dev->cn_queue = create_singlethread_workqueue(dev->name); - if (!dev->cn_queue) { - kfree(dev); - return NULL; - } - return dev; } @@ -159,16 +145,13 @@ void cn_queue_free_dev(struct cn_queue_dev *dev) { struct cn_callback_entry *cbq, *n; - flush_workqueue(dev->cn_queue); - destroy_workqueue(dev->cn_queue); - spin_lock_bh(&dev->queue_lock); list_for_each_entry_safe(cbq, n, &dev->queue_list, callback_entry) list_del(&cbq->callback_entry); spin_unlock_bh(&dev->queue_lock); while (atomic_read(&dev->refcnt)) { - printk(KERN_INFO "Waiting for %s to become free: refcnt=%d.\n", + pr_info("Waiting for %s to become free: refcnt=%d.\n", dev->name, atomic_read(&dev->refcnt)); msleep(1000); } diff --git a/drivers/connector/connector.c b/drivers/connector/connector.c index 85e2ba7fcfb..f612d68629d 100644 --- a/drivers/connector/connector.c +++ b/drivers/connector/connector.c @@ -1,9 +1,9 @@ /* - * connector.c - * - * 2004-2005 Copyright (c) Evgeniy Polyakov <johnpol@2ka.mipt.ru> + * connector.c + * + * 2004+ Copyright (c) Evgeniy Polyakov <zbr@ioremap.net> * All rights reserved. - * + * * This program 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 @@ -23,33 +23,28 @@ #include <linux/module.h> #include <linux/list.h> #include <linux/skbuff.h> -#include <linux/netlink.h> +#include <net/netlink.h> #include <linux/moduleparam.h> #include <linux/connector.h> +#include <linux/slab.h> #include <linux/mutex.h> +#include <linux/proc_fs.h> +#include <linux/spinlock.h> #include <net/sock.h> MODULE_LICENSE("GPL"); -MODULE_AUTHOR("Evgeniy Polyakov <johnpol@2ka.mipt.ru>"); +MODULE_AUTHOR("Evgeniy Polyakov <zbr@ioremap.net>"); MODULE_DESCRIPTION("Generic userspace <-> kernelspace connector."); - -static u32 cn_idx = CN_IDX_CONNECTOR; -static u32 cn_val = CN_VAL_CONNECTOR; - -module_param(cn_idx, uint, 0); -module_param(cn_val, uint, 0); -MODULE_PARM_DESC(cn_idx, "Connector's main device idx."); -MODULE_PARM_DESC(cn_val, "Connector's main device val."); - -static DEFINE_MUTEX(notify_lock); -static LIST_HEAD(notify_list); +MODULE_ALIAS_NET_PF_PROTO(PF_NETLINK, NETLINK_CONNECTOR); static struct cn_dev cdev; static int cn_already_initialized; /* + * Sends mult (multiple) cn_msg at a time. + * * msg->seq and msg->ack are used to determine message genealogy. * When someone sends message it puts there locally unique sequence * and random acknowledge numbers. Sequence number may be copied into @@ -57,7 +52,7 @@ static int cn_already_initialized; * * Sequence number is incremented with each message to be sent. * - * If we expect reply to our message then the sequence number in + * If we expect a reply to our message then the sequence number in * received message MUST be the same as in original message, and * acknowledge number MUST be the same + 1. * @@ -69,8 +64,14 @@ static int cn_already_initialized; * the acknowledgement number in the original message + 1, then it is * a new message. * + * If msg->len != len, then additional cn_msg messages are expected following + * the first msg. + * + * The message is sent to, the portid if given, the group if given, both if + * both, or if both are zero then the group is looked up and sent there. */ -int cn_netlink_send(struct cn_msg *msg, u32 __group, gfp_t gfp_mask) +int cn_netlink_send_mult(struct cn_msg *msg, u16 len, u32 portid, u32 __group, + gfp_t gfp_mask) { struct cn_callback_entry *__cbq; unsigned int size; @@ -81,7 +82,9 @@ int cn_netlink_send(struct cn_msg *msg, u32 __group, gfp_t gfp_mask) u32 group = 0; int found = 0; - if (!__group) { + if (portid || __group) { + group = __group; + } else { spin_lock_bh(&dev->cbdev->queue_lock); list_for_each_entry(__cbq, &dev->cbdev->queue_list, callback_entry) { @@ -95,89 +98,72 @@ int cn_netlink_send(struct cn_msg *msg, u32 __group, gfp_t gfp_mask) if (!found) return -ENODEV; - } else { - group = __group; } - if (!netlink_has_listeners(dev->nls, group)) + if (!portid && !netlink_has_listeners(dev->nls, group)) return -ESRCH; - size = NLMSG_SPACE(sizeof(*msg) + msg->len); + size = sizeof(*msg) + len; - skb = alloc_skb(size, gfp_mask); + skb = nlmsg_new(size, gfp_mask); if (!skb) return -ENOMEM; - nlh = NLMSG_PUT(skb, 0, msg->seq, NLMSG_DONE, size - sizeof(*nlh)); + nlh = nlmsg_put(skb, 0, msg->seq, NLMSG_DONE, size, 0); + if (!nlh) { + kfree_skb(skb); + return -EMSGSIZE; + } - data = NLMSG_DATA(nlh); + data = nlmsg_data(nlh); - memcpy(data, msg, sizeof(*data) + msg->len); + memcpy(data, msg, size); NETLINK_CB(skb).dst_group = group; - return netlink_broadcast(dev->nls, skb, 0, group, gfp_mask); + if (group) + return netlink_broadcast(dev->nls, skb, portid, group, + gfp_mask); + return netlink_unicast(dev->nls, skb, portid, !(gfp_mask&__GFP_WAIT)); +} +EXPORT_SYMBOL_GPL(cn_netlink_send_mult); -nlmsg_failure: - kfree_skb(skb); - return -EINVAL; +/* same as cn_netlink_send_mult except msg->len is used for len */ +int cn_netlink_send(struct cn_msg *msg, u32 portid, u32 __group, + gfp_t gfp_mask) +{ + return cn_netlink_send_mult(msg, msg->len, portid, __group, gfp_mask); } EXPORT_SYMBOL_GPL(cn_netlink_send); /* * Callback helper - queues work and setup destructor for given data. */ -static int cn_call_callback(struct cn_msg *msg, void (*destruct_data)(void *), void *data) +static int cn_call_callback(struct sk_buff *skb) { - struct cn_callback_entry *__cbq, *__new_cbq; + struct cn_callback_entry *i, *cbq = NULL; struct cn_dev *dev = &cdev; + struct cn_msg *msg = nlmsg_data(nlmsg_hdr(skb)); + struct netlink_skb_parms *nsp = &NETLINK_CB(skb); int err = -ENODEV; spin_lock_bh(&dev->cbdev->queue_lock); - list_for_each_entry(__cbq, &dev->cbdev->queue_list, callback_entry) { - if (cn_cb_equal(&__cbq->id.id, &msg->id)) { - if (likely(!work_pending(&__cbq->work) && - __cbq->data.ddata == NULL)) { - __cbq->data.callback_priv = msg; - - __cbq->data.ddata = data; - __cbq->data.destruct_data = destruct_data; - - if (queue_work(dev->cbdev->cn_queue, - &__cbq->work)) - err = 0; - else - err = -EINVAL; - } else { - struct cn_callback_data *d; - - err = -ENOMEM; - __new_cbq = kzalloc(sizeof(struct cn_callback_entry), GFP_ATOMIC); - if (__new_cbq) { - d = &__new_cbq->data; - d->callback_priv = msg; - d->callback = __cbq->data.callback; - d->ddata = data; - d->destruct_data = destruct_data; - d->free = __new_cbq; - - INIT_WORK(&__new_cbq->work, - &cn_queue_wrapper); - - if (queue_work(dev->cbdev->cn_queue, - &__new_cbq->work)) - err = 0; - else { - kfree(__new_cbq); - err = -EINVAL; - } - } - } + list_for_each_entry(i, &dev->cbdev->queue_list, callback_entry) { + if (cn_cb_equal(&i->id.id, &msg->id)) { + atomic_inc(&i->refcnt); + cbq = i; break; } } spin_unlock_bh(&dev->cbdev->queue_lock); + if (cbq != NULL) { + cbq->callback(msg, nsp); + kfree_skb(skb); + cn_queue_release_callback(cbq); + err = 0; + } + return err; } @@ -188,85 +174,38 @@ static int cn_call_callback(struct cn_msg *msg, void (*destruct_data)(void *), v */ static void cn_rx_skb(struct sk_buff *__skb) { - struct cn_msg *msg; struct nlmsghdr *nlh; - int err; struct sk_buff *skb; + int len, err; skb = skb_get(__skb); - if (skb->len >= NLMSG_SPACE(0)) { + if (skb->len >= NLMSG_HDRLEN) { nlh = nlmsg_hdr(skb); + len = nlmsg_len(nlh); - if (nlh->nlmsg_len < sizeof(struct cn_msg) || + if (len < (int)sizeof(struct cn_msg) || skb->len < nlh->nlmsg_len || - nlh->nlmsg_len > CONNECTOR_MAX_MSG_SIZE) { + len > CONNECTOR_MAX_MSG_SIZE) { kfree_skb(skb); return; } - msg = NLMSG_DATA(nlh); - err = cn_call_callback(msg, (void (*)(void *))kfree_skb, skb); + err = cn_call_callback(skb); if (err < 0) kfree_skb(skb); } } /* - * Notification routing. - * - * Gets id and checks if there are notification request for it's idx - * and val. If there are such requests notify the listeners with the - * given notify event. - * - */ -static void cn_notify(struct cb_id *id, u32 notify_event) -{ - struct cn_ctl_entry *ent; - - mutex_lock(¬ify_lock); - list_for_each_entry(ent, ¬ify_list, notify_entry) { - int i; - struct cn_notify_req *req; - struct cn_ctl_msg *ctl = ent->msg; - int idx_found, val_found; - - idx_found = val_found = 0; - - req = (struct cn_notify_req *)ctl->data; - for (i = 0; i < ctl->idx_notify_num; ++i, ++req) { - if (id->idx >= req->first && - id->idx < req->first + req->range) { - idx_found = 1; - break; - } - } - - for (i = 0; i < ctl->val_notify_num; ++i, ++req) { - if (id->val >= req->first && - id->val < req->first + req->range) { - val_found = 1; - break; - } - } - - if (idx_found && val_found) { - struct cn_msg m = { .ack = notify_event, }; - - memcpy(&m.id, id, sizeof(m.id)); - cn_netlink_send(&m, ctl->group, GFP_KERNEL); - } - } - mutex_unlock(¬ify_lock); -} - -/* * Callback add routing - adds callback with given ID and name. * If there is registered callback with the same ID it will not be added. * * May sleep. */ -int cn_add_callback(struct cb_id *id, char *name, void (*callback)(void *)) +int cn_add_callback(struct cb_id *id, const char *name, + void (*callback)(struct cn_msg *, + struct netlink_skb_parms *)) { int err; struct cn_dev *dev = &cdev; @@ -278,8 +217,6 @@ int cn_add_callback(struct cb_id *id, char *name, void (*callback)(void *)) if (err) return err; - cn_notify(id, 0); - return 0; } EXPORT_SYMBOL_GPL(cn_add_callback); @@ -297,124 +234,56 @@ void cn_del_callback(struct cb_id *id) struct cn_dev *dev = &cdev; cn_queue_del_callback(dev->cbdev, id); - cn_notify(id, 1); } EXPORT_SYMBOL_GPL(cn_del_callback); -/* - * Checks two connector's control messages to be the same. - * Returns 1 if they are the same or if the first one is corrupted. - */ -static int cn_ctl_msg_equals(struct cn_ctl_msg *m1, struct cn_ctl_msg *m2) +static int cn_proc_show(struct seq_file *m, void *v) { - int i; - struct cn_notify_req *req1, *req2; - - if (m1->idx_notify_num != m2->idx_notify_num) - return 0; + struct cn_queue_dev *dev = cdev.cbdev; + struct cn_callback_entry *cbq; - if (m1->val_notify_num != m2->val_notify_num) - return 0; + seq_printf(m, "Name ID\n"); - if (m1->len != m2->len) - return 0; + spin_lock_bh(&dev->queue_lock); - if ((m1->idx_notify_num + m1->val_notify_num) * sizeof(*req1) != - m1->len) - return 1; - - req1 = (struct cn_notify_req *)m1->data; - req2 = (struct cn_notify_req *)m2->data; - - for (i = 0; i < m1->idx_notify_num; ++i) { - if (req1->first != req2->first || req1->range != req2->range) - return 0; - req1++; - req2++; + list_for_each_entry(cbq, &dev->queue_list, callback_entry) { + seq_printf(m, "%-15s %u:%u\n", + cbq->id.name, + cbq->id.id.idx, + cbq->id.id.val); } - for (i = 0; i < m1->val_notify_num; ++i) { - if (req1->first != req2->first || req1->range != req2->range) - return 0; - req1++; - req2++; - } + spin_unlock_bh(&dev->queue_lock); - return 1; + return 0; } -/* - * Main connector device's callback. - * - * Used for notification of a request's processing. - */ -static void cn_callback(void *data) +static int cn_proc_open(struct inode *inode, struct file *file) { - struct cn_msg *msg = data; - struct cn_ctl_msg *ctl; - struct cn_ctl_entry *ent; - u32 size; - - if (msg->len < sizeof(*ctl)) - return; - - ctl = (struct cn_ctl_msg *)msg->data; - - size = (sizeof(*ctl) + ((ctl->idx_notify_num + - ctl->val_notify_num) * - sizeof(struct cn_notify_req))); - - if (msg->len != size) - return; - - if (ctl->len + sizeof(*ctl) != msg->len) - return; - - /* - * Remove notification. - */ - if (ctl->group == 0) { - struct cn_ctl_entry *n; - - mutex_lock(¬ify_lock); - list_for_each_entry_safe(ent, n, ¬ify_list, notify_entry) { - if (cn_ctl_msg_equals(ent->msg, ctl)) { - list_del(&ent->notify_entry); - kfree(ent); - } - } - mutex_unlock(¬ify_lock); - - return; - } - - size += sizeof(*ent); - - ent = kzalloc(size, GFP_KERNEL); - if (!ent) - return; + return single_open(file, cn_proc_show, NULL); +} - ent->msg = (struct cn_ctl_msg *)(ent + 1); +static const struct file_operations cn_file_ops = { + .owner = THIS_MODULE, + .open = cn_proc_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release +}; - memcpy(ent->msg, ctl, size - sizeof(*ent)); +static struct cn_dev cdev = { + .input = cn_rx_skb, +}; - mutex_lock(¬ify_lock); - list_add(&ent->notify_entry, ¬ify_list); - mutex_unlock(¬ify_lock); -} - -static int __devinit cn_init(void) +static int cn_init(void) { struct cn_dev *dev = &cdev; - int err; + struct netlink_kernel_cfg cfg = { + .groups = CN_NETLINK_USERS + 0xf, + .input = dev->input, + }; - dev->input = cn_rx_skb; - dev->id.idx = cn_idx; - dev->id.val = cn_val; - - dev->nls = netlink_kernel_create(&init_net, NETLINK_CONNECTOR, - CN_NETLINK_USERS + 0xf, - dev->input, NULL, THIS_MODULE); + dev->nls = netlink_kernel_create(&init_net, NETLINK_CONNECTOR, &cfg); if (!dev->nls) return -EIO; @@ -423,27 +292,22 @@ static int __devinit cn_init(void) netlink_kernel_release(dev->nls); return -EINVAL; } - + cn_already_initialized = 1; - err = cn_add_callback(&dev->id, "connector", &cn_callback); - if (err) { - cn_already_initialized = 0; - cn_queue_free_dev(dev->cbdev); - netlink_kernel_release(dev->nls); - return -EINVAL; - } + proc_create("connector", S_IRUGO, init_net.proc_net, &cn_file_ops); return 0; } -static void __devexit cn_fini(void) +static void cn_fini(void) { struct cn_dev *dev = &cdev; cn_already_initialized = 0; - cn_del_callback(&dev->id); + remove_proc_entry("connector", init_net.proc_net); + cn_queue_free_dev(dev->cbdev); netlink_kernel_release(dev->nls); } |
