powerpc/iseries: Convert to proc_fops
[safe/jmp/linux-2.6] / fs / sysfs / dir.c
index 6b8fe71..f05f230 100644 (file)
 #include <linux/completion.h>
 #include <linux/mutex.h>
 #include <linux/slab.h>
+#include <linux/security.h>
 #include "sysfs.h"
 
 DEFINE_MUTEX(sysfs_mutex);
-DEFINE_MUTEX(sysfs_rename_mutex);
 DEFINE_SPINLOCK(sysfs_assoc_lock);
 
 static DEFINE_SPINLOCK(sysfs_ino_lock);
@@ -84,46 +84,6 @@ static void sysfs_unlink_sibling(struct sysfs_dirent *sd)
 }
 
 /**
- *     sysfs_get_dentry - get dentry for the given sysfs_dirent
- *     @sd: sysfs_dirent of interest
- *
- *     Get dentry for @sd.  Dentry is looked up if currently not
- *     present.  This function descends from the root looking up
- *     dentry for each step.
- *
- *     LOCKING:
- *     mutex_lock(sysfs_rename_mutex)
- *
- *     RETURNS:
- *     Pointer to found dentry on success, ERR_PTR() value on error.
- */
-struct dentry *sysfs_get_dentry(struct sysfs_dirent *sd)
-{
-       struct dentry *dentry = dget(sysfs_sb->s_root);
-
-       while (dentry->d_fsdata != sd) {
-               struct sysfs_dirent *cur;
-               struct dentry *parent;
-
-               /* find the first ancestor which hasn't been looked up */
-               cur = sd;
-               while (cur->s_parent != dentry->d_fsdata)
-                       cur = cur->s_parent;
-
-               /* look it up */
-               parent = dentry;
-               mutex_lock(&parent->d_inode->i_mutex);
-               dentry = lookup_one_noperm(cur->s_name, parent);
-               mutex_unlock(&parent->d_inode->i_mutex);
-               dput(parent);
-
-               if (IS_ERR(dentry))
-                       break;
-       }
-       return dentry;
-}
-
-/**
  *     sysfs_get_active - get an active reference to sysfs_dirent
  *     @sd: sysfs_dirent to get an active reference to
  *
@@ -285,6 +245,9 @@ void release_sysfs_dirent(struct sysfs_dirent * sd)
                sysfs_put(sd->s_symlink.target_sd);
        if (sysfs_type(sd) & SYSFS_COPY_NAME)
                kfree(sd->s_name);
+       if (sd->s_iattr && sd->s_iattr->ia_secdata)
+               security_release_secctx(sd->s_iattr->ia_secdata,
+                                       sd->s_iattr->ia_secdata_len);
        kfree(sd->s_iattr);
        sysfs_free_ino(sd->s_ino);
        kmem_cache_free(sysfs_dir_cachep, sd);
@@ -294,7 +257,61 @@ void release_sysfs_dirent(struct sysfs_dirent * sd)
                goto repeat;
 }
 
-static void sysfs_d_iput(struct dentry * dentry, struct inode * inode)
+static int sysfs_dentry_delete(struct dentry *dentry)
+{
+       struct sysfs_dirent *sd = dentry->d_fsdata;
+       return !!(sd->s_flags & SYSFS_FLAG_REMOVED);
+}
+
+static int sysfs_dentry_revalidate(struct dentry *dentry, struct nameidata *nd)
+{
+       struct sysfs_dirent *sd = dentry->d_fsdata;
+       int is_dir;
+
+       mutex_lock(&sysfs_mutex);
+
+       /* The sysfs dirent has been deleted */
+       if (sd->s_flags & SYSFS_FLAG_REMOVED)
+               goto out_bad;
+
+       /* The sysfs dirent has been moved? */
+       if (dentry->d_parent->d_fsdata != sd->s_parent)
+               goto out_bad;
+
+       /* The sysfs dirent has been renamed */
+       if (strcmp(dentry->d_name.name, sd->s_name) != 0)
+               goto out_bad;
+
+       mutex_unlock(&sysfs_mutex);
+out_valid:
+       return 1;
+out_bad:
+       /* Remove the dentry from the dcache hashes.
+        * If this is a deleted dentry we use d_drop instead of d_delete
+        * so sysfs doesn't need to cope with negative dentries.
+        *
+        * If this is a dentry that has simply been renamed we
+        * use d_drop to remove it from the dcache lookup on its
+        * old parent.  If this dentry persists later when a lookup
+        * is performed at its new name the dentry will be readded
+        * to the dcache hashes.
+        */
+       is_dir = (sysfs_type(sd) == SYSFS_DIR);
+       mutex_unlock(&sysfs_mutex);
+       if (is_dir) {
+               /* If we have submounts we must allow the vfs caches
+                * to lie about the state of the filesystem to prevent
+                * leaks and other nasty things.
+                */
+               if (have_submounts(dentry))
+                       goto out_valid;
+               shrink_dcache_parent(dentry);
+       }
+       d_drop(dentry);
+       return 0;
+}
+
+static void sysfs_dentry_iput(struct dentry *dentry, struct inode *inode)
 {
        struct sysfs_dirent * sd = dentry->d_fsdata;
 
@@ -303,7 +320,9 @@ static void sysfs_d_iput(struct dentry * dentry, struct inode * inode)
 }
 
 static const struct dentry_operations sysfs_dentry_ops = {
-       .d_iput         = sysfs_d_iput,
+       .d_revalidate   = sysfs_dentry_revalidate,
+       .d_delete       = sysfs_dentry_delete,
+       .d_iput         = sysfs_dentry_iput,
 };
 
 struct sysfs_dirent *sysfs_new_dirent(const char *name, umode_t mode, int type)
