Skip to content

Commit

Permalink
kernfs: allow nodes to be created in the deactivated state
Browse files Browse the repository at this point in the history
Currently, kernfs_nodes are made visible to userland on creation,
which makes it difficult for kernfs users to atomically succeed or
fail creation of multiple nodes.  In addition, if something fails
after creating some nodes, the created nodes might already be in use
and their active refs need to be drained for removal, which has the
potential to introduce tricky reverse locking dependency on active_ref
depending on how the error path is synchronized.

This patch introduces per-root flag KERNFS_ROOT_CREATE_DEACTIVATED.
If set, all nodes under the root are created in the deactivated state
and stay invisible to userland until explicitly enabled by the new
kernfs_activate() API.  Also, nodes which have never been activated
are guaranteed to bypass draining on removal thus allowing error paths
to not worry about lockding dependency on active_ref draining.

Signed-off-by: Tejun Heo <tj@kernel.org>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
  • Loading branch information
Tejun Heo authored and Greg Kroah-Hartman committed Feb 7, 2014
1 parent b9c9dad commit d35258e
Show file tree
Hide file tree
Showing 3 changed files with 78 additions and 10 deletions.
71 changes: 64 additions & 7 deletions fs/kernfs/dir.c
Original file line number Diff line number Diff line change
Expand Up @@ -435,7 +435,7 @@ int kernfs_add_one(struct kernfs_node *kn)
goto out_unlock;

ret = -ENOENT;
if (!kernfs_active(parent))
if ((parent->flags & KERNFS_ACTIVATED) && !kernfs_active(parent))
goto out_unlock;

kn->hash = kernfs_name_hash(kn->name, kn->ns);
Expand All @@ -451,9 +451,19 @@ int kernfs_add_one(struct kernfs_node *kn)
ps_iattrs->ia_ctime = ps_iattrs->ia_mtime = CURRENT_TIME;
}

/* Mark the entry added into directory tree */
atomic_sub(KN_DEACTIVATED_BIAS, &kn->active);
ret = 0;
mutex_unlock(&kernfs_mutex);

/*
* Activate the new node unless CREATE_DEACTIVATED is requested.
* If not activated here, the kernfs user is responsible for
* activating the node with kernfs_activate(). A node which hasn't
* been activated is not visible to userland and its removal won't
* trigger deactivation.
*/
if (!(kernfs_root(kn)->flags & KERNFS_ROOT_CREATE_DEACTIVATED))
kernfs_activate(kn);
return 0;

out_unlock:
mutex_unlock(&kernfs_mutex);
return ret;
Expand Down Expand Up @@ -528,13 +538,14 @@ EXPORT_SYMBOL_GPL(kernfs_find_and_get_ns);
/**
* kernfs_create_root - create a new kernfs hierarchy
* @scops: optional syscall operations for the hierarchy
* @flags: KERNFS_ROOT_* flags
* @priv: opaque data associated with the new directory
*
* Returns the root of the new hierarchy on success, ERR_PTR() value on
* failure.
*/
struct kernfs_root *kernfs_create_root(struct kernfs_syscall_ops *scops,
void *priv)
unsigned int flags, void *priv)
{
struct kernfs_root *root;
struct kernfs_node *kn;
Expand All @@ -553,14 +564,17 @@ struct kernfs_root *kernfs_create_root(struct kernfs_syscall_ops *scops,
return ERR_PTR(-ENOMEM);
}

atomic_sub(KN_DEACTIVATED_BIAS, &kn->active);
kn->priv = priv;
kn->dir.root = root;

root->syscall_ops = scops;
root->flags = flags;
root->kn = kn;
init_waitqueue_head(&root->deactivate_waitq);

if (!(root->flags & KERNFS_ROOT_CREATE_DEACTIVATED))
kernfs_activate(kn);

return root;
}

Expand Down Expand Up @@ -783,6 +797,40 @@ static struct kernfs_node *kernfs_next_descendant_post(struct kernfs_node *pos,
return pos->parent;
}

/**
* kernfs_activate - activate a node which started deactivated
* @kn: kernfs_node whose subtree is to be activated
*
* If the root has KERNFS_ROOT_CREATE_DEACTIVATED set, a newly created node
* needs to be explicitly activated. A node which hasn't been activated
* isn't visible to userland and deactivation is skipped during its
* removal. This is useful to construct atomic init sequences where
* creation of multiple nodes should either succeed or fail atomically.
*
* The caller is responsible for ensuring that this function is not called
* after kernfs_remove*() is invoked on @kn.
*/
void kernfs_activate(struct kernfs_node *kn)
{
struct kernfs_node *pos;

mutex_lock(&kernfs_mutex);

pos = NULL;
while ((pos = kernfs_next_descendant_post(pos, kn))) {
if (!pos || (pos->flags & KERNFS_ACTIVATED))
continue;

WARN_ON_ONCE(pos->parent && RB_EMPTY_NODE(&pos->rb));
WARN_ON_ONCE(atomic_read(&pos->active) != KN_DEACTIVATED_BIAS);

atomic_sub(KN_DEACTIVATED_BIAS, &pos->active);
pos->flags |= KERNFS_ACTIVATED;
}

mutex_unlock(&kernfs_mutex);
}

