genetlink: properly support per-op policy dumping
[sfrench/cifs-2.6.git] / net / netlink / genetlink.c
index 5e33c79384703f52252607eabf95bd00c27a775f..eb916c44884f18dc8edb42928703f868fdd3dd31 100644 (file)
@@ -1112,7 +1112,10 @@ static int genl_ctrl_event(int event, const struct genl_family *family,
 
 struct ctrl_dump_policy_ctx {
        struct netlink_policy_dump_state *state;
+       const struct genl_family *rt;
+       unsigned int opidx;
        u16 fam_id;
+       u8 policies:1;
 };
 
 static const struct nla_policy ctrl_policy_policy[] = {
@@ -1127,6 +1130,8 @@ static int ctrl_dumppolicy_start(struct netlink_callback *cb)
        struct ctrl_dump_policy_ctx *ctx = (void *)cb->ctx;
        struct nlattr **tb = info->attrs;
        const struct genl_family *rt;
+       struct genl_ops op;
+       int err, i;
 
        BUILD_BUG_ON(sizeof(*ctx) > sizeof(cb->ctx));
 
@@ -1147,11 +1152,23 @@ static int ctrl_dumppolicy_start(struct netlink_callback *cb)
        if (!rt)
                return -ENOENT;
 
-       if (!rt->policy)
-               return -ENODATA;
+       ctx->rt = rt;
+
+       for (i = 0; i < genl_get_cmd_cnt(rt); i++) {
+               genl_get_cmd_by_index(i, rt, &op);
+
+               if (op.policy) {
+                       err = netlink_policy_dump_add_policy(&ctx->state,
+                                                            op.policy,
+                                                            op.maxattr);
+                       if (err)
+                               return err;
+               }
+       }
 
-       return netlink_policy_dump_add_policy(&ctx->state, rt->policy,
-                                             rt->maxattr);
+       if (!ctx->state)
+               return -ENODATA;
+       return 0;
 }
 
 static void *ctrl_dumppolicy_prep(struct sk_buff *skb,
@@ -1172,12 +1189,78 @@ static void *ctrl_dumppolicy_prep(struct sk_buff *skb,
        return hdr;
 }
 
+static int ctrl_dumppolicy_put_op(struct sk_buff *skb,
+                                 struct netlink_callback *cb,
+                                 struct genl_ops *op)
+{
+       struct ctrl_dump_policy_ctx *ctx = (void *)cb->ctx;
+       struct nlattr *nest_pol, *nest_op;
+       void *hdr;
+       int idx;
+
+       /* skip if we have nothing to show */
+       if (!op->policy)
+               return 0;
+       if (!op->doit &&
+           (!op->dumpit || op->validate & GENL_DONT_VALIDATE_DUMP))
+               return 0;
+
+       hdr = ctrl_dumppolicy_prep(skb, cb);
+       if (!hdr)
+               return -ENOBUFS;
+
+       nest_pol = nla_nest_start(skb, CTRL_ATTR_OP_POLICY);
+       if (!nest_pol)
+               goto err;
+
+       nest_op = nla_nest_start(skb, op->cmd);
+       if (!nest_op)
+               goto err;
+
+       /* for now both do/dump are always the same */
+       idx = netlink_policy_dump_get_policy_idx(ctx->state,
+                                                op->policy,
+                                                op->maxattr);
+
+       if (op->doit && nla_put_u32(skb, CTRL_ATTR_POLICY_DO, idx))
+               goto err;
+
+       if (op->dumpit && !(op->validate & GENL_DONT_VALIDATE_DUMP) &&
+           nla_put_u32(skb, CTRL_ATTR_POLICY_DUMP, idx))
+               goto err;
+
+       nla_nest_end(skb, nest_op);
+       nla_nest_end(skb, nest_pol);
+       genlmsg_end(skb, hdr);
+
+       return 0;
+err:
+       genlmsg_cancel(skb, hdr);
+       return -ENOBUFS;
+}
+
 static int ctrl_dumppolicy(struct sk_buff *skb, struct netlink_callback *cb)
 {
        struct ctrl_dump_policy_ctx *ctx = (void *)cb->ctx;
+       void *hdr;
+
+       if (!ctx->policies) {
+               while (ctx->opidx < genl_get_cmd_cnt(ctx->rt)) {
+                       struct genl_ops op;
+
+                       genl_get_cmd_by_index(ctx->opidx, ctx->rt, &op);
+
+                       if (ctrl_dumppolicy_put_op(skb, cb, &op))
+                               return skb->len;
+
+                       ctx->opidx++;
+               }
+
+               /* completed with the per-op policy index list */
+               ctx->policies = true;
+       }
 
        while (netlink_policy_dump_loop(ctx->state)) {
-               void *hdr;
                struct nlattr *nest;
 
                hdr = ctrl_dumppolicy_prep(skb, cb);
@@ -1194,14 +1277,13 @@ static int ctrl_dumppolicy(struct sk_buff *skb, struct netlink_callback *cb)
                nla_nest_end(skb, nest);
 
                genlmsg_end(skb, hdr);
-               continue;
-
-nla_put_failure:
-               genlmsg_cancel(skb, hdr);
-               break;
        }
 
        return skb->len;
+
+nla_put_failure:
+       genlmsg_cancel(skb, hdr);
+       return skb->len;
 }
 
 static int ctrl_dumppolicy_done(struct netlink_callback *cb)