net_sched: act: refuse to remove bound action outside
[cascardo/linux.git] / net / sched / act_api.c
index 72bdc71..27e4c53 100644 (file)
 #include <net/act_api.h>
 #include <net/netlink.h>
 
-void tcf_hash_destroy(struct tcf_common *p, struct tcf_hashinfo *hinfo)
+void tcf_hash_destroy(struct tc_action *a)
 {
+       struct tcf_common *p = a->priv;
+       struct tcf_hashinfo *hinfo = a->ops->hinfo;
+
        spin_lock_bh(&hinfo->lock);
        hlist_del(&p->tcfc_head);
        spin_unlock_bh(&hinfo->lock);
@@ -42,18 +45,22 @@ void tcf_hash_destroy(struct tcf_common *p, struct tcf_hashinfo *hinfo)
 }
 EXPORT_SYMBOL(tcf_hash_destroy);
 
-int tcf_hash_release(struct tcf_common *p, int bind,
-                    struct tcf_hashinfo *hinfo)
+int tcf_hash_release(struct tc_action *a, int bind)
 {
+       struct tcf_common *p = a->priv;
        int ret = 0;
 
        if (p) {
                if (bind)
                        p->tcfc_bindcnt--;
+               else if (p->tcfc_bindcnt > 0)
+                       return -EPERM;
 
                p->tcfc_refcnt--;
                if (p->tcfc_bindcnt <= 0 && p->tcfc_refcnt <= 0) {
-                       tcf_hash_destroy(p, hinfo);
+                       if (a->ops->cleanup)
+                               a->ops->cleanup(a, bind);
+                       tcf_hash_destroy(a);
                        ret = 1;
                }
        }
@@ -118,6 +125,7 @@ static int tcf_del_walker(struct sk_buff *skb, struct tc_action *a)
        struct tcf_common *p;
        struct nlattr *nest;
        int i = 0, n_i = 0;
+       int ret = -EINVAL;
 
        nest = nla_nest_start(skb, a->order);
        if (nest == NULL)
@@ -127,10 +135,13 @@ static int tcf_del_walker(struct sk_buff *skb, struct tc_action *a)
        for (i = 0; i < (hinfo->hmask + 1); i++) {
                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)) {
+                       a->priv = p;
+                       ret = tcf_hash_release(a, 0);
+                       if (ret == ACT_P_DELETED) {
                                module_put(a->ops->owner);
                                n_i++;
-                       }
+                       } else if (ret < 0)
+                               goto nla_put_failure;
                }
        }
        if (nla_put_u32(skb, TCA_FCNT, n_i))
@@ -140,7 +151,7 @@ static int tcf_del_walker(struct sk_buff *skb, struct tc_action *a)
        return n_i;
 nla_put_failure:
        nla_nest_cancel(skb, nest);
-       return -EINVAL;
+       return ret;
 }
 
 static int tcf_generic_walker(struct sk_buff *skb, struct netlink_callback *cb,
@@ -198,7 +209,7 @@ int tcf_hash_search(struct tc_action *a, u32 index)
 }
 EXPORT_SYMBOL(tcf_hash_search);
 
-struct tcf_common *tcf_hash_check(u32 index, struct tc_action *a, int bind)
+int tcf_hash_check(u32 index, struct tc_action *a, int bind)
 {
        struct tcf_hashinfo *hinfo = a->ops->hinfo;
        struct tcf_common *p = NULL;
@@ -207,19 +218,30 @@ struct tcf_common *tcf_hash_check(u32 index, struct tc_action *a, int bind)
                        p->tcfc_bindcnt++;
                p->tcfc_refcnt++;
                a->priv = p;
+               return 1;
        }
-       return p;
+       return 0;
 }
 EXPORT_SYMBOL(tcf_hash_check);
 