@@ -340,12 +359,6 @@ struct sysfs_dirent *sysfs_new_dirent(const char *name, umode_t mode, int type)
        return NULL;
 }
 
-static int sysfs_ilookup_test(struct inode *inode, void *arg)
-{
-       struct sysfs_dirent *sd = arg;
-       return inode->i_ino == sd->s_ino;
-}
-
 /**
  *     sysfs_addrm_start - prepare for sysfs_dirent add/remove
  *     @acxt: pointer to sysfs_addrm_cxt to be used
@@ -353,47 +366,20 @@ static int sysfs_ilookup_test(struct inode *inode, void *arg)
  *
  *     This function is called when the caller is about to add or
  *     remove sysfs_dirent under @parent_sd.  This function acquires
- *     sysfs_mutex, grabs inode for @parent_sd if available and lock
- *     i_mutex of it.  @acxt is used to keep and pass context to
+ *     sysfs_mutex.  @acxt is used to keep and pass context to
  *     other addrm functions.
  *
  *     LOCKING:
  *     Kernel thread context (may sleep).  sysfs_mutex is locked on
- *     return.  i_mutex of parent inode is locked on return if
- *     available.
+ *     return.
  */
 void sysfs_addrm_start(struct sysfs_addrm_cxt *acxt,
                       struct sysfs_dirent *parent_sd)
 {
-       struct inode *inode;
-
        memset(acxt, 0, sizeof(*acxt));
        acxt->parent_sd = parent_sd;
 
-       /* Lookup parent inode.  inode initialization is protected by
-        * sysfs_mutex, so inode existence can be determined by
-        * looking up inode while holding sysfs_mutex.
-        */
        mutex_lock(&sysfs_mutex);
-
-       inode = ilookup5(sysfs_sb, parent_sd->s_ino, sysfs_ilookup_test,
-                        parent_sd);
-       if (inode) {
-               WARN_ON(inode->i_state & I_NEW);
-
-               /* parent inode available */
-               acxt->parent_inode = inode;
-
-               /* sysfs_mutex is below i_mutex in lock hierarchy.
-                * First, trylock i_mutex.  If fails, unlock
-                * sysfs_mutex and lock them in order.
-                */
-               if (!mutex_trylock(&inode->i_mutex)) {
-                       mutex_unlock(&sysfs_mutex);
-                       mutex_lock(&inode->i_mutex);
-                       mutex_lock(&sysfs_mutex);
-               }
-       }
 }
 
 /**
@@ -418,22 +404,46 @@ void sysfs_addrm_start(struct sysfs_addrm_cxt *acxt,
  */
 int __sysfs_add_one(struct sysfs_addrm_cxt *acxt, struct sysfs_dirent *sd)
 {
+       struct sysfs_inode_attrs *ps_iattr;
+
        if (sysfs_find_dirent(acxt->parent_sd, sd->s_name))
                return -EEXIST;
 
        sd->s_parent = sysfs_get(acxt->parent_sd);
 
-       if (sysfs_type(sd) == SYSFS_DIR && acxt->parent_inode)
-               inc_nlink(acxt->parent_inode);
-
-       acxt->cnt++;
-
        sysfs_link_sibling(sd);
 
+       /* Update timestamps on the parent */
+       ps_iattr = acxt->parent_sd->s_iattr;
+       if (ps_iattr) {
+               struct iattr *ps_iattrs = &ps_iattr->ia_iattr;
+               ps_iattrs->ia_ctime = ps_iattrs->ia_mtime = CURRENT_TIME;
+       }
+
        return 0;
 }
 
 /**
+ *     sysfs_pathname - return full path to sysfs dirent
+ *     @sd: sysfs_dirent whose path we want
+ *     @path: caller allocated buffer
+ *
+ *     Gives the name "/" to the sysfs_root entry; any path returned
+ *     is relative to wherever sysfs is mounted.
+ *
+ *     XXX: does no error checking on @path size
+ */
+static char *sysfs_pathname(struct sysfs_dirent *sd, char *path)
+{
+       if (sd->s_parent) {
+               sysfs_pathname(sd->s_parent, path);
+               strcat(path, "/");
+       }
+       strcat(path, sd->s_name);
+       return path;
+}
+
+/**
  *     sysfs_add_one - add sysfs_dirent to parent
  *     @acxt: addrm context to use
  *     @sd: sysfs_dirent to be added
@@ -458,8 +468,16 @@ int sysfs_add_one(struct sysfs_addrm_cxt *acxt, struct sysfs_dirent *sd)
        int ret;
 
        ret = __sysfs_add_one(acxt, sd);
-       WARN(ret == -EEXIST, KERN_WARNING "sysfs: duplicate filename '%s' "
-                      "can not be created\n", sd->s_name);
+       if (ret == -EEXIST) {
+               char *path = kzalloc(PATH_MAX, GFP_KERNEL);
+               WARN(1, KERN_WARNING
+                    "sysfs: cannot create duplicate filename '%s'\n",
+                    (path == NULL) ? sd->s_name :
+                    strcat(strcat(sysfs_pathname(acxt->parent_sd, path), "/"),
+                           sd->s_name));
+               kfree(path);
+       }
+
        return ret;
 }
 
@@ -480,70 +498,22 @@ int sysfs_add_one(struct sysfs_addrm_cxt *acxt, struct sysfs_dirent *sd)
  */
 void sysfs_remove_one(struct sysfs_addrm_cxt *acxt, struct sysfs_dirent *sd)
 {
+       struct sysfs_inode_attrs *ps_iattr;
+
        BUG_ON(sd->s_flags & SYSFS_FLAG_REMOVED);
 
        sysfs_unlink_sibling(sd);
 
+       /* Update timestamps on the parent */
+       ps_iattr = acxt->parent_sd->s_iattr;
+       if (ps_iattr) {
+               struct iattr *ps_iattrs = &ps_iattr->ia_iattr;
+               ps_iattrs->ia_ctime = ps_iattrs->ia_mtime = CURRENT_TIME;
+       }
+
        sd->s_flags |= SYSFS_FLAG_REMOVED;
        sd->s_sibling = acxt->removed;
        acxt->removed = sd;
-
-       if (sysfs_type(sd) == SYSFS_DIR && acxt->parent_inode)
-               drop_nlink(acxt->parent_inode);
-
-       acxt->cnt++;
-}
-
-/**
- *     sysfs_drop_dentry - drop dentry for the specified sysfs_dirent
- *     @sd: target sysfs_dirent
- *
- *     Drop dentry for @sd.  @sd must have been unlinked from its
- *     parent on entry to this function such that it can't be looked
- *     up anymore.
- */
-static void sysfs_drop_dentry(struct sysfs_dirent *sd)
-{
-       struct inode *inode;
-       struct dentry *dentry;
-
-       inode = ilookup(sysfs_sb, sd->s_ino);
-       if (!inode)
-               return;
-
-       /* Drop any existing dentries associated with sd.
-        *
-        * For the dentry to be properly freed we need to grab a
-        * reference to the dentry under the dcache lock,  unhash it,
-        * and then put it.  The playing with the dentry count allows
-        * dput to immediately free the dentry  if it is not in use.
-        */
-repeat:
-       spin_lock(&dcache_lock);
-       list_for_each_entry(dentry, &inode->i_dentry, d_alias) {
-               if (d_unhashed(dentry))
-                       continue;
-               dget_locked(dentry);
-               spin_lock(&dentry->d_lock);
-               __d_drop(dentry);
-               spin_unlock(&dentry->d_lock);
-               spin_unlock(&dcache_lock);
-               dput(dentry);
-               goto repeat;
-       }
-       spin_unlock(&dcache_lock);
-
-       /* adjust nlink and update timestamp */
-       mutex_lock(&inode->i_mutex);
-
-       inode->i_ctime = CURRENT_TIME;
-       drop_nlink(inode);
-       if (sysfs_type(sd) == SYSFS_DIR)
-               drop_nlink(inode);
-
-       mutex_unlock(&inode->i_mutex);
-
-       iput(inode);
 }
 
 /**
@@ -552,25 +522,15 @@ repeat:
  *
  *     Finish up sysfs_dirent add/remove.  Resources acquired by
  *     sysfs_addrm_start() are released and removed sysfs_dirents are
- *     cleaned up.  Timestamps on the parent inode are updated.
+ *     cleaned up.
  *
  *     LOCKING:
- *     All mutexes acquired by sysfs_addrm_start() are released.
+ *     sysfs_mutex is released.
  */
 void sysfs_addrm_finish(struct sysfs_addrm_cxt *acxt)
 {
        /* release resources acquired by sysfs_addrm_start() */
        mutex_unlock(&sysfs_mutex);
-       if (acxt->parent_inode) {
-               struct inode *inode = acxt->parent_inode;
-
-               /* if added/removed, update timestamps on the parent */
-               if (acxt->cnt)
-                       inode->i_ctime = inode->i_mtime = CURRENT_TIME;
-
-               mutex_unlock(&inode->i_mutex);
-               iput(inode);
-       }
 
        /* kill removed sysfs_dirents */
        while (acxt->removed) {
@@ -579,8 +539,8 @@ void sysfs_addrm_finish(struct sysfs_addrm_cxt *acxt)
                acxt->removed = sd->s_sibling;
                sd->s_sibling = NULL;
 
-               sysfs_drop_dentry(sd);
                sysfs_deactivate(sd);
+               unmap_bin_file(sd);
                sysfs_put(sd);
        }
 }
