lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite: Windows password security audit tool. GUI, reports in PDF.
[<prev] [next>] [day] [month] [year] [list]
Date:	Mon, 14 Jul 2008 16:00:34 -0700 (PDT)
From:	David Miller <davem@...emloft.net>
To:	netdev@...r.kernel.org
Subject: [PATCH 13/14]: pkt_sched: Convert Qdisc_class_ops->change to
 validate/commit/cancel.


Now it will be possible for the qdisc change operation requests
to made fully multiqueue aware.

New versions of the generic estimator attach routines were needed
which take a pointer to the attribute structure itself rather than
the nlattr.

Signed-off-by: David S. Miller <davem@...emloft.net>
---
 include/net/gen_stats.h   |    8 +
 include/net/sch_generic.h |   10 +-
 net/core/gen_estimator.c  |   34 ++++-
 net/sched/sch_api.c       |    7 +-
 net/sched/sch_atm.c       |  119 +++++++++-------
 net/sched/sch_cbq.c       |  355 +++++++++++++++++++++++++++++----------------
 net/sched/sch_dsmark.c    |   54 ++++---
 net/sched/sch_hfsc.c      |  252 +++++++++++++++++++++-----------
 net/sched/sch_htb.c       |  249 +++++++++++++++++++++-----------
 net/sched/sch_ingress.c   |   18 ++-
 net/sched/sch_netem.c     |   23 +++-
 net/sched/sch_prio.c      |   19 ++-
 net/sched/sch_red.c       |   23 +++-
 net/sched/sch_sfq.c       |   23 +++-
 net/sched/sch_tbf.c       |   23 +++-
 15 files changed, 818 insertions(+), 399 deletions(-)

diff --git a/include/net/gen_stats.h b/include/net/gen_stats.h
index 8cd8185..bff1970 100644
--- a/include/net/gen_stats.h
+++ b/include/net/gen_stats.h
@@ -40,10 +40,18 @@ extern int gnet_stats_finish_copy(struct gnet_dump *d);
 extern int gen_new_estimator(struct gnet_stats_basic *bstats,
 			     struct gnet_stats_rate_est *rate_est,
 			     spinlock_t *stats_lock, struct nlattr *opt);
+extern int __gen_new_estimator(struct gnet_stats_basic *bstats,
+			       struct gnet_stats_rate_est *rate_est,
+			       spinlock_t *stats_lock,
+			       struct gnet_estimator *parm);
 extern void gen_kill_estimator(struct gnet_stats_basic *bstats,
 			       struct gnet_stats_rate_est *rate_est);
 extern int gen_replace_estimator(struct gnet_stats_basic *bstats,
 				 struct gnet_stats_rate_est *rate_est,
 				 spinlock_t *stats_lock, struct nlattr *opt);
+extern int __gen_replace_estimator(struct gnet_stats_basic *bstats,
+				   struct gnet_stats_rate_est *rate_est,
+				   spinlock_t *stats_lock,
+				   struct gnet_estimator *parm);
 
 #endif
diff --git a/include/net/sch_generic.h b/include/net/sch_generic.h
index b17dece..64d60a8 100644
--- a/include/net/sch_generic.h
+++ b/include/net/sch_generic.h
@@ -69,8 +69,14 @@ struct Qdisc_class_ops
 	/* Class manipulation routines */
 	unsigned long		(*get)(struct Qdisc *, u32 classid);
 	void			(*put)(struct Qdisc *, unsigned long);
