Commit 63a16f90 authored by Linus Torvalds's avatar Linus Torvalds

Merge branch 'upstream-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/mfasheh/ocfs2

* 'upstream-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/mfasheh/ocfs2:
  [PATCH] ocfs2: Release mutex in error handling code
  [PATCH] ocfs2: Fix oops when racing files truncates with writes into an mmap region
  [PATCH 2/2] ocfs2: Fix race between mount and recovery
  [PATCH 1/2] ocfs2: Add counter in struct ocfs2_dinode to track journal replays
  [PATCH] configfs: Convenience macros for attribute definition.
  [PATCH] configfs: Pin configfs subsystems separately from new config_items.
  [PATCH] configfs: Fix open directory making rmdir() fail
  [PATCH] configfs: Lock new directory inodes before removing on cleanup after failure
  [PATCH] configfs: Prevent userspace from creating new entries under attaching directories
  [PATCH] configfs: Fix failing symlink() making rmdir() fail
  [PATCH] configfs: Fix symlink() to a removing item
  [PATCH] configfs: Include linux/err.h in linux/configfs.h
parents 5adf2b03 c259ae52
......@@ -311,9 +311,20 @@ the subsystem must be ready for it.
[An Example]
The best example of these basic concepts is the simple_children
subsystem/group and the simple_child item in configfs_example.c It
shows a trivial object displaying and storing an attribute, and a simple
group creating and destroying these children.
subsystem/group and the simple_child item in configfs_example_explicit.c
and configfs_example_macros.c. It shows a trivial object displaying and
storing an attribute, and a simple group creating and destroying these
children.
The only difference between configfs_example_explicit.c and
configfs_example_macros.c is how the attributes of the childless item
are defined. The childless item has extended attributes, each with
their own show()/store() operation. This follows a convention commonly
used in sysfs. configfs_example_explicit.c creates these attributes
by explicitly defining the structures involved. Conversely
configfs_example_macros.c uses some convenience macros from configfs.h
to define the attributes. These macros are similar to their sysfs
counterparts.
[Hierarchy Navigation and the Subsystem Mutex]
......
/*
* vim: noexpandtab ts=8 sts=0 sw=8:
*
* configfs_example.c - This file is a demonstration module containing
* a number of configfs subsystems.
* configfs_example_explicit.c - This file is a demonstration module
* containing a number of configfs subsystems. It explicitly defines
* each structure without using the helper macros defined in
* configfs.h.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public
......@@ -281,7 +283,6 @@ static struct config_item *simple_children_make_item(struct config_group *group,
if (!simple_child)
return ERR_PTR(-ENOMEM);
config_item_init_type_name(&simple_child->item, name,
&simple_child_type);
......@@ -302,8 +303,8 @@ static struct configfs_attribute *simple_children_attrs[] = {
};
static ssize_t simple_children_attr_show(struct config_item *item,
struct configfs_attribute *attr,
char *page)
struct configfs_attribute *attr,
char *page)
{
return sprintf(page,
"[02-simple-children]\n"
......@@ -318,7 +319,7 @@ static void simple_children_release(struct config_item *item)
}
static struct configfs_item_operations simple_children_item_ops = {
.release = simple_children_release,
.release = simple_children_release,
.show_attribute = simple_children_attr_show,
};
......@@ -368,7 +369,6 @@ static struct config_group *group_children_make_group(struct config_group *group
if (!simple_children)
return ERR_PTR(-ENOMEM);
config_group_init_type_name(&simple_children->group, name,
&simple_children_type);
......@@ -387,8 +387,8 @@ static struct configfs_attribute *group_children_attrs[] = {
};
static ssize_t group_children_attr_show(struct config_item *item,
struct configfs_attribute *attr,
char *page)
struct configfs_attribute *attr,
char *page)
{
return sprintf(page,
"[03-group-children]\n"
......
This diff is collapsed.
......@@ -49,8 +49,10 @@ struct configfs_dirent {
#define CONFIGFS_USET_DEFAULT 0x0080
#define CONFIGFS_USET_DROPPING 0x0100
#define CONFIGFS_USET_IN_MKDIR 0x0200
#define CONFIGFS_USET_CREATING 0x0400
#define CONFIGFS_NOT_PINNED (CONFIGFS_ITEM_ATTR)
extern struct mutex configfs_symlink_mutex;
extern spinlock_t configfs_dirent_lock;
extern struct vfsmount * configfs_mount;
......@@ -66,6 +68,7 @@ extern void configfs_inode_exit(void);
extern int configfs_create_file(struct config_item *, const struct configfs_attribute *);
extern int configfs_make_dirent(struct configfs_dirent *,
struct dentry *, void *, umode_t, int);
extern int configfs_dirent_is_ready(struct configfs_dirent *);
extern int configfs_add_file(struct dentry *, const struct configfs_attribute *, int);
extern void configfs_hash_and_remove(struct dentry * dir, const char * name);
......
This diff is collapsed.
......@@ -31,6 +31,9 @@
#include <linux/configfs.h>
#include "configfs_internal.h"
/* Protects attachments of new symlinks */
DEFINE_MUTEX(configfs_symlink_mutex);
static int item_depth(struct config_item * item)
{
struct config_item * p = item;
......@@ -73,11 +76,20 @@ static int create_link(struct config_item *parent_item,
struct configfs_symlink *sl;
int ret;
ret = -ENOENT;
if (!configfs_dirent_is_ready(target_sd))
goto out;
ret = -ENOMEM;
sl = kmalloc(sizeof(struct configfs_symlink), GFP_KERNEL);
if (sl) {
sl->sl_target = config_item_get(item);
spin_lock(&configfs_dirent_lock);
if (target_sd->s_type & CONFIGFS_USET_DROPPING) {
spin_unlock(&configfs_dirent_lock);
config_item_put(item);
kfree(sl);
return -ENOENT;
}
list_add(&sl->sl_list, &target_sd->s_links);
spin_unlock(&configfs_dirent_lock);
ret = configfs_create_link(sl, parent_item->ci_dentry,
......@@ -91,6 +103,7 @@ static int create_link(struct config_item *parent_item,
}
}
out:
return ret;
}
......@@ -120,6 +133,7 @@ int configfs_symlink(struct inode *dir, struct dentry *dentry, const char *symna
{
int ret;
struct nameidata nd;
struct configfs_dirent *sd;
struct config_item *parent_item;
struct config_item *target_item;
struct config_item_type *type;
......@@ -128,9 +142,19 @@ int configfs_symlink(struct inode *dir, struct dentry *dentry, const char *symna
if (dentry->d_parent == configfs_sb->s_root)
goto out;
sd = dentry->d_parent->d_fsdata;
/*
* Fake invisibility if dir belongs to a group/default groups hierarchy
* being attached
*/
ret = -ENOENT;
if (!configfs_dirent_is_ready(sd))
goto out;
parent_item = configfs_get_config_item(dentry->d_parent);
type = parent_item->ci_type;
ret = -EPERM;
if (!type || !type->ct_item_ops ||
!type->ct_item_ops->allow_link)
goto out_put;
......@@ -141,7 +165,9 @@ int configfs_symlink(struct inode *dir, struct dentry *dentry, const char *symna
ret = type->ct_item_ops->allow_link(parent_item, target_item);
if (!ret) {
mutex_lock(&configfs_symlink_mutex);
ret = create_link(parent_item, target_item, dentry);
mutex_unlock(&configfs_symlink_mutex);
if (ret && type->ct_item_ops->drop_link)
type->ct_item_ops->drop_link(parent_item,
target_item);
......
......@@ -1073,12 +1073,15 @@ static void ocfs2_write_failure(struct inode *inode,
for(i = 0; i < wc->w_num_pages; i++) {
tmppage = wc->w_pages[i];
if (ocfs2_should_order_data(inode))
walk_page_buffers(wc->w_handle, page_buffers(tmppage),
from, to, NULL,
ocfs2_journal_dirty_data);
block_commit_write(tmppage, from, to);
if (page_has_buffers(tmppage)) {
if (ocfs2_should_order_data(inode))
walk_page_buffers(wc->w_handle,
page_buffers(tmppage),
from, to, NULL,
ocfs2_journal_dirty_data);
block_commit_write(tmppage, from, to);
}
}
}
......@@ -1901,12 +1904,14 @@ int ocfs2_write_end_nolock(struct address_space *mapping,
to = PAGE_CACHE_SIZE;
}
if (ocfs2_should_order_data(inode))
walk_page_buffers(wc->w_handle, page_buffers(tmppage),
from, to, NULL,
ocfs2_journal_dirty_data);
block_commit_write(tmppage, from, to);
if (page_has_buffers(tmppage)) {
if (ocfs2_should_order_data(inode))
walk_page_buffers(wc->w_handle,
page_buffers(tmppage),
from, to, NULL,
ocfs2_journal_dirty_data);
block_commit_write(tmppage, from, to);
}
}
out_write_size:
......
......@@ -1766,8 +1766,8 @@ out_inode_unlock:
out_rw_unlock:
ocfs2_rw_unlock(inode, 1);
mutex_unlock(&inode->i_mutex);
out:
mutex_unlock(&inode->i_mutex);
return ret;
}
......
......@@ -57,7 +57,7 @@ static int __ocfs2_recovery_thread(void *arg);
static int ocfs2_commit_cache(struct ocfs2_super *osb);
static int ocfs2_wait_on_mount(struct ocfs2_super *osb);
static int ocfs2_journal_toggle_dirty(struct ocfs2_super *osb,
int dirty);
int dirty, int replayed);
static int ocfs2_trylock_journal(struct ocfs2_super *osb,
int slot_num);
static int ocfs2_recover_orphans(struct ocfs2_super *osb,
......@@ -562,8 +562,18 @@ done:
return status;
}
static void ocfs2_bump_recovery_generation(struct ocfs2_dinode *di)
{
le32_add_cpu(&(di->id1.journal1.ij_recovery_generation), 1);
}
static u32 ocfs2_get_recovery_generation(struct ocfs2_dinode *di)
{
return le32_to_cpu(di->id1.journal1.ij_recovery_generation);
}
static int ocfs2_journal_toggle_dirty(struct ocfs2_super *osb,
int dirty)
int dirty, int replayed)
{
int status;
unsigned int flags;
......@@ -593,6 +603,9 @@ static int ocfs2_journal_toggle_dirty(struct ocfs2_super *osb,
flags &= ~OCFS2_JOURNAL_DIRTY_FL;
fe->id1.journal1.ij_flags = cpu_to_le32(flags);
if (replayed)
ocfs2_bump_recovery_generation(fe);
status = ocfs2_write_block(osb, bh, journal->j_inode);
if (status < 0)
mlog_errno(status);
......@@ -667,7 +680,7 @@ void ocfs2_journal_shutdown(struct ocfs2_super *osb)
* Do not toggle if flush was unsuccessful otherwise
* will leave dirty metadata in a "clean" journal
*/
status = ocfs2_journal_toggle_dirty(osb, 0);
status = ocfs2_journal_toggle_dirty(osb, 0, 0);
if (status < 0)
mlog_errno(status);
}
......@@ -710,7 +723,7 @@ static void ocfs2_clear_journal_error(struct super_block *sb,
}
}
int ocfs2_journal_load(struct ocfs2_journal *journal, int local)
int ocfs2_journal_load(struct ocfs2_journal *journal, int local, int replayed)
{
int status = 0;
struct ocfs2_super *osb;
......@@ -729,7 +742,7 @@ int ocfs2_journal_load(struct ocfs2_journal *journal, int local)
ocfs2_clear_journal_error(osb->sb, journal->j_journal, osb->slot_num);
status = ocfs2_journal_toggle_dirty(osb, 1);
status = ocfs2_journal_toggle_dirty(osb, 1, replayed);
if (status < 0) {
mlog_errno(status);
goto done;
......@@ -771,7 +784,7 @@ int ocfs2_journal_wipe(struct ocfs2_journal *journal, int full)
goto bail;
}
status = ocfs2_journal_toggle_dirty(journal->j_osb, 0);
status = ocfs2_journal_toggle_dirty(journal->j_osb, 0, 0);
if (status < 0)
mlog_errno(status);
......@@ -1034,6 +1047,12 @@ restart:
spin_unlock(&osb->osb_lock);
mlog(0, "All nodes recovered\n");
/* Refresh all journal recovery generations from disk */
status = ocfs2_check_journals_nolocks(osb);
status = (status == -EROFS) ? 0 : status;
if (status < 0)
mlog_errno(status);
ocfs2_super_unlock(osb, 1);
/* We always run recovery on our own orphan dir - the dead
......@@ -1096,6 +1115,42 @@ out:
mlog_exit_void();
}
static int ocfs2_read_journal_inode(struct ocfs2_super *osb,
int slot_num,
struct buffer_head **bh,
struct inode **ret_inode)
{
int status = -EACCES;
struct inode *inode = NULL;
BUG_ON(slot_num >= osb->max_slots);
inode = ocfs2_get_system_file_inode(osb, JOURNAL_SYSTEM_INODE,
slot_num);
if (!inode || is_bad_inode(inode)) {
mlog_errno(status);
goto bail;
}
SET_INODE_JOURNAL(inode);
status = ocfs2_read_block(osb, OCFS2_I(inode)->ip_blkno, bh, 0, inode);
if (status < 0) {
mlog_errno(status);
goto bail;
}
status = 0;
bail:
if (inode) {
if (status || !ret_inode)
iput(inode);
else
*ret_inode = inode;
}
return status;
}
/* Does the actual journal replay and marks the journal inode as
* clean. Will only replay if the journal inode is marked dirty. */
static int ocfs2_replay_journal(struct ocfs2_super *osb,
......@@ -1109,22 +1164,36 @@ static int ocfs2_replay_journal(struct ocfs2_super *osb,
struct ocfs2_dinode *fe;
journal_t *journal = NULL;
struct buffer_head *bh = NULL;
u32 slot_reco_gen;
inode = ocfs2_get_system_file_inode(osb, JOURNAL_SYSTEM_INODE,
slot_num);
if (inode == NULL) {
status = -EACCES;
status = ocfs2_read_journal_inode(osb, slot_num, &bh, &inode);
if (status) {
mlog_errno(status);
goto done;
}
if (is_bad_inode(inode)) {
status = -EACCES;
iput(inode);
inode = NULL;
mlog_errno(status);
fe = (struct ocfs2_dinode *)bh->b_data;
slot_reco_gen = ocfs2_get_recovery_generation(fe);
brelse(bh);
bh = NULL;
/*
* As the fs recovery is asynchronous, there is a small chance that
* another node mounted (and recovered) the slot before the recovery
* thread could get the lock. To handle that, we dirty read the journal
* inode for that slot to get the recovery generation. If it is
* different than what we expected, the slot has been recovered.
* If not, it needs recovery.
*/
if (osb->slot_recovery_generations[slot_num] != slot_reco_gen) {
mlog(0, "Slot %u already recovered (old/new=%u/%u)\n", slot_num,
osb->slot_recovery_generations[slot_num], slot_reco_gen);
osb->slot_recovery_generations[slot_num] = slot_reco_gen;
status = -EBUSY;
goto done;
}
SET_INODE_JOURNAL(inode);
/* Continue with recovery as the journal has not yet been recovered */
status = ocfs2_inode_lock_full(inode, &bh, 1, OCFS2_META_LOCK_RECOVERY);
if (status < 0) {
......@@ -1138,9 +1207,12 @@ static int ocfs2_replay_journal(struct ocfs2_super *osb,
fe = (struct ocfs2_dinode *) bh->b_data;
flags = le32_to_cpu(fe->id1.journal1.ij_flags);
slot_reco_gen = ocfs2_get_recovery_generation(fe);
if (!(flags & OCFS2_JOURNAL_DIRTY_FL)) {
mlog(0, "No recovery required for node %d\n", node_num);
/* Refresh recovery generation for the slot */
osb->slot_recovery_generations[slot_num] = slot_reco_gen;
goto done;
}
......@@ -1188,6 +1260,11 @@ static int ocfs2_replay_journal(struct ocfs2_super *osb,
flags &= ~OCFS2_JOURNAL_DIRTY_FL;
fe->id1.journal1.ij_flags = cpu_to_le32(flags);
/* Increment recovery generation to indicate successful recovery */
ocfs2_bump_recovery_generation(fe);
osb->slot_recovery_generations[slot_num] =
ocfs2_get_recovery_generation(fe);
status = ocfs2_write_block(osb, bh, inode);
if (status < 0)
mlog_errno(status);
......@@ -1252,6 +1329,13 @@ static int ocfs2_recover_node(struct ocfs2_super *osb,
status = ocfs2_replay_journal(osb, node_num, slot_num);
if (status < 0) {
if (status == -EBUSY) {
mlog(0, "Skipping recovery for slot %u (node %u) "
"as another node has recovered it\n", slot_num,
node_num);
status = 0;
goto done;
}
mlog_errno(status);
goto done;
}
......@@ -1334,12 +1418,29 @@ int ocfs2_mark_dead_nodes(struct ocfs2_super *osb)
{
unsigned int node_num;
int status, i;
struct buffer_head *bh = NULL;
struct ocfs2_dinode *di;
/* This is called with the super block cluster lock, so we
* know that the slot map can't change underneath us. */
spin_lock(&osb->osb_lock);
for (i = 0; i < osb->max_slots; i++) {
/* Read journal inode to get the recovery generation */
status = ocfs2_read_journal_inode(osb, i, &bh, NULL);
if (status) {
mlog_errno(status);
goto bail;
}
di = (struct ocfs2_dinode *)bh->b_data;
osb->slot_recovery_generations[i] =
ocfs2_get_recovery_generation(di);
brelse(bh);
bh = NULL;
mlog(0, "Slot %u recovery generation is %u\n", i,
osb->slot_recovery_generations[i]);
if (i == osb->slot_num)
continue;
......@@ -1603,49 +1704,41 @@ static int ocfs2_commit_thread(void *arg)
return 0;
}
/* Look for a dirty journal without taking any cluster locks. Used for
* hard readonly access to determine whether the file system journals
* require recovery. */
/* Reads all the journal inodes without taking any cluster locks. Used
* for hard readonly access to determine whether any journal requires
* recovery. Also used to refresh the recovery generation numbers after
* a journal has been recovered by another node.
*/
int ocfs2_check_journals_nolocks(struct ocfs2_super *osb)
{
int ret = 0;
unsigned int slot;
struct buffer_head *di_bh;
struct buffer_head *di_bh = NULL;
struct ocfs2_dinode *di;
struct inode *journal = NULL;
int journal_dirty = 0;
for(slot = 0; slot < osb->max_slots; slot++) {
journal = ocfs2_get_system_file_inode(osb,
JOURNAL_SYSTEM_INODE,
slot);
if (!journal || is_bad_inode(journal)) {
ret = -EACCES;
mlog_errno(ret);
goto out;
}
di_bh = NULL;
ret = ocfs2_read_block(osb, OCFS2_I(journal)->ip_blkno, &di_bh,
0, journal);
if (ret < 0) {
ret = ocfs2_read_journal_inode(osb, slot, &di_bh, NULL);
if (ret) {
mlog_errno(ret);
goto out;
}
di = (struct ocfs2_dinode *) di_bh->b_data;
osb->slot_recovery_generations[slot] =
ocfs2_get_recovery_generation(di);
if (le32_to_cpu(di->id1.journal1.ij_flags) &
OCFS2_JOURNAL_DIRTY_FL)
ret = -EROFS;
journal_dirty = 1;
brelse(di_bh);
if (ret)
break;
di_bh = NULL;
}
out:
if (journal)
iput(journal);
if (journal_dirty)
ret = -EROFS;
return ret;
}
......@@ -161,7 +161,8 @@ int ocfs2_journal_init(struct ocfs2_journal *journal,
void ocfs2_journal_shutdown(struct ocfs2_super *osb);
int ocfs2_journal_wipe(struct ocfs2_journal *journal,
int full);
int ocfs2_journal_load(struct ocfs2_journal *journal, int local);
int ocfs2_journal_load(struct ocfs2_journal *journal, int local,
int replayed);
int ocfs2_check_journals_nolocks(struct ocfs2_super *osb);
void ocfs2_recovery_thread(struct ocfs2_super *osb,
int node_num);
......
......@@ -204,6 +204,8 @@ struct ocfs2_super
struct ocfs2_slot_info *slot_info;
u32 *slot_recovery_generations;
spinlock_t node_map_lock;
u64 root_blkno;
......
......@@ -660,7 +660,10 @@ struct ocfs2_dinode {
struct { /* Info for journal system
inodes */
__le32 ij_flags; /* Mounted, version, etc. */
__le32 ij_pad;
__le32 ij_recovery_generation; /* Incremented when the
journal is recovered
after an unclean
shutdown */
} journal1;
} id1; /* Inode type dependant 1 */
/*C0*/ union {
......
......@@ -1442,6 +1442,15 @@ static int ocfs2_initialize_super(struct super_block *sb,
}
mlog(0, "max_slots for this device: %u\n", osb->max_slots);
osb->slot_recovery_generations =
kcalloc(osb->max_slots, sizeof(*osb->slot_recovery_generations),
GFP_KERNEL);
if (!osb->slot_recovery_generations) {
status = -ENOMEM;
mlog_errno(status);
goto bail;
}
init_waitqueue_head(&osb->osb_wipe_event);
osb->osb_orphan_wipes = kcalloc(osb->max_slots,
sizeof(*osb->osb_orphan_wipes),
......@@ -1703,7 +1712,7 @@ static int ocfs2_check_volume(struct ocfs2_super *osb)
local = ocfs2_mount_local(osb);
/* will play back anything left in the journal. */
status = ocfs2_journal_load(osb->journal, local);
status = ocfs2_journal_load(osb->journal, local, dirty);
if (status < 0) {
mlog(ML_ERROR, "ocfs2 journal load failed! %d\n", status);
goto finally;
......@@ -1768,6 +1777,7 @@ static void ocfs2_delete_osb(struct ocfs2_super *osb)
ocfs2_free_slot_info(osb);
kfree(osb->osb_orphan_wipes);
kfree(osb->slot_recovery_generations);
/* FIXME
* This belongs in journal shutdown, but because we have to
* allocate osb->journal at the start of ocfs2_initalize_osb(),
......
......@@ -40,6 +40,7 @@
#include <linux/list.h>
#include <linux/kref.h>
#include <linux/mutex.h>
#include <linux/err.h>
#include <asm/atomic.h>
......@@ -129,8 +130,25 @@ struct configfs_attribute {
/*
* Users often need to create attribute structures for their configurable
* attributes, containing a configfs_attribute member and function pointers
* for the show() and store() operations on that attribute. They can use
* this macro (similar to sysfs' __ATTR) to make defining attributes easier.
* for the show() and store() operations on that attribute. If they don't
* need anything else on the extended attribute structure, they can use
* this macro to define it The argument _item is the name of the
* config_item structure.
*/
#define CONFIGFS_ATTR_STRUCT(_item) \
struct _item##_attribute { \
struct configfs_attribute attr; \
ssize_t (*show)(struct _item *, char *); \
ssize_t (*store)(struct _item *, const char *, size_t); \
}
/*
* With the extended attribute structure, users can use this macro
* (similar to sysfs' __ATTR) to make defining attributes easier.
* An example:
* #define MYITEM_ATTR(_name, _mode, _show, _store) \
* struct myitem_attribute childless_attr_##_name = \
* __CONFIGFS_ATTR(_name, _mode, _show, _store)
*/
#define __CONFIGFS_ATTR(_name, _mode, _show, _store) \
{ \
......@@ -142,6 +160,52 @@ struct configfs_attribute {
.show = _show, \
.store = _store, \
}
/* Here is a readonly version, only requiring a show() operation */
#define __CONFIGFS_ATTR_RO(_name, _show) \
{ \
.attr = { \
.ca_name = __stringify(_name), \
.ca_mode = 0444, \
.ca_owner = THIS_MODULE, \
}, \
.show = _show, \
}
/*
* With these extended attributes, the simple show_attribute() and
* store_attribute() operations need to call the show() and store() of the
* attributes. This is a common pattern, so we provide a macro to define
* them. The argument _item is the name of the config_item structure.
* This macro expects the attributes to be named "struct <name>_attribute"
* and the function to_<name>() to exist;
*/
#define CONFIGFS_ATTR_OPS(_item) \
static ssize_t _item##_attr_show(struct config_item *item, \
struct configfs_attribute *attr, \
char *page) \
{ \
struct _item *_item = to_##_item(item); \
struct _item##_attribute *_item##_attr = \
container_of(attr, struct _item##_attribute, attr); \
ssize_t ret = 0; \
\
if (_item##_attr->show) \
ret = _item##_attr->show(_item, page); \
return ret; \
} \
static ssize_t _item##_attr_store(struct config_item *item, \
struct configfs_attribute *attr, \
const char *page, size_t count) \
{ \
struct _item *_item = to_##_item(item); \
struct _item##_attribute *_item##_attr = \
container_of(attr, struct _item##_attribute, attr); \
ssize_t ret = -EINVAL; \
\
if (_item##_attr->store) \
ret = _item##_attr->store(_item, page, count); \
return ret; \
}
/*
* If allow_link() exists, the item can symlink(2) out to other
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment