fuse: add store request
authorMiklos Szeredi <mszeredi@suse.cz>
Mon, 12 Jul 2010 12:41:40 +0000 (14:41 +0200)
committerMiklos Szeredi <mszeredi@suse.cz>
Mon, 12 Jul 2010 12:41:40 +0000 (14:41 +0200)
Userspace filesystem can request data to be stored in the inode's
mapping.  This request is synchronous and has no reply.  If the write
to the fuse device returns an error then the store request was not
fully completed (but may have updated some pages).

If the stored data overflows the current file size, then the size is
extended, similarly to a write(2) on the filesystem.

Pages which have been completely stored are marked uptodate.

Signed-off-by: Miklos Szeredi <mszeredi@suse.cz>
fs/fuse/dev.c
fs/fuse/file.c
fs/fuse/fuse_i.h
include/linux/fuse.h

index 7eb80d33c4f3fc8bf48b65707dff1f2f406c2522..8e01c865586e90c6047f7e20cef6930c9be23533 100644 (file)
@@ -1231,6 +1231,91 @@ err:
        return err;
 }
 
+static int fuse_notify_store(struct fuse_conn *fc, unsigned int size,
+                            struct fuse_copy_state *cs)
+{
+       struct fuse_notify_store_out outarg;
+       struct inode *inode;
+       struct address_space *mapping;
+       u64 nodeid;
+       int err;
+       pgoff_t index;
+       unsigned int offset;
+       unsigned int num;
+       loff_t file_size;
+       loff_t end;
+
+       err = -EINVAL;
+       if (size < sizeof(outarg))
+               goto out_finish;
+
+       err = fuse_copy_one(cs, &outarg, sizeof(outarg));
+       if (err)
+               goto out_finish;
+
+       err = -EINVAL;
+       if (size - sizeof(outarg) != outarg.size)
+               goto out_finish;
+
+       nodeid = outarg.nodeid;
+
+       down_read(&fc->killsb);
+
+       err = -ENOENT;
+       if (!fc->sb)
+               goto out_up_killsb;
+
+       inode = ilookup5(fc->sb, nodeid, fuse_inode_eq, &nodeid);
+       if (!inode)
+               goto out_up_killsb;
+
+       mapping = inode->i_mapping;
+       index = outarg.offset >> PAGE_CACHE_SHIFT;
+       offset = outarg.offset & ~PAGE_CACHE_MASK;
+       file_size = i_size_read(inode);
+       end = outarg.offset + outarg.size;
+       if (end > file_size) {
+               file_size = end;
+               fuse_write_update_size(inode, file_size);
+       }
+
+       num = outarg.size;
+       while (num) {
+               struct page *page;
+               unsigned int this_num;
+
+               err = -ENOMEM;
+               page = find_or_create_page(mapping, index,
+                                          mapping_gfp_mask(mapping));
+               if (!page)
+                       goto out_iput;
+
+               this_num = min_t(unsigned, num, PAGE_CACHE_SIZE - offset);
+               err = fuse_copy_page(cs, &page, offset, this_num, 0);
+               if (!err && offset == 0 && (num != 0 || file_size == end))
+                       SetPageUptodate(page);
+               unlock_page(page);
+               page_cache_release(page);
+
+               if (err)
+                       goto out_iput;
+
+               num -= this_num;
+               offset = 0;
+               index++;
+       }
+
+       err = 0;
+
+out_iput:
+       iput(inode);
+out_up_killsb:
+       up_read(&fc->killsb);
+out_finish:
+       fuse_copy_finish(cs);
+       return err;
+}
+
 static int fuse_notify(struct fuse_conn *fc, enum fuse_notify_code code,
                       unsigned int size, struct fuse_copy_state *cs)
 {
@@ -1244,6 +1329,9 @@ static int fuse_notify(struct fuse_conn *fc, enum fuse_notify_code code,
        case FUSE_NOTIFY_INVAL_ENTRY:
                return fuse_notify_inval_entry(fc, size, cs);
 
+       case FUSE_NOTIFY_STORE:
+               return fuse_notify_store(fc, size, cs);
+
        default:
                fuse_copy_finish(cs);
                return -EINVAL;
index ada0adeb3bb581a7a9dad2f1378201d3031d917c..147c1f71bdb9f0213307fd3e63f6e3b30fc3f403 100644 (file)
@@ -706,7 +706,7 @@ static int fuse_write_begin(struct file *file, struct address_space *mapping,
        return 0;
 }
 
-static void fuse_write_update_size(struct inode *inode, loff_t pos)
+void fuse_write_update_size(struct inode *inode, loff_t pos)
 {
        struct fuse_conn *fc = get_fuse_conn(inode);
        struct fuse_inode *fi = get_fuse_inode(inode);
index 8f309f04064e9044e992a619b363db2f9e8d6ef5..61267d8d527be2ba7d3ce0161abd71e2537c5f20 100644 (file)
@@ -748,4 +748,6 @@ long fuse_do_ioctl(struct file *file, unsigned int cmd, unsigned long arg,
 unsigned fuse_file_poll(struct file *file, poll_table *wait);
 int fuse_dev_release(struct inode *inode, struct file *file);
 
+void fuse_write_update_size(struct inode *inode, loff_t pos);
+
 #endif /* _FS_FUSE_I_H */
index 88e0eb5969196e6a14e0932fb75ad1174381493f..a90bd49834aacaee2cbff695fc6a80238449f0af 100644 (file)
@@ -37,6 +37,9 @@
  *
  * 7.14
  *  - add splice support to fuse device
+ *
+ * 7.15
+ *  - add store notify
  */
 
 #ifndef _LINUX_FUSE_H
@@ -68,7 +71,7 @@
 #define FUSE_KERNEL_VERSION 7
 
 /** Minor version number of this interface */
-#define FUSE_KERNEL_MINOR_VERSION 14
+#define FUSE_KERNEL_MINOR_VERSION 15
 
 /** The node ID of the root inode */
 #define FUSE_ROOT_ID 1
@@ -260,6 +263,7 @@ enum fuse_notify_code {
        FUSE_NOTIFY_POLL   = 1,
        FUSE_NOTIFY_INVAL_INODE = 2,
        FUSE_NOTIFY_INVAL_ENTRY = 3,
+       FUSE_NOTIFY_STORE = 4,
        FUSE_NOTIFY_CODE_MAX,
 };
 
@@ -568,4 +572,11 @@ struct fuse_notify_inval_entry_out {
        __u32   padding;
 };
 
+struct fuse_notify_store_out {
+       __u64   nodeid;
+       __u64   offset;
+       __u32   size;
+       __u32   padding;
+};
+
 #endif /* _LINUX_FUSE_H */