Merge branch 'release' of git://git.kernel.org/pub/scm/linux/kernel/git/aegl/linux-2.6
[cascardo/linux.git] / net / wireless / wext.c
index 252c201..5b4a0ce 100644 (file)
@@ -417,6 +417,21 @@ static const int event_type_size[] = {
        IW_EV_QUAL_LEN,                 /* IW_HEADER_TYPE_QUAL */
 };
 
+#ifdef CONFIG_COMPAT
+static const int compat_event_type_size[] = {
+       IW_EV_COMPAT_LCP_LEN,           /* IW_HEADER_TYPE_NULL */
+       0,
+       IW_EV_COMPAT_CHAR_LEN,          /* IW_HEADER_TYPE_CHAR */
+       0,
+       IW_EV_COMPAT_UINT_LEN,          /* IW_HEADER_TYPE_UINT */
+       IW_EV_COMPAT_FREQ_LEN,          /* IW_HEADER_TYPE_FREQ */
+       IW_EV_COMPAT_ADDR_LEN,          /* IW_HEADER_TYPE_ADDR */
+       0,
+       IW_EV_COMPAT_POINT_LEN,         /* Without variable payload */
+       IW_EV_COMPAT_PARAM_LEN,         /* IW_HEADER_TYPE_PARAM */
+       IW_EV_COMPAT_QUAL_LEN,          /* IW_HEADER_TYPE_QUAL */
+};
+#endif
 
 /************************ COMMON SUBROUTINES ************************/
 /*
@@ -610,6 +625,11 @@ static void wireless_seq_printf_stats(struct seq_file *seq,
 {
        /* Get stats from the driver */
        struct iw_statistics *stats = get_wireless_stats(dev);
+       static struct iw_statistics nullstats = {};
+
+       /* show device if it's wireless regardless of current stats */
+       if (!stats && dev->wireless_handlers)
+               stats = &nullstats;
 
        if (stats) {
                seq_printf(seq, "%6s: %04x  %3d%c  %3d%c  %3d%c  %6d %6d %6d "
@@ -628,7 +648,9 @@ static void wireless_seq_printf_stats(struct seq_file *seq,
                           stats->discard.nwid, stats->discard.code,
                           stats->discard.fragment, stats->discard.retries,
                           stats->discard.misc, stats->miss.beacon);
-               stats->qual.updated &= ~IW_QUAL_ALL_UPDATED;
+
+               if (stats != &nullstats)
+                       stats->qual.updated &= ~IW_QUAL_ALL_UPDATED;
        }
 }
 
@@ -1250,65 +1272,57 @@ int compat_wext_handle_ioctl(struct net *net, unsigned int cmd,
 }
 #endif
 
-/************************* EVENT PROCESSING *************************/
-/*
- * Process events generated by the wireless layer or the driver.
- * Most often, the event will be propagated through rtnetlink
- */
+static int __net_init wext_pernet_init(struct net *net)
+{
+       skb_queue_head_init(&net->wext_nlevents);
+       return 0;
+}
 
-/* ---------------------------------------------------------------- */
-/*
- * Locking...
- * ----------
- *
- * Thanks to Herbert Xu <herbert@gondor.apana.org.au> for fixing
- * the locking issue in here and implementing this code !
- *
- * The issue : wireless_send_event() is often called in interrupt context,
- * while the Netlink layer can never be called in interrupt context.
- * The fully formed RtNetlink events are queued, and then a tasklet is run
- * to feed those to Netlink.
- * The skb_queue is interrupt safe, and its lock is not held while calling
- * Netlink, so there is no possibility of dealock.
- * Jean II
- */
+static void __net_exit wext_pernet_exit(struct net *net)
+{
+       skb_queue_purge(&net->wext_nlevents);
+}
 
-static struct sk_buff_head wireless_nlevent_queue;
+static struct pernet_operations wext_pernet_ops = {
+       .init = wext_pernet_init,
+       .exit = wext_pernet_exit,
+};
 
 static int __init wireless_nlevent_init(void)
 {
-       skb_queue_head_init(&wireless_nlevent_queue);
-       return 0;
+       return register_pernet_subsys(&wext_pernet_ops);
 }
 
 subsys_initcall(wireless_nlevent_init);
 
-static void wireless_nlevent_process(unsigned long data)
+/* Process events generated by the wireless layer or the driver. */
+static void wireless_nlevent_process(struct work_struct *work)
 {
        struct sk_buff *skb;
+       struct net *net;
 
-       while ((skb = skb_dequeue(&wireless_nlevent_queue)))
-               rtnl_notify(skb, &init_net, 0, RTNLGRP_LINK, NULL, GFP_ATOMIC);
+       rtnl_lock();
+
+       for_each_net(net) {
+               while ((skb = skb_dequeue(&net->wext_nlevents)))
+                       rtnl_notify(skb, net, 0, RTNLGRP_LINK, NULL,
+                                   GFP_KERNEL);
+       }
+
+       rtnl_unlock();
 }
 