-struct tcf_common *tcf_hash_create(u32 index, struct nlattr *est,
-                                  struct tc_action *a, int size, int bind)
+void tcf_hash_cleanup(struct tc_action *a, struct nlattr *est)
+{
+       struct tcf_common *pc = a->priv;
+       if (est)
+               gen_kill_estimator(&pc->tcfc_bstats,
+                                  &pc->tcfc_rate_est);
+       kfree_rcu(pc, tcfc_rcu);
+}
+EXPORT_SYMBOL(tcf_hash_cleanup);
+
+int tcf_hash_create(u32 index, struct nlattr *est, struct tc_action *a,
+                   int size, int bind)
 {
        struct tcf_hashinfo *hinfo = a->ops->hinfo;
        struct tcf_common *p = kzalloc(size, GFP_KERNEL);
 
        if (unlikely(!p))
-               return ERR_PTR(-ENOMEM);
+               return -ENOMEM;
        p->tcfc_refcnt = 1;
        if (bind)
                p->tcfc_bindcnt = 1;
@@ -234,17 +256,19 @@ struct tcf_common *tcf_hash_create(u32 index, struct nlattr *est,
                                            &p->tcfc_lock, est);
                if (err) {
                        kfree(p);
-                       return ERR_PTR(err);
+                       return err;
                }
        }
 
        a->priv = (void *) p;
-       return p;
+       return 0;
 }
 EXPORT_SYMBOL(tcf_hash_create);
 
-void tcf_hash_insert(struct tcf_common *p, struct tcf_hashinfo *hinfo)
+void tcf_hash_insert(struct tc_action *a)
 {
+       struct tcf_common *p = a->priv;
+       struct tcf_hashinfo *hinfo = a->ops->hinfo;
        unsigned int h = tcf_hash(p->tcfc_index, hinfo->hmask);
 
        spin_lock_bh(&hinfo->lock);
@@ -256,12 +280,13 @@ EXPORT_SYMBOL(tcf_hash_insert);
 static LIST_HEAD(act_base);
 static DEFINE_RWLOCK(act_mod_lock);
 
-int tcf_register_action(struct tc_action_ops *act)
+int tcf_register_action(struct tc_action_ops *act, unsigned int mask)
 {
        struct tc_action_ops *a;
+       int err;
 
-       /* Must supply act, dump, cleanup and init */
-       if (!act->act || !act->dump || !act->cleanup || !act->init)
+       /* Must supply act, dump and init */
+       if (!act->act || !act->dump || !act->init)
                return -EINVAL;
 
        /* Supply defaults */
@@ -270,10 +295,21 @@ int tcf_register_action(struct tc_action_ops *act)
        if (!act->walk)
                act->walk = tcf_generic_walker;
 
+       act->hinfo = kmalloc(sizeof(struct tcf_hashinfo), GFP_KERNEL);
+       if (!act->hinfo)
+               return -ENOMEM;
+       err = tcf_hashinfo_init(act->hinfo, mask);
+       if (err) {
+               kfree(act->hinfo);
+               return err;
+       }
+
        write_lock(&act_mod_lock);
        list_for_each_entry(a, &act_base, head) {
                if (act->type == a->type || (strcmp(act->kind, a->kind) == 0)) {
                        write_unlock(&act_mod_lock);
+                       tcf_hashinfo_destroy(act->hinfo);
+                       kfree(act->hinfo);
                        return -EEXIST;
                }
        }
@@ -292,6 +328,8 @@ int tcf_unregister_action(struct tc_action_ops *act)
        list_for_each_entry(a, &act_base, head) {
                if (a == act) {
                        list_del(&act->head);
+                       tcf_hashinfo_destroy(act->hinfo);
+                       kfree(act->hinfo);
                        err = 0;
                        break;
                }
@@ -368,16 +406,21 @@ exec_done:
 }
 EXPORT_SYMBOL(tcf_action_exec);
 
-void tcf_action_destroy(struct list_head *actions, int bind)
+int tcf_action_destroy(struct list_head *actions, int bind)
 {
        struct tc_action *a, *tmp;
+       int ret = 0;
 
        list_for_each_entry_safe(a, tmp, actions, list) {
-               if (a->ops->cleanup(a, bind) == ACT_P_DELETED)
+               ret = tcf_hash_release(a, bind);
+               if (ret == ACT_P_DELETED)
                        module_put(a->ops->owner);
+               else if (ret < 0)
+                       return ret;
                list_del(&a->list);
                kfree(a);
        }
+       return ret;
 }
 
 int
@@ -805,7 +848,11 @@ tcf_del_notify(struct net *net, struct nlmsghdr *n, struct list_head *actions,
        }
 
        /* now do the delete */
-       tcf_action_destroy(actions, 0);
+       ret = tcf_action_destroy(actions, 0);
+       if (ret < 0) {
+               kfree_skb(skb);
+               return ret;
+       }
 
        ret = rtnetlink_send(skb, net, portid, RTNLGRP_TC,
                             n->nlmsg_flags & NLM_F_ECHO);