static void __kernfs_remove(struct kernfs_node *kn)
{
struct kernfs_node *pos;
Expand Down Expand Up @@ -817,7 +865,16 @@ static void __kernfs_remove(struct kernfs_node *kn)
*/
kernfs_get(pos);

kernfs_drain(pos);
/*
* Drain iff @kn was activated. This avoids draining and
* its lockdep annotations for nodes which have never been
* activated and allows embedding kernfs_remove() in create
* error paths without worrying about draining.
*/
if (kn->flags & KERNFS_ACTIVATED)
kernfs_drain(pos);
else
WARN_ON_ONCE(atomic_read(&kn->active) != KN_DEACTIVATED_BIAS);

/*
* kernfs_unlink_sibling() succeeds once per node. Use it
Expand Down
2 changes: 1 addition & 1 deletion fs/sysfs/mount.c
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ int __init sysfs_init(void)
{
int err;

sysfs_root = kernfs_create_root(NULL, NULL);
sysfs_root = kernfs_create_root(NULL, 0, NULL);
if (IS_ERR(sysfs_root))
return PTR_ERR(sysfs_root);

Expand Down
15 changes: 13 additions & 2 deletions include/linux/kernfs.h
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ enum kernfs_node_type {
#define KERNFS_FLAG_MASK ~KERNFS_TYPE_MASK

enum kernfs_node_flag {
KERNFS_ACTIVATED = 0x0010,
KERNFS_NS = 0x0020,
KERNFS_HAS_SEQ_SHOW = 0x0040,
KERNFS_HAS_MMAP = 0x0080,
Expand All @@ -47,6 +48,11 @@ enum kernfs_node_flag {
KERNFS_SUICIDED = 0x0800,
};

/* @flags for kernfs_create_root() */
enum kernfs_root_flag {
KERNFS_ROOT_CREATE_DEACTIVATED = 0x0001,
};

/* type-specific structures for kernfs_node union members */
struct kernfs_elem_dir {
unsigned long subdirs;
Expand Down Expand Up @@ -128,6 +134,7 @@ struct kernfs_syscall_ops {
struct kernfs_root {
/* published fields */
struct kernfs_node *kn;
unsigned int flags; /* KERNFS_ROOT_* flags */

/* private fields, do not use outside kernfs proper */
struct ida ino_ida;
Expand Down Expand Up @@ -223,7 +230,7 @@ void kernfs_get(struct kernfs_node *kn);
void kernfs_put(struct kernfs_node *kn);

struct kernfs_root *kernfs_create_root(struct kernfs_syscall_ops *scops,
void *priv);
unsigned int flags, void *priv);
void kernfs_destroy_root(struct kernfs_root *root);

struct kernfs_node *kernfs_create_dir_ns(struct kernfs_node *parent,
Expand All @@ -239,6 +246,7 @@ struct kernfs_node *__kernfs_create_file(struct kernfs_node *parent,
struct kernfs_node *kernfs_create_link(struct kernfs_node *parent,
const char *name,
struct kernfs_node *target);
void kernfs_activate(struct kernfs_node *kn);
void kernfs_remove(struct kernfs_node *kn);
void kernfs_break_active_protection(struct kernfs_node *kn);
void kernfs_unbreak_active_protection(struct kernfs_node *kn);
Expand Down Expand Up @@ -276,7 +284,8 @@ static inline void kernfs_get(struct kernfs_node *kn) { }
static inline void kernfs_put(struct kernfs_node *kn) { }

static inline struct kernfs_root *
kernfs_create_root(struct kernfs_syscall_ops *scops, void *priv)
kernfs_create_root(struct kernfs_syscall_ops *scops, unsigned int flags,
void *priv)
{ return ERR_PTR(-ENOSYS); }

static inline void kernfs_destroy_root(struct kernfs_root *root) { }
Expand All @@ -298,6 +307,8 @@ kernfs_create_link(struct kernfs_node *parent, const char *name,
struct kernfs_node *target)
{ return ERR_PTR(-ENOSYS); }

static inline void kernfs_activate(struct kernfs_node *kn) { }

static inline void kernfs_remove(struct kernfs_node *kn) { }

static inline bool kernfs_remove_self(struct kernfs_node *kn)
Expand Down

0 comments on commit d35258e

Please sign in to comment.