]> Pileus Git - ~andy/linux/blobdiff - net/sched/act_api.c
net_sched: fix regression in tc_action_ops
[~andy/linux] / net / sched / act_api.c
index 69cb848e83455035a3e9ad85b2d7922434069d7e..dce2b6ecdbd897371dbe93decefc58aa2deca223 100644 (file)
 
 void tcf_hash_destroy(struct tcf_common *p, struct tcf_hashinfo *hinfo)
 {
-       unsigned int h = tcf_hash(p->tcfc_index, hinfo->hmask);
-       struct tcf_common **p1p;
-
-       for (p1p = &hinfo->htab[h]; *p1p; p1p = &(*p1p)->tcfc_next) {
-               if (*p1p == p) {
-                       write_lock_bh(hinfo->lock);
-                       *p1p = p->tcfc_next;
-                       write_unlock_bh(hinfo->lock);
-                       gen_kill_estimator(&p->tcfc_bstats,
-                                          &p->tcfc_rate_est);
-                       /*
-                        * gen_estimator est_timer() might access p->tcfc_lock
-                        * or bstats, wait a RCU grace period before freeing p
-                        */
-                       kfree_rcu(p, tcfc_rcu);
-                       return;
-               }
-       }
-       WARN_ON(1);
+       spin_lock_bh(&hinfo->lock);
+       hlist_del(&p->tcfc_head);
+       spin_unlock_bh(&hinfo->lock);
+       gen_kill_estimator(&p->tcfc_bstats,
+                          &p->tcfc_rate_est);
+       /*
+        * gen_estimator est_timer() might access p->tcfc_lock
+        * or bstats, wait a RCU grace period before freeing p
+        */
+       kfree_rcu(p, tcfc_rcu);
 }
 EXPORT_SYMBOL(tcf_hash_destroy);
 
