net_sched: Fix qdisc_notify()
[safe/jmp/linux-2.6] / net / sched / sch_drr.c
index e7a7e87..b74046a 100644 (file)
@@ -9,6 +9,7 @@
  */
 
 #include <linux/module.h>
+#include <linux/slab.h>
 #include <linux/init.h>
 #include <linux/errno.h>
 #include <linux/netdevice.h>
@@ -22,7 +23,7 @@ struct drr_class {
        unsigned int                    refcnt;
        unsigned int                    filter_cnt;
 
-       struct gnet_stats_basic         bstats;
+       struct gnet_stats_basic_packed          bstats;
        struct gnet_stats_queue         qstats;
        struct gnet_stats_rate_est      rate_est;
        struct list_head                alist;
@@ -66,11 +67,15 @@ static int drr_change_class(struct Qdisc *sch, u32 classid, u32 parentid,
 {
        struct drr_sched *q = qdisc_priv(sch);
        struct drr_class *cl = (struct drr_class *)*arg;
+       struct nlattr *opt = tca[TCA_OPTIONS];
        struct nlattr *tb[TCA_DRR_MAX + 1];
        u32 quantum;
        int err;
 
-       err = nla_parse_nested(tb, TCA_DRR_MAX, tca[TCA_OPTIONS], drr_policy);
+       if (!opt)
+               return -EINVAL;
+
+       err = nla_parse_nested(tb, TCA_DRR_MAX, opt, drr_policy);
        if (err < 0)
                return err;
 
@@ -82,15 +87,19 @@ static int drr_change_class(struct Qdisc *sch, u32 classid, u32 parentid,
                quantum = psched_mtu(qdisc_dev(sch));
 
        if (cl != NULL) {
+               if (tca[TCA_RATE]) {
+                       err = gen_replace_estimator(&cl->bstats, &cl->rate_est,
+                                                   qdisc_root_sleeping_lock(sch),
+                                                   tca[TCA_RATE]);
+                       if (err)
+                               return err;
+               }
+
                sch_tree_lock(sch);
                if (tb[TCA_DRR_QUANTUM])
                        cl->quantum = quantum;
                sch_tree_unlock(sch);
 
-               if (tca[TCA_RATE])
-                       gen_replace_estimator(&cl->bstats, &cl->rate_est,
-                                             qdisc_root_sleeping_lock(sch),
-                                             tca[TCA_RATE]);
                return 0;
        }
 
@@ -106,10 +115,16 @@ static int drr_change_class(struct Qdisc *sch, u32 classid, u32 parentid,
        if (cl->qdisc == NULL)
                cl->qdisc = &noop_qdisc;
 
-       if (tca[TCA_RATE])
-               gen_replace_estimator(&cl->bstats, &cl->rate_est,
-                                     qdisc_root_sleeping_lock(sch),
-                                     tca[TCA_RATE]);
+       if (tca[TCA_RATE]) {
+               err = gen_replace_estimator(&cl->bstats, &cl->rate_est,
+                                           qdisc_root_sleeping_lock(sch),
+                                           tca[TCA_RATE]);
+               if (err) {
+                       qdisc_destroy(cl->qdisc);
+                       kfree(cl);
+                       return err;
+               }
+       }
 
        sch_tree_lock(sch);
        qdisc_class_hash_insert(&q->clhash, &cl->common);
@@ -141,8 +156,11 @@ static int drr_delete_class(struct Qdisc *sch, unsigned long arg)
        drr_purge_queue(cl);
        qdisc_class_hash_remove(&q->clhash, &cl->common);
 
-       if (--cl->refcnt == 0)
-               drr_destroy_class(sch, cl);
+       BUG_ON(--cl->refcnt == 0);
+       /*
+        * This shouldn't happen: we "hold" one cops->get() when called
+        * from tc_ctl_tclass; the destroy method is done from cops->put().
+        */
 
        sch_tree_unlock(sch);
        return 0;
@@ -257,11 +275,13 @@ static int drr_dump_class_stats(struct Qdisc *sch, unsigned long arg,
        struct tc_drr_stats xstats;
 
        memset(&xstats, 0, sizeof(xstats));
-       if (cl->qdisc->q.qlen)
+       if (cl->qdisc->q.qlen) {
                xstats.deficit = cl->deficit;
+               cl->qdisc->qstats.qlen = cl->qdisc->q.qlen;
+       }
 
        if (gnet_stats_copy_basic(d, &cl->bstats) < 0 ||
-           gnet_stats_copy_rate_est(d, &cl->rate_est) < 0 ||
+           gnet_stats_copy_rate_est(d, &cl->bstats, &cl->rate_est) < 0 ||
            gnet_stats_copy_queue(d, &cl->qdisc->qstats) < 0)
                return -1;