-static DECLARE_TASKLET(wireless_nlevent_tasklet, wireless_nlevent_process, 0);
+static DECLARE_WORK(wireless_nlevent_work, wireless_nlevent_process);
 
-/* ---------------------------------------------------------------- */
-/*
- * Fill a rtnetlink message with our event data.
- * Note that we propage only the specified event and don't dump the
- * current wireless config. Dumping the wireless config is far too
- * expensive (for each parameter, the driver need to query the hardware).
- */
-static int rtnetlink_fill_iwinfo(struct sk_buff *skb, struct net_device *dev,
-                                int type, char *event, int event_len)
+static struct nlmsghdr *rtnetlink_ifinfo_prep(struct net_device *dev,
+                                             struct sk_buff *skb)
 {
        struct ifinfomsg *r;
        struct nlmsghdr  *nlh;
 
-       nlh = nlmsg_put(skb, 0, 0, type, sizeof(*r), 0);
-       if (nlh == NULL)
-               return -EMSGSIZE;
+       nlh = nlmsg_put(skb, 0, 0, RTM_NEWLINK, sizeof(*r), 0);
+       if (!nlh)
+               return NULL;
 
        r = nlmsg_data(nlh);
        r->ifi_family = AF_UNSPEC;
@@ -1319,48 +1333,14 @@ static int rtnetlink_fill_iwinfo(struct sk_buff *skb, struct net_device *dev,
        r->ifi_change = 0;      /* Wireless changes don't affect those flags */
 
        NLA_PUT_STRING(skb, IFLA_IFNAME, dev->name);
-       /* Add the wireless events in the netlink packet */
-       NLA_PUT(skb, IFLA_WIRELESS, event_len, event);
 
-       return nlmsg_end(skb, nlh);
-
-nla_put_failure:
+       return nlh;
+ nla_put_failure:
        nlmsg_cancel(skb, nlh);
-       return -EMSGSIZE;
+       return NULL;
 }
 
-/* ---------------------------------------------------------------- */
-/*
- * Create and broadcast and send it on the standard rtnetlink socket
- * This is a pure clone rtmsg_ifinfo() in net/core/rtnetlink.c
- * Andrzej Krzysztofowicz mandated that I used a IFLA_XXX field
- * within a RTM_NEWLINK event.
- */
-static void rtmsg_iwinfo(struct net_device *dev, char *event, int event_len)
-{
-       struct sk_buff *skb;
-       int err;
-
-       if (!net_eq(dev_net(dev), &init_net))
-               return;
-
-       skb = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_ATOMIC);
-       if (!skb)
-               return;
 
-       err = rtnetlink_fill_iwinfo(skb, dev, RTM_NEWLINK, event, event_len);
-       if (err < 0) {
-               WARN_ON(err == -EMSGSIZE);
-               kfree_skb(skb);
-               return;
-       }
-
-       NETLINK_CB(skb).dst_group = RTNLGRP_LINK;
-       skb_queue_tail(&wireless_nlevent_queue, skb);
-       tasklet_schedule(&wireless_nlevent_tasklet);
-}
-
-/* ---------------------------------------------------------------- */
 /*
  * Main event dispatcher. Called from other parts and drivers.
  * Send the event on the appropriate channels.
@@ -1369,7 +1349,7 @@ static void rtmsg_iwinfo(struct net_device *dev, char *event, int event_len)
 void wireless_send_event(struct net_device *   dev,
                         unsigned int           cmd,
                         union iwreq_data *     wrqu,
-                        char *                 extra)
+                        const char *           extra)
 {
        const struct iw_ioctl_description *     descr = NULL;
        int extra_len = 0;
@@ -1379,6 +1359,25 @@ void wireless_send_event(struct net_device *     dev,
        int wrqu_off = 0;                       /* Offset in wrqu */
        /* Don't "optimise" the following variable, it will crash */
        unsigned        cmd_index;              /* *MUST* be unsigned */