@@ -73,18 +64,19 @@ EXPORT_SYMBOL(tcf_hash_release);
 static int tcf_dump_walker(struct sk_buff *skb, struct netlink_callback *cb,
                           struct tc_action *a, struct tcf_hashinfo *hinfo)
 {
+       struct hlist_head *head;
        struct tcf_common *p;
        int err = 0, index = -1, i = 0, s_i = 0, n_i = 0;
        struct nlattr *nest;
 
-       read_lock_bh(hinfo->lock);
+       spin_lock_bh(&hinfo->lock);
 
        s_i = cb->args[0];
 
        for (i = 0; i < (hinfo->hmask + 1); i++) {
-               p = hinfo->htab[tcf_hash(i, hinfo->hmask)];
+               head = &hinfo->htab[tcf_hash(i, hinfo->hmask)];
 
-               for (; p; p = p->tcfc_next) {
+               hlist_for_each_entry_rcu(p, head, tcfc_head) {
                        index++;
                        if (index < s_i)
                                continue;
@@ -107,7 +99,7 @@ static int tcf_dump_walker(struct sk_buff *skb, struct netlink_callback *cb,
                }
        }
 done:
-       read_unlock_bh(hinfo->lock);
+       spin_unlock_bh(&hinfo->lock);
        if (n_i)
                cb->args[0] += n_i;
        return n_i;
@@ -120,7 +112,9 @@ nla_put_failure:
 static int tcf_del_walker(struct sk_buff *skb, struct tc_action *a,
                          struct tcf_hashinfo *hinfo)
 {
-       struct tcf_common *p, *s_p;
+       struct hlist_head *head;
+       struct hlist_node *n;
+       struct tcf_common *p;
        struct nlattr *nest;
        int i = 0, n_i = 0;
 
@@ -130,14 +124,11 @@ static int tcf_del_walker(struct sk_buff *skb, struct tc_action *a,
        if (nla_put_string(skb, TCA_KIND, a->ops->kind))
                goto nla_put_failure;
        for (i = 0; i < (hinfo->hmask + 1); i++) {
-               p = hinfo->htab[tcf_hash(i, hinfo->hmask)];
-
-               while (p != NULL) {
-                       s_p = p->tcfc_next;
+               head = &hinfo->htab[tcf_hash(i, hinfo->hmask)];
+               hlist_for_each_entry_safe(p, n, head, tcfc_head) {
                        if (ACT_P_DELETED == tcf_hash_release(p, 0, hinfo))
                                module_put(a->ops->owner);
                        n_i++;
-                       p = s_p;
                }
        }
        if (nla_put_u32(skb, TCA_FCNT, n_i))
@@ -168,15 +159,15 @@ EXPORT_SYMBOL(tcf_generic_walker);
 
 struct tcf_common *tcf_hash_lookup(u32 index, struct tcf_hashinfo *hinfo)
 {
-       struct tcf_common *p;
+       struct tcf_common *p = NULL;
+       struct hlist_head *head;
 
-       read_lock_bh(hinfo->lock);
-       for (p = hinfo->htab[tcf_hash(index, hinfo->hmask)]; p;
-            p = p->tcfc_next) {
+       spin_lock_bh(&hinfo->lock);
+       head = &hinfo->htab[tcf_hash(index, hinfo->hmask)];
+       hlist_for_each_entry_rcu(p, head, tcfc_head)
                if (p->tcfc_index == index)
                        break;
-       }
-       read_unlock_bh(hinfo->lock);
+       spin_unlock_bh(&hinfo->lock);
 
        return p;
 }
@@ -191,7 +182,8 @@ u32 tcf_hash_new_index(u32 *idx_gen, struct tcf_hashinfo *hinfo)
                        val = 1;
        } while (tcf_hash_lookup(val, hinfo));
 
-       return (*idx_gen = val);
+       *idx_gen = val;
+       return val;
 }
 EXPORT_SYMBOL(tcf_hash_new_index);
 
@@ -235,6 +227,7 @@ struct tcf_common *tcf_hash_create(u32 index, struct nlattr *est,
                p->tcfc_bindcnt = 1;
 
        spin_lock_init(&p->tcfc_lock);
+       INIT_HLIST_NODE(&p->tcfc_head);
        p->tcfc_index = index ? index : tcf_hash_new_index(idx_gen, hinfo);
        p->tcfc_tm.install = jiffies;
        p->tcfc_tm.lastuse = jiffies;
@@ -256,19 +249,18 @@ void tcf_hash_insert(struct tcf_common *p, struct tcf_hashinfo *hinfo)
 {
        unsigned int h = tcf_hash(p->tcfc_index, hinfo->hmask);
 
-       write_lock_bh(hinfo->lock);
-       p->tcfc_next = hinfo->htab[h];
-       hinfo->htab[h] = p;
-       write_unlock_bh(hinfo->lock);
+       spin_lock_bh(&hinfo->lock);
+       hlist_add_head(&p->tcfc_head, &hinfo->htab[h]);
+       spin_unlock_bh(&hinfo->lock);
 }
 EXPORT_SYMBOL(tcf_hash_insert);
 
-static struct tc_action_ops *act_base = NULL;
+static LIST_HEAD(act_base);
 static DEFINE_RWLOCK(act_mod_lock);
 
 int tcf_register_action(struct tc_action_ops *act)
 {
-       struct tc_action_ops *a, **ap;
+       struct tc_action_ops *a;
 
        /* Must supply act, dump, cleanup and init */
        if (!act->act || !act->dump || !act->cleanup || !act->init)
@@ -281,14 +273,13 @@ int tcf_register_action(struct tc_action_ops *act)
                act->walk = tcf_generic_walker;
 
        write_lock(&act_mod_lock);
-       for (ap = &act_base; (a = *ap) != NULL; ap = &a->next) {
+       list_for_each_entry(a, &act_base, head) {
                if (act->type == a->type || (strcmp(act->kind, a->kind) == 0)) {
                        write_unlock(&act_mod_lock);
                        return -EEXIST;
                }
        }
-       act->next = NULL;
-       *ap = act;
+       list_add_tail(&act->head, &act_base);
        write_unlock(&act_mod_lock);
        return 0;
 }
@@ -296,17 +287,16 @@ EXPORT_SYMBOL(tcf_register_action);
 
 int tcf_unregister_action(struct tc_action_ops *act)
 {
-       struct tc_action_ops *a, **ap;
+       struct tc_action_ops *a;
        int err = -ENOENT;
 
        write_lock(&act_mod_lock);
-       for (ap = &act_base; (a = *ap) != NULL; ap = &a->next)
-               if (a == act)
+       list_for_each_entry(a, &act_base, head) {
+               if (a == act) {
+                       list_del(&act->head);
+                       err = 0;
                        break;
-       if (a) {
-               *ap = a->next;
-               a->next = NULL;
-               err = 0;
+               }
        }
        write_unlock(&act_mod_lock);
        return err;
@@ -316,69 +306,42 @@ EXPORT_SYMBOL(tcf_unregister_action);
 /* lookup by name */
 static struct tc_action_ops *tc_lookup_action_n(char *kind)
 {
-       struct tc_action_ops *a = NULL;
+       struct tc_action_ops *a, *res = NULL;
 
        if (kind) {
                read_lock(&act_mod_lock);
-               for (a = act_base; a; a = a->next) {
+               list_for_each_entry(a, &act_base, head) {
                        if (strcmp(kind, a->kind) == 0) {
-                               if (!try_module_get(a->owner)) {
-                                       read_unlock(&act_mod_lock);
-                                       return NULL;
-                               }
+                               if (try_module_get(a->owner))
+                                       res = a;
                                break;
                        }
                }
                read_unlock(&act_mod_lock);
        }
-       return a;
+       return res;
 }
 
 /* lookup by nlattr */
 static struct tc_action_ops *tc_lookup_action(struct nlattr *kind)
 {
-       struct tc_action_ops *a = NULL;
+       struct tc_action_ops *a, *res = NULL;
 
        if (kind) {
                read_lock(&act_mod_lock);
-               for (a = act_base; a; a = a->next) {
+               list_for_each_entry(a, &act_base, head) {
                        if (nla_strcmp(kind, a->kind) == 0) {
-                               if (!try_module_get(a->owner)) {
-                                       read_unlock(&act_mod_lock);
-                                       return NULL;
-                               }
+                               if (try_module_get(a->owner))
+                                       res = a;
                                break;
                        }
                }
                read_unlock(&act_mod_lock);
        }
-       return a;
+       return res;
 }
 
-#if 0
-/* lookup by id */
-static struct tc_action_ops *tc_lookup_action_id(u32 type)
-{
-       struct tc_action_ops *a = NULL;
-
-       if (type) {
-               read_lock(&act_mod_lock);
-               for (a = act_base; a; a = a->next) {
-                       if (a->type == type) {
-                               if (!try_module_get(a->owner)) {
-                                       read_unlock(&act_mod_lock);
-                                       return NULL;
-                               }
-                               break;
-                       }
-               }
-               read_unlock(&act_mod_lock);
-       }
-       return a;
-}
-#endif
-
-int tcf_action_exec(struct sk_buff *skb, const struct tc_action *act,
+int tcf_action_exec(struct sk_buff *skb, const struct list_head *actions,
                    struct tcf_result *res)
 {
        const struct tc_action *a;
@@ -389,7 +352,7 @@ int tcf_action_exec(struct sk_buff *skb, const struct tc_action *act,
                ret = TC_ACT_OK;
                goto exec_done;
        }
-       while ((a = act) != NULL) {
+       list_for_each_entry(a, actions, list) {
 repeat:
                if (a->ops) {
                        ret = a->ops->act(skb, a, res);
@@ -403,27 +366,26 @@ repeat:
                        if (ret != TC_ACT_PIPE)
                                goto exec_done;
                }
-               act = a->next;
        }
 exec_done:
        return ret;
 }
 EXPORT_SYMBOL(tcf_action_exec);
 
-void tcf_action_destroy(struct tc_action *act, int bind)
+void tcf_action_destroy(struct list_head *actions, int bind)
 {
-       struct tc_action *a;
+       struct tc_action *a, *tmp;
 
-       for (a = act; a; a = act) {
+       list_for_each_entry_safe(a, tmp, actions, list) {
                if (a->ops) {
                        if (a->ops->cleanup(a, bind) == ACT_P_DELETED)
                                module_put(a->ops->owner);
-                       act = act->next;
+                       list_del(&a->list);
                        kfree(a);
                } else {
                        /*FIXME: Remove later - catch insertion bugs*/
                        WARN(1, "tcf_action_destroy: BUG? destroying NULL ops\n");
-                       act = act->next;
+                       list_del(&a->list);
                        kfree(a);
                }
        }
@@ -469,14 +431,13 @@ nla_put_failure:
 EXPORT_SYMBOL(tcf_action_dump_1);
 
 int
-tcf_action_dump(struct sk_buff *skb, struct tc_action *act, int bind, int ref)
+tcf_action_dump(struct sk_buff *skb, struct list_head *actions, int bind, int ref)
 {
        struct tc_action *a;
        int err = -EINVAL;
        struct nlattr *nest;
 
-       while ((a = act) != NULL) {
-               act = a->next;
+       list_for_each_entry(a, actions, list) {
                nest = nla_nest_start(skb, a->order);
                if (nest == NULL)
                        goto nla_put_failure;
@@ -551,6 +512,7 @@ struct tc_action *tcf_action_init_1(struct net *net, struct nlattr *nla,
        if (a == NULL)
                goto err_mod;
 
+       INIT_LIST_HEAD(&a->list);
        /* backward compatibility for policer */
        if (name == NULL)
                err = a_o->init(net, tb[TCA_ACT_OPTIONS], est, a, ovr, bind);
@@ -577,37 +539,33 @@ err_out:
        return ERR_PTR(err);
 }
 
-struct tc_action *tcf_action_init(struct net *net, struct nlattr *nla,
+int tcf_action_init(struct net *net, struct nlattr *nla,
                                  struct nlattr *est, char *name, int ovr,
-                                 int bind)
+                                 int bind, struct list_head *actions)
 {
        struct nlattr *tb[TCA_ACT_MAX_PRIO + 1];
-       struct tc_action *head = NULL, *act, *act_prev = NULL;
+       struct tc_action *act;
        int err;
        int i;
 
        err = nla_parse_nested(tb, TCA_ACT_MAX_PRIO, nla, NULL);
        if (err < 0)
-               return ERR_PTR(err);
+               return err;
 
        for (i = 1; i <= TCA_ACT_MAX_PRIO && tb[i]; i++) {
                act = tcf_action_init_1(net, tb[i], est, name, ovr, bind);
-               if (IS_ERR(act))
+               if (IS_ERR(act)) {
+                       err = PTR_ERR(act);
                        goto err;
+               }
                act->order = i;
-
-               if (head == NULL)
-                       head = act;
-               else
-                       act_prev->next = act;
-               act_prev = act;
+               list_add_tail(&act->list, actions);
        }
-       return head;
+       return 0;
 
 err:
-       if (head != NULL)
-               tcf_action_destroy(head, bind);
-       return act;
+       tcf_action_destroy(actions, bind);
+       return err;
 }
 
 int tcf_action_copy_stats(struct sk_buff *skb, struct tc_action *a,
@@ -636,10 +594,6 @@ int tcf_action_copy_stats(struct sk_buff *skb, struct tc_action *a,
        if (err < 0)
                goto errout;
 
-       if (a->ops != NULL && a->ops->get_stats != NULL)
-               if (a->ops->get_stats(skb, a) < 0)
-                       goto errout;
-
        if (gnet_stats_copy_basic(&d, &h->tcf_bstats) < 0 ||
            gnet_stats_copy_rate_est(&d, &h->tcf_bstats,
                                     &h->tcf_rate_est) < 0 ||
@@ -656,7 +610,7 @@ errout:
 }
 
 static int
-tca_get_fill(struct sk_buff *skb, struct tc_action *a, u32 portid, u32 seq,
+tca_get_fill(struct sk_buff *skb, struct list_head *actions, u32 portid, u32 seq,
             u16 flags, int event, int bind, int ref)
 {
        struct tcamsg *t;
@@ -676,7 +630,7 @@ tca_get_fill(struct sk_buff *skb, struct tc_action *a, u32 portid, u32 seq,
        if (nest == NULL)
                goto out_nlmsg_trim;
 
-       if (tcf_action_dump(skb, a, bind, ref) < 0)
+       if (tcf_action_dump(skb, actions, bind, ref) < 0)
                goto out_nlmsg_trim;
 
        nla_nest_end(skb, nest);
@@ -691,14 +645,14 @@ out_nlmsg_trim:
 
 static int
 act_get_notify(struct net *net, u32 portid, struct nlmsghdr *n,
-              struct tc_action *a, int event)
+              struct list_head *actions, int event)
 {
        struct sk_buff *skb;
 
        skb = alloc_skb(NLMSG_GOODSIZE, GFP_KERNEL);
        if (!skb)
                return -ENOBUFS;
-       if (tca_get_fill(skb, a, portid, n->nlmsg_seq, 0, event, 0, 0) <= 0) {
+       if (tca_get_fill(skb, actions, portid, n->nlmsg_seq, 0, event, 0, 0) <= 0) {
                kfree_skb(skb);
                return -EINVAL;
        }
@@ -729,6 +683,7 @@ tcf_action_get_1(struct nlattr *nla, struct nlmsghdr *n, u32 portid)
        if (a == NULL)
                goto err_out;
 
+       INIT_LIST_HEAD(&a->list);
        err = -EINVAL;
        a->ops = tc_lookup_action(tb[TCA_ACT_KIND]);
        if (a->ops == NULL)
@@ -748,12 +703,12 @@ err_out:
        return ERR_PTR(err);
 }
 
-static void cleanup_a(struct tc_action *act)
+static void cleanup_a(struct list_head *actions)
 {
-       struct tc_action *a;
+       struct tc_action *a, *tmp;
 
-       for (a = act; a; a = act) {
-               act = a->next;
+       list_for_each_entry_safe(a, tmp, actions, list) {
+               list_del(&a->list);
                kfree(a);
        }
 }
@@ -768,6 +723,7 @@ static struct tc_action *create_a(int i)
                return NULL;
        }
        act->order = i;
+       INIT_LIST_HEAD(&act->list);
        return act;
 }
 
@@ -855,7 +811,8 @@ tca_action_gd(struct net *net, struct nlattr *nla, struct nlmsghdr *n,
 {
        int i, ret;
        struct nlattr *tb[TCA_ACT_MAX_PRIO + 1];
-       struct tc_action *head = NULL, *act, *act_prev = NULL;
+       struct tc_action *act;
+       LIST_HEAD(actions);
 
        ret = nla_parse_nested(tb, TCA_ACT_MAX_PRIO, nla, NULL);
        if (ret < 0)
@@ -875,16 +832,11 @@ tca_action_gd(struct net *net, struct nlattr *nla, struct nlmsghdr *n,
                        goto err;
                }
                act->order = i;
-
-               if (head == NULL)
-                       head = act;
-               else
-                       act_prev->next = act;
-               act_prev = act;
+               list_add_tail(&act->list, &actions);
        }
 
        if (event == RTM_GETACTION)
-               ret = act_get_notify(net, portid, n, head, event);
+               ret = act_get_notify(net, portid, n, &actions, event);
        else { /* delete */
                struct sk_buff *skb;
 
@@ -894,7 +846,7 @@ tca_action_gd(struct net *net, struct nlattr *nla, struct nlmsghdr *n,
                        goto err;
                }
 
-               if (tca_get_fill(skb, head, portid, n->nlmsg_seq, 0, event,
+               if (tca_get_fill(skb, &actions, portid, n->nlmsg_seq, 0, event,
                                 0, 1) <= 0) {
                        kfree_skb(skb);
                        ret = -EINVAL;
@@ -902,7 +854,7 @@ tca_action_gd(struct net *net, struct nlattr *nla, struct nlmsghdr *n,
                }
 
                /* now do the delete */
-               tcf_action_destroy(head, 0);
+               tcf_action_destroy(&actions, 0);
                ret = rtnetlink_send(skb, net, portid, RTNLGRP_TC,
                                     n->nlmsg_flags & NLM_F_ECHO);
                if (ret > 0)
@@ -910,11 +862,11 @@ tca_action_gd(struct net *net, struct nlattr *nla, struct nlmsghdr *n,
                return ret;
        }
 err:
-       cleanup_a(head);
+       cleanup_a(&actions);
        return ret;
 }
 
-static int tcf_add_notify(struct net *net, struct tc_action *a,
+static int tcf_add_notify(struct net *net, struct list_head *actions,
                          u32 portid, u32 seq, int event, u16 flags)
 {
        struct tcamsg *t;
@@ -942,7 +894,7 @@ static int tcf_add_notify(struct net *net, struct tc_action *a,
        if (nest == NULL)
                goto out_kfree_skb;
 
-       if (tcf_action_dump(skb, a, 0, 0) < 0)
+       if (tcf_action_dump(skb, actions, 0, 0) < 0)
                goto out_kfree_skb;
 
        nla_nest_end(skb, nest);
@@ -966,26 +918,18 @@ tcf_action_add(struct net *net, struct nlattr *nla, struct nlmsghdr *n,
               u32 portid, int ovr)
 {
        int ret = 0;
-       struct tc_action *act;
-       struct tc_action *a;
+       LIST_HEAD(actions);
        u32 seq = n->nlmsg_seq;
 
-       act = tcf_action_init(net, nla, NULL, NULL, ovr, 0);
-       if (act == NULL)
-               goto done;
-       if (IS_ERR(act)) {
-               ret = PTR_ERR(act);
+       ret = tcf_action_init(net, nla, NULL, NULL, ovr, 0, &actions);
+       if (ret)
                goto done;
-       }
 
        /* dump then free all the actions after update; inserted policy
         * stays intact
         */
-       ret = tcf_add_notify(net, act, portid, seq, RTM_NEWACTION, n->nlmsg_flags);
-       for (a = act; a; a = act) {
-               act = a->next;
-               kfree(a);
-       }
+       ret = tcf_add_notify(net, &actions, portid, seq, RTM_NEWACTION, n->nlmsg_flags);
+       cleanup_a(&actions);
 done:
        return ret;
 }