linux/fs/sysfs/group.c
Linus Torvalds 63f4f7e8df platform/chrome: Branch for v4.4
Here's the branch of chrome platform changes for v4.4. Some have been queued
 up for the full 4.3 release cycle since I forgot to send them in for that
 round (rebased early on to deal with fixes conflicts).
 
 Most of these enable EC communication stuff -- Pixel 2015 support, enabling
 building for ARM64 platforms, and a few fixes for memory leaks.
 
 There's also a patch in here to allow reading/writing the verified boot
 context, which depends on a sysfs patch acked by Greg.
 -----BEGIN PGP SIGNATURE-----
 Version: GnuPG v1
 
 iQIcBAABAgAGBQJWQWj/AAoJEIwa5zzehBx3figQAJuZK+2PDMeOe3hg38WEHpAA
 NVaSkZss2VLN8SQCJ8BqB/xoM2lm3GSef2R705m7s20/FsPP7rcJZoeeNkWUK+rn
 Dc01uJpVwGVtBIjst0FkxS+3xBeUrX6QLQyAnxQV4SIOg4t1EtZvgxK/4wnE726H
 BV0bTsUHKo0+FSrtI/QoHFkSFbUBansQ7JuN00YOfuwJPdhKgpInbJPqVkmRAYqI
 ylFFiTUR946SNDMUvmDnd37I2FrIzxslNTBu/oKl0DKI7DPmN3ITvX2JqnnuxktA
 ru7TWbny+oa2TQAwynYxBq9hpMFHMVVbtDPXE/7MisfL4+KUaSE0e95b4ONfG7yZ
 oxzf6S1RxzhA0Eo/bFZ0i29DZ4ogR8ql65RNolWJg13XACrPVyGrTWW5ZFe6xbI9
 HS13oBOHXIllSDfq9QjMRtNHAv268/oTiF8LXLPrxSCYllGZnKL7g9Ae9dFqOdXx
 mnoqYtVPsaErek4ktqOZIMRY5mXpX2JyR642PjGbK71IjRKmGvvRqpee+iCpF7ew
 OR80tAq56/R3NQqp5pgReshglOwQsLdu99spSc+6anjdeSb85ujM2jwXGjLNHwDi
 9XpyIAXox5dy3CX8LqgqryIKp/vqRd4q1vugKXLL1dBMSAm/QltjqPWxB2fCRCve
 IqNEhz4EI6uhJVeqDSNo
 =dv1O
 -----END PGP SIGNATURE-----

Merge tag 'chrome-platform-4.4' of git://git.kernel.org/pub/scm/linux/kernel/git/olof/chrome-platform

Pull chrome platform updates from Olof Johansson:
 "Here's the branch of chrome platform changes for v4.4.  Some have been
  queued up for the full 4.3 release cycle since I forgot to send them
  in for that round (rebased early on to deal with fixes conflicts).

  Most of these enable EC communication stuff -- Pixel 2015 support,
  enabling building for ARM64 platforms, and a few fixes for memory
  leaks.

  There's also a patch in here to allow reading/writing the verified
  boot context, which depends on a sysfs patch acked by Greg"

* tag 'chrome-platform-4.4' of git://git.kernel.org/pub/scm/linux/kernel/git/olof/chrome-platform:
  platform/chrome: Fix i2c-designware adapter name
  platform/chrome: Support reading/writing the vboot context
  sysfs: Support is_visible() on binary attributes
  platform/chrome: cros_ec: Fix possible leak in led_rgb_store()
  platform/chrome: cros_ec: Fix leak in sequence_store()
  platform/chrome: Enable Chrome platforms on 64-bit ARM
  platform/chrome: cros_ec_dev - Add a platform device ID table
  platform/chrome: cros_ec_lpc - Add support for Google Pixel 2
  platform/chrome: cros_ec_lpc - Use existing function to check EC result
  platform/chrome: Make depends on MFD_CROS_EC instead CROS_EC_PROTO
  Revert "platform/chrome: Don't make CHROME_PLATFORMS depends on X86 || ARM"
2015-11-13 21:53:18 -08:00

411 lines
11 KiB
C

/*
* 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
*
* This file is released undert the GPL v2.
*
*/
#include <linux/kobject.h>
#include <linux/module.h>
#include <linux/dcache.h>
#include <linux/namei.h>
#include <linux/err.h>
#include "sysfs.h"
static void remove_files(struct kernfs_node *parent,
const struct attribute_group *grp)
{
struct attribute *const *attr;
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 int create_files(struct kernfs_node *parent, struct kobject *kobj,
const struct attribute_group *grp, int update)
{
struct attribute *const *attr;
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);
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, false,
mode, 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;
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);
if (!mode)
continue;
}
WARN(mode & ~(SYSFS_PREALLOC | 0664),
"Attribute %s: Invalid permissions 0%o\n",
(*bin_attr)->attr.name, mode);
mode &= SYSFS_PREALLOC | 0664;
error = sysfs_add_file_mode_ns(parent,
&(*bin_attr)->attr, true,
mode, 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;
int error;
BUG_ON(!kobj || (!update && !kobj->sd));
/* Updates may happen before the object has been instantiated */
if (unlikely(update && !kobj->sd))
return -EINVAL;
if (!grp->attrs && !grp->bin_attrs) {
WARN(1, "sysfs: (bin_)attrs not set by subsystem for group: %s/%s\n",
kobj->name, grp->name ?: "");
return -EINVAL;
}
if (grp->name) {
kn = kernfs_create_dir(kobj->sd, grp->name,
S_IRWXU | S_IRUGO | S_IXUGO, kobj);
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, grp, update);
if (error) {
if (grp->name)
kernfs_remove(kn);
}
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);
/**
* 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)
{
int error = 0;
int i;
if (!groups)
return 0;
for (i = 0; groups[i]; i++) {
error = sysfs_create_group(kobj, groups[i]);
if (error) {
while (--i >= 0)
sysfs_remove_group(kobj, groups[i]);
break;
}
}
return error;
}
EXPORT_SYMBOL_GPL(sysfs_create_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.
*
* 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) {
WARN(!kn, KERN_WARNING
"sysfs group %p not found for kobject '%s'\n",
grp, 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 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 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;
int error = 0;
struct attribute *const *attr;
int i;
parent = kernfs_find_and_get(kobj->sd, grp->name);
if (!parent)
return -ENOENT;
for ((i = 0, attr = grp->attrs); *attr && !error; (++i, ++attr))
error = sysfs_add_file(parent, *attr, false);
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 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.
*/
int __compat_only_sysfs_link_entry_to_kobj(struct kobject *kobj,
struct kobject *target_kobj,
const char *target_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_kobj->sd, target_name);
if (!entry) {
kernfs_put(target);
return -ENOENT;
}
link = kernfs_create_link(kobj->sd, target_name, entry);
if (IS_ERR(link) && PTR_ERR(link) == -EEXIST)
sysfs_warn_dup(kobj->sd, target_name);
kernfs_put(entry);
kernfs_put(target);
return IS_ERR(link) ? PTR_ERR(link) : 0;
}
EXPORT_SYMBOL_GPL(__compat_only_sysfs_link_entry_to_kobj);