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 PHC | |
Open Source and information security mailing list archives
| ||
|
Date: Mon, 17 Nov 2014 10:11:31 -0000 From: "Hamad Kadmany" <hkadmany@...eaurora.org> To: jiri@...nulli.us Cc: netdev@...r.kernel.org Subject: [PATCH] net: team: expose sysfs attributes for each team option Current code provides only netlink API for user space to read/write options. Exposing sysfs API is useful for systems that don't have the required netlink user space support. Upon registration of team option, corresponding sysfs attribute is created. Signed-off-by: Hamad Kadmany <hkadmany@...eaurora.org> --- drivers/net/team/team.c | 282 ++++++++++++++++++++++++++++++++++++++++++++++-- include/linux/if_team.h | 3 + 2 files changed, 278 insertions(+), 7 deletions(-) diff --git a/drivers/net/team/team.c b/drivers/net/team/team.c index 1222229..afd2f8f 100644 --- a/drivers/net/team/team.c +++ b/drivers/net/team/team.c @@ -110,8 +110,198 @@ struct team_option_inst { /* One for each option instance */ struct team_option_inst_info info; bool changed; bool removed; + bool dev_attr_file_exist; + struct device_attribute dev_attr; /* corresponding sysfs attribute */ }; +static struct attribute *team_sysfs_attrs[] = { + NULL, +}; + +static struct attribute_group team_sysfs_attr_group = { + .attrs = team_sysfs_attrs, +}; + +/* Device attributes (sysfs) */ + +static ssize_t show_attribute(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct team *team = dev_get_drvdata(dev); + struct team_option_inst *opt_inst; + ssize_t ret; + struct team_option *option; + struct team_gsetter_ctx ctx; + + if (mutex_lock_interruptible(&team->lock)) + return -ERESTARTSYS; + + opt_inst = container_of(attr, struct team_option_inst, dev_attr); + option = opt_inst->option; + if (!option->getter) { + ret = -EOPNOTSUPP; + netdev_err(team->dev, + "Option %s is write only\n", attr->attr.name); + goto exit; + } + + ctx.info = &opt_inst->info; + /* let the option getter do its job */ + ret = option->getter(team, &ctx); + if (ret) + goto exit; + + /* translate option's output into sysfs output */ + switch (option->type) { + case TEAM_OPTION_TYPE_U32: + ret = scnprintf(buf, PAGE_SIZE, "%u\n", ctx.data.u32_val); + break; + case TEAM_OPTION_TYPE_STRING: + ret = scnprintf(buf, PAGE_SIZE, "%s\n", ctx.data.str_val); + break; + case TEAM_OPTION_TYPE_BINARY: + if (ctx.data.bin_val.len > PAGE_SIZE) { + netdev_err(team->dev, + "Option output is too long (%d)\n", + ctx.data.bin_val.len); + break; + } + + memcpy(buf, ctx.data.bin_val.ptr, ctx.data.bin_val.len); + ret = ctx.data.bin_val.len; + break; + case TEAM_OPTION_TYPE_BOOL: + ret = scnprintf(buf, PAGE_SIZE, "%d\n", ctx.data.bool_val); + break; + case TEAM_OPTION_TYPE_S32: + ret = scnprintf(buf, PAGE_SIZE, "%d\n", ctx.data.s32_val); + break; + default: + BUG(); + } + +exit: + mutex_unlock(&team->lock); + + return ret; +} + +static int team_nl_send_event_options_get(struct team *team, + struct list_head *sel_opt_inst_list); + +static ssize_t set_attribute(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct team_option_inst *opt_inst; + struct team *team = dev_get_drvdata(dev); + int err = 0; + struct team_option *option = NULL; + struct team_gsetter_ctx ctx; + + LIST_HEAD(opt_inst_list); + + if (mutex_lock_interruptible(&team->lock)) + return -ERESTARTSYS; + + opt_inst = container_of(attr, struct team_option_inst, dev_attr); + option = opt_inst->option; + if (!option->setter) { + netdev_err(team->dev, + "Option %s is read only\n", attr->attr.name); + err = -EOPNOTSUPP; + goto exit; + } + + ctx.info = &opt_inst->info; + + /* translate sysfs input into option's input */ + switch (option->type) { + case TEAM_OPTION_TYPE_U32: + err = kstrtoint(buf, 0, &ctx.data.u32_val); + break; + case TEAM_OPTION_TYPE_STRING: + if (count > TEAM_STRING_MAX_LEN) { + netdev_err(team->dev, + "Input buffer too long (%zu)\n", count); + err = -EINVAL; + break; + } + ctx.data.str_val = buf; + break; + case TEAM_OPTION_TYPE_BINARY: + ctx.data.bin_val.len = count; + ctx.data.bin_val.ptr = buf; + break; + case TEAM_OPTION_TYPE_BOOL: + err = strtobool(buf, &ctx.data.bool_val); + break; + case TEAM_OPTION_TYPE_S32: + err = kstrtoint(buf, 0, &ctx.data.s32_val); + break; + default: + BUG(); + } + + if (err) { + netdev_err(team->dev, "Failed to translate input buffer\n"); + goto exit; + } + + /* let the option setter do its job */ + err = option->setter(team, &ctx); + if (err) + goto exit; + + /* propagate option changed event */ + opt_inst->changed = true; + list_add(&opt_inst->tmp_list, &opt_inst_list); + err = team_nl_send_event_options_get(team, &opt_inst_list); + if (err == -ESRCH) /* no listeners, not a real error */ + err = 0; + +exit: + mutex_unlock(&team->lock); + + if (!err) + err = count; + return err; +} + +/* create sysfs attribute for each option that is being registered */ +static int __team_option_add_sysfs_attr(struct team *team, + struct team_option_inst *opt_inst, + bool create_sysfs_file) +{ + int err = 0; + struct device_attribute *new_dev_attr = &opt_inst->dev_attr; + + new_dev_attr->attr.name = opt_inst->option->name; + new_dev_attr->attr.mode = S_IRUGO | S_IWUSR; + new_dev_attr->show = show_attribute; + new_dev_attr->store = set_attribute; + + if (create_sysfs_file) { + err = sysfs_create_file(&team->dev->dev.kobj, + &new_dev_attr->attr); + if (err) + netdev_err(team->dev, + "Failed to create sysfs attribute %s\n", + new_dev_attr->attr.name); + } + + return err; +} + +static void __team_option_del_sysfs_attr(struct team *team, + struct team_option_inst *opt_inst) +{ + if (opt_inst->dev_attr_file_exist) + sysfs_remove_file(&team->dev->dev.kobj, + &opt_inst->dev_attr.attr); +} + static struct team_option *__team_find_option(struct team *team, const char *opt_name) { @@ -124,8 +314,10 @@ static struct team_option *__team_find_option(struct team *team, return NULL; } -static void __team_option_inst_del(struct team_option_inst *opt_inst) +static void __team_option_inst_del(struct team *team, + struct team_option_inst *opt_inst) { + __team_option_del_sysfs_attr(team, opt_inst); list_del(&opt_inst->list); kfree(opt_inst); } @@ -137,7 +329,7 @@ static void __team_option_inst_del_option(struct team *team, list_for_each_entry_safe(opt_inst, tmp, &team->option_inst_list, list) { if (opt_inst->option == option) - __team_option_inst_del(opt_inst); + __team_option_inst_del(team, opt_inst); } } @@ -162,6 +354,7 @@ static int __team_option_inst_add(struct team *team, struct team_option *option, opt_inst->info.array_index = i; opt_inst->changed = true; opt_inst->removed = false; + opt_inst->dev_attr_file_exist = false; list_add_tail(&opt_inst->list, &team->option_inst_list); if (option->init) { err = option->init(team, &opt_inst->info); @@ -170,6 +363,20 @@ static int __team_option_inst_add(struct team *team, struct team_option *option, } } + + /* add sysfs attribute. per-port and array options are skipped */ + if (!option->per_port && !option->array_size) { + /* create the sysfs file only if our state allows it */ + bool create_sysfs_file = device_is_registered(&team->dev->dev); + + err = __team_option_add_sysfs_attr(team, opt_inst, + create_sysfs_file); + if (err) + return err; + + opt_inst->dev_attr_file_exist = true; + } + return 0; } @@ -218,7 +425,7 @@ static void __team_option_inst_del_port(struct team *team, list_for_each_entry_safe(opt_inst, tmp, &team->option_inst_list, list) { if (opt_inst->option->per_port && opt_inst->info.port == port) - __team_option_inst_del(opt_inst); + __team_option_inst_del(team, opt_inst); } } @@ -337,6 +544,51 @@ static void __team_options_unregister(struct team *team, static void __team_options_change_check(struct team *team); +static void team_attr_grp_free(struct team *team) +{ + kfree(team->attr_grp.attrs); +} + +/* allocate attribute group for creating sysfs for team's own options */ +static int team_attr_grp_alloc(struct team *team) +{ + struct attribute **attributes; + struct team_option_inst *opt_inst; + int num_attr = 0; + struct team_option *option; + + list_for_each_entry(opt_inst, &team->option_inst_list, list) { + option = opt_inst->option; + /* per-port and array options currently not supported as + * sysfs attributes + */ + if (option->per_port || option->array_size) + continue; + + num_attr++; + } + + /* +1 for having NULL as last item in the array */ + attributes = kzalloc((num_attr + 1) * sizeof(*attributes), GFP_KERNEL); + if (!attributes) + return -ENOMEM; + team->attr_grp.attrs = attributes; + + num_attr = 0; + list_for_each_entry(opt_inst, &team->option_inst_list, list) { + option = opt_inst->option; + /* per-port and array options currently not supported as + * sysfs attributes + */ + if (option->per_port || option->array_size) + continue; + + attributes[num_attr++] = &opt_inst->dev_attr.attr; + } + + return 0; +} + int team_options_register(struct team *team, const struct team_option *option, size_t option_count) @@ -1380,15 +1632,28 @@ static int team_init(struct net_device *dev) INIT_LIST_HEAD(&team->option_list); INIT_LIST_HEAD(&team->option_inst_list); - err = team_options_register(team, team_options, ARRAY_SIZE(team_options)); + + err = team_options_register(team, team_options, + ARRAY_SIZE(team_options)); if (err) goto err_options_register; netif_carrier_off(dev); team_set_lockdep_class(dev); + /* store team context, to be used by Device attributes getter/setter */ + dev_set_drvdata(&dev->dev, team); + + /* allocate and register sysfs attributes for team's own options */ + err = team_attr_grp_alloc(team); + if (err) + goto err_grp_alloc; + dev->sysfs_groups[0] = &team->attr_grp; + return 0; +err_grp_alloc: + team_options_unregister(team, team_options, ARRAY_SIZE(team_options)); err_options_register: team_queue_override_fini(team); err_team_queue_override_init: @@ -1407,9 +1672,15 @@ static void team_uninit(struct net_device *dev) list_for_each_entry_safe(port, tmp, &team->port_list, list) team_port_del(team, port->dev); + sysfs_remove_group(&team->dev->dev.kobj, &team->attr_grp); + team_attr_grp_free(team); + /* set to dummy group to avoid double free by core */ + dev->sysfs_groups[0] = &team_sysfs_attr_group; + __team_change_mode(team, NULL); /* cleanup */ __team_options_unregister(team, team_options, ARRAY_SIZE(team_options)); team_queue_override_fini(team); + mutex_unlock(&team->lock); } @@ -2194,9 +2465,6 @@ static int team_nl_cmd_options_get(struct sk_buff *skb, struct genl_info *info) return err; } -static int team_nl_send_event_options_get(struct team *team, - struct list_head *sel_opt_inst_list); - static int team_nl_cmd_options_set(struct sk_buff *skb, struct genl_info *info) { struct team *team; diff --git a/include/linux/if_team.h b/include/linux/if_team.h index 25b8b15..2e9fb2a 100644 --- a/include/linux/if_team.h +++ b/include/linux/if_team.h @@ -188,6 +188,9 @@ struct team { struct list_head option_list; struct list_head option_inst_list; /* list of option instances */ + /* attribute group for registering team's own options at init */ + struct attribute_group attr_grp; + const struct team_mode *mode; struct team_mode_ops ops; bool user_carrier_enabled; -- 1.8.5.2 -- Qualcomm Israel, on behalf of Qualcomm Innovation Center, Inc. The Qualcomm Innovation Center, Inc. is a member of the Code Aurora Forum, a Linux Foundation Collaborative Project -- 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