+       struct sk_buff *skb;
+       struct nlmsghdr *nlh;
+       struct nlattr *nla;
+#ifdef CONFIG_COMPAT
+       struct __compat_iw_event *compat_event;
+       struct compat_iw_point compat_wrqu;
+       struct sk_buff *compskb;
+#endif
+
+       /*
+        * Nothing in the kernel sends scan events with data, be safe.
+        * This is necessary because we cannot fix up scan event data
+        * for compat, due to being contained in 'extra', but normally
+        * applications are required to retrieve the scan data anyway
+        * and no data is included in the event, this codifies that
+        * practice.
+        */
+       if (WARN_ON(cmd == SIOCGIWSCAN && extra))
+               extra = NULL;
 
        /* Get the description of the Event */
        if (cmd <= SIOCIWLAST) {
@@ -1426,25 +1425,107 @@ void wireless_send_event(struct net_device *   dev,
        hdr_len = event_type_size[descr->header_type];
        event_len = hdr_len + extra_len;
 
-       /* Create temporary buffer to hold the event */
-       event = kmalloc(event_len, GFP_ATOMIC);
-       if (event == NULL)
+       /*
+        * The problem for 64/32 bit.
+        *
+        * On 64-bit, a regular event is laid out as follows:
+        *      |  0  |  1  |  2  |  3  |  4  |  5  |  6  |  7  |
+        *      | event.len | event.cmd |     p a d d i n g     |
+        *      | wrqu data ... (with the correct size)         |
+        *
+        * This padding exists because we manipulate event->u,
+        * and 'event' is not packed.
+        *
+        * An iw_point event is laid out like this instead:
+        *      |  0  |  1  |  2  |  3  |  4  |  5  |  6  |  7  |
+        *      | event.len | event.cmd |     p a d d i n g     |
+        *      | iwpnt.len | iwpnt.flg |     p a d d i n g     |
+        *      | extra data  ...
+        *
+        * The second padding exists because struct iw_point is extended,
+        * but this depends on the platform...
+        *
+        * On 32-bit, all the padding shouldn't be there.
+        */
+
+       skb = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_ATOMIC);
+       if (!skb)
+               return;
+
+       /* Send via the RtNetlink event channel */
+       nlh = rtnetlink_ifinfo_prep(dev, skb);
+       if (WARN_ON(!nlh)) {
+               kfree_skb(skb);
+               return;
+       }
+
+       /* Add the wireless events in the netlink packet */
+       nla = nla_reserve(skb, IFLA_WIRELESS, event_len);
+       if (!nla) {
+               kfree_skb(skb);
                return;
+       }
+       event = nla_data(nla);
 
-       /* Fill event */
+       /* Fill event - first clear to avoid data leaking */
+       memset(event, 0, hdr_len);
        event->len = event_len;
        event->cmd = cmd;
        memcpy(&event->u, ((char *) wrqu) + wrqu_off, hdr_len - IW_EV_LCP_LEN);
-       if (extra)
+       if (extra_len)
                memcpy(((char *) event) + hdr_len, extra, extra_len);
 
+       nlmsg_end(skb, nlh);
+#ifdef CONFIG_COMPAT
+       hdr_len = compat_event_type_size[descr->header_type];
+       event_len = hdr_len + extra_len;
+
+       compskb = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_ATOMIC);
+       if (!compskb) {
+               kfree_skb(skb);
+               return;
+       }
+
        /* Send via the RtNetlink event channel */
-       rtmsg_iwinfo(dev, (char *) event, event_len);
+       nlh = rtnetlink_ifinfo_prep(dev, compskb);
+       if (WARN_ON(!nlh)) {
+               kfree_skb(skb);
+               kfree_skb(compskb);
+               return;
+       }
 
-       /* Cleanup */
-       kfree(event);
+       /* Add the wireless events in the netlink packet */
+       nla = nla_reserve(compskb, IFLA_WIRELESS, event_len);
+       if (!nla) {
+               kfree_skb(skb);
+               kfree_skb(compskb);
+               return;
+       }
+       compat_event = nla_data(nla);
 
-       return;         /* Always success, I guess ;-) */
+       compat_event->len = event_len;
+       compat_event->cmd = cmd;
+       if (descr->header_type == IW_HEADER_TYPE_POINT) {
+               compat_wrqu.length = wrqu->data.length;
+               compat_wrqu.flags = wrqu->data.flags;
+               memcpy(&compat_event->pointer,
+                       ((char *) &compat_wrqu) + IW_EV_COMPAT_POINT_OFF,
+                       hdr_len - IW_EV_COMPAT_LCP_LEN);
+               if (extra_len)
+                       memcpy(((char *) compat_event) + hdr_len,
+                               extra, extra_len);
+       } else {
+               /* extra_len must be zero, so no if (extra) needed */
+               memcpy(&compat_event->pointer, wrqu,
+                       hdr_len - IW_EV_COMPAT_LCP_LEN);
+       }
+
+       nlmsg_end(compskb, nlh);
+
+       skb_shinfo(skb)->frag_list = compskb;
+#endif
+       skb_queue_tail(&dev_net(dev)->wext_nlevents, skb);
+       schedule_work(&wireless_nlevent_work);
 }
 EXPORT_SYMBOL(wireless_send_event);