-	int			(*change)(struct Qdisc *, u32, u32,
-					  struct nlattr **, unsigned long *);
+	int			(*validate_change)(struct Qdisc *, u32, u32,
+						   struct nlattr **,
+						   unsigned long *);
+	void			(*commit_change)(struct Qdisc *, u32, u32,
+					  struct nlattr **, unsigned long);
+	void			(*cancel_change)(struct Qdisc *, u32, u32,
+						 struct nlattr **,
+						 unsigned long);
 	int			(*validate_delete)(struct Qdisc *,
 						   unsigned long);
 	void			(*commit_delete)(struct Qdisc *,
diff --git a/net/core/gen_estimator.c b/net/core/gen_estimator.c
index 57abe82..c711471 100644
--- a/net/core/gen_estimator.c
+++ b/net/core/gen_estimator.c
@@ -156,18 +156,14 @@ skip:
  *
  * NOTE: Called under rtnl_mutex
  */
-int gen_new_estimator(struct gnet_stats_basic *bstats,
-		      struct gnet_stats_rate_est *rate_est,
-		      spinlock_t *stats_lock,
-		      struct nlattr *opt)
+int __gen_new_estimator(struct gnet_stats_basic *bstats,
+			struct gnet_stats_rate_est *rate_est,
+			spinlock_t *stats_lock,
+			struct gnet_estimator *parm)
 {
 	struct gen_estimator *est;
-	struct gnet_estimator *parm = nla_data(opt);
 	int idx;
 
-	if (nla_len(opt) < sizeof(*parm))
-		return -EINVAL;
-
 	if (parm->interval < -2 || parm->interval > 3)
 		return -EINVAL;
 
@@ -197,6 +193,19 @@ int gen_new_estimator(struct gnet_stats_basic *bstats,
 	return 0;
 }
 
+int gen_new_estimator(struct gnet_stats_basic *bstats,
+		      struct gnet_stats_rate_est *rate_est,
+		      spinlock_t *stats_lock,
+		      struct nlattr *opt)
+{
+	struct gnet_estimator *parm = nla_data(opt);
+
+	if (nla_len(opt) < sizeof(*parm))
+		return -EINVAL;
+
+	return __gen_new_estimator(bstats, rate_est, stats_lock, parm);
+}
+
 static void __gen_kill_estimator(struct rcu_head *head)
 {
 	struct gen_estimator *e = container_of(head,
@@ -260,7 +269,16 @@ int gen_replace_estimator(struct gnet_stats_basic *bstats,
 	return gen_new_estimator(bstats, rate_est, stats_lock, opt);
 }
 
+int __gen_replace_estimator(struct gnet_stats_basic *bstats,
+			    struct gnet_stats_rate_est *rate_est,
+			    spinlock_t *stats_lock, struct gnet_estimator *parm)
+{
+	gen_kill_estimator(bstats, rate_est);
+	return __gen_new_estimator(bstats, rate_est, stats_lock, parm);
+}
 
 EXPORT_SYMBOL(gen_kill_estimator);
 EXPORT_SYMBOL(gen_new_estimator);
+EXPORT_SYMBOL(__gen_new_estimator);
 EXPORT_SYMBOL(gen_replace_estimator);
+EXPORT_SYMBOL(__gen_replace_estimator);
diff --git a/net/sched/sch_api.c b/net/sched/sch_api.c
index 5230d2d..a4020a6 100644
--- a/net/sched/sch_api.c
+++ b/net/sched/sch_api.c
@@ -1610,9 +1610,12 @@ static int tc_ctl_tclass(struct sk_buff *skb, struct nlmsghdr *n, void *arg)
 	}
 
 	new_cl = cl;
-	err = cops->change(q, clid, pid, tca, &new_cl);
-	if (err == 0)
+
+	err = cops->validate_change(q, clid, pid, tca, &new_cl);
+	if (!err) {
+		cops->commit_change(q, clid, pid, tca, new_cl);
 		tclass_notify(skb, n, q, new_cl, RTM_NEWTCLASS);
+	}
 
 out:
 	if (cl)
diff --git a/net/sched/sch_atm.c b/net/sched/sch_atm.c
index c240275..2e32b59 100644
--- a/net/sched/sch_atm.c
+++ b/net/sched/sch_atm.c
@@ -212,27 +212,25 @@ static const struct nla_policy atm_policy[TCA_ATM_MAX + 1] = {
 	[TCA_ATM_EXCESS]	= { .type = NLA_U32 },
 };
 
-static int atm_tc_change(struct Qdisc *sch, u32 classid, u32 parent,
-			 struct nlattr **tca, unsigned long *arg)
+static int atm_tc_validate_change(struct Qdisc *sch, u32 classid, u32 parent,
+				  struct nlattr **tca, unsigned long *arg)
 {
+	struct atm_flow_data *flow = (struct atm_flow_data *) *arg;
 	struct atm_qdisc_data *p = qdisc_priv(sch);
-	struct atm_flow_data *flow = (struct atm_flow_data *)*arg;
-	struct atm_flow_data *excess = NULL;
 	struct nlattr *opt = tca[TCA_OPTIONS];
 	struct nlattr *tb[TCA_ATM_MAX + 1];
+	struct atm_flow_data *excess;
+	int err, fd, hdr_len;
 	struct socket *sock;
-	int fd, error, hdr_len;
 	void *hdr;
 
-	pr_debug("atm_tc_change(sch %p,[qdisc %p],classid %x,parent %x,"
-		"flow %p,opt %p)\n", sch, p, classid, parent, flow, opt);
-	/*
-	 * The concept of parents doesn't apply for this qdisc.
-	 */
+	if (!opt)
+		return -EINVAL;
+
+	/* The concept of parents doesn't apply for this qdisc.  */
 	if (parent && parent != TC_H_ROOT && parent != sch->handle)
 		return -EINVAL;
-	/*
-	 * ATM classes cannot be changed. In order to change properties of the
+	/* ATM classes cannot be changed. In order to change properties of the
 	 * ATM connection, that socket needs to be modified directly (via the
 	 * native ATM API. In order to send a flow to a different VC, the old
 	 * class needs to be removed and a new one added. (This may be changed
@@ -240,17 +238,15 @@ static int atm_tc_change(struct Qdisc *sch, u32 classid, u32 parent,
 	 */
 	if (flow)
 		return -EBUSY;
-	if (opt == NULL)
-		return -EINVAL;
 
-	error = nla_parse_nested(tb, TCA_ATM_MAX, opt, atm_policy);
-	if (error < 0)
-		return error;
+	err = nla_parse_nested(tb, TCA_ATM_MAX, opt, atm_policy);
+	if (err < 0)
+		return err;
 
 	if (!tb[TCA_ATM_FD])
 		return -EINVAL;
 	fd = nla_get_u32(tb[TCA_ATM_FD]);
-	pr_debug("atm_tc_change: fd %d\n", fd);
+
 	if (tb[TCA_ATM_HDR]) {
 		hdr_len = nla_len(tb[TCA_ATM_HDR]);
 		hdr = nla_data(tb[TCA_ATM_HDR]);
@@ -258,39 +254,36 @@ static int atm_tc_change(struct Qdisc *sch, u32 classid, u32 parent,
 		hdr_len = RFC1483LLC_LEN;
 		hdr = NULL;	/* default LLC/SNAP for IP */
 	}
-	if (!tb[TCA_ATM_EXCESS])
-		excess = NULL;
-	else {
+
+	excess = NULL;
+	if (tb[TCA_ATM_EXCESS]) {
 		excess = (struct atm_flow_data *)
 			atm_tc_get(sch, nla_get_u32(tb[TCA_ATM_EXCESS]));
 		if (!excess)
 			return -ENOENT;
 	}
-	pr_debug("atm_tc_change: type %d, payload %d, hdr_len %d\n",
-		 opt->nla_type, nla_len(opt), hdr_len);
-	sock = sockfd_lookup(fd, &error);
+
+	sock = sockfd_lookup(fd, &err);
 	if (!sock)
-		return error;	/* f_count++ */
-	pr_debug("atm_tc_change: f_count %d\n", file_count(sock->file));
-	if (sock->ops->family != PF_ATMSVC && sock->ops->family != PF_ATMPVC) {
-		error = -EPROTOTYPE;
-		goto err_out;
-	}
-	/* @@@ should check if the socket is really operational or we'll crash
-	   on vcc->send */
+		return err;
+
+	err = -EPROTOTYPE;
+	if (sock->ops->family != PF_ATMSVC && sock->ops->family != PF_ATMPVC)
+		goto err_sock_put;
+
 	if (classid) {
 		if (TC_H_MAJ(classid ^ sch->handle)) {
 			pr_debug("atm_tc_change: classid mismatch\n");
-			error = -EINVAL;
-			goto err_out;
+			err = -EINVAL;
+			goto err_sock_put;
 		}
 		if (find_flow(p, flow)) {
-			error = -EEXIST;
-			goto err_out;
+			err = -EEXIST;
+			goto err_sock_put;
 		}
 	} else {
-		int i;
 		unsigned long cl;
+		int i;
 
 		for (i = 1; i < 0x8000; i++) {
 			classid = TC_H_MAKE(sch->handle, 0x8000 | i);
@@ -300,23 +293,22 @@ static int atm_tc_change(struct Qdisc *sch, u32 classid, u32 parent,
 			atm_tc_put(sch, cl);
 		}
 	}
-	pr_debug("atm_tc_change: new id %x\n", classid);
+
 	flow = kzalloc(sizeof(struct atm_flow_data) + hdr_len, GFP_KERNEL);
-	pr_debug("atm_tc_change: flow %p\n", flow);
-	if (!flow) {
-		error = -ENOBUFS;
-		goto err_out;
-	}
+	err = -ENOBUFS;
+	if (!flow)
+		goto err_sock_put;
+
 	flow->filter_list = NULL;
 	flow->q = qdisc_create_dflt(qdisc_dev(sch), sch->dev_queue,
 				    &pfifo_qdisc_ops, classid);
+	err = -ENOMEM;
 	if (!flow->q)
-		flow->q = &noop_qdisc;
-	pr_debug("atm_tc_change: qdisc %p\n", flow->q);
+		goto err_free_flow;
+
 	flow->sock = sock;
 	flow->vcc = ATM_SD(sock);	/* speedup */
 	flow->vcc->user_back = flow;
-	pr_debug("atm_tc_change: vcc %p\n", flow->vcc);
 	flow->old_pop = flow->vcc->pop;
 	flow->parent = p;
 	flow->vcc->pop = sch_atm_pop;
@@ -324,7 +316,6 @@ static int atm_tc_change(struct Qdisc *sch, u32 classid, u32 parent,
 	flow->ref = 1;
 	flow->excess = excess;
 	flow->next = p->link.next;
-	p->link.next = flow;
 	flow->hdr_len = hdr_len;
 	if (hdr)
 		memcpy(flow->hdr, hdr, hdr_len);
@@ -332,11 +323,33 @@ static int atm_tc_change(struct Qdisc *sch, u32 classid, u32 parent,
 		memcpy(flow->hdr, llc_oui_ip, sizeof(llc_oui_ip));
 	*arg = (unsigned long)flow;
 	return 0;
-err_out:
-	if (excess)
-		atm_tc_put(sch, (unsigned long)excess);
+
+err_free_flow:
+	kfree(flow);
+
+err_sock_put:
 	sockfd_put(sock);
-	return error;
+	return err;
+}
+
+static void atm_tc_commit_change(struct Qdisc *sch, u32 classid, u32 parent,
+				 struct nlattr **tca, unsigned long arg)
+{
+	struct atm_flow_data *flow = (struct atm_flow_data *) arg;
+	struct atm_qdisc_data *p = qdisc_priv(sch);
+
+	flow->next = p->link.next;
+	p->link.next = flow;
+}
+
+static void atm_tc_cancel_change(struct Qdisc *sch, u32 classid, u32 parent,
+				  struct nlattr **tca, unsigned long arg)
+{
+	struct atm_flow_data *flow = (struct atm_flow_data *) arg;
+
+	qdisc_destroy(flow->q);
+	sockfd_put(flow->sock);
+	kfree(flow);
 }
 
 static int atm_tc_validate_delete(struct Qdisc *sch, unsigned long arg)
@@ -693,7 +706,9 @@ static const struct Qdisc_class_ops atm_class_ops = {
 	.leaf		= atm_tc_leaf,
 	.get		= atm_tc_get,
 	.put		= atm_tc_put,
-	.change		= atm_tc_change,
+	.validate_change= atm_tc_validate_change,
+	.commit_change	= atm_tc_commit_change,
+	.cancel_change	= atm_tc_cancel_change,
 	.validate_delete= atm_tc_validate_delete,
 	.commit_delete	= atm_tc_commit_delete,
 	.walk		= atm_tc_walk,
diff --git a/net/sched/sch_cbq.c b/net/sched/sch_cbq.c
index 7138a04..a6896f8 100644
--- a/net/sched/sch_cbq.c
+++ b/net/sched/sch_cbq.c
@@ -171,6 +171,8 @@ struct cbq_sched_data
 	psched_tdiff_t		wd_expires;
 	int			toplevel;
 	u32			hgenerator;
+
+	void			*pending_config;
 };
 
 
@@ -1771,44 +1773,213 @@ static void cbq_put(struct Qdisc *sch, unsigned long arg)
 	}
 }
 
-static int
-cbq_change_class(struct Qdisc *sch, u32 classid, u32 parentid, struct nlattr **tca,
-		 unsigned long *arg)
+struct cbq_pending_config {
+	struct tc_cbq_lssopt	lss;
+	struct tc_cbq_wrropt	wrr;
+	struct tc_cbq_ovl	ovl;
+	struct tc_cbq_police	police;
+	struct tc_cbq_fopt	fopt;
+	struct gnet_estimator	est;
+	struct qdisc_rate_table	*rtab;
+	struct cbq_class	*new_class;
+	unsigned int		flags;
+#define FLAG_HAVE_LSSOPT	0x00000001
+#define FLAG_HAVE_WRROPT	0x00000002
+#define FLAG_HAVE_STRATEGY	0x00000004
+#define FLAG_HAVE_POLICE	0x00000008
+#define FLAG_HAVE_FOPT		0x00000010
+#define FLAG_HAVE_RATE		0x00000020
+};
+
+static int cbq_validate_change_class(struct Qdisc *sch, u32 classid,
+				     u32 parentid, struct nlattr **tca,
+				     unsigned long *arg)
 {
-	int err;
+	struct cbq_class *cl = (struct cbq_class *) *arg;
 	struct cbq_sched_data *q = qdisc_priv(sch);
-	struct cbq_class *cl = (struct cbq_class*)*arg;
 	struct nlattr *opt = tca[TCA_OPTIONS];
 	struct nlattr *tb[TCA_CBQ_MAX + 1];
-	struct cbq_class *parent;
-	struct qdisc_rate_table *rtab = NULL;
+	struct cbq_pending_config *p;
+	int err;
 
-	if (opt == NULL)
+	if (!opt)
 		return -EINVAL;
 
 	err = nla_parse_nested(tb, TCA_CBQ_MAX, opt, cbq_policy);
 	if (err < 0)
 		return err;
 
+	p = kzalloc(sizeof(*p), GFP_KERNEL);
+	if (!p)
+		return -ENOMEM;
+
+	err = -EINVAL;
+	if (tb[TCA_CBQ_RATE]) {
+		p->rtab = qdisc_get_rtab(nla_data(tb[TCA_CBQ_RATE]),
+					 tb[TCA_CBQ_RTAB]);
+		if (!p->rtab)
+			goto err_free_config;
+	}
+
+	if (tb[TCA_CBQ_LSSOPT]) {
+		memcpy(&p->lss, nla_data(tb[TCA_CBQ_LSSOPT]), sizeof(p->lss));
+		p->flags |= FLAG_HAVE_LSSOPT;
+	}
+	if (tb[TCA_CBQ_WRROPT]) {
+		memcpy(&p->wrr, nla_data(tb[TCA_CBQ_WRROPT]), sizeof(p->wrr));
+		p->flags |= FLAG_HAVE_WRROPT;
+	}
+	if (tb[TCA_CBQ_OVL_STRATEGY]) {
+		memcpy(&p->ovl, nla_data(tb[TCA_CBQ_OVL_STRATEGY]),
+		       sizeof(p->ovl));
+		p->flags |= FLAG_HAVE_STRATEGY;
+	}
+	if (tb[TCA_CBQ_POLICE]) {
+		memcpy(&p->police, nla_data(tb[TCA_CBQ_POLICE]),
+		       sizeof(p->police));
+		p->flags |= FLAG_HAVE_POLICE;
+	}
+	if (tb[TCA_CBQ_FOPT]) {
+		memcpy(&p->fopt, nla_data(tb[TCA_CBQ_FOPT]), sizeof(p->fopt));
+		p->flags |= FLAG_HAVE_FOPT;
+	}
+	if (tb[TCA_RATE]) {
+		memcpy(&p->est, nla_data(tb[TCA_RATE]), sizeof(p->est));
+		p->flags |= FLAG_HAVE_RATE;
+	}
+
 	if (cl) {
 		/* Check parent */
 		if (parentid) {
 			if (cl->tparent &&
 			    cl->tparent->common.classid != parentid)
-				return -EINVAL;
+				goto err_release_rtab;
 			if (!cl->tparent && parentid != TC_H_ROOT)
-				return -EINVAL;
+				goto err_release_rtab;
 		}
+	} else {
+		struct cbq_class *parent;
+
+		if (parentid == TC_H_ROOT)
+			goto err_release_rtab;
+
+		if (!tb[TCA_CBQ_WRROPT] ||
+		    !tb[TCA_CBQ_RATE] ||
+		    !tb[TCA_CBQ_LSSOPT])
+			goto err_release_rtab;
 
-		if (tb[TCA_CBQ_RATE]) {
-			rtab = qdisc_get_rtab(nla_data(tb[TCA_CBQ_RATE]), tb[TCA_CBQ_RTAB]);
-			if (rtab == NULL)
-				return -EINVAL;
+		if (classid) {
+			if (TC_H_MAJ(classid^sch->handle) ||
+			    cbq_class_lookup(q, classid))
+				goto err_release_rtab;
+		} else {
+			int i;
+
+			classid = TC_H_MAKE(sch->handle, 0x8000);
+			for (i = 0; i < 0x8000; i++) {
+				if (++q->hgenerator >= 0x8000)
+					q->hgenerator = 1;
+				if (cbq_class_lookup(q, classid | q->hgenerator) == NULL)
+					break;
+			}
+			err = -ENOSR;
+			if (i >= 0x8000)
+				goto err_release_rtab;
+			classid = classid | q->hgenerator;
 		}
 
-		/* Change class parameters */
-		sch_tree_lock(sch);
+		parent = &q->link;
+		if (parentid) {
+			parent = cbq_class_lookup(q, parentid);
+			err = -EINVAL;
+			if (!parent)
+				goto err_release_rtab;
+		}
+
+		err = -ENOBUFS;
+		cl = kzalloc(sizeof(*cl), GFP_KERNEL);
+		if (!cl)
+			goto err_release_rtab;
+		cl->R_tab = p->rtab;
+		cl->refcnt = 1;
+		if (!(cl->q = qdisc_create_dflt(qdisc_dev(sch), sch->dev_queue,
+						&pfifo_qdisc_ops, classid)))
+			goto err_free_class;
+		cl->common.classid = classid;
+		cl->tparent = parent;
+		cl->qdisc = sch;
+		cl->allot = parent->allot;
+		cl->quantum = cl->allot;
+		cl->weight = cl->R_tab->rate.rate;
+
+		p->new_class = cl;
+	}
+
+	q->pending_config = p;
+	*arg = (unsigned long) cl;
+
+	return 0;
+
+err_free_class:
+	kfree(cl);
+
+err_release_rtab:
+	if (p->rtab)
+		qdisc_put_rtab(p->rtab);
 
+err_free_config:
+	kfree(p);
+	return err;
+}
+
+static void cbq_cancel_change_class(struct Qdisc *sch, u32 classid,
+				    u32 parentid, struct nlattr **tca,
+				    unsigned long arg)
+{
+	struct cbq_sched_data *q = qdisc_priv(sch);
+	struct cbq_pending_config *p;
+
+	BUG_ON(!q->pending_config);
+	p = q->pending_config;
+	q->pending_config = NULL;
+
+	if (p->new_class) {
+		struct cbq_class *cl = p->new_class;
+
+		p->new_class = NULL;
+
+		if (cl->q) {
+			qdisc_destroy(cl->q);
+			cl->q = NULL;
+		}
+		kfree(cl);
+	}
+
+	if (p->rtab) {
+		qdisc_put_rtab(p->rtab);
+		p->rtab = NULL;
+	}
+
+	kfree(p);
+}
+
+static void cbq_commit_change_class(struct Qdisc *sch, u32 classid,
+				    u32 parentid, struct nlattr **tca,
+				    unsigned long arg)
+{
+	struct cbq_class *cl = (struct cbq_class *) arg;
+	struct cbq_sched_data *q = qdisc_priv(sch);
+	struct qdisc_rate_table *rtab;
+	struct cbq_pending_config *p;
+
+	BUG_ON(!q->pending_config);
+	p = q->pending_config;
+	q->pending_config = NULL;
+
+	rtab = p->rtab;
+
+	if (!p->new_class) {
+		sch_tree_lock(sch);
 		if (cl->next_alive != NULL)
 			cbq_deactivate_class(cl);
 
@@ -1817,131 +1988,69 @@ cbq_change_class(struct Qdisc *sch, u32 classid, u32 parentid, struct nlattr **t
 			qdisc_put_rtab(rtab);
 		}
 
-		if (tb[TCA_CBQ_LSSOPT])
-			cbq_set_lss(cl, nla_data(tb[TCA_CBQ_LSSOPT]));
+		if (p->flags & FLAG_HAVE_LSSOPT)
+			cbq_set_lss(cl, &p->lss);
 
-		if (tb[TCA_CBQ_WRROPT]) {
+		if (p->flags & FLAG_HAVE_WRROPT) {
 			cbq_rmprio(q, cl);
-			cbq_set_wrr(cl, nla_data(tb[TCA_CBQ_WRROPT]));
+			cbq_set_wrr(cl, &p->wrr);
 		}
 
-		if (tb[TCA_CBQ_OVL_STRATEGY])
-			cbq_set_overlimit(cl, nla_data(tb[TCA_CBQ_OVL_STRATEGY]));
+		if (p->flags & FLAG_HAVE_STRATEGY)
+			cbq_set_overlimit(cl, &p->ovl);
 
 #ifdef CONFIG_NET_CLS_ACT
-		if (tb[TCA_CBQ_POLICE])
-			cbq_set_police(cl, nla_data(tb[TCA_CBQ_POLICE]));
+		if (p->flags & FLAG_HAVE_POLICE)
+			cbq_set_police(cl, &p->police);
 #endif
 
-		if (tb[TCA_CBQ_FOPT])
-			cbq_set_fopt(cl, nla_data(tb[TCA_CBQ_FOPT]));
+		if (p->flags & FLAG_HAVE_FOPT)
+			cbq_set_fopt(cl, &p->fopt);
 
 		if (cl->q->q.qlen)
 			cbq_activate_class(cl);
 
 		sch_tree_unlock(sch);
 
-		if (tca[TCA_RATE])
-			gen_replace_estimator(&cl->bstats, &cl->rate_est,
-					      &sch->dev_queue->lock,
-					      tca[TCA_RATE]);
-		return 0;
-	}
-
-	if (parentid == TC_H_ROOT)
-		return -EINVAL;
-
-	if (tb[TCA_CBQ_WRROPT] == NULL || tb[TCA_CBQ_RATE] == NULL ||
-	    tb[TCA_CBQ_LSSOPT] == NULL)
-		return -EINVAL;
-
-	rtab = qdisc_get_rtab(nla_data(tb[TCA_CBQ_RATE]), tb[TCA_CBQ_RTAB]);
-	if (rtab == NULL)
-		return -EINVAL;
-
-	if (classid) {
-		err = -EINVAL;
-		if (TC_H_MAJ(classid^sch->handle) || cbq_class_lookup(q, classid))
-			goto failure;
+		if (p->flags & FLAG_HAVE_RATE)
+			__gen_replace_estimator(&cl->bstats, &cl->rate_est,
+						&sch->dev_queue->lock,
+						&p->est);
 	} else {
-		int i;
-		classid = TC_H_MAKE(sch->handle,0x8000);
-
-		for (i=0; i<0x8000; i++) {
-			if (++q->hgenerator >= 0x8000)
-				q->hgenerator = 1;
-			if (cbq_class_lookup(q, classid|q->hgenerator) == NULL)
-				break;
-		}
-		err = -ENOSR;
-		if (i >= 0x8000)
-			goto failure;
-		classid = classid|q->hgenerator;
-	}
-
-	parent = &q->link;
-	if (parentid) {
-		parent = cbq_class_lookup(q, parentid);
-		err = -EINVAL;
-		if (parent == NULL)
-			goto failure;
-	}
-
-	err = -ENOBUFS;
-	cl = kzalloc(sizeof(*cl), GFP_KERNEL);
-	if (cl == NULL)
-		goto failure;
-	cl->R_tab = rtab;
-	rtab = NULL;
-	cl->refcnt = 1;
-	if (!(cl->q = qdisc_create_dflt(qdisc_dev(sch), sch->dev_queue,
-					&pfifo_qdisc_ops, classid)))
-		cl->q = &noop_qdisc;
-	cl->common.classid = classid;
-	cl->tparent = parent;
-	cl->qdisc = sch;
-	cl->allot = parent->allot;
-	cl->quantum = cl->allot;
-	cl->weight = cl->R_tab->rate.rate;
-
-	sch_tree_lock(sch);
-	cbq_link_class(cl);
-	cl->borrow = cl->tparent;
-	if (cl->tparent != &q->link)
-		cl->share = cl->tparent;
-	cbq_adjust_levels(parent);
-	cl->minidle = -0x7FFFFFFF;
-	cbq_set_lss(cl, nla_data(tb[TCA_CBQ_LSSOPT]));
-	cbq_set_wrr(cl, nla_data(tb[TCA_CBQ_WRROPT]));
-	if (cl->ewma_log==0)
-		cl->ewma_log = q->link.ewma_log;
-	if (cl->maxidle==0)
-		cl->maxidle = q->link.maxidle;
-	if (cl->avpkt==0)
-		cl->avpkt = q->link.avpkt;
-	cl->overlimit = cbq_ovl_classic;
-	if (tb[TCA_CBQ_OVL_STRATEGY])
-		cbq_set_overlimit(cl, nla_data(tb[TCA_CBQ_OVL_STRATEGY]));
+		sch_tree_lock(sch);
+		cbq_link_class(cl);
+		cl->borrow = cl->tparent;
+		if (cl->tparent != &q->link)
+			cl->share = cl->tparent;
+		cbq_adjust_levels(cl->tparent);
+		cl->minidle = -0x7FFFFFFF;
+		cbq_set_lss(cl, &p->lss);
+		cbq_set_wrr(cl, &p->wrr);
+		if (!cl->ewma_log)
+			cl->ewma_log = q->link.ewma_log;
+		if (!cl->maxidle)
+			cl->maxidle = q->link.maxidle;
+		if (!cl->avpkt)
+			cl->avpkt = q->link.avpkt;
+		cl->overlimit = cbq_ovl_classic;
+		if (p->flags & FLAG_HAVE_STRATEGY)
+			cbq_set_overlimit(cl, &p->ovl);
 #ifdef CONFIG_NET_CLS_ACT
-	if (tb[TCA_CBQ_POLICE])
-		cbq_set_police(cl, nla_data(tb[TCA_CBQ_POLICE]));
+		if (p->flags & FLAG_HAVE_POLICE)
+			cbq_set_police(cl, &p->police);
 #endif
-	if (tb[TCA_CBQ_FOPT])
-		cbq_set_fopt(cl, nla_data(tb[TCA_CBQ_FOPT]));
-	sch_tree_unlock(sch);
-
-	qdisc_class_hash_grow(sch, &q->clhash);
+		if (p->flags & FLAG_HAVE_FOPT)
+			cbq_set_fopt(cl, &p->fopt);
+		sch_tree_unlock(sch);
 
-	if (tca[TCA_RATE])
-		gen_new_estimator(&cl->bstats, &cl->rate_est,
-				  &sch->dev_queue->lock, tca[TCA_RATE]);
+		qdisc_class_hash_grow(sch, &q->clhash);
 
-	*arg = (unsigned long)cl;
-	return 0;
+		if (p->flags & FLAG_HAVE_RATE)
+			__gen_new_estimator(&cl->bstats, &cl->rate_est,
+					    &sch->dev_queue->lock, &p->est);
+	}
 
-failure:
-	qdisc_put_rtab(rtab);
-	return err;
+	kfree(p);
 }
 
 static int cbq_validate_delete(struct Qdisc *sch, unsigned long arg)
@@ -2059,7 +2168,9 @@ static const struct Qdisc_class_ops cbq_class_ops = {
 	.qlen_notify	=	cbq_qlen_notify,
 	.get		=	cbq_get,
 	.put		=	cbq_put,
-	.change		=	cbq_change_class,
+	.validate_change=	cbq_validate_change_class,
+	.commit_change	=	cbq_commit_change_class,
+	.cancel_change	=	cbq_cancel_change_class,
 	.validate_delete=	cbq_validate_delete,
 	.commit_delete	=	cbq_commit_delete,
 	.walk		=	cbq_walk,
diff --git a/net/sched/sch_dsmark.c b/net/sched/sch_dsmark.c
index 03c8bcf..10d3381 100644
--- a/net/sched/sch_dsmark.c
+++ b/net/sched/sch_dsmark.c
@@ -42,6 +42,9 @@ struct dsmark_qdisc_data {
 	u16			indices;
 	u32			default_index;	/* index range is 0...0xffff */
 	int			set_tc_index;
+
+	u8			pending_mask;
+	u8			pending_value;
 };
 
 static inline int dsmark_valid_index(struct dsmark_qdisc_data *p, u16 index)
@@ -122,43 +125,48 @@ static const struct nla_policy dsmark_policy[TCA_DSMARK_MAX + 1] = {
 	[TCA_DSMARK_VALUE]		= { .type = NLA_U8 },
 };
 
-static int dsmark_change(struct Qdisc *sch, u32 classid, u32 parent,
-			 struct nlattr **tca, unsigned long *arg)
+static int dsmark_validate_change(struct Qdisc *sch, u32 classid, u32 parent,
+				  struct nlattr **tca, unsigned long *arg)
 {
 	struct dsmark_qdisc_data *p = qdisc_priv(sch);
 	struct nlattr *opt = tca[TCA_OPTIONS];
 	struct nlattr *tb[TCA_DSMARK_MAX + 1];
 	int err = -EINVAL;
-	u8 mask = 0;
-
-	pr_debug("dsmark_change(sch %p,[qdisc %p],classid %x,parent %x),"
-		"arg 0x%lx\n", sch, p, classid, parent, *arg);
-
-	if (!dsmark_valid_index(p, *arg)) {
-		err = -ENOENT;
-		goto errout;
-	}
 
 	if (!opt)
-		goto errout;
+		return -EINVAL;
+	if (!dsmark_valid_index(p, *arg))
+		return -ENOENT;
 
 	err = nla_parse_nested(tb, TCA_DSMARK_MAX, opt, dsmark_policy);
 	if (err < 0)
-		goto errout;
-
-	if (tb[TCA_DSMARK_MASK])
-		mask = nla_get_u8(tb[TCA_DSMARK_MASK]);
+		return err;
 
 	if (tb[TCA_DSMARK_VALUE])
-		p->value[*arg-1] = nla_get_u8(tb[TCA_DSMARK_VALUE]);
+		p->pending_value = nla_get_u8(tb[TCA_DSMARK_VALUE]);
+	else
+		p->pending_value = p->value[*arg-1];
 
 	if (tb[TCA_DSMARK_MASK])
-		p->mask[*arg-1] = mask;
+		p->pending_mask = nla_get_u8(tb[TCA_DSMARK_MASK]);
+	else
+		p->pending_mask = p->mask[*arg-1];
 
-	err = 0;
+	return 0;
+}
 
-errout:
-	return err;
+static void dsmark_commit_change(struct Qdisc *sch, u32 classid, u32 parent,
+				 struct nlattr **tca, unsigned long arg)
+{
+	struct dsmark_qdisc_data *p = qdisc_priv(sch);
+
+	p->value[arg - 1] = p->pending_value;
+	p->mask[arg - 1] = p->pending_mask;
+}
+
+static void dsmark_cancel_change(struct Qdisc *sch, u32 classid, u32 parent,
+				 struct nlattr **tca, unsigned long arg)
+{
 }
 
 static int dsmark_validate_delete(struct Qdisc *sch, unsigned long arg)
@@ -498,7 +506,9 @@ static const struct Qdisc_class_ops dsmark_class_ops = {
 	.leaf		=	dsmark_leaf,
 	.get		=	dsmark_get,
 	.put		=	dsmark_put,
-	.change		=	dsmark_change,
+	.validate_change=	dsmark_validate_change,
+	.commit_change	=	dsmark_commit_change,
+	.cancel_change	=	dsmark_cancel_change,
 	.validate_delete=	dsmark_validate_delete,
 	.commit_delete	=	dsmark_commit_delete,
 	.walk		=	dsmark_walk,
diff --git a/net/sched/sch_hfsc.c b/net/sched/sch_hfsc.c
index 95a497e..8e2d601 100644
--- a/net/sched/sch_hfsc.c
+++ b/net/sched/sch_hfsc.c
@@ -186,6 +186,8 @@ struct hfsc_sched
 						   dropping) */
 	struct sk_buff_head requeue;		/* requeued packet */
 	struct qdisc_watchdog watchdog;		/* watchdog timer */
+
+	void *pending_config;
 };
 
 #define	HT_INFINITY	0xffffffffffffffffULL	/* infinite time value */
@@ -979,61 +981,150 @@ static const struct nla_policy hfsc_policy[TCA_HFSC_MAX + 1] = {
 	[TCA_HFSC_USC]	= { .len = sizeof(struct tc_service_curve) },
 };
 
-static int
-hfsc_change_class(struct Qdisc *sch, u32 classid, u32 parentid,
-		  struct nlattr **tca, unsigned long *arg)
+struct hfsc_pending_config {
+	struct tc_service_curve	*rsc;
+	struct tc_service_curve	*fsc;
+	struct tc_service_curve	*usc;
+	struct gnet_estimator	est;
+	struct hfsc_class	*new_class;
+	unsigned int		flags;
+#define FLAG_HAVE_EST		0x00000001
+};
+
+static int hfsc_validate_change_class(struct Qdisc *sch, u32 classid,
+				      u32 parentid, struct nlattr **tca,
+				      unsigned long *arg)
 {
+	struct hfsc_class *cl = (struct hfsc_class *) *arg, *parent = NULL;
 	struct hfsc_sched *q = qdisc_priv(sch);
-	struct hfsc_class *cl = (struct hfsc_class *)*arg;
-	struct hfsc_class *parent = NULL;
 	struct nlattr *opt = tca[TCA_OPTIONS];
 	struct nlattr *tb[TCA_HFSC_MAX + 1];
-	struct tc_service_curve *rsc = NULL, *fsc = NULL, *usc = NULL;
-	u64 cur_time;
+	struct hfsc_pending_config *p;
 	int err;
 
-	if (opt == NULL)
+	if (!opt)
 		return -EINVAL;
 
 	err = nla_parse_nested(tb, TCA_HFSC_MAX, opt, hfsc_policy);
 	if (err < 0)
 		return err;
 
+	if (cl) {
+		if (parentid) {
+			if (cl->cl_parent &&
+			    cl->cl_parent->cl_common.classid != parentid)
+				return -EINVAL;
+			if (cl->cl_parent == NULL && parentid != TC_H_ROOT)
+				return -EINVAL;
+		}
+	} else {
+		if (parentid == TC_H_ROOT)
+			return -EEXIST;
+		parent = &q->root;
+		if (parentid) {
+			parent = hfsc_find_class(parentid, sch);
+			if (!parent)
+				return -ENOENT;
+		}
+		if (classid == 0 || TC_H_MAJ(classid ^ sch->handle) != 0)
+			return -EINVAL;
+		if (hfsc_find_class(classid, sch))
+			return -EEXIST;
+	}
+
+	p = kzalloc(sizeof(*p), GFP_KERNEL);
+	if (p)
+		return -ENOMEM;
+
 	if (tb[TCA_HFSC_RSC]) {
-		rsc = nla_data(tb[TCA_HFSC_RSC]);
-		if (rsc->m1 == 0 && rsc->m2 == 0)
-			rsc = NULL;
+		p->rsc = nla_data(tb[TCA_HFSC_RSC]);
+		if (p->rsc->m1 == 0 && p->rsc->m2 == 0)
+			p->rsc = NULL;
 	}
 
 	if (tb[TCA_HFSC_FSC]) {
-		fsc = nla_data(tb[TCA_HFSC_FSC]);
-		if (fsc->m1 == 0 && fsc->m2 == 0)
-			fsc = NULL;
+		p->fsc = nla_data(tb[TCA_HFSC_FSC]);
+		if (p->fsc->m1 == 0 && p->fsc->m2 == 0)
+			p->fsc = NULL;
 	}
 
 	if (tb[TCA_HFSC_USC]) {
-		usc = nla_data(tb[TCA_HFSC_USC]);
-		if (usc->m1 == 0 && usc->m2 == 0)
-			usc = NULL;
+		p->usc = nla_data(tb[TCA_HFSC_USC]);
+		if (p->usc->m1 == 0 && p->usc->m2 == 0)
+			p->usc = NULL;
 	}
 
-	if (cl != NULL) {
-		if (parentid) {
-			if (cl->cl_parent &&
-			    cl->cl_parent->cl_common.classid != parentid)
-				return -EINVAL;
-			if (cl->cl_parent == NULL && parentid != TC_H_ROOT)
-				return -EINVAL;
-		}
-		cur_time = psched_get_time();
+	if (tca[TCA_RATE]) {
+		memcpy(&p->est, nla_data(tca[TCA_RATE]), sizeof(p->est));
+		p->flags |= FLAG_HAVE_EST;
+	}
+	if (!cl) {
+		err = -EINVAL;
+		if (!p->rsc && !p->fsc)
+			goto err_free_config;
+		cl = kzalloc(sizeof(struct hfsc_class), GFP_KERNEL);
+		err = -ENOBUFS;
+		if (!cl)
+			goto err_free_config;
+		if (p->rsc)
+			hfsc_change_rsc(cl, p->rsc, 0);
+		if (p->fsc)
+			hfsc_change_fsc(cl, p->fsc);
+		if (p->usc)
+			hfsc_change_usc(cl, p->usc, 0);
+
+		cl->cl_common.classid = classid;
+		cl->refcnt = 1;
+		cl->sched = q;
+		cl->cl_parent = parent;
+		cl->qdisc = qdisc_create_dflt(qdisc_dev(sch), sch->dev_queue,
+					      &pfifo_qdisc_ops, classid);
+		err = -ENOMEM;
+		if (!cl->qdisc)
+			goto err_free_class;
+
+		INIT_LIST_HEAD(&cl->children);
+		cl->vt_tree = RB_ROOT;
+		cl->cf_tree = RB_ROOT;
+
+		p->new_class = cl;
+	}
+
+	*arg = (unsigned long) cl;
+	q->pending_config = p;
+
+	return 0;
+
+err_free_class:
+	kfree(cl);
+
+err_free_config:
+	kfree(p);
+	return err;
+}
+
+static void hfsc_commit_change_class(struct Qdisc *sch, u32 classid,
+				     u32 parentid, struct nlattr **tca,
+				     unsigned long arg)
+{
+	struct hfsc_class *cl = (struct hfsc_class *) arg;
+	struct hfsc_sched *q = qdisc_priv(sch);
+	struct hfsc_pending_config *p;
+
+	BUG_ON(!q->pending_config);
+	p = q->pending_config;
+	q->pending_config = NULL;
+
+	if (!p->new_class) {
+		u64 cur_time = psched_get_time();
 
 		sch_tree_lock(sch);
-		if (rsc != NULL)
-			hfsc_change_rsc(cl, rsc, cur_time);
-		if (fsc != NULL)
-			hfsc_change_fsc(cl, fsc);
-		if (usc != NULL)
-			hfsc_change_usc(cl, usc, cur_time);
+		if (p->rsc)
+			hfsc_change_rsc(cl, p->rsc, cur_time);
+		if (p->fsc)
+			hfsc_change_fsc(cl, p->fsc);
+		if (p->usc)
+			hfsc_change_usc(cl, p->usc, cur_time);
 
 		if (cl->qdisc->q.qlen != 0) {
 			if (cl->cl_flags & HFSC_RSC)
@@ -1043,70 +1134,53 @@ hfsc_change_class(struct Qdisc *sch, u32 classid, u32 parentid,
 		}
 		sch_tree_unlock(sch);
 
-		if (tca[TCA_RATE])
-			gen_replace_estimator(&cl->bstats, &cl->rate_est,
-					      &sch->dev_queue->lock,
-					      tca[TCA_RATE]);
-		return 0;
-	}
+		if (p->flags & FLAG_HAVE_EST)
+			__gen_replace_estimator(&cl->bstats, &cl->rate_est,
+						&sch->dev_queue->lock,
+						&p->est);
+	} else {
+		sch_tree_lock(sch);
+		qdisc_class_hash_insert(&q->clhash, &cl->cl_common);
+		list_add_tail(&cl->siblings, &cl->cl_parent->children);
+		if (cl->cl_parent->level == 0)
+			hfsc_purge_queue(sch, cl->cl_parent);
+		hfsc_adjust_levels(cl->cl_parent);
+		cl->cl_pcvtoff = cl->cl_parent->cl_cvtoff;
+		sch_tree_unlock(sch);
 
-	if (parentid == TC_H_ROOT)
-		return -EEXIST;
+		qdisc_class_hash_grow(sch, &q->clhash);
 
-	parent = &q->root;
-	if (parentid) {
-		parent = hfsc_find_class(parentid, sch);
-		if (parent == NULL)
-			return -ENOENT;
+		if (p->flags & FLAG_HAVE_EST)
+			__gen_new_estimator(&cl->bstats, &cl->rate_est,
+					    &sch->dev_queue->lock, &p->est);
 	}
 
-	if (classid == 0 || TC_H_MAJ(classid ^ sch->handle) != 0)
-		return -EINVAL;
-	if (hfsc_find_class(classid, sch))
-		return -EEXIST;
+	kfree(p);
+}
 
-	if (rsc == NULL && fsc == NULL)
-		return -EINVAL;
+static void hfsc_cancel_change_class(struct Qdisc *sch, u32 classid,
+				     u32 parentid, struct nlattr **tca,
+				     unsigned long arg)
+{
+	struct hfsc_sched *q = qdisc_priv(sch);
+	struct hfsc_pending_config *p;
 
-	cl = kzalloc(sizeof(struct hfsc_class), GFP_KERNEL);
-	if (cl == NULL)
-		return -ENOBUFS;
-
-	if (rsc != NULL)
-		hfsc_change_rsc(cl, rsc, 0);
-	if (fsc != NULL)
-		hfsc_change_fsc(cl, fsc);
-	if (usc != NULL)
-		hfsc_change_usc(cl, usc, 0);
-
-	cl->cl_common.classid = classid;
-	cl->refcnt    = 1;
-	cl->sched     = q;
-	cl->cl_parent = parent;
-	cl->qdisc = qdisc_create_dflt(qdisc_dev(sch), sch->dev_queue,
-				      &pfifo_qdisc_ops, classid);
-	if (cl->qdisc == NULL)
-		cl->qdisc = &noop_qdisc;
-	INIT_LIST_HEAD(&cl->children);
-	cl->vt_tree = RB_ROOT;
-	cl->cf_tree = RB_ROOT;
+	BUG_ON(!q->pending_config);
+	p = q->pending_config;
+	q->pending_config = NULL;
 
-	sch_tree_lock(sch);
-	qdisc_class_hash_insert(&q->clhash, &cl->cl_common);
-	list_add_tail(&cl->siblings, &parent->children);
-	if (parent->level == 0)
-		hfsc_purge_queue(sch, parent);
-	hfsc_adjust_levels(parent);
-	cl->cl_pcvtoff = parent->cl_cvtoff;
-	sch_tree_unlock(sch);
+	if (p->new_class) {
+		struct hfsc_class *cl = p->new_class;
 
-	qdisc_class_hash_grow(sch, &q->clhash);
+		p->new_class = NULL;
 
-	if (tca[TCA_RATE])
-		gen_new_estimator(&cl->bstats, &cl->rate_est,
-				  &sch->dev_queue->lock, tca[TCA_RATE]);
-	*arg = (unsigned long)cl;
-	return 0;
+		if (cl->qdisc)
+			qdisc_destroy(cl->qdisc);
+
+		kfree(cl);
+	}
+
+	kfree(p);
 }
 
 static void
@@ -1737,7 +1811,9 @@ hfsc_drop(struct Qdisc *sch)
 }
 
 static const struct Qdisc_class_ops hfsc_class_ops = {
-	.change		= hfsc_change_class,
+	.validate_change= hfsc_validate_change_class,
+	.commit_change	= hfsc_commit_change_class,
+	.cancel_change	= hfsc_cancel_change_class,
 	.validate_delete= hfsc_validate_delete_class,
 	.commit_delete	= hfsc_commit_delete_class,
 	.prepare_graft	= hfsc_prepare_graft,
diff --git a/net/sched/sch_htb.c b/net/sched/sch_htb.c
index a9cf7bb..315493b 100644
--- a/net/sched/sch_htb.c
+++ b/net/sched/sch_htb.c
@@ -169,6 +169,8 @@ struct htb_sched {
 	int direct_qlen;	/* max qlen of above */
 
 	long direct_pkts;
+
+	void *pending_config;
 };
 
 /* find class in global hash table using given handle */
@@ -1326,74 +1328,73 @@ static void htb_put(struct Qdisc *sch, unsigned long arg)
 		htb_destroy_class(sch, cl);
 }
 
-static int htb_change_class(struct Qdisc *sch, u32 classid,
-			    u32 parentid, struct nlattr **tca,
-			    unsigned long *arg)
+struct htb_pending_config {
+	struct tc_htb_opt	hopt;
+	struct qdisc_rate_table	*rtab;
+	struct qdisc_rate_table	*ctab;
+	struct htb_class	*new_class;
+	struct gnet_estimator	est;
+	unsigned int		flags;
+#define FLAG_HAVE_EST		0x00000001
+};
+
+static int htb_validate_change_class(struct Qdisc *sch, u32 classid,
+				     u32 parentid, struct nlattr **tca,
+				     unsigned long *arg)
 {
-	int err = -EINVAL;
+	struct htb_class *cl = (struct htb_class *) *arg, *parent;
 	struct htb_sched *q = qdisc_priv(sch);
-	struct htb_class *cl = (struct htb_class *)*arg, *parent;
 	struct nlattr *opt = tca[TCA_OPTIONS];
-	struct qdisc_rate_table *rtab = NULL, *ctab = NULL;
+	struct qdisc_rate_table *rtab, *ctab;
 	struct nlattr *tb[TCA_HTB_RTAB + 1];
+	struct htb_pending_config *p;
 	struct tc_htb_opt *hopt;
+	int err;
 
-	/* extract all subattrs from opt attr */
 	if (!opt)
-		goto failure;
-
+		return -EINVAL;
 	err = nla_parse_nested(tb, TCA_HTB_RTAB, opt, htb_policy);
 	if (err < 0)
-		goto failure;
-
-	err = -EINVAL;
-	if (tb[TCA_HTB_PARMS] == NULL)
-		goto failure;
+		return err;
+	if (!tb[TCA_HTB_PARMS])
+		return -EINVAL;
 
 	parent = parentid == TC_H_ROOT ? NULL : htb_find(parentid, sch);
-
 	hopt = nla_data(tb[TCA_HTB_PARMS]);
 
 	rtab = qdisc_get_rtab(&hopt->rate, tb[TCA_HTB_RTAB]);
+	if (!rtab)
+		return -EINVAL;
+	err = -EINVAL;
 	ctab = qdisc_get_rtab(&hopt->ceil, tb[TCA_HTB_CTAB]);
-	if (!rtab || !ctab)
-		goto failure;
+	if (!ctab)
+		goto err_put_rtab;
+
+	p = kzalloc(sizeof(*p), GFP_KERNEL);
+	err = -ENOMEM;
+	if (!p)
+		goto err_put_ctab;
+
+	memcpy(&p->hopt, hopt, sizeof(p->hopt));
+	p->rtab = rtab;
+	p->ctab = ctab;
 
-	if (!cl) {		/* new class */
+	if (!cl) {
 		struct Qdisc *new_q;
 		int prio;
-		struct {
-			struct nlattr		nla;
-			struct gnet_estimator	opt;
-		} est = {
-			.nla = {
-				.nla_len	= nla_attr_size(sizeof(est.opt)),
-				.nla_type	= TCA_RATE,
-			},
-			.opt = {
-				/* 4s interval, 16s averaging constant */
-				.interval	= 2,
-				.ewma_log	= 2,
-			},
-		};
-
-		/* check for valid classid */
+
+		err = -EINVAL;
 		if (!classid || TC_H_MAJ(classid ^ sch->handle)
 		    || htb_find(classid, sch))
-			goto failure;
+			goto err_free_config;
+		if (parent && parent->parent && parent->parent->level < 2)
+			goto err_free_config;
 
-		/* check maximal depth */
-		if (parent && parent->parent && parent->parent->level < 2) {
-			printk(KERN_ERR "htb: tree is too deep\n");
-			goto failure;
-		}
 		err = -ENOBUFS;
-		if ((cl = kzalloc(sizeof(*cl), GFP_KERNEL)) == NULL)
-			goto failure;
+		cl = kzalloc(sizeof(*cl), GFP_KERNEL);
+		if (!cl)
+			goto err_free_config;
 
-		gen_new_estimator(&cl->bstats, &cl->rate_est,
-				  &sch->dev_queue->lock,
-				  tca[TCA_RATE] ? : &est.nla);
 		cl->refcnt = 1;
 		cl->children = 0;
 		INIT_LIST_HEAD(&cl->un.leaf.drop_list);
@@ -1402,12 +1403,81 @@ static int htb_change_class(struct Qdisc *sch, u32 classid,
 		for (prio = 0; prio < TC_HTB_NUMPRIO; prio++)
 			RB_CLEAR_NODE(&cl->node[prio]);
 
-		/* create leaf qdisc early because it uses kmalloc(GFP_KERNEL)
-		   so that can't be used inside of sch_tree_lock
-		   -- thanks to Karlis Peisenieks */
 		new_q = qdisc_create_dflt(qdisc_dev(sch), sch->dev_queue,
 					  &pfifo_qdisc_ops, classid);
-		sch_tree_lock(sch);
+		err = -ENOMEM;
+		if (!new_q)
+			goto err_free_class;
+
+		cl->un.leaf.q = new_q;
+
+		if (tca[TCA_RATE]) {
+			memcpy(&p->est, nla_data(tca[TCA_RATE]),
+			       sizeof(p->est));
+		} else {
+			/* default: 4s interval, 16s averaging constant */
+			p->est.interval = 2;
+			p->est.ewma_log = 2;
+		}
+
+		__gen_new_estimator(&cl->bstats, &cl->rate_est,
+				    &sch->dev_queue->lock, &p->est);
+
+		cl->common.classid = classid;
+		cl->parent = parent;
+
+		/* set class to be in HTB_CAN_SEND state */
+		cl->tokens = hopt->buffer;
+		cl->ctokens = hopt->cbuffer;
+		cl->mbuffer = 60 * PSCHED_TICKS_PER_SEC;	/* 1min */
+		cl->t_c = psched_get_time();
+		cl->cmode = HTB_CAN_SEND;
+
+		p->new_class = cl;
+	} else {
+		if (tca[TCA_RATE]) {
+			memcpy(&p->est, nla_data(tca[TCA_RATE]),
+			       sizeof(p->est));
+			p->flags |= FLAG_HAVE_EST;
+		}
+	}
+
+	q->pending_config = p;
+	*arg = (unsigned long) cl;
+
+	return 0;
+
+err_free_class:
+	kfree(cl);
+
+err_free_config:
+	kfree(p);
+
+err_put_ctab:
+	qdisc_put_rtab(ctab);
+
+err_put_rtab:
+	qdisc_put_rtab(rtab);
+	return err;
+}
+
+static void htb_commit_change_class(struct Qdisc *sch, u32 classid,
+				    u32 parentid, struct nlattr **tca,
+				    unsigned long arg)
+{
+	struct htb_class *cl = (struct htb_class *) arg;
+	struct htb_sched *q = qdisc_priv(sch);
+	struct htb_pending_config *p;
+
+	BUG_ON(!q->pending_config);
+	p = q->pending_config;
+	q->pending_config = NULL;
+
+	sch_tree_lock(sch);
+	if (p->new_class) {
+		struct htb_class *parent;
+
+		parent = cl->parent;
 		if (parent && !parent->level) {
 			unsigned int qlen = parent->un.leaf.q->q.qlen;
 
@@ -1427,50 +1497,38 @@ static int htb_change_class(struct Qdisc *sch, u32 classid,
 					 : TC_HTB_MAXDEPTH) - 1;
 			memset(&parent->un.inner, 0, sizeof(parent->un.inner));
 		}
-		/* leaf (we) needs elementary qdisc */
-		cl->un.leaf.q = new_q ? new_q : &noop_qdisc;
-
-		cl->common.classid = classid;
-		cl->parent = parent;
-
-		/* set class to be in HTB_CAN_SEND state */
-		cl->tokens = hopt->buffer;
-		cl->ctokens = hopt->cbuffer;
-		cl->mbuffer = 60 * PSCHED_TICKS_PER_SEC;	/* 1min */
-		cl->t_c = psched_get_time();
-		cl->cmode = HTB_CAN_SEND;
 
 		/* attach to the hash list and parent's family */
 		qdisc_class_hash_insert(&q->clhash, &cl->common);
 		if (parent)
 			parent->children++;
 	} else {
-		if (tca[TCA_RATE])
-			gen_replace_estimator(&cl->bstats, &cl->rate_est,
-					      &sch->dev_queue->lock,
-					      tca[TCA_RATE]);
-		sch_tree_lock(sch);
+		if (p->flags & FLAG_HAVE_EST)
+			__gen_replace_estimator(&cl->bstats, &cl->rate_est,
+						&sch->dev_queue->lock,
+						&p->est);
+
 	}
 
 	/* it used to be a nasty bug here, we have to check that node
 	   is really leaf before changing cl->un.leaf ! */
 	if (!cl->level) {
-		cl->un.leaf.quantum = rtab->rate.rate / q->rate2quantum;
-		if (!hopt->quantum && cl->un.leaf.quantum < 1000) {
+		cl->un.leaf.quantum = p->rtab->rate.rate / q->rate2quantum;
+		if (!p->hopt.quantum && cl->un.leaf.quantum < 1000) {
 			printk(KERN_WARNING
 			       "HTB: quantum of class %X is small. Consider r2q change.\n",
 			       cl->common.classid);
 			cl->un.leaf.quantum = 1000;
 		}
-		if (!hopt->quantum && cl->un.leaf.quantum > 200000) {
+		if (!p->hopt.quantum && cl->un.leaf.quantum > 200000) {
 			printk(KERN_WARNING
 			       "HTB: quantum of class %X is big. Consider r2q change.\n",
 			       cl->common.classid);
 			cl->un.leaf.quantum = 200000;
 		}
-		if (hopt->quantum)
-			cl->un.leaf.quantum = hopt->quantum;
-		if ((cl->un.leaf.prio = hopt->prio) >= TC_HTB_NUMPRIO)
+		if (p->hopt.quantum)
+			cl->un.leaf.quantum = p->hopt.quantum;
+		if ((cl->un.leaf.prio = p->hopt.prio) >= TC_HTB_NUMPRIO)
 			cl->un.leaf.prio = TC_HTB_NUMPRIO - 1;
 
 		/* backup for htb_parent_to_leaf */
@@ -1478,27 +1536,46 @@ static int htb_change_class(struct Qdisc *sch, u32 classid,
 		cl->prio = cl->un.leaf.prio;
 	}
 
-	cl->buffer = hopt->buffer;
-	cl->cbuffer = hopt->cbuffer;
+	cl->buffer = p->hopt.buffer;
+	cl->cbuffer = p->hopt.cbuffer;
 	if (cl->rate)
 		qdisc_put_rtab(cl->rate);
-	cl->rate = rtab;
+	cl->rate = p->rtab;
 	if (cl->ceil)
 		qdisc_put_rtab(cl->ceil);
-	cl->ceil = ctab;
+	cl->ceil = p->ctab;
 	sch_tree_unlock(sch);
 
 	qdisc_class_hash_grow(sch, &q->clhash);
 
-	*arg = (unsigned long)cl;
-	return 0;
+	kfree(p);
+}
 
-failure:
-	if (rtab)
-		qdisc_put_rtab(rtab);
-	if (ctab)
-		qdisc_put_rtab(ctab);
-	return err;
+static void htb_cancel_change_class(struct Qdisc *sch, u32 classid,
+				    u32 parentid, struct nlattr **tca,
+				    unsigned long arg)
+{
+	struct htb_sched *q = qdisc_priv(sch);
+	struct htb_pending_config *p;
+
+	BUG_ON(!q->pending_config);
+	p = q->pending_config;
+	q->pending_config = NULL;
+
+	if (p->new_class) {
+		struct htb_class *cl = p->new_class;
+
+		if (cl->un.leaf.q)
+			qdisc_destroy(cl->un.leaf.q);
+
+		p->new_class = NULL;
+		kfree(p->new_class);
+	}
+
+	qdisc_put_rtab(p->ctab);
+	qdisc_put_rtab(p->rtab);
+
+	kfree(p);
 }
 
 static struct tcf_proto **htb_find_tcf(struct Qdisc *sch, unsigned long arg)
@@ -1570,7 +1647,9 @@ static const struct Qdisc_class_ops htb_class_ops = {
 	.qlen_notify	=	htb_qlen_notify,
 	.get		=	htb_get,
 	.put		=	htb_put,
-	.change		=	htb_change_class,
+	.validate_change=	htb_validate_change_class,
+	.commit_change	=	htb_commit_change_class,
+	.cancel_change	=	htb_cancel_change_class,
 	.validate_delete=	htb_validate_delete,
 	.commit_delete	=	htb_commit_delete,
 	.walk		=	htb_walk,
diff --git a/net/sched/sch_ingress.c b/net/sched/sch_ingress.c
index 1cfb57e..924e1d4 100644
--- a/net/sched/sch_ingress.c
+++ b/net/sched/sch_ingress.c
@@ -48,12 +48,22 @@ static void ingress_put(struct Qdisc *sch, unsigned long cl)
 {
 }
 
-static int ingress_change(struct Qdisc *sch, u32 classid, u32 parent,
-			  struct nlattr **tca, unsigned long *arg)
+static int ingress_validate_change(struct Qdisc *sch, u32 classid, u32 parent,
+				   struct nlattr **tca, unsigned long *arg)
 {
 	return 0;
 }
 
+static void ingress_commit_change(struct Qdisc *sch, u32 classid, u32 parent,
+				  struct nlattr **tca, unsigned long arg)
+{
+}
+
+static void ingress_cancel_change(struct Qdisc *sch, u32 classid, u32 parent,
+				  struct nlattr **tca, unsigned long arg)
+{
+}
+
 static void ingress_walk(struct Qdisc *sch, struct qdisc_walker *walker)
 {
 	return;
@@ -127,7 +137,9 @@ static const struct Qdisc_class_ops ingress_class_ops = {
 	.leaf		=	ingress_leaf,
 	.get		=	ingress_get,
 	.put		=	ingress_put,
-	.change		=	ingress_change,
+	.validate_change=	ingress_validate_change,
+	.commit_change	=	ingress_commit_change,
+	.cancel_change	=	ingress_cancel_change,
 	.walk		=	ingress_walk,
 	.tcf_chain	=	ingress_find_tcf,
 	.bind_tcf	=	ingress_bind_filter,
diff --git a/net/sched/sch_netem.c b/net/sched/sch_netem.c
index ca8bcd4..ca34c7d 100644
--- a/net/sched/sch_netem.c
+++ b/net/sched/sch_netem.c
@@ -702,12 +702,27 @@ static void netem_put(struct Qdisc *sch, unsigned long arg)
 {
 }
 
-static int netem_change_class(struct Qdisc *sch, u32 classid, u32 parentid,
-			    struct nlattr **tca, unsigned long *arg)
+static int netem_validate_change_class(struct Qdisc *sch, u32 classid,
+				       u32 parentid, struct nlattr **tca,
+				       unsigned long *arg)
 {
 	return -ENOSYS;
 }
 
+static void netem_commit_change_class(struct Qdisc *sch, u32 classid,
+				      u32 parentid, struct nlattr **tca,
+				      unsigned long arg)
+{
+	WARN_ON(1);
+}
+
+static void netem_cancel_change_class(struct Qdisc *sch, u32 classid,
+				      u32 parentid, struct nlattr **tca,
+				      unsigned long arg)
+{
+	WARN_ON(1);
+}
+
 static int netem_validate_delete(struct Qdisc *sch, unsigned long arg)
 {
 	return -ENOSYS;
@@ -741,7 +756,9 @@ static const struct Qdisc_class_ops netem_class_ops = {
 	.leaf		=	netem_leaf,
 	.get		=	netem_get,
 	.put		=	netem_put,
-	.change		=	netem_change_class,
+	.validate_change=	netem_validate_change_class,
+	.commit_change	=	netem_commit_change_class,
+	.cancel_change	=	netem_cancel_change_class,
 	.validate_delete=	netem_validate_delete,
 	.commit_delete	=	netem_commit_delete,
 	.walk		=	netem_walk,
diff --git a/net/sched/sch_prio.c b/net/sched/sch_prio.c
index 25e07db..e2bf0e2 100644
--- a/net/sched/sch_prio.c
+++ b/net/sched/sch_prio.c
@@ -394,16 +394,27 @@ static void prio_put(struct Qdisc *q, unsigned long cl)
 	return;
 }
 
-static int prio_change(struct Qdisc *sch, u32 handle, u32 parent, struct nlattr **tca, unsigned long *arg)
+static int prio_validate_tc_change(struct Qdisc *sch, u32 handle, u32 parent,
+				   struct nlattr **tca, unsigned long *arg)
 {
-	unsigned long cl = *arg;
 	struct prio_sched_data *q = qdisc_priv(sch);
+	unsigned long cl = *arg;
 
 	if (cl - 1 > q->bands)
 		return -ENOENT;
 	return 0;
 }
 
+static void prio_commit_tc_change(struct Qdisc *sch, u32 handle, u32 parent,
+				  struct nlattr **tca, unsigned long arg)
+{
+}
+
+static void prio_cancel_tc_change(struct Qdisc *sch, u32 handle, u32 parent,
+				  struct nlattr **tca, unsigned long arg)
+{
+}
+
 static int prio_validate_delete(struct Qdisc *sch, unsigned long cl)
 {
 	struct prio_sched_data *q = qdisc_priv(sch);
@@ -480,7 +491,9 @@ static const struct Qdisc_class_ops prio_class_ops = {
 	.leaf		=	prio_leaf,
 	.get		=	prio_get,
 	.put		=	prio_put,
-	.change		=	prio_change,
+	.validate_change=	prio_validate_tc_change,
+	.commit_change	=	prio_commit_tc_change,
+	.cancel_change	=	prio_cancel_tc_change,
 	.validate_delete=	prio_validate_delete,
 	.commit_delete	=	prio_commit_delete,
 	.walk		=	prio_walk,
diff --git a/net/sched/sch_red.c b/net/sched/sch_red.c
index 5ad4ed0..4804047 100644
--- a/net/sched/sch_red.c
+++ b/net/sched/sch_red.c
@@ -368,12 +368,27 @@ static void red_put(struct Qdisc *sch, unsigned long arg)
 	return;
 }
 
-static int red_change_class(struct Qdisc *sch, u32 classid, u32 parentid,
-			    struct nlattr **tca, unsigned long *arg)
+static int red_validate_change_class(struct Qdisc *sch, u32 classid,
+				     u32 parentid, struct nlattr **tca,
+				     unsigned long *arg)
 {
 	return -ENOSYS;
 }
 
+static void red_commit_change_class(struct Qdisc *sch, u32 classid,
+				    u32 parentid, struct nlattr **tca,
+				    unsigned long arg)
+{
+	WARN_ON(1);
+}
+
+static void red_cancel_change_class(struct Qdisc *sch, u32 classid,
+				    u32 parentid, struct nlattr **tca,
+				    unsigned long arg)
+{
+	WARN_ON(1);
+}
+
 static int red_validate_delete(struct Qdisc *sch, unsigned long cl)
 {
 	return -ENOSYS;
@@ -408,7 +423,9 @@ static const struct Qdisc_class_ops red_class_ops = {
 	.leaf		=	red_leaf,
 	.get		=	red_get,
 	.put		=	red_put,
-	.change		=	red_change_class,
+	.validate_change=	red_validate_change_class,
+	.commit_change	=	red_commit_change_class,
+	.cancel_change	=	red_cancel_change_class,
 	.validate_delete=	red_validate_delete,
 	.commit_delete	=	red_commit_delete,
 	.walk		=	red_walk,
diff --git a/net/sched/sch_sfq.c b/net/sched/sch_sfq.c
index a52ca74..dba7ecf 100644
--- a/net/sched/sch_sfq.c
+++ b/net/sched/sch_sfq.c
@@ -547,12 +547,27 @@ nla_put_failure:
 	return -1;
 }
 
-static int sfq_change_class(struct Qdisc *sch, u32 classid, u32 parentid,
-			    struct nlattr **tca, unsigned long *arg)
+static int sfq_validate_change_class(struct Qdisc *sch, u32 classid,
+				     u32 parentid, struct nlattr **tca,
+				     unsigned long *arg)
 {
 	return -EOPNOTSUPP;
 }
 
+static void sfq_commit_change_class(struct Qdisc *sch, u32 classid,
+				    u32 parentid, struct nlattr **tca,
+				    unsigned long arg)
+{
+	WARN_ON(1);
+}
+
+static void sfq_cancel_change_class(struct Qdisc *sch, u32 classid,
+				    u32 parentid, struct nlattr **tca,
+				    unsigned long arg)
+{
+	WARN_ON(1);
+}
+
 static unsigned long sfq_get(struct Qdisc *sch, u32 classid)
 {
 	return 0;
@@ -611,7 +626,9 @@ static void sfq_walk(struct Qdisc *sch, struct qdisc_walker *arg)
 
 static const struct Qdisc_class_ops sfq_class_ops = {
 	.get		=	sfq_get,
-	.change		=	sfq_change_class,
+	.validate_change=	sfq_validate_change_class,
+	.commit_change	=	sfq_commit_change_class,
+	.cancel_change	=	sfq_cancel_change_class,
 	.tcf_chain	=	sfq_find_tcf,
 	.dump		=	sfq_dump_class,
 	.dump_stats	=	sfq_dump_class_stats,
diff --git a/net/sched/sch_tbf.c b/net/sched/sch_tbf.c
index f9de373..5474f87 100644
--- a/net/sched/sch_tbf.c
+++ b/net/sched/sch_tbf.c
@@ -511,12 +511,27 @@ static void tbf_put(struct Qdisc *sch, unsigned long arg)
 {
 }
 
-static int tbf_change_class(struct Qdisc *sch, u32 classid, u32 parentid,
-			    struct nlattr **tca, unsigned long *arg)
+static int tbf_validate_change_class(struct Qdisc *sch, u32 classid,
+				     u32 parentid, struct nlattr **tca,
+				     unsigned long *arg)
 {
 	return -ENOSYS;
 }
 
+static void tbf_commit_change_class(struct Qdisc *sch, u32 classid,
+				    u32 parentid, struct nlattr **tca,
+				    unsigned long arg)
+{
+	WARN_ON(1);
+}
+
+static void tbf_cancel_change_class(struct Qdisc *sch, u32 classid,
+				    u32 parentid, struct nlattr **tca,
+				    unsigned long arg)
+{
+	WARN_ON(1);
+}
+
 static int tbf_validate_delete(struct Qdisc *sch, unsigned long arg)
 {
 	return -ENOSYS;
@@ -552,7 +567,9 @@ static const struct Qdisc_class_ops tbf_class_ops =
 	.leaf		=	tbf_leaf,
 	.get		=	tbf_get,
 	.put		=	tbf_put,
-	.change		=	tbf_change_class,
+	.validate_change=	tbf_validate_change_class,
+	.commit_change	=	tbf_commit_change_class,
+	.cancel_change	=	tbf_cancel_change_class,
 	.validate_delete=	tbf_validate_delete,
 	.commit_delete	=	tbf_commit_delete,
 	.walk		=	tbf_walk,
-- 
1.5.6.2.255.gbed62

--
To unsubscribe from this list: send the line "unsubscribe netdev" in
the body of a message to majordomo@...r.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