]> pilppa.org Git - linux-2.6-omap-h63xx.git/blobdiff - fs/configfs/dir.c
[PATCH] configfs: Include linux/err.h in linux/configfs.h
[linux-2.6-omap-h63xx.git] / fs / configfs / dir.c
index a48dc7dd8765399936cd8c1fbd7fe80d7949e68c..2495f23e33f45b767a88e825f6606126c073814d 100644 (file)
 #include <linux/mount.h>
 #include <linux/module.h>
 #include <linux/slab.h>
+#include <linux/err.h>
 
 #include <linux/configfs.h>
 #include "configfs_internal.h"
 
 DECLARE_RWSEM(configfs_rename_sem);
+/*
+ * Protects mutations of configfs_dirent linkage together with proper i_mutex
+ * Also protects mutations of symlinks linkage to target configfs_dirent
+ * Mutators of configfs_dirent linkage must *both* have the proper inode locked
+ * and configfs_dirent_lock locked, in that order.
+ * This allows one to safely traverse configfs_dirent trees and symlinks without
+ * having to lock inodes.
+ *
+ * Protects setting of CONFIGFS_USET_DROPPING: checking the flag
+ * unlocked is not reliable unless in detach_groups() called from
+ * rmdir()/unregister() and from configfs_attach_group()
+ */
+DEFINE_SPINLOCK(configfs_dirent_lock);
 
 static void configfs_d_iput(struct dentry * dentry,
                            struct inode * inode)
@@ -74,13 +88,20 @@ static struct configfs_dirent *configfs_new_dirent(struct configfs_dirent * pare
 
        sd = kmem_cache_zalloc(configfs_dir_cachep, GFP_KERNEL);
        if (!sd)
-               return NULL;
+               return ERR_PTR(-ENOMEM);
 
        atomic_set(&sd->s_count, 1);
        INIT_LIST_HEAD(&sd->s_links);
        INIT_LIST_HEAD(&sd->s_children);
-       list_add(&sd->s_sibling, &parent_sd->s_children);
        sd->s_element = element;
+       spin_lock(&configfs_dirent_lock);
+       if (parent_sd->s_type & CONFIGFS_USET_DROPPING) {
+               spin_unlock(&configfs_dirent_lock);
+               kmem_cache_free(configfs_dir_cachep, sd);
+               return ERR_PTR(-ENOENT);
+       }
+       list_add(&sd->s_sibling, &parent_sd->s_children);
+       spin_unlock(&configfs_dirent_lock);
 
        return sd;
 }
