// SPDX-License-Identifier: GPL-2.0
/*
* fs / sysfs / group . c - Operations for adding / removing multiple files at once .
*
* Copyright ( c ) 2003 Patrick Mochel
* Copyright ( c ) 2003 Open Source Development Lab
* Copyright ( c ) 2013 Greg Kroah - Hartman
* Copyright ( c ) 2013 The Linux Foundation
*/
#include <linux/kobject.h>
#include <linux/module.h>
#include <linux/dcache.h>
#include <linux/namei.h>
#include <linux/err.h>
#include <linux/fs.h>
#include "sysfs.h"
static void remove_files(struct kernfs_node *parent,
const struct attribute_group *grp)
{
struct attribute *const *attr;
const struct bin_attribute *const *bin_attr;
if (grp->attrs)
for (attr = grp->attrs; *attr; attr++)
kernfs_remove_by_name(parent, (*attr)->name);
if (grp->bin_attrs)
for (bin_attr = grp->bin_attrs; *bin_attr; bin_attr++)
kernfs_remove_by_name(parent, (*bin_attr)->attr.name);
}
static umode_t __first_visible(const struct attribute_group *grp, struct kobject *kobj)
{
if (grp->attrs && grp->attrs[0 ] && grp->is_visible)
return grp->is_visible(kobj, grp->attrs[0 ], 0 );
if (grp->bin_attrs && grp->bin_attrs[0 ] && grp->is_bin_visible)
return grp->is_bin_visible(kobj, grp->bin_attrs[0 ], 0 );
return 0 ;
}
static int create_files(struct kernfs_node *parent, struct kobject *kobj,
kuid_t uid, kgid_t gid,
const struct attribute_group *grp, int update)
{
struct attribute *const *attr;
const struct bin_attribute *const *bin_attr;
int error = 0 , i;
if (grp->attrs) {
for (i = 0 , attr = grp->attrs; *attr && !error; i++, attr++) {
umode_t mode = (*attr)->mode;
/*
* In update mode , we ' re changing the permissions or
* visibility . Do this by first removing then
* re - adding ( if required ) the file .
*/
if (update)
kernfs_remove_by_name(parent, (*attr)->name);
if (grp->is_visible) {
mode = grp->is_visible(kobj, *attr, i);
mode &= ~SYSFS_GROUP_INVISIBLE;
if (!mode)
continue ;
}
WARN(mode & ~(SYSFS_PREALLOC | 0664 ),
"Attribute %s: Invalid permissions 0%o\n" ,
(*attr)->name, mode);
mode &= SYSFS_PREALLOC | 0664 ;
error = sysfs_add_file_mode_ns(parent, *attr, mode, uid,
gid, NULL);
if (unlikely(error))
break ;
}
if (error) {
remove_files(parent, grp);
goto exit ;
}
}
if (grp->bin_attrs) {
for (i = 0 , bin_attr = grp->bin_attrs; *bin_attr; i++, bin_attr++) {
umode_t mode = (*bin_attr)->attr.mode;
size_t size = (*bin_attr)->size;
if (update)
kernfs_remove_by_name(parent,
(*bin_attr)->attr.name);
if (grp->is_bin_visible) {
mode = grp->is_bin_visible(kobj, *bin_attr, i);
mode &= ~SYSFS_GROUP_INVISIBLE;
if (!mode)
continue ;
}
if (grp->bin_size)
size = grp->bin_size(kobj, *bin_attr, i);
WARN(mode & ~(SYSFS_PREALLOC | 0664 ),
"Attribute %s: Invalid permissions 0%o\n" ,
(*bin_attr)->attr.name, mode);
mode &= SYSFS_PREALLOC | 0664 ;
error = sysfs_add_bin_file_mode_ns(parent, *bin_attr,
mode, size, uid, gid,
NULL);
if (error)
break ;
}
if (error)
remove_files(parent, grp);
}
exit :
return error;
}
static int internal_create_group(struct kobject *kobj, int update,
const struct attribute_group *grp)
{
struct kernfs_node *kn;
kuid_t uid;
kgid_t gid;
int error;
if (WARN_ON(!kobj || (!update && !kobj->sd)))
return -EINVAL;
/* Updates may happen before the object has been instantiated */
if (unlikely(update && !kobj->sd))
return -EINVAL;
if (!grp->attrs && !grp->bin_attrs) {
pr_debug("sysfs: (bin_)attrs not set by subsystem for group: %s/%s, skipping\n" ,
kobj->name, grp->name ?: "" );
return 0 ;
}
kobject_get_ownership(kobj, &uid, &gid);
if (grp->name) {
umode_t mode = __first_visible(grp, kobj);
if (mode & SYSFS_GROUP_INVISIBLE)
mode = 0 ;
else
mode = S_IRWXU | S_IRUGO | S_IXUGO;
if (update) {
kn = kernfs_find_and_get(kobj->sd, grp->name);
if (!kn) {
pr_debug("attr grp %s/%s not created yet\n" ,
kobj->name, grp->name);
/* may have been invisible prior to this update */
update = 0 ;
} else if (!mode) {
sysfs_remove_group(kobj, grp);
kernfs_put(kn);
return 0 ;
}
}
if (!update) {
if (!mode)
return 0 ;
kn = kernfs_create_dir_ns(kobj->sd, grp->name, mode,
uid, gid, kobj, NULL);
if (IS_ERR(kn)) {
if (PTR_ERR(kn) == -EEXIST)
sysfs_warn_dup(kobj->sd, grp->name);
return PTR_ERR(kn);
}
}
} else {
kn = kobj->sd;
}
kernfs_get(kn);
error = create_files(kn, kobj, uid, gid, grp, update);
if (error) {
if (grp->name)
kernfs_remove(kn);
}
kernfs_put(kn);
if (grp->name && update)
kernfs_put(kn);
return error;
}
/**
* sysfs_create_group - given a directory kobject , create an attribute group
* @ kobj : The kobject to create the group on
* @ grp : The attribute group to create
*
* This function creates a group for the first time . It will explicitly
* warn and error if any of the attribute files being created already exist .
*
* Returns 0 on success or error code on failure .
*/
int sysfs_create_group(struct kobject *kobj,
const struct attribute_group *grp)
{
return internal_create_group(kobj, 0 , grp);
}
EXPORT_SYMBOL_GPL(sysfs_create_group);
static int internal_create_groups(struct kobject *kobj, int update,
const struct attribute_group **groups)
{
int error = 0 ;
int i;
if (!groups)
return 0 ;
for (i = 0 ; groups[i]; i++) {
error = internal_create_group(kobj, update, groups[i]);
if (error) {
while (--i >= 0 )
sysfs_remove_group(kobj, groups[i]);
break ;
}
}
return error;
}
/**
* sysfs_create_groups - given a directory kobject , create a bunch of attribute groups
* @ kobj : The kobject to create the group on
* @ groups : The attribute groups to create , NULL terminated
*
* This function creates a bunch of attribute groups . If an error occurs when
* creating a group , all previously created groups will be removed , unwinding
* everything back to the original state when this function was called .
* It will explicitly warn and error if any of the attribute files being
* created already exist .
*
* Returns 0 on success or error code from sysfs_create_group on failure .
*/
int sysfs_create_groups(struct kobject *kobj,
const struct attribute_group **groups)
{
return internal_create_groups(kobj, 0 , groups);
}
EXPORT_SYMBOL_GPL(sysfs_create_groups);
/**
* sysfs_update_groups - given a directory kobject , create a bunch of attribute groups
* @ kobj : The kobject to update the group on
* @ groups : The attribute groups to update , NULL terminated
*
* This function update a bunch of attribute groups . If an error occurs when
* updating a group , all previously updated groups will be removed together
* with already existing ( not updated ) attributes .
*
* Returns 0 on success or error code from sysfs_update_group on failure .
*/
int sysfs_update_groups(struct kobject *kobj,
const struct attribute_group **groups)
{
return internal_create_groups(kobj, 1 , groups);
}
EXPORT_SYMBOL_GPL(sysfs_update_groups);
/**
* sysfs_update_group - given a directory kobject , update an attribute group
* @ kobj : The kobject to update the group on
* @ grp : The attribute group to update
*
* This function updates an attribute group . Unlike
* sysfs_create_group ( ) , it will explicitly not warn or error if any
* of the attribute files being created already exist . Furthermore ,
* if the visibility of the files has changed through the is_visible ( )
* callback , it will update the permissions and add or remove the
* relevant files . Changing a group ' s name ( subdirectory name under
* kobj ' s directory in sysfs ) is not allowed .
*
* The primary use for this function is to call it after making a change
* that affects group visibility .
*
* Returns 0 on success or error code on failure .
*/
int sysfs_update_group(struct kobject *kobj,
const struct attribute_group *grp)
{
return internal_create_group(kobj, 1 , grp);
}
EXPORT_SYMBOL_GPL(sysfs_update_group);
/**
* sysfs_remove_group : remove a group from a kobject
* @ kobj : kobject to remove the group from
* @ grp : group to remove
*
* This function removes a group of attributes from a kobject . The attributes
* previously have to have been created for this group , otherwise it will fail .
*/
void sysfs_remove_group(struct kobject *kobj,
const struct attribute_group *grp)
{
struct kernfs_node *parent = kobj->sd;
struct kernfs_node *kn;
if (grp->name) {
kn = kernfs_find_and_get(parent, grp->name);
if (!kn) {
pr_debug("sysfs group '%s' not found for kobject '%s'\n" ,
grp->name, kobject_name(kobj));
return ;
}
} else {
kn = parent;
kernfs_get(kn);
}
remove_files(kn, grp);
if (grp->name)
kernfs_remove(kn);
kernfs_put(kn);
}
EXPORT_SYMBOL_GPL(sysfs_remove_group);
/**
* sysfs_remove_groups - remove a list of groups
*
* @ kobj : The kobject for the groups to be removed from
* @ groups : NULL terminated list of groups to be removed
*
* If groups is not NULL , remove the specified groups from the kobject .
*/
void sysfs_remove_groups(struct kobject *kobj,
const struct attribute_group **groups)
{
int i;
if (!groups)
return ;
for (i = 0 ; groups[i]; i++)
sysfs_remove_group(kobj, groups[i]);
}
EXPORT_SYMBOL_GPL(sysfs_remove_groups);
/**
* sysfs_merge_group - merge files into a pre - existing named attribute group .
* @ kobj : The kobject containing the group .
* @ grp : The files to create and the attribute group they belong to .
*
* This function returns an error if the group doesn ' t exist , the . name field is
* NULL or any of the files already exist in that group , in which case none of
* the new files are created .
*/
int sysfs_merge_group(struct kobject *kobj,
const struct attribute_group *grp)
{
struct kernfs_node *parent;
kuid_t uid;
kgid_t gid;
int error = 0 ;
struct attribute *const *attr;
int i;
parent = kernfs_find_and_get(kobj->sd, grp->name);
if (!parent)
return -ENOENT;
kobject_get_ownership(kobj, &uid, &gid);
for ((i = 0 , attr = grp->attrs); *attr && !error; (++i, ++attr))
error = sysfs_add_file_mode_ns(parent, *attr, (*attr)->mode,
uid, gid, NULL);
if (error) {
while (--i >= 0 )
kernfs_remove_by_name(parent, (*--attr)->name);
}
kernfs_put(parent);
return error;
}
EXPORT_SYMBOL_GPL(sysfs_merge_group);
/**
* sysfs_unmerge_group - remove files from a pre - existing named attribute group .
* @ kobj : The kobject containing the group .
* @ grp : The files to remove and the attribute group they belong to .
*/
void sysfs_unmerge_group(struct kobject *kobj,
const struct attribute_group *grp)
{
struct kernfs_node *parent;
struct attribute *const *attr;
parent = kernfs_find_and_get(kobj->sd, grp->name);
if (parent) {
for (attr = grp->attrs; *attr; ++attr)
kernfs_remove_by_name(parent, (*attr)->name);
kernfs_put(parent);
}
}
EXPORT_SYMBOL_GPL(sysfs_unmerge_group);
/**
* sysfs_add_link_to_group - add a symlink to an attribute group .
* @ kobj : The kobject containing the group .
* @ group_name : The name of the group .
* @ target : The target kobject of the symlink to create .
* @ link_name : The name of the symlink to create .
*/
int sysfs_add_link_to_group(struct kobject *kobj, const char *group_name,
struct kobject *target, const char *link_name)
{
struct kernfs_node *parent;
int error = 0 ;
parent = kernfs_find_and_get(kobj->sd, group_name);
if (!parent)
return -ENOENT;
error = sysfs_create_link_sd(parent, target, link_name);
kernfs_put(parent);
return error;
}
EXPORT_SYMBOL_GPL(sysfs_add_link_to_group);
/**
* sysfs_remove_link_from_group - remove a symlink from an attribute group .
* @ kobj : The kobject containing the group .
* @ group_name : The name of the group .
* @ link_name : The name of the symlink to remove .
*/
void sysfs_remove_link_from_group(struct kobject *kobj, const char *group_name,
const char *link_name)
{
struct kernfs_node *parent;
parent = kernfs_find_and_get(kobj->sd, group_name);
if (parent) {
kernfs_remove_by_name(parent, link_name);
kernfs_put(parent);
}
}
EXPORT_SYMBOL_GPL(sysfs_remove_link_from_group);
/**
* compat_only_sysfs_link_entry_to_kobj - add a symlink to a kobject pointing
* to a group or an attribute
* @ kobj : The kobject containing the group .
* @ target_kobj : The target kobject .
* @ target_name : The name of the target group or attribute .
* @ symlink_name : The name of the symlink file ( target_name will be
* considered if symlink_name is NULL ) .
*/
int compat_only_sysfs_link_entry_to_kobj(struct kobject *kobj,
struct kobject *target_kobj,
const char *target_name,
const char *symlink_name)
{
struct kernfs_node *target;
struct kernfs_node *entry;
struct kernfs_node *link;
/*
* We don ' t own @ target_kobj and it may be removed at any time .
* Synchronize using sysfs_symlink_target_lock . See sysfs_remove_dir ( )
* for details .
*/
spin_lock(&sysfs_symlink_target_lock);
target = target_kobj->sd;
if (target)
kernfs_get(target);
spin_unlock(&sysfs_symlink_target_lock);
if (!target)
return -ENOENT;
entry = kernfs_find_and_get(target, target_name);
if (!entry) {
kernfs_put(target);
return -ENOENT;
}
if (!symlink_name)
symlink_name = target_name;
link = kernfs_create_link(kobj->sd, symlink_name, entry);
if (PTR_ERR(link) == -EEXIST)
sysfs_warn_dup(kobj->sd, symlink_name);
kernfs_put(entry);
kernfs_put(target);
return PTR_ERR_OR_ZERO(link);
}
EXPORT_SYMBOL_GPL(compat_only_sysfs_link_entry_to_kobj);
static int sysfs_group_attrs_change_owner(struct kobject *kobj,
struct kernfs_node *grp_kn,
const struct attribute_group *grp,
struct iattr *newattrs)
{
struct kernfs_node *kn;
int error, i;
umode_t mode;
if (grp->attrs) {
struct attribute *const *attr;
for (i = 0 , attr = grp->attrs; *attr; i++, attr++) {
if (grp->is_visible) {
mode = grp->is_visible(kobj, *attr, i);
if (mode & SYSFS_GROUP_INVISIBLE)
break ;
if (!mode)
continue ;
}
kn = kernfs_find_and_get(grp_kn, (*attr)->name);
if (!kn)
return -ENOENT;
error = kernfs_setattr(kn, newattrs);
kernfs_put(kn);
if (error)
return error;
}
}
if (grp->bin_attrs) {
const struct bin_attribute *const *bin_attr;
for (i = 0 , bin_attr = grp->bin_attrs; *bin_attr; i++, bin_attr++) {
if (grp->is_bin_visible) {
mode = grp->is_bin_visible(kobj, *bin_attr, i);
if (mode & SYSFS_GROUP_INVISIBLE)
break ;
if (!mode)
continue ;
}
kn = kernfs_find_and_get(grp_kn, (*bin_attr)->attr.name);
if (!kn)
return -ENOENT;
error = kernfs_setattr(kn, newattrs);
kernfs_put(kn);
if (error)
return error;
}
}
return 0 ;
}
/**
* sysfs_group_change_owner - change owner of an attribute group .
* @ kobj : The kobject containing the group .
* @ grp : The attribute group .
* @ kuid : new owner ' s kuid
* @ kgid : new owner ' s kgid
*
* Returns 0 on success or error code on failure .
*/
int sysfs_group_change_owner(struct kobject *kobj,
const struct attribute_group *grp, kuid_t kuid,
kgid_t kgid)
{
struct kernfs_node *grp_kn;
int error;
struct iattr newattrs = {
.ia_valid = ATTR_UID | ATTR_GID,
.ia_uid = kuid,
.ia_gid = kgid,
};
if (!kobj->state_in_sysfs)
return -EINVAL;
if (grp->name) {
grp_kn = kernfs_find_and_get(kobj->sd, grp->name);
} else {
kernfs_get(kobj->sd);
grp_kn = kobj->sd;
}
if (!grp_kn)
return -ENOENT;
error = kernfs_setattr(grp_kn, &newattrs);
if (!error)
error = sysfs_group_attrs_change_owner(kobj, grp_kn, grp, &newattrs);
kernfs_put(grp_kn);
return error;
}
EXPORT_SYMBOL_GPL(sysfs_group_change_owner);
/**
* sysfs_groups_change_owner - change owner of a set of attribute groups .
* @ kobj : The kobject containing the groups .
* @ groups : The attribute groups .
* @ kuid : new owner ' s kuid
* @ kgid : new owner ' s kgid
*
* Returns 0 on success or error code on failure .
*/
int sysfs_groups_change_owner(struct kobject *kobj,
const struct attribute_group **groups,
kuid_t kuid, kgid_t kgid)
{
int error = 0 , i;
if (!kobj->state_in_sysfs)
return -EINVAL;
if (!groups)
return 0 ;
for (i = 0 ; groups[i]; i++) {
error = sysfs_group_change_owner(kobj, groups[i], kuid, kgid);
if (error)
break ;
}
return error;
}
EXPORT_SYMBOL_GPL(sysfs_groups_change_owner);
Messung V0.5 in Prozent C=98 H=90 G=94
¤ Dauer der Verarbeitung: 0.13 Sekunden
(vorverarbeitet am 2026-09-29)
¤
*© Formatika GbR, Deutschland