vserver 1.9.5.x5
[linux-2.6.git] / fs / sysfs / dir.c
index d688117..fe19821 100644 (file)
 
 DECLARE_RWSEM(sysfs_rename_sem);
 
+static void sysfs_d_iput(struct dentry * dentry, struct inode * inode)
+{
+       struct sysfs_dirent * sd = dentry->d_fsdata;
+
+       if (sd) {
+               BUG_ON(sd->s_dentry != dentry);
+               sd->s_dentry = NULL;
+               sysfs_put(sd);
+       }
+       iput(inode);
+}
+
+static struct dentry_operations sysfs_dentry_ops = {
+       .d_iput         = sysfs_d_iput,
+};
+
+/*
+ * Allocates a new sysfs_dirent and links it to the parent sysfs_dirent
+ */
+static struct sysfs_dirent * sysfs_new_dirent(struct sysfs_dirent * parent_sd,
+                                               void * element)
+{
+       struct sysfs_dirent * sd;
+
+       sd = kmem_cache_alloc(sysfs_dir_cachep, GFP_KERNEL);
+       if (!sd)
+               return NULL;
+
+       memset(sd, 0, sizeof(*sd));
+       atomic_set(&sd->s_count, 1);
+       INIT_LIST_HEAD(&sd->s_children);
+       list_add(&sd->s_sibling, &parent_sd->s_children);
+       sd->s_element = element;
+
+       return sd;
+}
+
+int sysfs_make_dirent(struct sysfs_dirent * parent_sd, struct dentry * dentry,
+                       void * element, umode_t mode, int type)
+{
+       struct sysfs_dirent * sd;
+
+       sd = sysfs_new_dirent(parent_sd, element);
+       if (!sd)
+               return -ENOMEM;
+
+       sd->s_mode = mode;
+       sd->s_type = type;
+       sd->s_dentry = dentry;
+       if (dentry) {
+               dentry->d_fsdata = sysfs_get(sd);
+               dentry->d_op = &sysfs_dentry_ops;
+       }
+
+       return 0;
+}
+
 static int init_dir(struct inode * inode)
 {
-       inode->i_op = &simple_dir_inode_operations;
-       inode->i_fop = &simple_dir_operations;
+       inode->i_op = &sysfs_dir_inode_operations;
+       inode->i_fop = &sysfs_dir_operations;
 
        /* directory inodes start off with i_nlink == 2 (for "." entry) */
        inode->i_nlink++;
        return 0;
 }
 
+static int init_file(struct inode * inode)
+{
+       inode->i_size = PAGE_SIZE;
+       inode->i_fop = &sysfs_file_operations;
+       return 0;
+}
+
+static int init_symlink(struct inode * inode)
+{
+       inode->i_op = &sysfs_symlink_inode_operations;
+       return 0;
+}
 
 static int create_dir(struct kobject * k, struct dentry * p,
                      const char * n, struct dentry ** d)
 {
        int error;
+       umode_t mode = S_IFDIR| S_IRWXU | S_IRUGO | S_IXUGO;
 
        down(&p->d_inode->i_sem);
        *d = sysfs_get_dentry(p,n);
        if (!IS_ERR(*d)) {
-               error = sysfs_create(*d,
-                                        S_IFDIR| S_IRWXU | S_IRUGO | S_IXUGO,
-                                        init_dir);
+               error = sysfs_create(*d, mode, init_dir);
                if (!error) {
-                       (*d)->d_fsdata = k;
-                       p->d_inode->i_nlink++;
+                       error = sysfs_make_dirent(p->d_fsdata, *d, k, mode,
+                                               SYSFS_DIR);
+                       if (!error) {
+                               p->d_inode->i_nlink++;
+                               (*d)->d_op = &sysfs_dentry_ops;
+                               d_rehash(*d);
+                       }
                }
+               if (error && (error != -EEXIST))
+                       d_drop(*d);
                dput(*d);
        } else
                error = PTR_ERR(*d);
@@ -63,8 +138,7 @@ int sysfs_create_dir(struct kobject * kobj)
        struct dentry * parent;
        int error = 0;
 
-       if (!kobj)
-               return -EINVAL;
+       BUG_ON(!kobj);
 
        if (kobj->parent)
                parent = kobj->parent->dentry;
@@ -79,12 +153,93 @@ int sysfs_create_dir(struct kobject * kobj)
        return error;
 }
 
