162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * fs/sysfs/group.c - Operations for adding/removing multiple files at once. 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Copyright (c) 2003 Patrick Mochel 662306a36Sopenharmony_ci * Copyright (c) 2003 Open Source Development Lab 762306a36Sopenharmony_ci * Copyright (c) 2013 Greg Kroah-Hartman 862306a36Sopenharmony_ci * Copyright (c) 2013 The Linux Foundation 962306a36Sopenharmony_ci */ 1062306a36Sopenharmony_ci 1162306a36Sopenharmony_ci#include <linux/kobject.h> 1262306a36Sopenharmony_ci#include <linux/module.h> 1362306a36Sopenharmony_ci#include <linux/dcache.h> 1462306a36Sopenharmony_ci#include <linux/namei.h> 1562306a36Sopenharmony_ci#include <linux/err.h> 1662306a36Sopenharmony_ci#include <linux/fs.h> 1762306a36Sopenharmony_ci#include "sysfs.h" 1862306a36Sopenharmony_ci 1962306a36Sopenharmony_ci 2062306a36Sopenharmony_cistatic void remove_files(struct kernfs_node *parent, 2162306a36Sopenharmony_ci const struct attribute_group *grp) 2262306a36Sopenharmony_ci{ 2362306a36Sopenharmony_ci struct attribute *const *attr; 2462306a36Sopenharmony_ci struct bin_attribute *const *bin_attr; 2562306a36Sopenharmony_ci 2662306a36Sopenharmony_ci if (grp->attrs) 2762306a36Sopenharmony_ci for (attr = grp->attrs; *attr; attr++) 2862306a36Sopenharmony_ci kernfs_remove_by_name(parent, (*attr)->name); 2962306a36Sopenharmony_ci if (grp->bin_attrs) 3062306a36Sopenharmony_ci for (bin_attr = grp->bin_attrs; *bin_attr; bin_attr++) 3162306a36Sopenharmony_ci kernfs_remove_by_name(parent, (*bin_attr)->attr.name); 3262306a36Sopenharmony_ci} 3362306a36Sopenharmony_ci 3462306a36Sopenharmony_cistatic int create_files(struct kernfs_node *parent, struct kobject *kobj, 3562306a36Sopenharmony_ci kuid_t uid, kgid_t gid, 3662306a36Sopenharmony_ci const struct attribute_group *grp, int update) 3762306a36Sopenharmony_ci{ 3862306a36Sopenharmony_ci struct attribute *const *attr; 3962306a36Sopenharmony_ci struct bin_attribute *const *bin_attr; 4062306a36Sopenharmony_ci int error = 0, i; 4162306a36Sopenharmony_ci 4262306a36Sopenharmony_ci if (grp->attrs) { 4362306a36Sopenharmony_ci for (i = 0, attr = grp->attrs; *attr && !error; i++, attr++) { 4462306a36Sopenharmony_ci umode_t mode = (*attr)->mode; 4562306a36Sopenharmony_ci 4662306a36Sopenharmony_ci /* 4762306a36Sopenharmony_ci * In update mode, we're changing the permissions or 4862306a36Sopenharmony_ci * visibility. Do this by first removing then 4962306a36Sopenharmony_ci * re-adding (if required) the file. 5062306a36Sopenharmony_ci */ 5162306a36Sopenharmony_ci if (update) 5262306a36Sopenharmony_ci kernfs_remove_by_name(parent, (*attr)->name); 5362306a36Sopenharmony_ci if (grp->is_visible) { 5462306a36Sopenharmony_ci mode = grp->is_visible(kobj, *attr, i); 5562306a36Sopenharmony_ci if (!mode) 5662306a36Sopenharmony_ci continue; 5762306a36Sopenharmony_ci } 5862306a36Sopenharmony_ci 5962306a36Sopenharmony_ci WARN(mode & ~(SYSFS_PREALLOC | 0664), 6062306a36Sopenharmony_ci "Attribute %s: Invalid permissions 0%o\n", 6162306a36Sopenharmony_ci (*attr)->name, mode); 6262306a36Sopenharmony_ci 6362306a36Sopenharmony_ci mode &= SYSFS_PREALLOC | 0664; 6462306a36Sopenharmony_ci error = sysfs_add_file_mode_ns(parent, *attr, mode, uid, 6562306a36Sopenharmony_ci gid, NULL); 6662306a36Sopenharmony_ci if (unlikely(error)) 6762306a36Sopenharmony_ci break; 6862306a36Sopenharmony_ci } 6962306a36Sopenharmony_ci if (error) { 7062306a36Sopenharmony_ci remove_files(parent, grp); 7162306a36Sopenharmony_ci goto exit; 7262306a36Sopenharmony_ci } 7362306a36Sopenharmony_ci } 7462306a36Sopenharmony_ci 7562306a36Sopenharmony_ci if (grp->bin_attrs) { 7662306a36Sopenharmony_ci for (i = 0, bin_attr = grp->bin_attrs; *bin_attr; i++, bin_attr++) { 7762306a36Sopenharmony_ci umode_t mode = (*bin_attr)->attr.mode; 7862306a36Sopenharmony_ci 7962306a36Sopenharmony_ci if (update) 8062306a36Sopenharmony_ci kernfs_remove_by_name(parent, 8162306a36Sopenharmony_ci (*bin_attr)->attr.name); 8262306a36Sopenharmony_ci if (grp->is_bin_visible) { 8362306a36Sopenharmony_ci mode = grp->is_bin_visible(kobj, *bin_attr, i); 8462306a36Sopenharmony_ci if (!mode) 8562306a36Sopenharmony_ci continue; 8662306a36Sopenharmony_ci } 8762306a36Sopenharmony_ci 8862306a36Sopenharmony_ci WARN(mode & ~(SYSFS_PREALLOC | 0664), 8962306a36Sopenharmony_ci "Attribute %s: Invalid permissions 0%o\n", 9062306a36Sopenharmony_ci (*bin_attr)->attr.name, mode); 9162306a36Sopenharmony_ci 9262306a36Sopenharmony_ci mode &= SYSFS_PREALLOC | 0664; 9362306a36Sopenharmony_ci error = sysfs_add_bin_file_mode_ns(parent, *bin_attr, 9462306a36Sopenharmony_ci mode, uid, gid, 9562306a36Sopenharmony_ci NULL); 9662306a36Sopenharmony_ci if (error) 9762306a36Sopenharmony_ci break; 9862306a36Sopenharmony_ci } 9962306a36Sopenharmony_ci if (error) 10062306a36Sopenharmony_ci remove_files(parent, grp); 10162306a36Sopenharmony_ci } 10262306a36Sopenharmony_ciexit: 10362306a36Sopenharmony_ci return error; 10462306a36Sopenharmony_ci} 10562306a36Sopenharmony_ci 10662306a36Sopenharmony_ci 10762306a36Sopenharmony_cistatic int internal_create_group(struct kobject *kobj, int update, 10862306a36Sopenharmony_ci const struct attribute_group *grp) 10962306a36Sopenharmony_ci{ 11062306a36Sopenharmony_ci struct kernfs_node *kn; 11162306a36Sopenharmony_ci kuid_t uid; 11262306a36Sopenharmony_ci kgid_t gid; 11362306a36Sopenharmony_ci int error; 11462306a36Sopenharmony_ci 11562306a36Sopenharmony_ci if (WARN_ON(!kobj || (!update && !kobj->sd))) 11662306a36Sopenharmony_ci return -EINVAL; 11762306a36Sopenharmony_ci 11862306a36Sopenharmony_ci /* Updates may happen before the object has been instantiated */ 11962306a36Sopenharmony_ci if (unlikely(update && !kobj->sd)) 12062306a36Sopenharmony_ci return -EINVAL; 12162306a36Sopenharmony_ci 12262306a36Sopenharmony_ci if (!grp->attrs && !grp->bin_attrs) { 12362306a36Sopenharmony_ci pr_debug("sysfs: (bin_)attrs not set by subsystem for group: %s/%s, skipping\n", 12462306a36Sopenharmony_ci kobj->name, grp->name ?: ""); 12562306a36Sopenharmony_ci return 0; 12662306a36Sopenharmony_ci } 12762306a36Sopenharmony_ci 12862306a36Sopenharmony_ci kobject_get_ownership(kobj, &uid, &gid); 12962306a36Sopenharmony_ci if (grp->name) { 13062306a36Sopenharmony_ci if (update) { 13162306a36Sopenharmony_ci kn = kernfs_find_and_get(kobj->sd, grp->name); 13262306a36Sopenharmony_ci if (!kn) { 13362306a36Sopenharmony_ci pr_warn("Can't update unknown attr grp name: %s/%s\n", 13462306a36Sopenharmony_ci kobj->name, grp->name); 13562306a36Sopenharmony_ci return -EINVAL; 13662306a36Sopenharmony_ci } 13762306a36Sopenharmony_ci } else { 13862306a36Sopenharmony_ci kn = kernfs_create_dir_ns(kobj->sd, grp->name, 13962306a36Sopenharmony_ci S_IRWXU | S_IRUGO | S_IXUGO, 14062306a36Sopenharmony_ci uid, gid, kobj, NULL); 14162306a36Sopenharmony_ci if (IS_ERR(kn)) { 14262306a36Sopenharmony_ci if (PTR_ERR(kn) == -EEXIST) 14362306a36Sopenharmony_ci sysfs_warn_dup(kobj->sd, grp->name); 14462306a36Sopenharmony_ci return PTR_ERR(kn); 14562306a36Sopenharmony_ci } 14662306a36Sopenharmony_ci } 14762306a36Sopenharmony_ci } else { 14862306a36Sopenharmony_ci kn = kobj->sd; 14962306a36Sopenharmony_ci } 15062306a36Sopenharmony_ci 15162306a36Sopenharmony_ci kernfs_get(kn); 15262306a36Sopenharmony_ci error = create_files(kn, kobj, uid, gid, grp, update); 15362306a36Sopenharmony_ci if (error) { 15462306a36Sopenharmony_ci if (grp->name) 15562306a36Sopenharmony_ci kernfs_remove(kn); 15662306a36Sopenharmony_ci } 15762306a36Sopenharmony_ci kernfs_put(kn); 15862306a36Sopenharmony_ci 15962306a36Sopenharmony_ci if (grp->name && update) 16062306a36Sopenharmony_ci kernfs_put(kn); 16162306a36Sopenharmony_ci 16262306a36Sopenharmony_ci return error; 16362306a36Sopenharmony_ci} 16462306a36Sopenharmony_ci 16562306a36Sopenharmony_ci/** 16662306a36Sopenharmony_ci * sysfs_create_group - given a directory kobject, create an attribute group 16762306a36Sopenharmony_ci * @kobj: The kobject to create the group on 16862306a36Sopenharmony_ci * @grp: The attribute group to create 16962306a36Sopenharmony_ci * 17062306a36Sopenharmony_ci * This function creates a group for the first time. It will explicitly 17162306a36Sopenharmony_ci * warn and error if any of the attribute files being created already exist. 17262306a36Sopenharmony_ci * 17362306a36Sopenharmony_ci * Returns 0 on success or error code on failure. 17462306a36Sopenharmony_ci */ 17562306a36Sopenharmony_ciint sysfs_create_group(struct kobject *kobj, 17662306a36Sopenharmony_ci const struct attribute_group *grp) 17762306a36Sopenharmony_ci{ 17862306a36Sopenharmony_ci return internal_create_group(kobj, 0, grp); 17962306a36Sopenharmony_ci} 18062306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(sysfs_create_group); 18162306a36Sopenharmony_ci 18262306a36Sopenharmony_cistatic int internal_create_groups(struct kobject *kobj, int update, 18362306a36Sopenharmony_ci const struct attribute_group **groups) 18462306a36Sopenharmony_ci{ 18562306a36Sopenharmony_ci int error = 0; 18662306a36Sopenharmony_ci int i; 18762306a36Sopenharmony_ci 18862306a36Sopenharmony_ci if (!groups) 18962306a36Sopenharmony_ci return 0; 19062306a36Sopenharmony_ci 19162306a36Sopenharmony_ci for (i = 0; groups[i]; i++) { 19262306a36Sopenharmony_ci error = internal_create_group(kobj, update, groups[i]); 19362306a36Sopenharmony_ci if (error) { 19462306a36Sopenharmony_ci while (--i >= 0) 19562306a36Sopenharmony_ci sysfs_remove_group(kobj, groups[i]); 19662306a36Sopenharmony_ci break; 19762306a36Sopenharmony_ci } 19862306a36Sopenharmony_ci } 19962306a36Sopenharmony_ci return error; 20062306a36Sopenharmony_ci} 20162306a36Sopenharmony_ci 20262306a36Sopenharmony_ci/** 20362306a36Sopenharmony_ci * sysfs_create_groups - given a directory kobject, create a bunch of attribute groups 20462306a36Sopenharmony_ci * @kobj: The kobject to create the group on 20562306a36Sopenharmony_ci * @groups: The attribute groups to create, NULL terminated 20662306a36Sopenharmony_ci * 20762306a36Sopenharmony_ci * This function creates a bunch of attribute groups. If an error occurs when 20862306a36Sopenharmony_ci * creating a group, all previously created groups will be removed, unwinding 20962306a36Sopenharmony_ci * everything back to the original state when this function was called. 21062306a36Sopenharmony_ci * It will explicitly warn and error if any of the attribute files being 21162306a36Sopenharmony_ci * created already exist. 21262306a36Sopenharmony_ci * 21362306a36Sopenharmony_ci * Returns 0 on success or error code from sysfs_create_group on failure. 21462306a36Sopenharmony_ci */ 21562306a36Sopenharmony_ciint sysfs_create_groups(struct kobject *kobj, 21662306a36Sopenharmony_ci const struct attribute_group **groups) 21762306a36Sopenharmony_ci{ 21862306a36Sopenharmony_ci return internal_create_groups(kobj, 0, groups); 21962306a36Sopenharmony_ci} 22062306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(sysfs_create_groups); 22162306a36Sopenharmony_ci 22262306a36Sopenharmony_ci/** 22362306a36Sopenharmony_ci * sysfs_update_groups - given a directory kobject, create a bunch of attribute groups 22462306a36Sopenharmony_ci * @kobj: The kobject to update the group on 22562306a36Sopenharmony_ci * @groups: The attribute groups to update, NULL terminated 22662306a36Sopenharmony_ci * 22762306a36Sopenharmony_ci * This function update a bunch of attribute groups. If an error occurs when 22862306a36Sopenharmony_ci * updating a group, all previously updated groups will be removed together 22962306a36Sopenharmony_ci * with already existing (not updated) attributes. 23062306a36Sopenharmony_ci * 23162306a36Sopenharmony_ci * Returns 0 on success or error code from sysfs_update_group on failure. 23262306a36Sopenharmony_ci */ 23362306a36Sopenharmony_ciint sysfs_update_groups(struct kobject *kobj, 23462306a36Sopenharmony_ci const struct attribute_group **groups) 23562306a36Sopenharmony_ci{ 23662306a36Sopenharmony_ci return internal_create_groups(kobj, 1, groups); 23762306a36Sopenharmony_ci} 23862306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(sysfs_update_groups); 23962306a36Sopenharmony_ci 24062306a36Sopenharmony_ci/** 24162306a36Sopenharmony_ci * sysfs_update_group - given a directory kobject, update an attribute group 24262306a36Sopenharmony_ci * @kobj: The kobject to update the group on 24362306a36Sopenharmony_ci * @grp: The attribute group to update 24462306a36Sopenharmony_ci * 24562306a36Sopenharmony_ci * This function updates an attribute group. Unlike 24662306a36Sopenharmony_ci * sysfs_create_group(), it will explicitly not warn or error if any 24762306a36Sopenharmony_ci * of the attribute files being created already exist. Furthermore, 24862306a36Sopenharmony_ci * if the visibility of the files has changed through the is_visible() 24962306a36Sopenharmony_ci * callback, it will update the permissions and add or remove the 25062306a36Sopenharmony_ci * relevant files. Changing a group's name (subdirectory name under 25162306a36Sopenharmony_ci * kobj's directory in sysfs) is not allowed. 25262306a36Sopenharmony_ci * 25362306a36Sopenharmony_ci * The primary use for this function is to call it after making a change 25462306a36Sopenharmony_ci * that affects group visibility. 25562306a36Sopenharmony_ci * 25662306a36Sopenharmony_ci * Returns 0 on success or error code on failure. 25762306a36Sopenharmony_ci */ 25862306a36Sopenharmony_ciint sysfs_update_group(struct kobject *kobj, 25962306a36Sopenharmony_ci const struct attribute_group *grp) 26062306a36Sopenharmony_ci{ 26162306a36Sopenharmony_ci return internal_create_group(kobj, 1, grp); 26262306a36Sopenharmony_ci} 26362306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(sysfs_update_group); 26462306a36Sopenharmony_ci 26562306a36Sopenharmony_ci/** 26662306a36Sopenharmony_ci * sysfs_remove_group: remove a group from a kobject 26762306a36Sopenharmony_ci * @kobj: kobject to remove the group from 26862306a36Sopenharmony_ci * @grp: group to remove 26962306a36Sopenharmony_ci * 27062306a36Sopenharmony_ci * This function removes a group of attributes from a kobject. The attributes 27162306a36Sopenharmony_ci * previously have to have been created for this group, otherwise it will fail. 27262306a36Sopenharmony_ci */ 27362306a36Sopenharmony_civoid sysfs_remove_group(struct kobject *kobj, 27462306a36Sopenharmony_ci const struct attribute_group *grp) 27562306a36Sopenharmony_ci{ 27662306a36Sopenharmony_ci struct kernfs_node *parent = kobj->sd; 27762306a36Sopenharmony_ci struct kernfs_node *kn; 27862306a36Sopenharmony_ci 27962306a36Sopenharmony_ci if (grp->name) { 28062306a36Sopenharmony_ci kn = kernfs_find_and_get(parent, grp->name); 28162306a36Sopenharmony_ci if (!kn) { 28262306a36Sopenharmony_ci WARN(!kn, KERN_WARNING 28362306a36Sopenharmony_ci "sysfs group '%s' not found for kobject '%s'\n", 28462306a36Sopenharmony_ci grp->name, kobject_name(kobj)); 28562306a36Sopenharmony_ci return; 28662306a36Sopenharmony_ci } 28762306a36Sopenharmony_ci } else { 28862306a36Sopenharmony_ci kn = parent; 28962306a36Sopenharmony_ci kernfs_get(kn); 29062306a36Sopenharmony_ci } 29162306a36Sopenharmony_ci 29262306a36Sopenharmony_ci remove_files(kn, grp); 29362306a36Sopenharmony_ci if (grp->name) 29462306a36Sopenharmony_ci kernfs_remove(kn); 29562306a36Sopenharmony_ci 29662306a36Sopenharmony_ci kernfs_put(kn); 29762306a36Sopenharmony_ci} 29862306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(sysfs_remove_group); 29962306a36Sopenharmony_ci 30062306a36Sopenharmony_ci/** 30162306a36Sopenharmony_ci * sysfs_remove_groups - remove a list of groups 30262306a36Sopenharmony_ci * 30362306a36Sopenharmony_ci * @kobj: The kobject for the groups to be removed from 30462306a36Sopenharmony_ci * @groups: NULL terminated list of groups to be removed 30562306a36Sopenharmony_ci * 30662306a36Sopenharmony_ci * If groups is not NULL, remove the specified groups from the kobject. 30762306a36Sopenharmony_ci */ 30862306a36Sopenharmony_civoid sysfs_remove_groups(struct kobject *kobj, 30962306a36Sopenharmony_ci const struct attribute_group **groups) 31062306a36Sopenharmony_ci{ 31162306a36Sopenharmony_ci int i; 31262306a36Sopenharmony_ci 31362306a36Sopenharmony_ci if (!groups) 31462306a36Sopenharmony_ci return; 31562306a36Sopenharmony_ci for (i = 0; groups[i]; i++) 31662306a36Sopenharmony_ci sysfs_remove_group(kobj, groups[i]); 31762306a36Sopenharmony_ci} 31862306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(sysfs_remove_groups); 31962306a36Sopenharmony_ci 32062306a36Sopenharmony_ci/** 32162306a36Sopenharmony_ci * sysfs_merge_group - merge files into a pre-existing attribute group. 32262306a36Sopenharmony_ci * @kobj: The kobject containing the group. 32362306a36Sopenharmony_ci * @grp: The files to create and the attribute group they belong to. 32462306a36Sopenharmony_ci * 32562306a36Sopenharmony_ci * This function returns an error if the group doesn't exist or any of the 32662306a36Sopenharmony_ci * files already exist in that group, in which case none of the new files 32762306a36Sopenharmony_ci * are created. 32862306a36Sopenharmony_ci */ 32962306a36Sopenharmony_ciint sysfs_merge_group(struct kobject *kobj, 33062306a36Sopenharmony_ci const struct attribute_group *grp) 33162306a36Sopenharmony_ci{ 33262306a36Sopenharmony_ci struct kernfs_node *parent; 33362306a36Sopenharmony_ci kuid_t uid; 33462306a36Sopenharmony_ci kgid_t gid; 33562306a36Sopenharmony_ci int error = 0; 33662306a36Sopenharmony_ci struct attribute *const *attr; 33762306a36Sopenharmony_ci int i; 33862306a36Sopenharmony_ci 33962306a36Sopenharmony_ci parent = kernfs_find_and_get(kobj->sd, grp->name); 34062306a36Sopenharmony_ci if (!parent) 34162306a36Sopenharmony_ci return -ENOENT; 34262306a36Sopenharmony_ci 34362306a36Sopenharmony_ci kobject_get_ownership(kobj, &uid, &gid); 34462306a36Sopenharmony_ci 34562306a36Sopenharmony_ci for ((i = 0, attr = grp->attrs); *attr && !error; (++i, ++attr)) 34662306a36Sopenharmony_ci error = sysfs_add_file_mode_ns(parent, *attr, (*attr)->mode, 34762306a36Sopenharmony_ci uid, gid, NULL); 34862306a36Sopenharmony_ci if (error) { 34962306a36Sopenharmony_ci while (--i >= 0) 35062306a36Sopenharmony_ci kernfs_remove_by_name(parent, (*--attr)->name); 35162306a36Sopenharmony_ci } 35262306a36Sopenharmony_ci kernfs_put(parent); 35362306a36Sopenharmony_ci 35462306a36Sopenharmony_ci return error; 35562306a36Sopenharmony_ci} 35662306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(sysfs_merge_group); 35762306a36Sopenharmony_ci 35862306a36Sopenharmony_ci/** 35962306a36Sopenharmony_ci * sysfs_unmerge_group - remove files from a pre-existing attribute group. 36062306a36Sopenharmony_ci * @kobj: The kobject containing the group. 36162306a36Sopenharmony_ci * @grp: The files to remove and the attribute group they belong to. 36262306a36Sopenharmony_ci */ 36362306a36Sopenharmony_civoid sysfs_unmerge_group(struct kobject *kobj, 36462306a36Sopenharmony_ci const struct attribute_group *grp) 36562306a36Sopenharmony_ci{ 36662306a36Sopenharmony_ci struct kernfs_node *parent; 36762306a36Sopenharmony_ci struct attribute *const *attr; 36862306a36Sopenharmony_ci 36962306a36Sopenharmony_ci parent = kernfs_find_and_get(kobj->sd, grp->name); 37062306a36Sopenharmony_ci if (parent) { 37162306a36Sopenharmony_ci for (attr = grp->attrs; *attr; ++attr) 37262306a36Sopenharmony_ci kernfs_remove_by_name(parent, (*attr)->name); 37362306a36Sopenharmony_ci kernfs_put(parent); 37462306a36Sopenharmony_ci } 37562306a36Sopenharmony_ci} 37662306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(sysfs_unmerge_group); 37762306a36Sopenharmony_ci 37862306a36Sopenharmony_ci/** 37962306a36Sopenharmony_ci * sysfs_add_link_to_group - add a symlink to an attribute group. 38062306a36Sopenharmony_ci * @kobj: The kobject containing the group. 38162306a36Sopenharmony_ci * @group_name: The name of the group. 38262306a36Sopenharmony_ci * @target: The target kobject of the symlink to create. 38362306a36Sopenharmony_ci * @link_name: The name of the symlink to create. 38462306a36Sopenharmony_ci */ 38562306a36Sopenharmony_ciint sysfs_add_link_to_group(struct kobject *kobj, const char *group_name, 38662306a36Sopenharmony_ci struct kobject *target, const char *link_name) 38762306a36Sopenharmony_ci{ 38862306a36Sopenharmony_ci struct kernfs_node *parent; 38962306a36Sopenharmony_ci int error = 0; 39062306a36Sopenharmony_ci 39162306a36Sopenharmony_ci parent = kernfs_find_and_get(kobj->sd, group_name); 39262306a36Sopenharmony_ci if (!parent) 39362306a36Sopenharmony_ci return -ENOENT; 39462306a36Sopenharmony_ci 39562306a36Sopenharmony_ci error = sysfs_create_link_sd(parent, target, link_name); 39662306a36Sopenharmony_ci kernfs_put(parent); 39762306a36Sopenharmony_ci 39862306a36Sopenharmony_ci return error; 39962306a36Sopenharmony_ci} 40062306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(sysfs_add_link_to_group); 40162306a36Sopenharmony_ci 40262306a36Sopenharmony_ci/** 40362306a36Sopenharmony_ci * sysfs_remove_link_from_group - remove a symlink from an attribute group. 40462306a36Sopenharmony_ci * @kobj: The kobject containing the group. 40562306a36Sopenharmony_ci * @group_name: The name of the group. 40662306a36Sopenharmony_ci * @link_name: The name of the symlink to remove. 40762306a36Sopenharmony_ci */ 40862306a36Sopenharmony_civoid sysfs_remove_link_from_group(struct kobject *kobj, const char *group_name, 40962306a36Sopenharmony_ci const char *link_name) 41062306a36Sopenharmony_ci{ 41162306a36Sopenharmony_ci struct kernfs_node *parent; 41262306a36Sopenharmony_ci 41362306a36Sopenharmony_ci parent = kernfs_find_and_get(kobj->sd, group_name); 41462306a36Sopenharmony_ci if (parent) { 41562306a36Sopenharmony_ci kernfs_remove_by_name(parent, link_name); 41662306a36Sopenharmony_ci kernfs_put(parent); 41762306a36Sopenharmony_ci } 41862306a36Sopenharmony_ci} 41962306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(sysfs_remove_link_from_group); 42062306a36Sopenharmony_ci 42162306a36Sopenharmony_ci/** 42262306a36Sopenharmony_ci * compat_only_sysfs_link_entry_to_kobj - add a symlink to a kobject pointing 42362306a36Sopenharmony_ci * to a group or an attribute 42462306a36Sopenharmony_ci * @kobj: The kobject containing the group. 42562306a36Sopenharmony_ci * @target_kobj: The target kobject. 42662306a36Sopenharmony_ci * @target_name: The name of the target group or attribute. 42762306a36Sopenharmony_ci * @symlink_name: The name of the symlink file (target_name will be 42862306a36Sopenharmony_ci * considered if symlink_name is NULL). 42962306a36Sopenharmony_ci */ 43062306a36Sopenharmony_ciint compat_only_sysfs_link_entry_to_kobj(struct kobject *kobj, 43162306a36Sopenharmony_ci struct kobject *target_kobj, 43262306a36Sopenharmony_ci const char *target_name, 43362306a36Sopenharmony_ci const char *symlink_name) 43462306a36Sopenharmony_ci{ 43562306a36Sopenharmony_ci struct kernfs_node *target; 43662306a36Sopenharmony_ci struct kernfs_node *entry; 43762306a36Sopenharmony_ci struct kernfs_node *link; 43862306a36Sopenharmony_ci 43962306a36Sopenharmony_ci /* 44062306a36Sopenharmony_ci * We don't own @target_kobj and it may be removed at any time. 44162306a36Sopenharmony_ci * Synchronize using sysfs_symlink_target_lock. See sysfs_remove_dir() 44262306a36Sopenharmony_ci * for details. 44362306a36Sopenharmony_ci */ 44462306a36Sopenharmony_ci spin_lock(&sysfs_symlink_target_lock); 44562306a36Sopenharmony_ci target = target_kobj->sd; 44662306a36Sopenharmony_ci if (target) 44762306a36Sopenharmony_ci kernfs_get(target); 44862306a36Sopenharmony_ci spin_unlock(&sysfs_symlink_target_lock); 44962306a36Sopenharmony_ci if (!target) 45062306a36Sopenharmony_ci return -ENOENT; 45162306a36Sopenharmony_ci 45262306a36Sopenharmony_ci entry = kernfs_find_and_get(target, target_name); 45362306a36Sopenharmony_ci if (!entry) { 45462306a36Sopenharmony_ci kernfs_put(target); 45562306a36Sopenharmony_ci return -ENOENT; 45662306a36Sopenharmony_ci } 45762306a36Sopenharmony_ci 45862306a36Sopenharmony_ci if (!symlink_name) 45962306a36Sopenharmony_ci symlink_name = target_name; 46062306a36Sopenharmony_ci 46162306a36Sopenharmony_ci link = kernfs_create_link(kobj->sd, symlink_name, entry); 46262306a36Sopenharmony_ci if (PTR_ERR(link) == -EEXIST) 46362306a36Sopenharmony_ci sysfs_warn_dup(kobj->sd, symlink_name); 46462306a36Sopenharmony_ci 46562306a36Sopenharmony_ci kernfs_put(entry); 46662306a36Sopenharmony_ci kernfs_put(target); 46762306a36Sopenharmony_ci return PTR_ERR_OR_ZERO(link); 46862306a36Sopenharmony_ci} 46962306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(compat_only_sysfs_link_entry_to_kobj); 47062306a36Sopenharmony_ci 47162306a36Sopenharmony_cistatic int sysfs_group_attrs_change_owner(struct kernfs_node *grp_kn, 47262306a36Sopenharmony_ci const struct attribute_group *grp, 47362306a36Sopenharmony_ci struct iattr *newattrs) 47462306a36Sopenharmony_ci{ 47562306a36Sopenharmony_ci struct kernfs_node *kn; 47662306a36Sopenharmony_ci int error; 47762306a36Sopenharmony_ci 47862306a36Sopenharmony_ci if (grp->attrs) { 47962306a36Sopenharmony_ci struct attribute *const *attr; 48062306a36Sopenharmony_ci 48162306a36Sopenharmony_ci for (attr = grp->attrs; *attr; attr++) { 48262306a36Sopenharmony_ci kn = kernfs_find_and_get(grp_kn, (*attr)->name); 48362306a36Sopenharmony_ci if (!kn) 48462306a36Sopenharmony_ci return -ENOENT; 48562306a36Sopenharmony_ci 48662306a36Sopenharmony_ci error = kernfs_setattr(kn, newattrs); 48762306a36Sopenharmony_ci kernfs_put(kn); 48862306a36Sopenharmony_ci if (error) 48962306a36Sopenharmony_ci return error; 49062306a36Sopenharmony_ci } 49162306a36Sopenharmony_ci } 49262306a36Sopenharmony_ci 49362306a36Sopenharmony_ci if (grp->bin_attrs) { 49462306a36Sopenharmony_ci struct bin_attribute *const *bin_attr; 49562306a36Sopenharmony_ci 49662306a36Sopenharmony_ci for (bin_attr = grp->bin_attrs; *bin_attr; bin_attr++) { 49762306a36Sopenharmony_ci kn = kernfs_find_and_get(grp_kn, (*bin_attr)->attr.name); 49862306a36Sopenharmony_ci if (!kn) 49962306a36Sopenharmony_ci return -ENOENT; 50062306a36Sopenharmony_ci 50162306a36Sopenharmony_ci error = kernfs_setattr(kn, newattrs); 50262306a36Sopenharmony_ci kernfs_put(kn); 50362306a36Sopenharmony_ci if (error) 50462306a36Sopenharmony_ci return error; 50562306a36Sopenharmony_ci } 50662306a36Sopenharmony_ci } 50762306a36Sopenharmony_ci 50862306a36Sopenharmony_ci return 0; 50962306a36Sopenharmony_ci} 51062306a36Sopenharmony_ci 51162306a36Sopenharmony_ci/** 51262306a36Sopenharmony_ci * sysfs_group_change_owner - change owner of an attribute group. 51362306a36Sopenharmony_ci * @kobj: The kobject containing the group. 51462306a36Sopenharmony_ci * @grp: The attribute group. 51562306a36Sopenharmony_ci * @kuid: new owner's kuid 51662306a36Sopenharmony_ci * @kgid: new owner's kgid 51762306a36Sopenharmony_ci * 51862306a36Sopenharmony_ci * Returns 0 on success or error code on failure. 51962306a36Sopenharmony_ci */ 52062306a36Sopenharmony_ciint sysfs_group_change_owner(struct kobject *kobj, 52162306a36Sopenharmony_ci const struct attribute_group *grp, kuid_t kuid, 52262306a36Sopenharmony_ci kgid_t kgid) 52362306a36Sopenharmony_ci{ 52462306a36Sopenharmony_ci struct kernfs_node *grp_kn; 52562306a36Sopenharmony_ci int error; 52662306a36Sopenharmony_ci struct iattr newattrs = { 52762306a36Sopenharmony_ci .ia_valid = ATTR_UID | ATTR_GID, 52862306a36Sopenharmony_ci .ia_uid = kuid, 52962306a36Sopenharmony_ci .ia_gid = kgid, 53062306a36Sopenharmony_ci }; 53162306a36Sopenharmony_ci 53262306a36Sopenharmony_ci if (!kobj->state_in_sysfs) 53362306a36Sopenharmony_ci return -EINVAL; 53462306a36Sopenharmony_ci 53562306a36Sopenharmony_ci if (grp->name) { 53662306a36Sopenharmony_ci grp_kn = kernfs_find_and_get(kobj->sd, grp->name); 53762306a36Sopenharmony_ci } else { 53862306a36Sopenharmony_ci kernfs_get(kobj->sd); 53962306a36Sopenharmony_ci grp_kn = kobj->sd; 54062306a36Sopenharmony_ci } 54162306a36Sopenharmony_ci if (!grp_kn) 54262306a36Sopenharmony_ci return -ENOENT; 54362306a36Sopenharmony_ci 54462306a36Sopenharmony_ci error = kernfs_setattr(grp_kn, &newattrs); 54562306a36Sopenharmony_ci if (!error) 54662306a36Sopenharmony_ci error = sysfs_group_attrs_change_owner(grp_kn, grp, &newattrs); 54762306a36Sopenharmony_ci 54862306a36Sopenharmony_ci kernfs_put(grp_kn); 54962306a36Sopenharmony_ci 55062306a36Sopenharmony_ci return error; 55162306a36Sopenharmony_ci} 55262306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(sysfs_group_change_owner); 55362306a36Sopenharmony_ci 55462306a36Sopenharmony_ci/** 55562306a36Sopenharmony_ci * sysfs_groups_change_owner - change owner of a set of attribute groups. 55662306a36Sopenharmony_ci * @kobj: The kobject containing the groups. 55762306a36Sopenharmony_ci * @groups: The attribute groups. 55862306a36Sopenharmony_ci * @kuid: new owner's kuid 55962306a36Sopenharmony_ci * @kgid: new owner's kgid 56062306a36Sopenharmony_ci * 56162306a36Sopenharmony_ci * Returns 0 on success or error code on failure. 56262306a36Sopenharmony_ci */ 56362306a36Sopenharmony_ciint sysfs_groups_change_owner(struct kobject *kobj, 56462306a36Sopenharmony_ci const struct attribute_group **groups, 56562306a36Sopenharmony_ci kuid_t kuid, kgid_t kgid) 56662306a36Sopenharmony_ci{ 56762306a36Sopenharmony_ci int error = 0, i; 56862306a36Sopenharmony_ci 56962306a36Sopenharmony_ci if (!kobj->state_in_sysfs) 57062306a36Sopenharmony_ci return -EINVAL; 57162306a36Sopenharmony_ci 57262306a36Sopenharmony_ci if (!groups) 57362306a36Sopenharmony_ci return 0; 57462306a36Sopenharmony_ci 57562306a36Sopenharmony_ci for (i = 0; groups[i]; i++) { 57662306a36Sopenharmony_ci error = sysfs_group_change_owner(kobj, groups[i], kuid, kgid); 57762306a36Sopenharmony_ci if (error) 57862306a36Sopenharmony_ci break; 57962306a36Sopenharmony_ci } 58062306a36Sopenharmony_ci 58162306a36Sopenharmony_ci return error; 58262306a36Sopenharmony_ci} 58362306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(sysfs_groups_change_owner); 584