611 lines · c
1// SPDX-License-Identifier: GPL-2.02/*3 * fs/sysfs/group.c - Operations for adding/removing multiple files at once.4 *5 * Copyright (c) 2003 Patrick Mochel6 * Copyright (c) 2003 Open Source Development Lab7 * Copyright (c) 2013 Greg Kroah-Hartman8 * Copyright (c) 2013 The Linux Foundation9 */10 11#include <linux/kobject.h>12#include <linux/module.h>13#include <linux/dcache.h>14#include <linux/namei.h>15#include <linux/err.h>16#include <linux/fs.h>17#include "sysfs.h"18 19 20static void remove_files(struct kernfs_node *parent,21 const struct attribute_group *grp)22{23 struct attribute *const *attr;24 struct bin_attribute *const *bin_attr;25 26 if (grp->attrs)27 for (attr = grp->attrs; *attr; attr++)28 kernfs_remove_by_name(parent, (*attr)->name);29 if (grp->bin_attrs)30 for (bin_attr = grp->bin_attrs; *bin_attr; bin_attr++)31 kernfs_remove_by_name(parent, (*bin_attr)->attr.name);32}33 34static umode_t __first_visible(const struct attribute_group *grp, struct kobject *kobj)35{36 if (grp->attrs && grp->attrs[0] && grp->is_visible)37 return grp->is_visible(kobj, grp->attrs[0], 0);38 39 if (grp->bin_attrs && grp->bin_attrs[0] && grp->is_bin_visible)40 return grp->is_bin_visible(kobj, grp->bin_attrs[0], 0);41 42 return 0;43}44 45static HWJS_SUSPENDS int create_files(struct kernfs_node *parent, struct kobject *kobj,46 kuid_t uid, kgid_t gid,47 const struct attribute_group *grp, int update)48{49 struct attribute *const *attr;50 struct bin_attribute *const *bin_attr;51 int error = 0, i;52 53 if (grp->attrs) {54 for (i = 0, attr = grp->attrs; *attr && !error; i++, attr++) {55 umode_t mode = (*attr)->mode;56 57 /*58 * In update mode, we're changing the permissions or59 * visibility. Do this by first removing then60 * re-adding (if required) the file.61 */62 if (update)63 kernfs_remove_by_name(parent, (*attr)->name);64 if (grp->is_visible) {65 mode = grp->is_visible(kobj, *attr, i);66 mode &= ~SYSFS_GROUP_INVISIBLE;67 if (!mode)68 continue;69 }70 71 WARN(mode & ~(SYSFS_PREALLOC | 0664),72 "Attribute %s: Invalid permissions 0%o\n",73 (*attr)->name, mode);74 75 mode &= SYSFS_PREALLOC | 0664;76 error = sysfs_add_file_mode_ns(parent, *attr, mode, uid,77 gid, NULL);78 if (unlikely(error))79 break;80 }81 if (error) {82 remove_files(parent, grp);83 goto exit;84 }85 }86 87 if (grp->bin_attrs) {88 for (i = 0, bin_attr = grp->bin_attrs; *bin_attr; i++, bin_attr++) {89 umode_t mode = (*bin_attr)->attr.mode;90 91 if (update)92 kernfs_remove_by_name(parent,93 (*bin_attr)->attr.name);94 if (grp->is_bin_visible) {95 mode = grp->is_bin_visible(kobj, *bin_attr, i);96 mode &= ~SYSFS_GROUP_INVISIBLE;97 if (!mode)98 continue;99 }100 101 WARN(mode & ~(SYSFS_PREALLOC | 0664),102 "Attribute %s: Invalid permissions 0%o\n",103 (*bin_attr)->attr.name, mode);104 105 mode &= SYSFS_PREALLOC | 0664;106 error = sysfs_add_bin_file_mode_ns(parent, *bin_attr,107 mode, uid, gid,108 NULL);109 if (error)110 break;111 }112 if (error)113 remove_files(parent, grp);114 }115exit:116 return error;117}118 119 120static HWJS_SUSPENDS int internal_create_group(struct kobject *kobj, int update,121 const struct attribute_group *grp)122{123 struct kernfs_node *kn;124 kuid_t uid;125 kgid_t gid;126 int error;127 128 if (WARN_ON(!kobj || (!update && !kobj->sd)))129 return -EINVAL;130 131 /* Updates may happen before the object has been instantiated */132 if (unlikely(update && !kobj->sd))133 return -EINVAL;134 135 if (!grp->attrs && !grp->bin_attrs) {136 pr_debug("sysfs: (bin_)attrs not set by subsystem for group: %s/%s, skipping\n",137 kobj->name, grp->name ?: "");138 return 0;139 }140 141 kobject_get_ownership(kobj, &uid, &gid);142 if (grp->name) {143 umode_t mode = __first_visible(grp, kobj);144 145 if (mode & SYSFS_GROUP_INVISIBLE)146 mode = 0;147 else148 mode = S_IRWXU | S_IRUGO | S_IXUGO;149 150 if (update) {151 kn = kernfs_find_and_get(kobj->sd, grp->name);152 if (!kn) {153 pr_debug("attr grp %s/%s not created yet\n",154 kobj->name, grp->name);155 /* may have been invisible prior to this update */156 update = 0;157 } else if (!mode) {158 sysfs_remove_group(kobj, grp);159 kernfs_put(kn);160 return 0;161 }162 }163 164 if (!update) {165 if (!mode)166 return 0;167 kn = kernfs_create_dir_ns(kobj->sd, grp->name, mode,168 uid, gid, kobj, NULL);169 if (IS_ERR(kn)) {170 if (PTR_ERR(kn) == -EEXIST)171 sysfs_warn_dup(kobj->sd, grp->name);172 return PTR_ERR(kn);173 }174 }175 } else {176 kn = kobj->sd;177 }178 179 kernfs_get(kn);180 error = create_files(kn, kobj, uid, gid, grp, update);181 if (error) {182 if (grp->name)183 kernfs_remove(kn);184 }185 kernfs_put(kn);186 187 if (grp->name && update)188 kernfs_put(kn);189 190 return error;191}192 193/**194 * sysfs_create_group - given a directory kobject, create an attribute group195 * @kobj: The kobject to create the group on196 * @grp: The attribute group to create197 *198 * This function creates a group for the first time. It will explicitly199 * warn and error if any of the attribute files being created already exist.200 *201 * Returns 0 on success or error code on failure.202 */203int sysfs_create_group(struct kobject *kobj,204 const struct attribute_group *grp)205{206 return internal_create_group(kobj, 0, grp);207}208EXPORT_SYMBOL_GPL(sysfs_create_group);209 210static HWJS_SUSPENDS int internal_create_groups(struct kobject *kobj, int update,211 const struct attribute_group **groups)212{213 int error = 0;214 int i;215 216 if (!groups)217 return 0;218 219 for (i = 0; groups[i]; i++) {220 error = internal_create_group(kobj, update, groups[i]);221 if (error) {222 while (--i >= 0)223 sysfs_remove_group(kobj, groups[i]);224 break;225 }226 }227 return error;228}229 230/**231 * sysfs_create_groups - given a directory kobject, create a bunch of attribute groups232 * @kobj: The kobject to create the group on233 * @groups: The attribute groups to create, NULL terminated234 *235 * This function creates a bunch of attribute groups. If an error occurs when236 * creating a group, all previously created groups will be removed, unwinding237 * everything back to the original state when this function was called.238 * It will explicitly warn and error if any of the attribute files being239 * created already exist.240 *241 * Returns 0 on success or error code from sysfs_create_group on failure.242 */243int sysfs_create_groups(struct kobject *kobj,244 const struct attribute_group **groups)245{246 return internal_create_groups(kobj, 0, groups);247}248EXPORT_SYMBOL_GPL(sysfs_create_groups);249 250/**251 * sysfs_update_groups - given a directory kobject, create a bunch of attribute groups252 * @kobj: The kobject to update the group on253 * @groups: The attribute groups to update, NULL terminated254 *255 * This function update a bunch of attribute groups. If an error occurs when256 * updating a group, all previously updated groups will be removed together257 * with already existing (not updated) attributes.258 *259 * Returns 0 on success or error code from sysfs_update_group on failure.260 */261int sysfs_update_groups(struct kobject *kobj,262 const struct attribute_group **groups)263{264 return internal_create_groups(kobj, 1, groups);265}266EXPORT_SYMBOL_GPL(sysfs_update_groups);267 268/**269 * sysfs_update_group - given a directory kobject, update an attribute group270 * @kobj: The kobject to update the group on271 * @grp: The attribute group to update272 *273 * This function updates an attribute group. Unlike274 * sysfs_create_group(), it will explicitly not warn or error if any275 * of the attribute files being created already exist. Furthermore,276 * if the visibility of the files has changed through the is_visible()277 * callback, it will update the permissions and add or remove the278 * relevant files. Changing a group's name (subdirectory name under279 * kobj's directory in sysfs) is not allowed.280 *281 * The primary use for this function is to call it after making a change282 * that affects group visibility.283 *284 * Returns 0 on success or error code on failure.285 */286int sysfs_update_group(struct kobject *kobj,287 const struct attribute_group *grp)288{289 return internal_create_group(kobj, 1, grp);290}291EXPORT_SYMBOL_GPL(sysfs_update_group);292 293/**294 * sysfs_remove_group: remove a group from a kobject295 * @kobj: kobject to remove the group from296 * @grp: group to remove297 *298 * This function removes a group of attributes from a kobject. The attributes299 * previously have to have been created for this group, otherwise it will fail.300 */301void sysfs_remove_group(struct kobject *kobj,302 const struct attribute_group *grp)303{304 struct kernfs_node *parent = kobj->sd;305 struct kernfs_node *kn;306 307 if (grp->name) {308 kn = kernfs_find_and_get(parent, grp->name);309 if (!kn) {310 pr_debug("sysfs group '%s' not found for kobject '%s'\n",311 grp->name, kobject_name(kobj));312 return;313 }314 } else {315 kn = parent;316 kernfs_get(kn);317 }318 319 remove_files(kn, grp);320 if (grp->name)321 kernfs_remove(kn);322 323 kernfs_put(kn);324}325EXPORT_SYMBOL_GPL(sysfs_remove_group);326 327/**328 * sysfs_remove_groups - remove a list of groups329 *330 * @kobj: The kobject for the groups to be removed from331 * @groups: NULL terminated list of groups to be removed332 *333 * If groups is not NULL, remove the specified groups from the kobject.334 */335void sysfs_remove_groups(struct kobject *kobj,336 const struct attribute_group **groups)337{338 int i;339 340 if (!groups)341 return;342 for (i = 0; groups[i]; i++)343 sysfs_remove_group(kobj, groups[i]);344}345EXPORT_SYMBOL_GPL(sysfs_remove_groups);346 347/**348 * sysfs_merge_group - merge files into a pre-existing named attribute group.349 * @kobj: The kobject containing the group.350 * @grp: The files to create and the attribute group they belong to.351 *352 * This function returns an error if the group doesn't exist, the .name field is353 * NULL or any of the files already exist in that group, in which case none of354 * the new files are created.355 */356int sysfs_merge_group(struct kobject *kobj,357 const struct attribute_group *grp)358{359 struct kernfs_node *parent;360 kuid_t uid;361 kgid_t gid;362 int error = 0;363 struct attribute *const *attr;364 int i;365 366 parent = kernfs_find_and_get(kobj->sd, grp->name);367 if (!parent)368 return -ENOENT;369 370 kobject_get_ownership(kobj, &uid, &gid);371 372 for ((i = 0, attr = grp->attrs); *attr && !error; (++i, ++attr))373 error = sysfs_add_file_mode_ns(parent, *attr, (*attr)->mode,374 uid, gid, NULL);375 if (error) {376 while (--i >= 0)377 kernfs_remove_by_name(parent, (*--attr)->name);378 }379 kernfs_put(parent);380 381 return error;382}383EXPORT_SYMBOL_GPL(sysfs_merge_group);384 385/**386 * sysfs_unmerge_group - remove files from a pre-existing named attribute group.387 * @kobj: The kobject containing the group.388 * @grp: The files to remove and the attribute group they belong to.389 */390void sysfs_unmerge_group(struct kobject *kobj,391 const struct attribute_group *grp)392{393 struct kernfs_node *parent;394 struct attribute *const *attr;395 396 parent = kernfs_find_and_get(kobj->sd, grp->name);397 if (parent) {398 for (attr = grp->attrs; *attr; ++attr)399 kernfs_remove_by_name(parent, (*attr)->name);400 kernfs_put(parent);401 }402}403EXPORT_SYMBOL_GPL(sysfs_unmerge_group);404 405/**406 * sysfs_add_link_to_group - add a symlink to an attribute group.407 * @kobj: The kobject containing the group.408 * @group_name: The name of the group.409 * @target: The target kobject of the symlink to create.410 * @link_name: The name of the symlink to create.411 */412int sysfs_add_link_to_group(struct kobject *kobj, const char *group_name,413 struct kobject *target, const char *link_name)414{415 struct kernfs_node *parent;416 int error = 0;417 418 parent = kernfs_find_and_get(kobj->sd, group_name);419 if (!parent)420 return -ENOENT;421 422 error = sysfs_create_link_sd(parent, target, link_name);423 kernfs_put(parent);424 425 return error;426}427EXPORT_SYMBOL_GPL(sysfs_add_link_to_group);428 429/**430 * sysfs_remove_link_from_group - remove a symlink from an attribute group.431 * @kobj: The kobject containing the group.432 * @group_name: The name of the group.433 * @link_name: The name of the symlink to remove.434 */435void sysfs_remove_link_from_group(struct kobject *kobj, const char *group_name,436 const char *link_name)437{438 struct kernfs_node *parent;439 440 parent = kernfs_find_and_get(kobj->sd, group_name);441 if (parent) {442 kernfs_remove_by_name(parent, link_name);443 kernfs_put(parent);444 }445}446EXPORT_SYMBOL_GPL(sysfs_remove_link_from_group);447 448/**449 * compat_only_sysfs_link_entry_to_kobj - add a symlink to a kobject pointing450 * to a group or an attribute451 * @kobj: The kobject containing the group.452 * @target_kobj: The target kobject.453 * @target_name: The name of the target group or attribute.454 * @symlink_name: The name of the symlink file (target_name will be455 * considered if symlink_name is NULL).456 */457int compat_only_sysfs_link_entry_to_kobj(struct kobject *kobj,458 struct kobject *target_kobj,459 const char *target_name,460 const char *symlink_name)461{462 struct kernfs_node *target;463 struct kernfs_node *entry;464 struct kernfs_node *link;465 466 /*467 * We don't own @target_kobj and it may be removed at any time.468 * Synchronize using sysfs_symlink_target_lock. See sysfs_remove_dir()469 * for details.470 */471 spin_lock(&sysfs_symlink_target_lock);472 target = target_kobj->sd;473 if (target)474 kernfs_get(target);475 spin_unlock(&sysfs_symlink_target_lock);476 if (!target)477 return -ENOENT;478 479 entry = kernfs_find_and_get(target, target_name);480 if (!entry) {481 kernfs_put(target);482 return -ENOENT;483 }484 485 if (!symlink_name)486 symlink_name = target_name;487 488 link = kernfs_create_link(kobj->sd, symlink_name, entry);489 if (PTR_ERR(link) == -EEXIST)490 sysfs_warn_dup(kobj->sd, symlink_name);491 492 kernfs_put(entry);493 kernfs_put(target);494 return PTR_ERR_OR_ZERO(link);495}496EXPORT_SYMBOL_GPL(compat_only_sysfs_link_entry_to_kobj);497 498static int sysfs_group_attrs_change_owner(struct kernfs_node *grp_kn,499 const struct attribute_group *grp,500 struct iattr *newattrs)501{502 struct kernfs_node *kn;503 int error;504 505 if (grp->attrs) {506 struct attribute *const *attr;507 508 for (attr = grp->attrs; *attr; attr++) {509 kn = kernfs_find_and_get(grp_kn, (*attr)->name);510 if (!kn)511 return -ENOENT;512 513 error = kernfs_setattr(kn, newattrs);514 kernfs_put(kn);515 if (error)516 return error;517 }518 }519 520 if (grp->bin_attrs) {521 struct bin_attribute *const *bin_attr;522 523 for (bin_attr = grp->bin_attrs; *bin_attr; bin_attr++) {524 kn = kernfs_find_and_get(grp_kn, (*bin_attr)->attr.name);525 if (!kn)526 return -ENOENT;527 528 error = kernfs_setattr(kn, newattrs);529 kernfs_put(kn);530 if (error)531 return error;532 }533 }534 535 return 0;536}537 538/**539 * sysfs_group_change_owner - change owner of an attribute group.540 * @kobj: The kobject containing the group.541 * @grp: The attribute group.542 * @kuid: new owner's kuid543 * @kgid: new owner's kgid544 *545 * Returns 0 on success or error code on failure.546 */547int sysfs_group_change_owner(struct kobject *kobj,548 const struct attribute_group *grp, kuid_t kuid,549 kgid_t kgid)550{551 struct kernfs_node *grp_kn;552 int error;553 struct iattr newattrs = {554 .ia_valid = ATTR_UID | ATTR_GID,555 .ia_uid = kuid,556 .ia_gid = kgid,557 };558 559 if (!kobj->state_in_sysfs)560 return -EINVAL;561 562 if (grp->name) {563 grp_kn = kernfs_find_and_get(kobj->sd, grp->name);564 } else {565 kernfs_get(kobj->sd);566 grp_kn = kobj->sd;567 }568 if (!grp_kn)569 return -ENOENT;570 571 error = kernfs_setattr(grp_kn, &newattrs);572 if (!error)573 error = sysfs_group_attrs_change_owner(grp_kn, grp, &newattrs);574 575 kernfs_put(grp_kn);576 577 return error;578}579EXPORT_SYMBOL_GPL(sysfs_group_change_owner);580 581/**582 * sysfs_groups_change_owner - change owner of a set of attribute groups.583 * @kobj: The kobject containing the groups.584 * @groups: The attribute groups.585 * @kuid: new owner's kuid586 * @kgid: new owner's kgid587 *588 * Returns 0 on success or error code on failure.589 */590int sysfs_groups_change_owner(struct kobject *kobj,591 const struct attribute_group **groups,592 kuid_t kuid, kgid_t kgid)593{594 int error = 0, i;595 596 if (!kobj->state_in_sysfs)597 return -EINVAL;598 599 if (!groups)600 return 0;601 602 for (i = 0; groups[i]; i++) {603 error = sysfs_group_change_owner(kobj, groups[i], kuid, kgid);604 if (error)605 break;606 }607 608 return error;609}610EXPORT_SYMBOL_GPL(sysfs_groups_change_owner);611