@@ -718,10 +678,15 @@ static struct dentry * sysfs_lookup(struct inode *dir, struct dentry *dentry,
        }
 
        /* instantiate and hash dentry */
-       dentry->d_op = &sysfs_dentry_ops;
-       dentry->d_fsdata = sysfs_get(sd);
-       d_instantiate(dentry, inode);
-       d_rehash(dentry);
+       ret = d_find_alias(inode);
+       if (!ret) {
+               dentry->d_op = &sysfs_dentry_ops;
+               dentry->d_fsdata = sysfs_get(sd);
+               d_add(dentry, inode);
+       } else {
+               d_move(ret, dentry);
+               iput(inode);
+       }
 
  out_unlock:
        mutex_unlock(&sysfs_mutex);
@@ -730,7 +695,10 @@ static struct dentry * sysfs_lookup(struct inode *dir, struct dentry *dentry,
 
 const struct inode_operations sysfs_dir_inode_operations = {
        .lookup         = sysfs_lookup,
+       .permission     = sysfs_permission,
        .setattr        = sysfs_setattr,
+       .getattr        = sysfs_getattr,
+       .setxattr       = sysfs_setxattr,
 };
 
 static void remove_dir(struct sysfs_dirent *sd)
@@ -792,138 +760,65 @@ void sysfs_remove_dir(struct kobject * kobj)
        __sysfs_remove_dir(sd);
 }
 
