root/fs/sysfs/group.c
// 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->attrs && grp->attrs[0] && grp->is_visible_const)
                return grp->is_visible_const(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 || grp->is_visible_const) {
                                if (grp->is_visible)
                                        mode = grp->is_visible(kobj, *attr, i);
                                else
                                        mode = grp->is_visible_const(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);