configfs: implement binary attributes
[cascardo/linux.git] / fs / configfs / file.c
index d39099e..3687187 100644 (file)
@@ -28,6 +28,7 @@
 #include <linux/module.h>
 #include <linux/slab.h>
 #include <linux/mutex.h>
+#include <linux/vmalloc.h>
 #include <asm/uaccess.h>
 
 #include <linux/configfs.h>
@@ -48,6 +49,10 @@ struct configfs_buffer {
        struct configfs_item_operations * ops;
        struct mutex            mutex;
        int                     needs_read_fill;
+       bool                    read_in_progress;
+       bool                    write_in_progress;
+       char                    *bin_buffer;
+       int                     bin_buffer_size;
 };
 
 
@@ -123,6 +128,87 @@ out:
        return retval;
 }
 
+/**
+ *     configfs_read_bin_file - read a binary attribute.
+ *     @file:  file pointer.
+ *     @buf:   buffer to fill.
+ *     @count: number of bytes to read.
+ *     @ppos:  starting offset in file.
+ *
+ *     Userspace wants to read a binary attribute file. The attribute
+ *     descriptor is in the file's ->d_fsdata. The target item is in the
+ *     directory's ->d_fsdata.
+ *
+ *     We check whether we need to refill the buffer. If so we will
+ *     call the attributes' attr->read() twice. The first time we
+ *     will pass a NULL as a buffer pointer, which the attributes' method
+ *     will use to return the size of the buffer required. If no error
+ *     occurs we will allocate the buffer using vmalloc and call
+ *     attr->read() again passing that buffer as an argument.
+ *     Then we just copy to user-space using simple_read_from_buffer.
+ */
+
+static ssize_t
+configfs_read_bin_file(struct file *file, char __user *buf,
+                      size_t count, loff_t *ppos)
+{
+       struct configfs_buffer *buffer = file->private_data;
+       struct dentry *dentry = file->f_path.dentry;
+       struct config_item *item = to_item(dentry->d_parent);
+       struct configfs_bin_attribute *bin_attr = to_bin_attr(dentry);
+       ssize_t retval = 0;
+       ssize_t len = min_t(size_t, count, PAGE_SIZE);
+
+       mutex_lock(&buffer->mutex);
+
+       /* we don't support switching read/write modes */
+       if (buffer->write_in_progress) {
+               retval = -ETXTBSY;
+               goto out;
+       }
+       buffer->read_in_progress = 1;
+
+       if (buffer->needs_read_fill) {
+               /* perform first read with buf == NULL to get extent */
+               len = bin_attr->read(item, NULL, 0);
+               if (len <= 0) {
+                       retval = len;
+                       goto out;
+               }
+
+               /* do not exceed the maximum value */
+               if (bin_attr->cb_max_size && len > bin_attr->cb_max_size) {
+                       retval = -EFBIG;
+                       goto out;
+               }
+
+               buffer->bin_buffer = vmalloc(len);
+               if (buffer->bin_buffer == NULL) {
+                       retval = -ENOMEM;
+                       goto out;
+               }
+               buffer->bin_buffer_size = len;
+
+               /* perform second read to fill buffer */
+               len = bin_attr->read(item, buffer->bin_buffer, len);
+               if (len < 0) {
+                       retval = len;
+                       vfree(buffer->bin_buffer);
+                       buffer->bin_buffer_size = 0;
+                       buffer->bin_buffer = NULL;
+                       goto out;
+               }
+
+               buffer->needs_read_fill = 0;
+       }
+
+       retval = simple_read_from_buffer(buf, count, ppos, buffer->bin_buffer,
+                                       buffer->bin_buffer_size);
+out:
+       mutex_unlock(&buffer->mutex);
+       return retval;
+}
+
 
 /**
  *     fill_write_buffer - copy buffer from userspace.
@@ -209,10 +295,80 @@ configfs_write_file(struct file *file, const char __user *buf, size_t count, lof
        return len;
 }
 
-static int check_perm(struct inode * inode, struct file * file)
+/**
+ *     configfs_write_bin_file - write a binary attribute.
+ *     @file:  file pointer
+ *     @buf:   data to write
+ *     @count: number of bytes
+ *     @ppos:  starting offset
+ *
+ *     Writing to a binary attribute file is similar to a normal read.
+ *     We buffer the consecutive writes (binary attribute files do not
+ *     support lseek) in a continuously growing buffer, but we don't
+ *     commit until the close of the file.
+ */
+
+static ssize_t
+configfs_write_bin_file(struct file *file, const char __user *buf,
+                       size_t count, loff_t *ppos)
+{
+       struct configfs_buffer *buffer = file->private_data;
+       struct dentry *dentry = file->f_path.dentry;
+       struct configfs_bin_attribute *bin_attr = to_bin_attr(dentry);
+       void *tbuf = NULL;
+       ssize_t len;
+
+       mutex_lock(&buffer->mutex);
+
+       /* we don't support switching read/write modes */
+       if (buffer->read_in_progress) {
+               len = -ETXTBSY;
+               goto out;
+       }
+       buffer->write_in_progress = 1;
+
+       /* buffer grows? */
+       if (*ppos + count > buffer->bin_buffer_size) {
+
+               if (bin_attr->cb_max_size &&
+                       *ppos + count > bin_attr->cb_max_size) {
+                       len = -EFBIG;
+               }
+
+               tbuf = vmalloc(*ppos + count);
+               if (tbuf == NULL) {
+                       len = -ENOMEM;
+                       goto out;
+               }
+
+               /* copy old contents */
+               if (buffer->bin_buffer) {
+                       memcpy(tbuf, buffer->bin_buffer,
+                               buffer->bin_buffer_size);
+                       vfree(buffer->bin_buffer);
+               }
+
+               /* clear the new area */
+               memset(tbuf + buffer->bin_buffer_size, 0,
+                       *ppos + count - buffer->bin_buffer_size);
+               buffer->bin_buffer = tbuf;
+               buffer->bin_buffer_size = *ppos + count;
+       }
+
+       len = simple_write_to_buffer(buffer->bin_buffer,
+                       buffer->bin_buffer_size, ppos, buf, count);
+       if (len > 0)
+               *ppos += len;
+out:
+       mutex_unlock(&buffer->mutex);
+       return len;
+}
+
+static int check_perm(struct inode * inode, struct file * file, int type)
 {
        struct config_item *item = configfs_get_config_item(file->f_path.dentry->d_parent);
        struct configfs_attribute * attr = to_attr(file->f_path.dentry);
+       struct configfs_bin_attribute *bin_attr = NULL;
        struct configfs_buffer * buffer;
        struct configfs_item_operations * ops = NULL;
        int error = 0;
@@ -220,6 +376,9 @@ static int check_perm(struct inode * inode, struct file * file)
        if (!item || !attr)
                goto Einval;
 
+       if (type & CONFIGFS_ITEM_BIN_ATTR)
+               bin_attr = to_bin_attr(file->f_path.dentry);
+
        /* Grab the module reference for this attribute if we have one */
        if (!try_module_get(attr->ca_owner)) {
                error = -ENODEV;
@@ -236,9 +395,14 @@ static int check_perm(struct inode * inode, struct file * file)
         * and we must have a store method.
         */
        if (file->f_mode & FMODE_WRITE) {
-               if (!(inode->i_mode & S_IWUGO) || !attr->store)
+               if (!(inode->i_mode & S_IWUGO))
+                       goto Eaccess;
+
+               if ((type & CONFIGFS_ITEM_ATTR) && !attr->store)
                        goto Eaccess;
 
+               if ((type & CONFIGFS_ITEM_BIN_ATTR) && !bin_attr->write)
+                       goto Eaccess;
        }
 
        /* File needs read support.
@@ -246,7 +410,13 @@ static int check_perm(struct inode * inode, struct file * file)
         * must be a show method for it.
         */
        if (file->f_mode & FMODE_READ) {
-               if (!(inode->i_mode & S_IRUGO) || !attr->show)
+               if (!(inode->i_mode & S_IRUGO))
+                       goto Eaccess;
+
+               if ((type & CONFIGFS_ITEM_ATTR) && !attr->show)
+                       goto Eaccess;
+
+               if ((type & CONFIGFS_ITEM_BIN_ATTR) && !bin_attr->read)
                        goto Eaccess;
        }
 
@@ -260,6 +430,8 @@ static int check_perm(struct inode * inode, struct file * file)
        }
        mutex_init(&buffer->mutex);
        buffer->needs_read_fill = 1;
+       buffer->read_in_progress = 0;
+       buffer->write_in_progress = 0;
        buffer->ops = ops;
        file->private_data = buffer;
        goto Done;
@@ -277,12 +449,7 @@ static int check_perm(struct inode * inode, struct file * file)
        return error;
 }
 