-int sysfs_rename_dir(struct kobject * kobj, const char *new_name)
+int sysfs_rename(struct sysfs_dirent *sd,
+       struct sysfs_dirent *new_parent_sd, const char *new_name)
 {
-       struct sysfs_dirent *sd = kobj->sd;
-       struct dentry *parent = NULL;
-       struct dentry *old_dentry = NULL, *new_dentry = NULL;
        const char *dup_name = NULL;
        int error;
 
-       mutex_lock(&sysfs_rename_mutex);
+       mutex_lock(&sysfs_mutex);
 
        error = 0;
-       if (strcmp(sd->s_name, new_name) == 0)
+       if ((sd->s_parent == new_parent_sd) &&
+           (strcmp(sd->s_name, new_name) == 0))
                goto out;       /* nothing to rename */
 
-       /* get the original dentry */
-       old_dentry = sysfs_get_dentry(sd);
-       if (IS_ERR(old_dentry)) {
-               error = PTR_ERR(old_dentry);
-               old_dentry = NULL;
-               goto out;
-       }
-
-       parent = old_dentry->d_parent;
-
-       /* lock parent and get dentry for new name */
-       mutex_lock(&parent->d_inode->i_mutex);
-       mutex_lock(&sysfs_mutex);
-
        error = -EEXIST;
-       if (sysfs_find_dirent(sd->s_parent, new_name))
-               goto out_unlock;
-
-       error = -ENOMEM;
-       new_dentry = d_alloc_name(parent, new_name);
-       if (!new_dentry)
-               goto out_unlock;
+       if (sysfs_find_dirent(new_parent_sd, new_name))
+               goto out;
 
        /* rename sysfs_dirent */
-       error = -ENOMEM;
-       new_name = dup_name = kstrdup(new_name, GFP_KERNEL);
-       if (!new_name)
-               goto out_unlock;
-
-       dup_name = sd->s_name;
-       sd->s_name = new_name;
+       if (strcmp(sd->s_name, new_name) != 0) {
+               error = -ENOMEM;
+               new_name = dup_name = kstrdup(new_name, GFP_KERNEL);
+               if (!new_name)
+                       goto out;
+
+               dup_name = sd->s_name;
+               sd->s_name = new_name;
+       }
 
-       /* rename */
-       d_add(new_dentry, NULL);
-       d_move(old_dentry, new_dentry);
+       /* Remove from old parent's list and insert into new parent's list. */
+       if (sd->s_parent != new_parent_sd) {
+               sysfs_unlink_sibling(sd);
+               sysfs_get(new_parent_sd);
+               sysfs_put(sd->s_parent);
+               sd->s_parent = new_parent_sd;
+               sysfs_link_sibling(sd);
+       }
 
        error = 0;
- out_unlock:
+ out:
        mutex_unlock(&sysfs_mutex);
-       mutex_unlock(&parent->d_inode->i_mutex);
        kfree(dup_name);
-       dput(old_dentry);
-       dput(new_dentry);
- out:
-       mutex_unlock(&sysfs_rename_mutex);
        return error;
 }
 