@@ -118,8 +139,8 @@ int configfs_make_dirent(struct configfs_dirent * parent_sd,
        struct configfs_dirent * sd;
 
        sd = configfs_new_dirent(parent_sd, element);
-       if (!sd)
-               return -ENOMEM;
+       if (IS_ERR(sd))
+               return PTR_ERR(sd);
 
        sd->s_mode = mode;
        sd->s_type = type;
@@ -173,7 +194,9 @@ static int create_dir(struct config_item * k, struct dentry * p,
                } else {
                        struct configfs_dirent *sd = d->d_fsdata;
                        if (sd) {
+                               spin_lock(&configfs_dirent_lock);
                                list_del_init(&sd->s_sibling);
+                               spin_unlock(&configfs_dirent_lock);
                                configfs_put(sd);
                        }
                }
@@ -224,7 +247,9 @@ int configfs_create_link(struct configfs_symlink *sl,
                else {
                        struct configfs_dirent *sd = dentry->d_fsdata;
                        if (sd) {
+                               spin_lock(&configfs_dirent_lock);
                                list_del_init(&sd->s_sibling);
+                               spin_unlock(&configfs_dirent_lock);
                                configfs_put(sd);
                        }
                }
@@ -238,7 +263,9 @@ static void remove_dir(struct dentry * d)
        struct configfs_dirent * sd;
 
        sd = d->d_fsdata;
+       spin_lock(&configfs_dirent_lock);
        list_del_init(&sd->s_sibling);
+       spin_unlock(&configfs_dirent_lock);
        configfs_put(sd);
        if (d->d_inode)
                simple_rmdir(parent->d_inode,d);
@@ -331,13 +358,13 @@ static struct dentry * configfs_lookup(struct inode *dir,
 
 /*
  * Only subdirectories count here.  Files (CONFIGFS_NOT_PINNED) are
- * attributes and are removed by rmdir().  We recurse, taking i_mutex
- * on all children that are candidates for default detach.  If the
- * result is clean, then configfs_detach_group() will handle dropping
- * i_mutex.  If there is an error, the caller will clean up the i_mutex
- * holders via configfs_detach_rollback().
+ * attributes and are removed by rmdir().  We recurse, setting
+ * CONFIGFS_USET_DROPPING on all children that are candidates for
+ * default detach.
+ * If there is an error, the caller will reset the flags via
+ * configfs_detach_rollback().
  */
-static int configfs_detach_prep(struct dentry *dentry)
+static int configfs_detach_prep(struct dentry *dentry, struct mutex **wait_mutex)
 {
        struct configfs_dirent *parent_sd = dentry->d_fsdata;
        struct configfs_dirent *sd;
@@ -352,15 +379,20 @@ static int configfs_detach_prep(struct dentry *dentry)
                if (sd->s_type & CONFIGFS_NOT_PINNED)
                        continue;
                if (sd->s_type & CONFIGFS_USET_DEFAULT) {
-                       mutex_lock(&sd->s_dentry->d_inode->i_mutex);
-                       /* Mark that we've taken i_mutex */
+                       /* Abort if racing with mkdir() */
+                       if (sd->s_type & CONFIGFS_USET_IN_MKDIR) {
+                               if (wait_mutex)
+                                       *wait_mutex = &sd->s_dentry->d_inode->i_mutex;
+                               return -EAGAIN;
+                       }
+                       /* Mark that we're trying to drop the group */
                        sd->s_type |= CONFIGFS_USET_DROPPING;
 
                        /*
                         * Yup, recursive.  If there's a problem, blame
                         * deep nesting of default_groups
                         */
-                       ret = configfs_detach_prep(sd->s_dentry);
+                       ret = configfs_detach_prep(sd->s_dentry, wait_mutex);
                        if (!ret)
                                continue;
                } else
@@ -374,7 +406,7 @@ out:
 }
 
 /*
- * Walk the tree, dropping i_mutex wherever CONFIGFS_USET_DROPPING is
+ * Walk the tree, resetting CONFIGFS_USET_DROPPING wherever it was
  * set.
  */
 static void configfs_detach_rollback(struct dentry *dentry)
@@ -385,11 +417,7 @@ static void configfs_detach_rollback(struct dentry *dentry)
        list_for_each_entry(sd, &parent_sd->s_children, s_sibling) {
                if (sd->s_type & CONFIGFS_USET_DEFAULT) {
                        configfs_detach_rollback(sd->s_dentry);
-
-                       if (sd->s_type & CONFIGFS_USET_DROPPING) {
-                               sd->s_type &= ~CONFIGFS_USET_DROPPING;
-                               mutex_unlock(&sd->s_dentry->d_inode->i_mutex);
-                       }
+                       sd->s_type &= ~CONFIGFS_USET_DROPPING;
                }
        }
 }
@@ -410,7 +438,9 @@ static void detach_attrs(struct config_item * item)
        list_for_each_entry_safe(sd, tmp, &parent_sd->s_children, s_sibling) {
                if (!sd->s_element || !(sd->s_type & CONFIGFS_NOT_PINNED))
                        continue;
+               spin_lock(&configfs_dirent_lock);
                list_del_init(&sd->s_sibling);
+               spin_unlock(&configfs_dirent_lock);
                configfs_drop_dentry(sd, dentry);
                configfs_put(sd);
        }
@@ -466,16 +496,12 @@ static void detach_groups(struct config_group *group)
 
                child = sd->s_dentry;
 
+               mutex_lock(&child->d_inode->i_mutex);
+
                configfs_detach_group(sd->s_element);
                child->d_inode->i_flags |= S_DEAD;
 
-               /*
-                * From rmdir/unregister, a configfs_detach_prep() pass
-                * has taken our i_mutex for us.  Drop it.
-                * From mkdir/register cleanup, there is no sem held.
-                */
-               if (sd->s_type & CONFIGFS_USET_DROPPING)
-                       mutex_unlock(&child->d_inode->i_mutex);
+               mutex_unlock(&child->d_inode->i_mutex);
 
                d_delete(child);
                dput(child);
@@ -1001,9 +1027,10 @@ EXPORT_SYMBOL(configfs_undepend_item);
 
 static int configfs_mkdir(struct inode *dir, struct dentry *dentry, int mode)
 {
-       int ret, module_got = 0;
-       struct config_group *group;
-       struct config_item *item;
+       int ret = 0;
+       int module_got = 0;
+       struct config_group *group = NULL;
+       struct config_item *item = NULL;
        struct config_item *parent_item;
        struct configfs_subsystem *subsys;
        struct configfs_dirent *sd;
@@ -1044,28 +1071,32 @@ static int configfs_mkdir(struct inode *dir, struct dentry *dentry, int mode)
        snprintf(name, dentry->d_name.len + 1, "%s", dentry->d_name.name);
 
        mutex_lock(&subsys->su_mutex);
-       group = NULL;
-       item = NULL;
        if (type->ct_group_ops->make_group) {
                group = type->ct_group_ops->make_group(to_config_group(parent_item), name);
-               if (group) {
+               if (!group)
+                       group = ERR_PTR(-ENOMEM);
+               if (!IS_ERR(group)) {
                        link_group(to_config_group(parent_item), group);
                        item = &group->cg_item;
-               }
+               } else
+                       ret = PTR_ERR(group);
        } else {
                item = type->ct_group_ops->make_item(to_config_group(parent_item), name);
-               if (item)
+               if (!item)
+                       item = ERR_PTR(-ENOMEM);
+               if (!IS_ERR(item))
                        link_obj(parent_item, item);
+               else
+                       ret = PTR_ERR(item);
        }
        mutex_unlock(&subsys->su_mutex);
 
        kfree(name);
-       if (!item) {
+       if (ret) {
                /*
-                * If item == NULL, then link_obj() was never called.
+                * If ret != 0, then link_obj() was never called.
                 * There are no extra references to clean up.
                 */
-               ret = -ENOMEM;
                goto out_put;
        }
 
@@ -1093,11 +1124,26 @@ static int configfs_mkdir(struct inode *dir, struct dentry *dentry, int mode)
         */
        module_got = 1;
 
+       /*
+        * Make racing rmdir() fail if it did not tag parent with
+        * CONFIGFS_USET_DROPPING
+        * Note: if CONFIGFS_USET_DROPPING is already set, attach_group() will
+        * fail and let rmdir() terminate correctly
+        */
+       spin_lock(&configfs_dirent_lock);
+       /* This will make configfs_detach_prep() fail */
+       sd->s_type |= CONFIGFS_USET_IN_MKDIR;
+       spin_unlock(&configfs_dirent_lock);
+
        if (group)
                ret = configfs_attach_group(parent_item, item, dentry);
        else
                ret = configfs_attach_item(parent_item, item, dentry);
 
+       spin_lock(&configfs_dirent_lock);
+       sd->s_type &= ~CONFIGFS_USET_IN_MKDIR;
+       spin_unlock(&configfs_dirent_lock);
+
 out_unlink:
        if (ret) {
                /* Tear down everything we built up */
@@ -1161,12 +1207,27 @@ static int configfs_rmdir(struct inode *dir, struct dentry *dentry)
                return -EINVAL;
        }
 
-       ret = configfs_detach_prep(dentry);
-       if (ret) {
-               configfs_detach_rollback(dentry);
-               config_item_put(parent_item);
-               return ret;
-       }
+       spin_lock(&configfs_dirent_lock);
+       do {
+               struct mutex *wait_mutex;
+
+               ret = configfs_detach_prep(dentry, &wait_mutex);
+               if (ret) {
+                       configfs_detach_rollback(dentry);
+                       spin_unlock(&configfs_dirent_lock);
+                       if (ret != -EAGAIN) {
+                               config_item_put(parent_item);
+                               return ret;
+                       }
+
+                       /* Wait until the racing operation terminates */
+                       mutex_lock(wait_mutex);
+                       mutex_unlock(wait_mutex);
+
+                       spin_lock(&configfs_dirent_lock);
+               }
+       } while (ret == -EAGAIN);
+       spin_unlock(&configfs_dirent_lock);
 
        /* Get a working ref for the duration of this function */
        item = configfs_get_config_item(dentry);
@@ -1258,7 +1319,7 @@ static int configfs_dir_open(struct inode *inode, struct file *file)
        file->private_data = configfs_new_dirent(parent_sd, NULL);
        mutex_unlock(&dentry->d_inode->i_mutex);
 
-       return file->private_data ? 0 : -ENOMEM;
+       return IS_ERR(file->private_data) ? PTR_ERR(file->private_data) : 0;
 
 }
 
@@ -1268,7 +1329,9 @@ static int configfs_dir_close(struct inode *inode, struct file *file)
        struct configfs_dirent * cursor = file->private_data;
 
        mutex_lock(&dentry->d_inode->i_mutex);
+       spin_lock(&configfs_dirent_lock);
        list_del_init(&cursor->s_sibling);
+       spin_unlock(&configfs_dirent_lock);
        mutex_unlock(&dentry->d_inode->i_mutex);
 
        release_configfs_dirent(cursor);
@@ -1308,7 +1371,9 @@ static int configfs_readdir(struct file * filp, void * dirent, filldir_t filldir
                        /* fallthrough */
                default:
                        if (filp->f_pos == 2) {
+                               spin_lock(&configfs_dirent_lock);
                                list_move(q, &parent_sd->s_children);
+                               spin_unlock(&configfs_dirent_lock);
                        }
                        for (p=q->next; p!= &parent_sd->s_children; p=p->next) {
                                struct configfs_dirent *next;
@@ -1331,7 +1396,9 @@ static int configfs_readdir(struct file * filp, void * dirent, filldir_t filldir
                                                 dt_type(next)) < 0)
                                        return 0;
 
+                               spin_lock(&configfs_dirent_lock);
                                list_move(q, p);
+                               spin_unlock(&configfs_dirent_lock);
                                p = q;
                                filp->f_pos++;
                        }
@@ -1362,6 +1429,7 @@ static loff_t configfs_dir_lseek(struct file * file, loff_t offset, int origin)
                        struct list_head *p;
                        loff_t n = file->f_pos - 2;
 
+                       spin_lock(&configfs_dirent_lock);
                        list_del(&cursor->s_sibling);
                        p = sd->s_children.next;
                        while (n && p != &sd->s_children) {
@@ -1373,6 +1441,7 @@ static loff_t configfs_dir_lseek(struct file * file, loff_t offset, int origin)
                                p = p->next;
                        }
                        list_add_tail(&cursor->s_sibling, p);
+                       spin_unlock(&configfs_dirent_lock);
                }
        }
        mutex_unlock(&dentry->d_inode->i_mutex);
@@ -1448,9 +1517,11 @@ void configfs_unregister_subsystem(struct configfs_subsystem *subsys)
        mutex_lock_nested(&configfs_sb->s_root->d_inode->i_mutex,
                          I_MUTEX_PARENT);
        mutex_lock_nested(&dentry->d_inode->i_mutex, I_MUTEX_CHILD);
-       if (configfs_detach_prep(dentry)) {
+       spin_lock(&configfs_dirent_lock);
+       if (configfs_detach_prep(dentry, NULL)) {
                printk(KERN_ERR "configfs: Tried to unregister non-empty subsystem!\n");
        }
+       spin_unlock(&configfs_dirent_lock);
        configfs_detach_group(&group->cg_item);
        dentry->d_inode->i_flags |= S_DEAD;
        mutex_unlock(&dentry->d_inode->i_mutex);