Skip to content

Commit 3d7c825

Browse files
edumazetdavem330
authored andcommitted
net_sched: prio: insure proper transactional behavior
Now prio_init() can return -ENOMEM, it also has to make sure any allocated qdiscs are freed, since the caller (qdisc_create()) wont call ->destroy() handler for us. More generally, we want a transactional behavior for "tc qdisc change ...", so prio_tune() should not make modifications if any error is returned. It means that we must validate parameters and allocate missing qdisc(s) before taking root qdisc lock exactly once, to not leave the prio qdisc in an intermediate state. Fixes: cbdf451 ("net_sched: prio: properly report out of memory errors") Signed-off-by: Eric Dumazet <[email protected]> Reported-by: Cong Wang <[email protected]> Acked-by: Cong Wang <[email protected]> Signed-off-by: David S. Miller <[email protected]>
1 parent 0c5ddb5 commit 3d7c825

File tree

1 file changed

+23
-34
lines changed

1 file changed

+23
-34
lines changed

net/sched/sch_prio.c

Lines changed: 23 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -172,8 +172,9 @@ prio_destroy(struct Qdisc *sch)
172172
static int prio_tune(struct Qdisc *sch, struct nlattr *opt)
173173
{
174174
struct prio_sched_data *q = qdisc_priv(sch);
175+
struct Qdisc *queues[TCQ_PRIO_BANDS];
176+
int oldbands = q->bands, i;
175177
struct tc_prio_qopt *qopt;
176-
int i;
177178

178179
if (nla_len(opt) < sizeof(*qopt))
179180
return -EINVAL;
@@ -187,54 +188,42 @@ static int prio_tune(struct Qdisc *sch, struct nlattr *opt)
187188
return -EINVAL;
188189
}
189190

191+
/* Before commit, make sure we can allocate all new qdiscs */
192+
for (i = oldbands; i < qopt->bands; i++) {
193+
queues[i] = qdisc_create_dflt(sch->dev_queue, &pfifo_qdisc_ops,
194+
TC_H_MAKE(sch->handle, i + 1));
195+
if (!queues[i]) {
196+
while (i > oldbands)
197+
qdisc_destroy(queues[--i]);
198+
return -ENOMEM;
199+
}
200+
}
201+
190202
sch_tree_lock(sch);
191203
q->bands = qopt->bands;
192204
memcpy(q->prio2band, qopt->priomap, TC_PRIO_MAX+1);
193205

194-
for (i = q->bands; i < TCQ_PRIO_BANDS; i++) {
206+
for (i = q->bands; i < oldbands; i++) {
195207
struct Qdisc *child = q->queues[i];
196-
q->queues[i] = &noop_qdisc;
197-
if (child != &noop_qdisc) {
198-
qdisc_tree_reduce_backlog(child, child->q.qlen, child->qstats.backlog);
199-
qdisc_destroy(child);
200-
}
201-
}
202-
sch_tree_unlock(sch);
203208

204-
for (i = 0; i < q->bands; i++) {
205-
struct Qdisc *child;
209+
qdisc_tree_reduce_backlog(child, child->q.qlen,
210+
child->qstats.backlog);
211+
qdisc_destroy(child);
212+
}
206213

207-
if (q->queues[i] != &noop_qdisc)
208-
continue;
214+
for (i = oldbands; i < q->bands; i++)
215+
q->queues[i] = queues[i];
209216

210-
child = qdisc_create_dflt(sch->dev_queue, &pfifo_qdisc_ops,
211-
TC_H_MAKE(sch->handle, i + 1));
212-
if (!child)
213-
return -ENOMEM;
214-
sch_tree_lock(sch);
215-
q->queues[i] = child;
216-
sch_tree_unlock(sch);
217-
}
217+
sch_tree_unlock(sch);
218218
return 0;
219219
}
220220

221221
static int prio_init(struct Qdisc *sch, struct nlattr *opt)
222222
{
223-
struct prio_sched_data *q = qdisc_priv(sch);
224-
int i;
225-
226-
for (i = 0; i < TCQ_PRIO_BANDS; i++)
227-
q->queues[i] = &noop_qdisc;
228-
229-
if (opt == NULL) {
223+
if (!opt)
230224
return -EINVAL;
231-
} else {
232-
int err;
233225

234-
if ((err = prio_tune(sch, opt)) != 0)
235-
return err;
236-
}
237-
return 0;
226+
return prio_tune(sch, opt);
238227
}
239228

240229
static int prio_dump(struct Qdisc *sch, struct sk_buff *skb)

0 commit comments

Comments
 (0)