+int sysfs_rename_dir(struct kobject *kobj, const char *new_name)
+{
+       return sysfs_rename(kobj->sd, kobj->sd->s_parent, new_name);
+}
+
 int sysfs_move_dir(struct kobject *kobj, struct kobject *new_parent_kobj)
 {
        struct sysfs_dirent *sd = kobj->sd;
        struct sysfs_dirent *new_parent_sd;
-       struct dentry *old_parent, *new_parent = NULL;
-       struct dentry *old_dentry = NULL, *new_dentry = NULL;
-       int error;
 
-       mutex_lock(&sysfs_rename_mutex);
        BUG_ON(!sd->s_parent);
-       new_parent_sd = new_parent_kobj->sd ? new_parent_kobj->sd : &sysfs_root;
-
-       error = 0;
-       if (sd->s_parent == new_parent_sd)
-               goto out;       /* nothing to move */
-
-       /* get dentries */
-       old_dentry = sysfs_get_dentry(sd);
-       if (IS_ERR(old_dentry)) {
-               error = PTR_ERR(old_dentry);
-               old_dentry = NULL;
-               goto out;
-       }
-       old_parent = old_dentry->d_parent;
+       new_parent_sd = new_parent_kobj && new_parent_kobj->sd ?
+               new_parent_kobj->sd : &sysfs_root;
 
-       new_parent = sysfs_get_dentry(new_parent_sd);
-       if (IS_ERR(new_parent)) {
-               error = PTR_ERR(new_parent);
-               new_parent = NULL;
-               goto out;
-       }
-
-again:
-       mutex_lock(&old_parent->d_inode->i_mutex);
-       if (!mutex_trylock(&new_parent->d_inode->i_mutex)) {
-               mutex_unlock(&old_parent->d_inode->i_mutex);
-               goto again;
-       }
-       mutex_lock(&sysfs_mutex);
-
-       error = -EEXIST;
-       if (sysfs_find_dirent(new_parent_sd, sd->s_name))
-               goto out_unlock;
-
-       error = -ENOMEM;
-       new_dentry = d_alloc_name(new_parent, sd->s_name);
-       if (!new_dentry)
-               goto out_unlock;
-
-       error = 0;
-       d_add(new_dentry, NULL);
-       d_move(old_dentry, new_dentry);
-
-       /* Remove from old parent's list and insert into new parent's list. */
-       sysfs_unlink_sibling(sd);
-       sysfs_get(new_parent_sd);
-       sysfs_put(sd->s_parent);
-       sd->s_parent = new_parent_sd;
-       sysfs_link_sibling(sd);
-
- out_unlock:
-       mutex_unlock(&sysfs_mutex);
-       mutex_unlock(&new_parent->d_inode->i_mutex);
-       mutex_unlock(&old_parent->d_inode->i_mutex);
- out:
-       dput(new_parent);
-       dput(old_dentry);
-       dput(new_dentry);
-       mutex_unlock(&sysfs_rename_mutex);
-       return error;
+       return sysfs_rename(sd, new_parent_sd, sd->s_name);
 }
 
 /* Relationship between s_mode and the DT_xxx types */