Release 4.11 net/netlink/genetlink.c
/*
* NETLINK Generic Netlink Family
*
* Authors: Jamal Hadi Salim
* Thomas Graf <tgraf@suug.ch>
* Johannes Berg <johannes@sipsolutions.net>
*/
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/errno.h>
#include <linux/types.h>
#include <linux/socket.h>
#include <linux/string.h>
#include <linux/skbuff.h>
#include <linux/mutex.h>
#include <linux/bitmap.h>
#include <linux/rwsem.h>
#include <linux/idr.h>
#include <net/sock.h>
#include <net/genetlink.h>
static DEFINE_MUTEX(genl_mutex); /* serialization of message processing */
static DECLARE_RWSEM(cb_lock);
atomic_t genl_sk_destructing_cnt = ATOMIC_INIT(0);
DECLARE_WAIT_QUEUE_HEAD(genl_sk_destructing_waitq);
void genl_lock(void)
{
mutex_lock(&genl_mutex);
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Thomas Graf | 11 | 84.62% | 1 | 50.00% |
Ingo Molnar | 2 | 15.38% | 1 | 50.00% |
Total | 13 | 100.00% | 2 | 100.00% |
EXPORT_SYMBOL(genl_lock);
void genl_unlock(void)
{
mutex_unlock(&genl_mutex);
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Thomas Graf | 11 | 84.62% | 1 | 50.00% |
Ingo Molnar | 2 | 15.38% | 1 | 50.00% |
Total | 13 | 100.00% | 2 | 100.00% |
EXPORT_SYMBOL(genl_unlock);
#ifdef CONFIG_LOCKDEP
bool lockdep_genl_is_held(void)
{
return lockdep_is_held(&genl_mutex);
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Pravin B Shelar | 13 | 92.86% | 1 | 50.00% |
Yaowei Bai | 1 | 7.14% | 1 | 50.00% |
Total | 14 | 100.00% | 2 | 100.00% |
EXPORT_SYMBOL(lockdep_genl_is_held);
#endif
static void genl_lock_all(void)
{
down_write(&cb_lock);
genl_lock();
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Pravin B Shelar | 17 | 100.00% | 1 | 100.00% |
Total | 17 | 100.00% | 1 | 100.00% |
static void genl_unlock_all(void)
{
genl_unlock();
up_write(&cb_lock);
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Pravin B Shelar | 17 | 100.00% | 1 | 100.00% |
Total | 17 | 100.00% | 1 | 100.00% |
static DEFINE_IDR(genl_fam_idr);
/*
* Bitmap of multicast groups that are currently in use.
*
* To avoid an allocation at boot of just one unsigned long,
* declare it global instead.
* Bit 0 is marked as already used since group 0 is invalid.
* Bit 1 is marked as already used since the drop-monitor code
* abuses the API and thinks it can statically use group 1.
* That group will typically conflict with other groups that
* any proper users use.
* Bit 16 is marked as used since it's used for generic netlink
* and the code no longer marks pre-reserved IDs as used.
* Bit 17 is marked as already used since the VFS quota code
* also abused this API and relied on family == group ID, we
* cater to that by giving it a static family and group ID.
* Bit 18 is marked as already used since the PMCRAID driver
* did the same thing as the VFS quota code (maybe copied?)
*/
static unsigned long mc_group_start = 0x3 | BIT(GENL_ID_CTRL) |
BIT(GENL_ID_VFS_DQUOT) |
BIT(GENL_ID_PMCRAID);
static unsigned long *mc_groups = &mc_group_start;
static unsigned long mc_groups_longs = 1;
static int genl_ctrl_event(int event, const struct genl_family *family,
const struct genl_multicast_group *grp,
int grp_id);
static const struct genl_family *genl_family_find_byid(unsigned int id)
{
return idr_find(&genl_fam_idr, id);
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Thomas Graf | 16 | 72.73% | 1 | 50.00% |
Johannes Berg | 6 | 27.27% | 1 | 50.00% |
Total | 22 | 100.00% | 2 | 100.00% |
static const struct genl_family *genl_family_find_byname(char *name)
{
const struct genl_family *family;
unsigned int id;
idr_for_each_entry(&genl_fam_idr, family, id)
if (strcmp(family->name, name) == 0)
return family;
return NULL;
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Thomas Graf | 39 | 78.00% | 1 | 50.00% |
Johannes Berg | 11 | 22.00% | 1 | 50.00% |
Total | 50 | 100.00% | 2 | 100.00% |
static const struct genl_ops *genl_get_cmd(u8 cmd,
const struct genl_family *family)
{
int i;
for (i = 0; i < family->n_ops; i++)
if (family->ops[i].cmd == cmd)
return &family->ops[i];
return NULL;
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Johannes Berg | 31 | 50.82% | 3 | 75.00% |
Thomas Graf | 30 | 49.18% | 1 | 25.00% |
Total | 61 | 100.00% | 4 | 100.00% |
static int genl_allocate_reserve_groups(int n_groups, int *first_id)
{
unsigned long *new_groups;
int start = 0;
int i;
int id;
bool fits;
do {
if (start == 0)
id = find_first_zero_bit(mc_groups,
mc_groups_longs *
BITS_PER_LONG);
else
id = find_next_zero_bit(mc_groups,
mc_groups_longs * BITS_PER_LONG,
start);
fits = true;
for (i = id;
i < min_t(int, id + n_groups,
mc_groups_longs * BITS_PER_LONG);
i++) {
if (test_bit(i, mc_groups)) {
start = i;
fits = false;
break;
}
}
if (id + n_groups > mc_groups_longs * BITS_PER_LONG) {
unsigned long new_longs = mc_groups_longs +
BITS_TO_LONGS(n_groups);
size_t nlen = new_longs * sizeof(unsigned long);
if (mc_groups == &mc_group_start) {
new_groups = kzalloc(nlen, GFP_KERNEL);
if (!new_groups)
return -ENOMEM;
mc_groups = new_groups;
*mc_groups = mc_group_start;
} else {
new_groups = krealloc(mc_groups, nlen,
GFP_KERNEL);
if (!new_groups)
return -ENOMEM;
mc_groups = new_groups;
for (i = 0; i < BITS_TO_LONGS(n_groups); i++)
mc_groups[mc_groups_longs + i] = 0;
}
mc_groups_longs = new_longs;
}
} while (!fits);
for (i = id; i < id + n_groups; i++)
set_bit(i, mc_groups);
*first_id = id;
return 0;
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Johannes Berg | 253 | 91.01% | 5 | 50.00% |
Thomas Graf | 16 | 5.76% | 1 | 10.00% |
Jamal Hadi Salim | 4 | 1.44% | 1 | 10.00% |
Brian Haley | 2 | 0.72% | 1 | 10.00% |
Matti Vaittinen | 2 | 0.72% | 1 | 10.00% |
David S. Miller | 1 | 0.36% | 1 | 10.00% |
Total | 278 | 100.00% | 10 | 100.00% |
static struct genl_family genl_ctrl;
static int genl_validate_assign_mc_groups(struct genl_family *family)
{
int first_id;
int n_groups = family->n_mcgrps;
int err = 0, i;
bool groups_allocated = false;
if (!n_groups)
return 0;
for (i = 0; i < n_groups; i++) {
const struct genl_multicast_group *grp = &family->mcgrps[i];
if (WARN_ON(grp->name[0] == '\0'))
return -EINVAL;
if (WARN_ON(memchr(grp->name, '\0', GENL_NAMSIZ) == NULL))
return -EINVAL;
}
/* special-case our own group and hacks */
if (family == &genl_ctrl) {
first_id = GENL_ID_CTRL;
BUG_ON(n_groups != 1);
} else if (strcmp(family->name, "NET_DM") == 0) {
first_id = 1;
BUG_ON(n_groups != 1);
} else if (family->id == GENL_ID_VFS_DQUOT) {
first_id = GENL_ID_VFS_DQUOT;
BUG_ON(n_groups != 1);
} else if (family->id == GENL_ID_PMCRAID) {
first_id = GENL_ID_PMCRAID;
BUG_ON(n_groups != 1);
} else {
groups_allocated = true;
err = genl_allocate_reserve_groups(n_groups, &first_id);
if (err)
return err;
}
family->mcgrp_offset = first_id;
/* if still initializing, can't and don't need to to realloc bitmaps */
if (!init_net.genl_sock)
return 0;
if (family->netnsok) {
struct net *net;
netlink_table_grab();
rcu_read_lock();
for_each_net_rcu(net) {
err = __netlink_change_ngroups(net->genl_sock,
mc_groups_longs * BITS_PER_LONG);
if (err) {
/*
* No need to roll back, can only fail if
* memory allocation fails and then the
* number of _possible_ groups has been
* increased on some sockets which is ok.
*/
break;
}
}
rcu_read_unlock();
netlink_table_ungrab();
} else {
err = netlink_change_ngroups(init_net.genl_sock,
mc_groups_longs * BITS_PER_LONG);
}
if (groups_allocated && err) {
for (i = 0; i < family->n_mcgrps; i++)
clear_bit(family->mcgrp_offset + i, mc_groups);
}
return err;
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Johannes Berg | 331 | 95.11% | 6 | 54.55% |
Thomas Graf | 11 | 3.16% | 3 | 27.27% |
Jamal Hadi Salim | 4 | 1.15% | 1 | 9.09% |
Geert Uytterhoeven | 2 | 0.57% | 1 | 9.09% |
Total | 348 | 100.00% | 11 | 100.00% |
static void genl_unregister_mc_groups(const struct genl_family *family)
{
struct net *net;
int i;
netlink_table_grab();
rcu_read_lock();
for_each_net_rcu(net) {
for (i = 0; i < family->n_mcgrps; i++)
__netlink_clear_multicast_users(
net->genl_sock, family->mcgrp_offset + i);
}
rcu_read_unlock();
netlink_table_ungrab();
for (i = 0; i < family->n_mcgrps; i++) {
int grp_id = family->mcgrp_offset + i;
if (grp_id != 1)
clear_bit(grp_id, mc_groups);
genl_ctrl_event(CTRL_CMD_DELMCAST_GRP, family,
&family->mcgrps[i], grp_id);
}
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Johannes Berg | 111 | 90.98% | 7 | 77.78% |
Thomas Graf | 11 | 9.02% | 2 | 22.22% |
Total | 122 | 100.00% | 9 | 100.00% |
static int genl_validate_ops(const struct genl_family *family)
{
const struct genl_ops *ops = family->ops;
unsigned int n_ops = family->n_ops;
int i, j;
if (WARN_ON(n_ops && !ops))
return -EINVAL;
if (!n_ops)
return 0;
for (i = 0; i < n_ops; i++) {
if (ops[i].dumpit == NULL && ops[i].doit == NULL)
return -EINVAL;
for (j = i + 1; j < n_ops; j++)
if (ops[i].cmd == ops[j].cmd)
return -EINVAL;
}
return 0;
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Johannes Berg | 133 | 99.25% | 5 | 83.33% |
Cheng Renquan | 1 | 0.75% | 1 | 16.67% |
Total | 134 | 100.00% | 6 | 100.00% |
/**
* genl_register_family - register a generic netlink family
* @family: generic netlink family
*
* Registers the specified family after validating it first. Only one
* family may be registered with the same family name or identifier.
*
* The family's ops, multicast groups and module pointer must already
* be assigned.
*
* Return 0 on success or a negative error code.
*/
int genl_register_family(struct genl_family *family)
{
int err, i;
int start = GENL_START_ALLOC, end = GENL_MAX_ID;
err = genl_validate_ops(family);
if (err)
return err;
genl_lock_all();
if (genl_family_find_byname(family->name)) {
err = -EEXIST;
goto errout_locked;
}
/*
* Sadly, a few cases need to be special-cased
* due to them having previously abused the API
* and having used their family ID also as their
* multicast group ID, so we use reserved IDs
* for both to be sure we can do that mapping.
*/
if (family == &genl_ctrl) {
/* and this needs to be special for initial family lookups */
start = end = GENL_ID_CTRL;
} else if (strcmp(family->name, "pmcraid") == 0) {
start = end = GENL_ID_PMCRAID;
} else if (strcmp(family->name, "VFS_DQUOT") == 0) {
start = end = GENL_ID_VFS_DQUOT;
}
if (family->maxattr && !family->parallel_ops) {
family->attrbuf = kmalloc((family->maxattr+1) *
sizeof(struct nlattr *), GFP_KERNEL);
if (family->attrbuf == NULL) {
err = -ENOMEM;
goto errout_locked;
}
} else
family->attrbuf = NULL;
family->id = idr_alloc(&genl_fam_idr, family,
start, end + 1, GFP_KERNEL);
if (family->id < 0) {
err = family->id;
goto errout_locked;
}
err = genl_validate_assign_mc_groups(family);
if (err)
goto errout_remove;
genl_unlock_all();
/* send all events */
genl_ctrl_event(CTRL_CMD_NEWFAMILY, family, NULL, 0);
for (i = 0; i < family->n_mcgrps; i++)
genl_ctrl_event(CTRL_CMD_NEWMCAST_GRP, family,
&family->mcgrps[i], family->mcgrp_offset + i);
return 0;
errout_remove:
idr_remove(&genl_fam_idr, family->id);
kfree(family->attrbuf);
errout_locked:
genl_unlock_all();
return err;
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Johannes Berg | 159 | 50.16% | 6 | 50.00% |
Thomas Graf | 123 | 38.80% | 1 | 8.33% |
Wei Yongjun | 10 | 3.15% | 1 | 8.33% |
Krishna Kumar | 9 | 2.84% | 1 | 8.33% |
Pravin B Shelar | 8 | 2.52% | 1 | 8.33% |
Américo Wang | 7 | 2.21% | 1 | 8.33% |
Jamal Hadi Salim | 1 | 0.32% | 1 | 8.33% |
Total | 317 | 100.00% | 12 | 100.00% |
EXPORT_SYMBOL(genl_register_family);
/**
* genl_unregister_family - unregister generic netlink family
* @family: generic netlink family
*
* Unregisters the specified family.
*
* Returns 0 on success or a negative error code.
*/
int genl_unregister_family(const struct genl_family *family)
{
genl_lock_all();
if (!genl_family_find_byid(family->id)) {
genl_unlock_all();
return -ENOENT;
}
genl_unregister_mc_groups(family);
idr_remove(&genl_fam_idr, family->id);
up_write(&cb_lock);
wait_event(genl_sk_destructing_waitq,
atomic_read(&genl_sk_destructing_cnt) == 0);
genl_unlock();
kfree(family->attrbuf);
genl_ctrl_event(CTRL_CMD_DELFAMILY, family, NULL, 0);
return 0;
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Johannes Berg | 46 | 50.55% | 4 | 50.00% |
Thomas Graf | 41 | 45.05% | 1 | 12.50% |
Pavel Emelyanov | 2 | 2.20% | 1 | 12.50% |
Pravin B Shelar | 1 | 1.10% | 1 | 12.50% |
pravin shelar | 1 | 1.10% | 1 | 12.50% |
Total | 91 | 100.00% | 8 | 100.00% |
EXPORT_SYMBOL(genl_unregister_family);
/**
* genlmsg_put - Add generic netlink header to netlink message
* @skb: socket buffer holding the message
* @portid: netlink portid the message is addressed to
* @seq: sequence number (usually the one of the sender)
* @family: generic netlink family
* @flags: netlink message flags
* @cmd: generic netlink command
*
* Returns pointer to user specific header
*/
void *genlmsg_put(struct sk_buff *skb, u32 portid, u32 seq,
const struct genl_family *family, int flags, u8 cmd)
{
struct nlmsghdr *nlh;
struct genlmsghdr *hdr;
nlh = nlmsg_put(skb, portid, seq, family->id, GENL_HDRLEN +
family->hdrsize, flags);
if (nlh == NULL)
return NULL;
hdr = nlmsg_data(nlh);
hdr->cmd = cmd;
hdr->version = family->version;
hdr->reserved = 0;
return (char *) hdr + GENL_HDRLEN;
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Denys Vlasenko | 104 | 97.20% | 1 | 33.33% |
Eric W. Biedermann | 2 | 1.87% | 1 | 33.33% |
Johannes Berg | 1 | 0.93% | 1 | 33.33% |
Total | 107 | 100.00% | 3 | 100.00% |
EXPORT_SYMBOL(genlmsg_put);
static int genl_lock_start(struct netlink_callback *cb)
{
/* our ops are always const - netlink API doesn't propagate that */
const struct genl_ops *ops = cb->data;
int rc = 0;
if (ops->start) {
genl_lock();
rc = ops->start(cb);
genl_unlock();
}
return rc;
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Tom Herbert | 53 | 100.00% | 1 | 100.00% |
Total | 53 | 100.00% | 1 | 100.00% |
static int genl_lock_dumpit(struct sk_buff *skb, struct netlink_callback *cb)
{
/* our ops are always const - netlink API doesn't propagate that */
const struct genl_ops *ops = cb->data;
int rc;
genl_lock();
rc = ops->dumpit(skb, cb);
genl_unlock();
return rc;
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Pravin B Shelar | 48 | 96.00% | 1 | 50.00% |
Johannes Berg | 2 | 4.00% | 1 | 50.00% |
Total | 50 | 100.00% | 2 | 100.00% |
static int genl_lock_done(struct netlink_callback *cb)
{
/* our ops are always const - netlink API doesn't propagate that */
const struct genl_ops *ops = cb->data;
int rc = 0;
if (ops->done) {
genl_lock();
rc = ops->done(cb);
genl_unlock();
}
return rc;
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Pravin B Shelar | 51 | 96.23% | 1 | 50.00% |
Johannes Berg | 2 | 3.77% | 1 | 50.00% |
Total | 53 | 100.00% | 2 | 100.00% |
static int genl_family_rcv_msg(const struct genl_family *family,
struct sk_buff *skb,
struct nlmsghdr *nlh)
{
const struct genl_ops *ops;
struct net *net = sock_net(skb->sk);
struct genl_info info;
struct genlmsghdr *hdr = nlmsg_data(nlh);
struct nlattr **attrbuf;
int hdrlen, err;
/* this family doesn't exist in this netns */
if (!family->netnsok && !net_eq(net, &init_net))
return -ENOENT;
hdrlen = GENL_HDRLEN + family->hdrsize;
if (nlh->nlmsg_len < nlmsg_msg_size(hdrlen))
return -EINVAL;
ops = genl_get_cmd(hdr->cmd, family);
if (ops == NULL)
return -EOPNOTSUPP;
if ((ops->flags & GENL_ADMIN_PERM) &&
!netlink_capable(skb, CAP_NET_ADMIN))
return -EPERM;
if ((ops->flags & GENL_UNS_ADMIN_PERM) &&
!netlink_ns_capable(skb, net->user_ns, CAP_NET_ADMIN))
return -EPERM;
if ((nlh->nlmsg_flags & NLM_F_DUMP) == NLM_F_DUMP) {
int rc;
if (ops->dumpit == NULL)
return -EOPNOTSUPP;
if (!family->parallel_ops) {
struct netlink_dump_control c = {
.module = family->module,
/* we have const, but the netlink API doesn't */
.data = (void *)ops,
.start = genl_lock_start,
.dump = genl_lock_dumpit,
.done = genl_lock_done,
};
genl_unlock();
rc = __netlink_dump_start(net->genl_sock, skb, nlh, &c);
genl_lock();
} else {
struct netlink_dump_control c = {
.module = family->module,
.start = ops->start,
.dump = ops->dumpit,
.done = ops->done,
};
rc = __netlink_dump_start(net->genl_sock, skb, nlh, &c);
}
return rc;
}
if (ops->doit == NULL)
return -EOPNOTSUPP;
if (family->maxattr && family->parallel_ops) {
attrbuf = kmalloc((family->maxattr+1) *
sizeof(struct nlattr *), GFP_KERNEL);
if (attrbuf == NULL)
return -ENOMEM;
} else
attrbuf = family->attrbuf;
if (attrbuf) {
err = nlmsg_parse(nlh, hdrlen, attrbuf, family->maxattr,
ops->policy);
if (err < 0)
goto out;
}
info.snd_seq = nlh->nlmsg_seq;
info.snd_portid = NETLINK_CB(skb).portid;
info.nlhdr = nlh;
info.genlhdr = nlmsg_data(nlh);
info.userhdr = nlmsg_data(nlh) + GENL_HDRLEN;
info.attrs = attrbuf;
genl_info_net_set(&info, net);
memset(&info.user_ptr, 0, sizeof(info.user_ptr));
if (family->pre_doit) {
err = family->pre_doit(ops, skb, &info);
if (err)
goto out;
}
err = ops->doit(skb, &info);
if (family->post_doit)
family->post_doit(ops, skb, &info);
out:
if (family->parallel_ops)
kfree(attrbuf);
return err;
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Thomas Graf | 221 | 38.10% | 2 | 11.11% |
Pravin B Shelar | 167 | 28.79% | 3 | 16.67% |
Johannes Berg | 114 | 19.66% | 4 | 22.22% |
Tycho Andersen | 26 | 4.48% | 1 | 5.56% |
Pablo Neira Ayuso | 25 | 4.31% | 2 | 11.11% |
Tom Herbert | 12 | 2.07% | 1 | 5.56% |
Wei Yongjun | 8 | 1.38% | 1 | 5.56% |
Eric W. Biedermann | 5 | 0.86% | 2 | 11.11% |
Darrel Goeddel | 1 | 0.17% | 1 | 5.56% |
Eric Paris | 1 | 0.17% | 1 | 5.56% |
Total | 580 | 100.00% | 18 | 100.00% |
static int genl_rcv_msg(struct sk_buff *skb, struct nlmsghdr *nlh)
{
const struct genl_family *family;
int err;
family = genl_family_find_byid(nlh->nlmsg_type);
if (family == NULL)
return -ENOENT;
if (!family->parallel_ops)
genl_lock();
err = genl_family_rcv_msg(family, skb, nlh);
if (!family->parallel_ops)
genl_unlock();
return err;
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Pravin B Shelar | 73 | 93.59% | 1 | 25.00% |
Johannes Berg | 3 | 3.85% | 2 | 50.00% |
Thomas Graf | 2 | 2.56% | 1 | 25.00% |
Total | 78 | 100.00% | 4 | 100.00% |
static void genl_rcv(struct sk_buff *skb)
{
down_read(&cb_lock);
netlink_rcv_skb(skb, &genl_rcv_msg);
up_read(&cb_lock);
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Thomas Graf | 15 | 48.39% | 1 | 25.00% |
Pravin B Shelar | 10 | 32.26% | 1 | 25.00% |
Denis V. Lunev | 6 | 19.35% | 2 | 50.00% |
Total | 31 | 100.00% | 4 | 100.00% |
/**************************************************************************
* Controller
**************************************************************************/
static struct genl_family genl_ctrl;
static int ctrl_fill_info(const struct genl_family *family, u32 portid, u32 seq,
u32 flags, struct sk_buff *skb, u8 cmd)
{
void *hdr;
hdr = genlmsg_put(skb, portid, seq, &genl_ctrl, flags, cmd);
if (hdr == NULL)
return -1;
if (nla_put_string(skb, CTRL_ATTR_FAMILY_NAME, family->name) ||
nla_put_u16(skb, CTRL_ATTR_FAMILY_ID, family->id) ||
nla_put_u32(skb, CTRL_ATTR_VERSION, family->version) ||
nla_put_u32(skb, CTRL_ATTR_HDRSIZE, family->hdrsize) ||
nla_put_u32(skb, CTRL_ATTR_MAXATTR, family->maxattr))
goto nla_put_failure;
if (family->n_ops) {
struct nlattr *nla_ops;
int i;
nla_ops = nla_nest_start(skb, CTRL_ATTR_OPS);
if (nla_ops == NULL)
goto nla_put_failure;
for (i = 0; i < family->n_ops; i++) {
struct nlattr *nest;
const struct genl_ops *ops = &family->ops[i];
u32 op_flags = ops->flags;
if (ops->dumpit)
op_flags |= GENL_CMD_CAP_DUMP;
if (ops->doit)
op_flags |= GENL_CMD_CAP_DO;
if (ops->policy)
op_flags |= GENL_CMD_CAP_HASPOL;
nest = nla_nest_start(skb, i + 1);
if (nest == NULL)
goto nla_put_failure;
if (nla_put_u32(skb, CTRL_ATTR_OP_ID, ops->cmd) ||
nla_put_u32(skb, CTRL_ATTR_OP_FLAGS, op_flags))
goto nla_put_failure;
nla_nest_end(skb, nest);
}
nla_nest_end(skb, nla_ops);
}
if (family->n_mcgrps) {
struct nlattr *nla_grps;
int i;
nla_grps = nla_nest_start(skb, CTRL_ATTR_MCAST_GROUPS);
if (nla_grps == NULL)
goto nla_put_failure;
for (i = 0; i < family->n_mcgrps; i++) {
struct nlattr *nest;
const struct genl_multicast_group *grp;
grp = &family->mcgrps[i];
nest = nla_nest_start(skb, i + 1);
if (nest == NULL)
goto nla_put_failure;
if (nla_put_u32(skb, CTRL_ATTR_MCAST_GRP_ID,
family->mcgrp_offset + i) ||
nla_put_string(skb, CTRL_ATTR_MCAST_GRP_NAME,
grp->