-static int configfs_open_file(struct inode * inode, struct file * filp)
-{
-       return check_perm(inode,filp);
-}
-
-static int configfs_release(struct inode * inode, struct file * filp)
+static int configfs_release(struct inode *inode, struct file *filp)
 {
        struct config_item * item = to_item(filp->f_path.dentry->d_parent);
        struct configfs_attribute * attr = to_attr(filp->f_path.dentry);
@@ -303,6 +470,47 @@ static int configfs_release(struct inode * inode, struct file * filp)
        return 0;
 }
 
+static int configfs_open_file(struct inode *inode, struct file *filp)
+{
+       return check_perm(inode, filp, CONFIGFS_ITEM_ATTR);
+}
+
+static int configfs_open_bin_file(struct inode *inode, struct file *filp)
+{
+       return check_perm(inode, filp, CONFIGFS_ITEM_BIN_ATTR);
+}
+
+static int configfs_release_bin_file(struct inode *inode, struct file *filp)
+{
+       struct configfs_buffer *buffer = filp->private_data;
+       struct dentry *dentry = filp->f_path.dentry;
+       struct config_item *item = to_item(dentry->d_parent);
+       struct configfs_bin_attribute *bin_attr = to_bin_attr(dentry);
+       ssize_t len = 0;
+       int ret;
+
+       buffer->read_in_progress = 0;
+
+       if (buffer->write_in_progress) {
+               buffer->write_in_progress = 0;
+
+               len = bin_attr->write(item, buffer->bin_buffer,
+                               buffer->bin_buffer_size);
+
+               /* vfree on NULL is safe */
+               vfree(buffer->bin_buffer);
+               buffer->bin_buffer = NULL;
+               buffer->bin_buffer_size = 0;
+               buffer->needs_read_fill = 1;
+       }
+
+       ret = configfs_release(inode, filp);
+       if (len < 0)
+               return len;
+       return ret;
+}
+
+
 const struct file_operations configfs_file_operations = {
        .read           = configfs_read_file,
        .write          = configfs_write_file,
@@ -311,6 +519,14 @@ const struct file_operations configfs_file_operations = {
        .release        = configfs_release,
 };
 
+const struct file_operations configfs_bin_file_operations = {
+       .read           = configfs_read_bin_file,
+       .write          = configfs_write_bin_file,
+       .llseek         = NULL,         /* bin file is not seekable */
+       .open           = configfs_open_bin_file,
+       .release        = configfs_release_bin_file,
+};
+
 /**
  *     configfs_create_file - create an attribute file for an item.
  *     @item:  item we're creating for.
@@ -332,3 +548,24 @@ int configfs_create_file(struct config_item * item, const struct configfs_attrib
        return error;
 }
 
+/**
+ *     configfs_create_bin_file - create a binary attribute file for an item.
+ *     @item:  item we're creating for.
+ *     @attr:  atrribute descriptor.
+ */
+
+int configfs_create_bin_file(struct config_item *item,
+               const struct configfs_bin_attribute *bin_attr)
+{
+       struct dentry *dir = item->ci_dentry;
+       struct configfs_dirent *parent_sd = dir->d_fsdata;
+       umode_t mode = (bin_attr->cb_attr.ca_mode & S_IALLUGO) | S_IFREG;
+       int error = 0;
+
+       mutex_lock_nested(&dir->d_inode->i_mutex, I_MUTEX_NORMAL);
+       error = configfs_make_dirent(parent_sd, NULL, (void *) bin_attr, mode,
+                                    CONFIGFS_ITEM_BIN_ATTR);
+       mutex_unlock(&dir->d_inode->i_mutex);
+
+       return error;
+}