+/* attaches attribute's sysfs_dirent to the dentry corresponding to the
+ * attribute file
+ */
+static int sysfs_attach_attr(struct sysfs_dirent * sd, struct dentry * dentry)
+{
+       struct attribute * attr = NULL;
+       struct bin_attribute * bin_attr = NULL;
+       int (* init) (struct inode *) = NULL;
+       int error = 0;
+
+        if (sd->s_type & SYSFS_KOBJ_BIN_ATTR) {
+                bin_attr = sd->s_element;
+                attr = &bin_attr->attr;
+        } else {
+                attr = sd->s_element;
+                init = init_file;
+        }
+
+       error = sysfs_create(dentry, (attr->mode & S_IALLUGO) | S_IFREG, init);
+       if (error)
+               return error;
+
+        if (bin_attr) {
+               dentry->d_inode->i_size = bin_attr->size;
+               dentry->d_inode->i_fop = &bin_fops;
+       }
+       dentry->d_op = &sysfs_dentry_ops;
+       dentry->d_fsdata = sysfs_get(sd);
+       sd->s_dentry = dentry;
+       d_rehash(dentry);
+
+       return 0;
+}
+
+static int sysfs_attach_link(struct sysfs_dirent * sd, struct dentry * dentry)
+{
+       int err = 0;
+
+       err = sysfs_create(dentry, S_IFLNK|S_IRWXUGO, init_symlink);
+       if (!err) {
+               dentry->d_op = &sysfs_dentry_ops;
+               dentry->d_fsdata = sysfs_get(sd);
+               sd->s_dentry = dentry;
+               d_rehash(dentry);
+       }
+       return err;
+}
+
+static struct dentry * sysfs_lookup(struct inode *dir, struct dentry *dentry,
+                               struct nameidata *nd)
+{
+       struct sysfs_dirent * parent_sd = dentry->d_parent->d_fsdata;
+       struct sysfs_dirent * sd;
+       int err = 0;
+
+       list_for_each_entry(sd, &parent_sd->s_children, s_sibling) {
+               if (sd->s_type & SYSFS_NOT_PINNED) {
+                       const unsigned char * name = sysfs_get_name(sd);
+
+                       if (strcmp(name, dentry->d_name.name))
+                               continue;
+
+                       if (sd->s_type & SYSFS_KOBJ_LINK)
+                               err = sysfs_attach_link(sd, dentry);
+                       else
+                               err = sysfs_attach_attr(sd, dentry);
+                       break;
+               }
+       }
+
+       return ERR_PTR(err);
+}
+
+struct inode_operations sysfs_dir_inode_operations = {
+       .lookup         = sysfs_lookup,
+};
 
 static void remove_dir(struct dentry * d)
 {
        struct dentry * parent = dget(d->d_parent);
+       struct sysfs_dirent * sd;
+
        down(&parent->d_inode->i_sem);
        d_delete(d);
+       sd = d->d_fsdata;
+       list_del_init(&sd->s_sibling);
+       sysfs_put(sd);
        if (d->d_inode)
                simple_rmdir(parent->d_inode,d);
 
@@ -112,47 +267,23 @@ void sysfs_remove_subdir(struct dentry * d)
 
 void sysfs_remove_dir(struct kobject * kobj)
 {
-       struct list_head * node;
        struct dentry * dentry = dget(kobj->dentry);
+       struct sysfs_dirent * parent_sd;
+       struct sysfs_dirent * sd, * tmp;
 
        if (!dentry)
                return;
 
        pr_debug("sysfs %s: removing dir\n",dentry->d_name.name);
        down(&dentry->d_inode->i_sem);
-
-       spin_lock(&dcache_lock);
-restart:
-       node = dentry->d_subdirs.next;
-       while (node != &dentry->d_subdirs) {
-               struct dentry * d = list_entry(node,struct dentry,d_child);
-
-               node = node->next;
-               pr_debug(" o %s (%d): ",d->d_name.name,atomic_read(&d->d_count));
-               if (!d_unhashed(d) && (d->d_inode)) {
-                       d = dget_locked(d);
-                       pr_debug("removing");
-
-                       /**
-                        * Unlink and unhash.
-                        */
-                       __d_drop(d);
-                       spin_unlock(&dcache_lock);
-                       /* release the target kobject in case of 
-                        * a symlink
-                        */
-                       if (S_ISLNK(d->d_inode->i_mode))
-                               kobject_put(d->d_fsdata);
-                       
-                       simple_unlink(dentry->d_inode,d);
-                       dput(d);
-                       pr_debug(" done\n");
-                       spin_lock(&dcache_lock);
-                       /* re-acquired dcache_lock, need to restart */
-                       goto restart;
-               }
+       parent_sd = dentry->d_fsdata;
+       list_for_each_entry_safe(sd, tmp, &parent_sd->s_children, s_sibling) {
+               if (!sd->s_element || !(sd->s_type & SYSFS_NOT_PINNED))
+                       continue;
+               list_del_init(&sd->s_sibling);
+               sysfs_drop_dentry(sd, dentry);
+               sysfs_put(sd);
        }
-       spin_unlock(&dcache_lock);
        up(&dentry->d_inode->i_sem);
 
        remove_dir(dentry);
@@ -181,10 +312,15 @@ int sysfs_rename_dir(struct kobject * kobj, const char *new_name)
        new_dentry = sysfs_get_dentry(parent, new_name);
        if (!IS_ERR(new_dentry)) {
                if (!new_dentry->d_inode) {
-                       error = kobject_set_name(kobj,new_name);
-                       if (!error)
+                       error = kobject_set_name(kobj, "%s", new_name);
+                       if (!error) {
+                               d_add(new_dentry, NULL);
                                d_move(kobj->dentry, new_dentry);
-               }
+                       }
+                       else
+                               d_drop(new_dentry);
+               } else
+                       error = -EEXIST;
                dput(new_dentry);
        }
        up(&parent->d_inode->i_sem);    
@@ -193,7 +329,147 @@ int sysfs_rename_dir(struct kobject * kobj, const char *new_name)
        return error;
 }
 
-EXPORT_SYMBOL(sysfs_create_dir);
-EXPORT_SYMBOL(sysfs_remove_dir);
-EXPORT_SYMBOL(sysfs_rename_dir);
+static int sysfs_dir_open(struct inode *inode, struct file *file)
+{
+       struct dentry * dentry = file->f_dentry;
+       struct sysfs_dirent * parent_sd = dentry->d_fsdata;
+
+       down(&dentry->d_inode->i_sem);
+       file->private_data = sysfs_new_dirent(parent_sd, NULL);
+       up(&dentry->d_inode->i_sem);
+
+       return file->private_data ? 0 : -ENOMEM;
+
+}
+
+static int sysfs_dir_close(struct inode *inode, struct file *file)
+{
+       struct dentry * dentry = file->f_dentry;
+       struct sysfs_dirent * cursor = file->private_data;
+
+       down(&dentry->d_inode->i_sem);
+       list_del_init(&cursor->s_sibling);
+       up(&dentry->d_inode->i_sem);
+
+       release_sysfs_dirent(cursor);
+
+       return 0;
+}
+
+/* Relationship between s_mode and the DT_xxx types */
+static inline unsigned char dt_type(struct sysfs_dirent *sd)
+{
+       return (sd->s_mode >> 12) & 15;
+}
+
+static int sysfs_readdir(struct file * filp, void * dirent, filldir_t filldir)
+{
+       struct dentry *dentry = filp->f_dentry;
+       struct sysfs_dirent * parent_sd = dentry->d_fsdata;
+       struct sysfs_dirent *cursor = filp->private_data;
+       struct list_head *p, *q = &cursor->s_sibling;
+       ino_t ino;
+       int i = filp->f_pos;
+
+       switch (i) {
+               case 0:
+                       ino = dentry->d_inode->i_ino;
+                       if (filldir(dirent, ".", 1, i, ino, DT_DIR) < 0)
+                               break;
+                       filp->f_pos++;
+                       i++;
+                       /* fallthrough */
+               case 1:
+                       ino = parent_ino(dentry);
+                       if (filldir(dirent, "..", 2, i, ino, DT_DIR) < 0)
+                               break;
+                       filp->f_pos++;
+                       i++;
+                       /* fallthrough */
+               default:
+                       if (filp->f_pos == 2) {
+                               list_del(q);
+                               list_add(q, &parent_sd->s_children);
+                       }
+                       for (p=q->next; p!= &parent_sd->s_children; p=p->next) {
+                               struct sysfs_dirent *next;
+                               const char * name;
+                               int len;
+
+                               next = list_entry(p, struct sysfs_dirent,
+                                                  s_sibling);
+                               if (!next->s_element)
+                                       continue;
+
+                               name = sysfs_get_name(next);
+                               len = strlen(name);
+                               if (next->s_dentry)
+                                       ino = next->s_dentry->d_inode->i_ino;
+                               else
+                                       ino = iunique(sysfs_sb, 2);
+
+                               if (filldir(dirent, name, len, filp->f_pos, ino,
+                                                dt_type(next)) < 0)
+                                       return 0;
+
+                               list_del(q);
+                               list_add(q, p);
+                               p = q;
+                               filp->f_pos++;
+                       }
+       }
+       return 0;
+}
+
+static loff_t sysfs_dir_lseek(struct file * file, loff_t offset, int origin)
+{
+       struct dentry * dentry = file->f_dentry;
+
+       down(&dentry->d_inode->i_sem);
+       switch (origin) {
+               case 1:
+                       offset += file->f_pos;
+               case 0:
+                       if (offset >= 0)
+                               break;
+               default:
+                       up(&file->f_dentry->d_inode->i_sem);
+                       return -EINVAL;
+       }
+       if (offset != file->f_pos) {
+               file->f_pos = offset;
+               if (file->f_pos >= 2) {
+                       struct sysfs_dirent *sd = dentry->d_fsdata;
+                       struct sysfs_dirent *cursor = file->private_data;
+                       struct list_head *p;
+                       loff_t n = file->f_pos - 2;
+
+                       list_del(&cursor->s_sibling);
+                       p = sd->s_children.next;
+                       while (n && p != &sd->s_children) {
+                               struct sysfs_dirent *next;
+                               next = list_entry(p, struct sysfs_dirent,
+                                                  s_sibling);
+                               if (next->s_element)
+                                       n--;
+                               p = p->next;
+                       }
+                       list_add_tail(&cursor->s_sibling, p);
+               }
+       }
+       up(&dentry->d_inode->i_sem);
+       return offset;
+}
+
+struct file_operations sysfs_dir_operations = {
+       .open           = sysfs_dir_open,
+       .release        = sysfs_dir_close,
+       .llseek         = sysfs_dir_lseek,
+       .read           = generic_read_dir,
+       .readdir        = sysfs_readdir,
+};
+
+EXPORT_SYMBOL_GPL(sysfs_create_dir);
+EXPORT_SYMBOL_GPL(sysfs_remove_dir);
+EXPORT_SYMBOL_GPL(sysfs_rename_dir);