backports: fully add netlink extack for generic netlink
[openwrt/staging/blogic.git] / backport / compat / backport-4.12.c
1 /*
2 * Copyright 2017 Intel Deutschland GmbH
3 */
4 #include <net/genetlink.h>
5 #include <net/sock.h>
6
7 enum nlmsgerr_attrs {
8 NLMSGERR_ATTR_UNUSED,
9 NLMSGERR_ATTR_MSG,
10 NLMSGERR_ATTR_OFFS,
11 NLMSGERR_ATTR_COOKIE,
12 __NLMSGERR_ATTR_MAX,
13 NLMSGERR_ATTR_MAX = __NLMSGERR_ATTR_MAX - 1
14 };
15
16 #define NLM_F_CAPPED 0x100 /* request was capped */
17 #define NLM_F_ACK_TLVS 0x200 /* extended ACK TVLs were included */
18
19 struct bp_extack_genl_family {
20 struct genl_family family;
21 struct genl_family *real_family;
22
23 struct genl_ops ops[];
24 };
25
26 static const struct nla_policy extack_dummy_policy[1] = {};
27
28 static struct bp_extack_genl_family *get_copy(const struct genl_ops *op)
29 {
30 do {
31 op--;
32 } while (op->policy != extack_dummy_policy);
33
34 return container_of(op, struct bp_extack_genl_family, ops[0]);
35 }
36
37 static int extack_pre_doit(const struct genl_ops *ops,
38 struct sk_buff *skb,
39 struct genl_info *info)
40 {
41 struct netlink_ext_ack *extack = kzalloc(sizeof(*extack), GFP_KERNEL);
42 struct bp_extack_genl_family *copy = get_copy(ops);
43 const struct genl_ops *real_ops;
44 int err;
45
46 info->userhdr = extack;
47
48 if (!extack) {
49 info->userhdr = ERR_PTR(-ENOMEM);
50 return -ENOMEM;
51 }
52
53 real_ops = &copy->real_family->ops[ops - &copy->ops[1]];
54 extack->__bp_genl_real_ops = real_ops;
55
56 if (copy->real_family->pre_doit)
57 err = copy->real_family->pre_doit(real_ops, skb, info);
58 else
59 err = 0;
60
61 if (err) {
62 info->userhdr = ERR_PTR(err);
63 kfree(extack);
64 }
65
66 return err;
67 }
68
69 static void extack_netlink_ack(struct sk_buff *in_skb, struct nlmsghdr *nlh,
70 int err, const struct netlink_ext_ack *extack)
71 {
72 struct sk_buff *skb;
73 struct nlmsghdr *rep;
74 struct nlmsgerr *errmsg;
75 size_t payload = sizeof(*errmsg);
76 size_t tlvlen = 0;
77 unsigned int flags = 0;
78 /* backports assumes everyone supports this - libnl does so it's true */
79 bool nlk_has_extack = true;
80
81 /* Error messages get the original request appened, unless the user
82 * requests to cap the error message, and get extra error data if
83 * requested.
84 * (ignored in backports)
85 */
86 if (err) {
87 if (1)
88 payload += nlmsg_len(nlh);
89 else
90 flags |= NLM_F_CAPPED;
91 if (nlk_has_extack && extack) {
92 if (extack->_msg)
93 tlvlen += nla_total_size(strlen(extack->_msg) + 1);
94 if (extack->bad_attr)
95 tlvlen += nla_total_size(sizeof(u32));
96 }
97 } else {
98 flags |= NLM_F_CAPPED;
99
100 if (nlk_has_extack && extack && extack->cookie_len)
101 tlvlen += nla_total_size(extack->cookie_len);
102 }
103
104 if (tlvlen)
105 flags |= NLM_F_ACK_TLVS;
106
107 skb = nlmsg_new(payload + tlvlen, GFP_KERNEL);
108 if (!skb) {
109 NETLINK_CB(in_skb).sk->sk_err = ENOBUFS;
110 NETLINK_CB(in_skb).sk->sk_error_report(NETLINK_CB(in_skb).sk);
111 return;
112 }
113
114 rep = __nlmsg_put(skb, NETLINK_CB(in_skb).portid, nlh->nlmsg_seq,
115 NLMSG_ERROR, payload, flags);
116 errmsg = nlmsg_data(rep);
117 errmsg->error = err;
118 memcpy(&errmsg->msg, nlh, payload > sizeof(*errmsg) ? nlh->nlmsg_len : sizeof(*nlh));
119
120 if (nlk_has_extack && extack) {
121 if (err) {
122 if (extack->_msg)
123 WARN_ON(nla_put_string(skb, NLMSGERR_ATTR_MSG,
124 extack->_msg));
125 if (extack->bad_attr &&
126 !WARN_ON((u8 *)extack->bad_attr < in_skb->data ||
127 (u8 *)extack->bad_attr >= in_skb->data +
128 in_skb->len))
129 WARN_ON(nla_put_u32(skb, NLMSGERR_ATTR_OFFS,
130 (u8 *)extack->bad_attr -
131 in_skb->data));
132 } else {
133 if (extack->cookie_len)
134 WARN_ON(nla_put(skb, NLMSGERR_ATTR_COOKIE,
135 extack->cookie_len,
136 extack->cookie));
137 }
138 }
139
140 nlmsg_end(skb, rep);
141
142 netlink_unicast(in_skb->sk, skb, NETLINK_CB(in_skb).portid, MSG_DONTWAIT);
143 }
144
145 static int extack_doit(struct sk_buff *skb, struct genl_info *info)
146 {
147 const struct genl_ops *real_ops;
148 int err;
149
150 /* older kernels have a bug here */
151 if (IS_ERR(info->userhdr)) {
152 extack_netlink_ack(skb, info->nlhdr,
153 PTR_ERR(info->userhdr),
154 genl_info_extack(info));
155 goto out;
156 }
157
158 real_ops = genl_info_extack(info)->__bp_genl_real_ops;
159 err = real_ops->doit(skb, info);
160
161 if (err == -EINTR)
162 return err;
163
164 if (info->nlhdr->nlmsg_flags & NLM_F_ACK || err)
165 extack_netlink_ack(skb, info->nlhdr, err,
166 genl_info_extack(info));
167
168 out:
169 /* suppress sending ACK from normal netlink code */
170 info->nlhdr->nlmsg_flags &= ~NLM_F_ACK;
171 return 0;
172 }
173
174 static void extack_post_doit(const struct genl_ops *ops,
175 struct sk_buff *skb,
176 struct genl_info *info)
177 {
178 void (*post_doit)(const struct genl_ops *ops,
179 struct sk_buff *skb,
180 struct genl_info *info);
181
182 post_doit = get_copy(ops)->real_family->post_doit;
183
184 if (post_doit)
185 post_doit(ops, skb, info);
186 kfree(info->userhdr);
187 }
188
189 int bp_extack_genl_register_family(struct genl_family *family)
190 {
191 unsigned int size = sizeof(struct bp_extack_genl_family) +
192 sizeof(family->ops[0]) * (family->n_ops + 1);
193 struct bp_extack_genl_family *copy;
194 int i, err;
195
196 copy = kzalloc(size, GFP_KERNEL);
197 if (!copy)
198 return -ENOMEM;
199
200 copy->family = *family;
201 copy->real_family = family;
202 copy->family.ops = &copy->ops[1];
203
204 for (i = 0; i < family->n_ops; i++) {
205 copy->ops[i + 1] = family->ops[i];
206 if (family->ops[i].doit)
207 copy->ops[i + 1].doit = extack_doit;
208 }
209
210 copy->ops[0].policy = extack_dummy_policy;
211
212 copy->family.pre_doit = extack_pre_doit;
213 copy->family.post_doit = extack_post_doit;
214
215 /*
216 * store in attrbuf, so that even if we re-register the family
217 * the data will be overwritten and we don't overwrite data
218 * that's used again later...
219 */
220 family->attrbuf = (void *)copy;
221
222 err = __real_bp_extack_genl_register_family(&copy->family);
223 if (err)
224 kfree(copy);
225 return err;
226 }
227 EXPORT_SYMBOL_GPL(bp_extack_genl_register_family);
228
229 int bp_extack_genl_unregister_family(struct genl_family *family)
230 {
231 struct bp_extack_genl_family *copy = (void *)family->attrbuf;
232 int err;
233
234 if (!copy)
235 return -ENOENT;
236
237 err = __real_bp_extack_genl_unregister_family(&copy->family);
238 WARN_ON(err);
239 kfree(copy);
240
241 return 0;
242 }
243 EXPORT_SYMBOL_GPL(bp_extack_genl_unregister_family);