netlink: use call_rcu for netlink_change_ngroups
Johannes Berg [Fri, 10 Jul 2009 09:51:32 +0000 (09:51 +0000)]
For the network namespace work in generic netlink I need
to be able to call this function under rcu_read_lock(),
otherwise the locking becomes a nightmare and more locks
would be needed. Instead, just embed a struct rcu_head
(actually a struct listeners_rcu_head that also carries
the pointer to the memory block) into the listeners
memory so we can use call_rcu() instead of synchronising
and then freeing. No rcu_barrier() is needed since this
code cannot be modular.

Signed-off-by: Johannes Berg <johannes@sipsolutions.net>
Signed-off-by: David S. Miller <davem@davemloft.net>

net/netlink/af_netlink.c

index d7d1b82..d46da6c 100644 (file)
@@ -83,6 +83,11 @@ struct netlink_sock {
        struct module           *module;
 };
 
+struct listeners_rcu_head {
+       struct rcu_head rcu_head;
+       void *ptr;
+};
+
 #define NETLINK_KERNEL_SOCKET  0x1
 #define NETLINK_RECV_PKTINFO   0x2
 #define NETLINK_BROADCAST_SEND_ERROR   0x4
@@ -1453,7 +1458,8 @@ netlink_kernel_create(struct net *net, int unit, unsigned int groups,
        if (groups < 32)
                groups = 32;
 
-       listeners = kzalloc(NLGRPSZ(groups), GFP_KERNEL);
+       listeners = kzalloc(NLGRPSZ(groups) + sizeof(struct listeners_rcu_head),
+                           GFP_KERNEL);
        if (!listeners)
                goto out_sock_release;
 
@@ -1501,6 +1507,14 @@ netlink_kernel_release(struct sock *sk)
 EXPORT_SYMBOL(netlink_kernel_release);
 
 
+static void netlink_free_old_listeners(struct rcu_head *rcu_head)
+{
+       struct listeners_rcu_head *lrh;
+
+       lrh = container_of(rcu_head, struct listeners_rcu_head, rcu_head);
+       kfree(lrh->ptr);
+}
+
 /**
  * netlink_change_ngroups - change number of multicast groups
  *
@@ -1516,6 +1530,7 @@ EXPORT_SYMBOL(netlink_kernel_release);
 int netlink_change_ngroups(struct sock *sk, unsigned int groups)
 {
        unsigned long *listeners, *old = NULL;
+       struct listeners_rcu_head *old_rcu_head;
        struct netlink_table *tbl = &nl_table[sk->sk_protocol];
        int err = 0;
 
@@ -1524,7 +1539,9 @@ int netlink_change_ngroups(struct sock *sk, unsigned int groups)
 
        netlink_table_grab();
        if (NLGRPSZ(tbl->groups) < NLGRPSZ(groups)) {
-               listeners = kzalloc(NLGRPSZ(groups), GFP_ATOMIC);
+               listeners = kzalloc(NLGRPSZ(groups) +
+                                   sizeof(struct listeners_rcu_head),
+                                   GFP_ATOMIC);
                if (!listeners) {
                        err = -ENOMEM;
                        goto out_ungrab;
@@ -1532,13 +1549,22 @@ int netlink_change_ngroups(struct sock *sk, unsigned int groups)
                old = tbl->listeners;
                memcpy(listeners, old, NLGRPSZ(tbl->groups));
                rcu_assign_pointer(tbl->listeners, listeners);
+               /*
+                * Free the old memory after an RCU grace period so we
+                * don't leak it. We use call_rcu() here in order to be
+                * able to call this function from atomic contexts. The
+                * allocation of this memory will have reserved enough
+                * space for struct listeners_rcu_head at the end.
+                */
+               old_rcu_head = (void *)(tbl->listeners +
+                                       NLGRPLONGS(tbl->groups));
+               old_rcu_head->ptr = old;
+               call_rcu(&old_rcu_head->rcu_head, netlink_free_old_listeners);
        }
        tbl->groups = groups;
 
  out_ungrab:
        netlink_table_ungrab();
-       synchronize_rcu();
-       kfree(old);
        return err;
 }