atomic open+create added
authorMiklos Szeredi <miklos@szeredi.hu>
Wed, 26 Oct 2005 15:29:06 +0000 (15:29 +0000)
committerMiklos Szeredi <miklos@szeredi.hu>
Wed, 26 Oct 2005 15:29:06 +0000 (15:29 +0000)
12 files changed:
ChangeLog
example/fusexmp_fh.c
include/fuse.h
include/fuse_lowlevel.h
kernel/configure.ac
kernel/dev.c
kernel/dir.c
kernel/file.c
kernel/fuse_i.h
kernel/fuse_kernel.h
lib/fuse.c
lib/fuse_lowlevel.c

index d5def4faf6fee8c54118ba2712b8a70cd8fc3210..a92cf6f1d4aecd84e3423702ddba45fe8f090bc3 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,11 +1,15 @@
 2005-10-26  Miklos Szeredi <miklos@szeredi.hu>
 
        * Add ACCESS operation.  This is called from the access() system
-       call if 'default_permissions' mount option is not given
+       call if 'default_permissions' mount option is not given, and is
+       not called on kernels 2.4.*
 
        * Fix kernel module compile if kernel source and build directories
        differ.  Report and initial patch by John Eastman
 
+       * Add atomic CREATE+OPEN operation.  This will only work with
+       2.6.15 (presumably) or later Linux kernels.
+
 2005-10-18  Miklos Szeredi <miklos@szeredi.hu>
 
        * lib: optimize buffer reallocation in fill_dir.
index d17699e07c094bf2c5aaf55cf772694e7d5aa2b9..262a868a6bd1752cedb35a08c3973a9040a08471 100644 (file)
@@ -215,6 +215,17 @@ static int xmp_utime(const char *path, struct utimbuf *buf)
     return 0;
 }
 
+static int xmp_create(const char *path, mode_t mode, struct fuse_file_info *fi)
+{
+    int fd;
+
+    fd = open(path, fi->flags, mode);
+    if(fd == -1)
+        return -errno;
+
+    fi->fh = fd;
+    return 0;
+}
 
 static int xmp_open(const char *path, struct fuse_file_info *fi)
 {
@@ -344,6 +355,7 @@ static struct fuse_operations xmp_oper = {
     .chown     = xmp_chown,
     .truncate  = xmp_truncate,
     .utime     = xmp_utime,
+    .create    = xmp_create,
     .open      = xmp_open,
     .read      = xmp_read,
     .write     = xmp_write,
index 4a099bc9eb907f9324fae7cd5241cb825f3e7a75..290e9d3ec40e7a24714aac28a3007f7db7e0e8c7 100644 (file)
@@ -63,9 +63,9 @@ typedef int (*fuse_dirfil_t) (fuse_dirh_t h, const char *name, int type,
  *
  * All methods are optional, but some are essential for a useful
  * filesystem (e.g. getattr).  Open, flush, release, fsync, opendir,
- * releasedir, fsyncdir, access, init and destroy are special purpose
- * methods, without which a full featured filesystem can still be
- * implemented.
+ * releasedir, fsyncdir, access, create, init and destroy are special
+ * purpose methods, without which a full featured filesystem can still
+ * be implemented.
  */
 struct fuse_operations {
     /** Get file attributes.
@@ -301,13 +301,29 @@ struct fuse_operations {
     /**
      * Check file access permissions
      *
-     * Need not be implemented.  This will be called for the access()
-     * system call.  If the 'default_permissions' mount option is
-     * given, this method is not called.
+     * This will be called for the access() system call.  If the
+     * 'default_permissions' mount option is given, this method is not
+     * called.
+     *
+     * This method is not called under Linux kernel versions 2.4.x
      *
      * Introduced in version 2.5
      */
     int (*access) (const char *, int);
+
+    /**
+     * Create and open a file
+     *
+     * If the file does not exist, first create it with the specified
+     * mode, and then open it.
+     *
+     * If this method is not implemented or under Linux kernel
+     * versions earlier than 2.6.15, the mknod() and open() methods
+     * will be called instead.
+     *
+     * Introduced in version 2.5
+     */
+    int (*create) (const char *, mode_t, struct fuse_file_info *);
 };
 
 /** Extra context that may be needed by some filesystems
index 73f57f0ae551e36688915cafca80812a0465a47d..e9c64ce14bac1d5a4749cb3e9aed9885a9027fd6 100644 (file)
@@ -643,7 +643,61 @@ struct fuse_lowlevel_ops {
      */
     void (*removexattr) (fuse_req_t req, fuse_ino_t ino, const char *name);
 
+    /**
+     * Check file access permissions
+     *
+     * This will be called for the access() system call.  If the
+     * 'default_permissions' mount option is given, this method is not
+     * called.
+     *
+     * This method is not called under Linux kernel versions 2.4.x
+     *
+     * Introduced in version 2.5
+     *
+     * Valid replies:
+     *   fuse_reply_err
+     *
+     * @param req request handle
+     * @param ino the inode number
+     * @param mask requested access mode
+     */
     void (*access) (fuse_req_t req, fuse_ino_t ino, int mask);
+
+    /**
+     * Create and open a file
+     *
+     * If the file does not exist, first create it with the specified
+     * mode, and then open it.
+     *
+     * Open flags (with the exception of O_NOCTTY) are available in
+     * fi->flags.
+     *
+     * Filesystem may store an arbitrary file handle (pointer, index,
+     * etc) in fi->fh, and use this in other all other file operations
+     * (read, write, flush, release, fsync).
+     *
+     * There are also some flags (direct_io, keep_cache) which the
+     * filesystem may set in fi, to change the way the file is opened.
+     * See fuse_file_info structure in <fuse_common.h> for more details.
+     *
+     * If this method is not implemented or under Linux kernel
+     * versions earlier than 2.6.15, the mknod() and open() methods
+     * will be called instead.
+     *
+     * Introduced in version 2.5
+     *
+     * Valid replies:
+     *   fuse_reply_create
+     *   fuse_reply_err
+     *
+     * @param req request handle
+     * @param parent inode number of the parent directory
+     * @param name to create
+     * @param mode file type and mode with which to create the new file
+     * @param fi file information
+     */
+    void (*create) (fuse_req_t req, fuse_ino_t parent, const char *name,
+                    mode_t mode, struct fuse_file_info *fi);
 };
 
 /**
@@ -683,6 +737,23 @@ void fuse_reply_none(fuse_req_t req);
  */
 int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e);
 
+/**
+ * Reply with a directory entry and open parameters
+ *
+ * currently the following members of 'fi' are used:
+ *   fh, direct_io, keep_cache
+ *
+ * Possible requests:
+ *   create
+ *
+ * @param req request handle
+ * @param e the entry parameters
+ * @param fi file information
+ * @return zero for success, -errno for failure to send reply
+ */
+int fuse_reply_create(fuse_req_t req, const struct fuse_entry_param *e,
+                      const struct fuse_file_info *fi);
+
 /**
  * Reply with attributes
  *
index 5a1f80dcdac67933655f4352762912f72b0781d2..41ebafc2478400fc351e03b6b5d1e3572437933a 100644 (file)
@@ -104,6 +104,14 @@ if test "$ENABLE_FUSE_MODULE" = y; then
                AC_MSG_RESULT([no])
        fi
 
+       AC_MSG_CHECKING([whether lookup_instantiate_filp is defined])
+       if test -f $kernelsrc/include/linux/namei.h && egrep -q "lookup_instantiate_filp" $kernelsrc/include/linux/namei.h; then
+               AC_DEFINE(HAVE_LOOKUP_INSTANTIATE_FILP, 1, [lookup_instantiate_filp() is defined])
+               AC_MSG_RESULT([yes])
+       else
+               AC_MSG_RESULT([no])
+       fi
+
        isuml=no
        KERNELMAKE_PARAMS=
        KERNELCPPFLAGS=
index 4df10f4c087cc992669ab2119678b7199d662087..30bed76ab2bb98b5f82db1da000a70372409d00b 100644 (file)
@@ -224,6 +224,13 @@ static void request_end(struct fuse_conn *fc, struct fuse_req *req)
                   fuse_putback_request() */
                for (i = 1; i < FUSE_MAX_OUTSTANDING; i++)
                        up(&fc->outstanding_sem);
+       } else if (req->in.h.opcode == FUSE_RELEASE && req->inode == NULL) {
+               /* Special case for failed iget in CREATE */
+               u64 nodeid = req->in.h.nodeid;
+               __fuse_get_request(req);
+               fuse_reset_request(req);
+               fuse_send_forget(fc, req, nodeid, 1);
+               putback = 0;
        }
        if (putback)
                fuse_putback_request(fc, req);
index 20a6aae80e8b8730d5f90cc3f8fcfdb53c0e01b3..bc792cbc0e669a4f9c57aeea28be7c09c0d09ae9 100644 (file)
@@ -150,6 +150,103 @@ static void fuse_invalidate_entry(struct dentry *entry)
        entry->d_time = jiffies - 1;
 }
 
+#ifdef HAVE_LOOKUP_INSTANTIATE_FILP
+static int fuse_create_open(struct inode *dir, struct dentry *entry, int mode,
+                           struct nameidata *nd)
+{
+       int err;
+       struct inode *inode;
+       struct fuse_conn *fc = get_fuse_conn(dir);
+       struct fuse_req *req;
+       struct fuse_open_in inarg;
+       struct fuse_open_out outopen;
+       struct fuse_entry_out outentry;
+       struct fuse_inode *fi;
+       struct fuse_file *ff;
+       struct file *file;
+       int flags = nd->intent.open.flags - 1;
+
+       err = -ENOSYS;
+       if (fc->no_create)
+               goto out;
+
+       err = -ENAMETOOLONG;
+       if (entry->d_name.len > FUSE_NAME_MAX)
+               goto out;
+
+       err = -EINTR;
+       req = fuse_get_request(fc);
+       if (!req)
+               goto out;
+
+       ff = fuse_file_alloc();
+       if (!ff)
+               goto out_put_request;
+
+       flags &= ~O_NOCTTY;
+       memset(&inarg, 0, sizeof(inarg));
+       inarg.flags = flags;
+       inarg.mode = mode;
+       req->in.h.opcode = FUSE_CREATE;
+       req->in.h.nodeid = get_node_id(dir);
+       req->inode = dir;
+       req->in.numargs = 2;
+       req->in.args[0].size = sizeof(inarg);
+       req->in.args[0].value = &inarg;
+       req->in.args[1].size = entry->d_name.len + 1;
+       req->in.args[1].value = entry->d_name.name;
+       req->out.numargs = 2;
+       req->out.args[0].size = sizeof(outentry);
+       req->out.args[0].value = &outentry;
+       req->out.args[1].size = sizeof(outopen);
+       req->out.args[1].value = &outopen;
+       request_send(fc, req);
+       err = req->out.h.error;
+       if (err) {
+               if (err == -ENOSYS)
+                       fc->no_create = 1;
+               goto out_free_ff;
+       }
+
+       err = -EIO;
+       if (!S_ISREG(outentry.attr.mode))
+               goto out_free_ff;
+
+       inode = fuse_iget(dir->i_sb, outentry.nodeid, outentry.generation,
+                         &outentry.attr);
+       err = -ENOMEM;
+       if (!inode) {
+               flags &= ~(O_CREAT | O_EXCL | O_TRUNC);
+               ff->fh = outopen.fh;
+               fuse_send_release(fc, ff, outentry.nodeid, NULL, flags, 0);
+               goto out_put_request;
+       }
+       fuse_put_request(fc, req);
+       entry->d_time = time_to_jiffies(outentry.entry_valid,
+                                       outentry.entry_valid_nsec);
+       fi = get_fuse_inode(inode);
+       fi->i_time = time_to_jiffies(outentry.attr_valid,
+                                    outentry.attr_valid_nsec);
+
+       d_instantiate(entry, inode);
+       file = lookup_instantiate_filp(nd, entry, generic_file_open);
+       if (IS_ERR(file)) {
+               ff->fh = outopen.fh;
+               fuse_send_release(fc, ff, outentry.nodeid, inode, flags, 0);
+               return PTR_ERR(file);
+       }
+       fuse_finish_open(inode, file, ff, &outopen);
+       return 0;
+
+ out_free_ff:
+       fuse_file_free(ff);
+ out_put_request:
+       fuse_put_request(fc, req);
+ out:
+       return err;
+}
+#endif
+
 static int create_new_entry(struct fuse_conn *fc, struct fuse_req *req,
                            struct inode *dir, struct dentry *entry,
                            int mode)
@@ -224,6 +321,14 @@ static int fuse_mknod(struct inode *dir, struct dentry *entry, int mode,
 static int fuse_create(struct inode *dir, struct dentry *entry, int mode,
                       struct nameidata *nd)
 {
+#ifdef HAVE_LOOKUP_INSTANTIATE_FILP
+       if (nd && (nd->flags & LOOKUP_CREATE)) {
+               int err = fuse_create_open(dir, entry, mode, nd);
+               if (err != -ENOSYS)
+                       return err;
+               /* Fall back on mknod */
+       }
+#endif
        return fuse_mknod(dir, entry, mode, 0);
 }
 
index 19bd296635a39ec567612b4df7e6b66ee763e8a7..c047c8427ecb4c05da8f6fb903d1ee96ced7f2ef 100644 (file)
 #endif
 static struct file_operations fuse_direct_io_file_operations;
 
-int fuse_open_common(struct inode *inode, struct file *file, int isdir)
+static int fuse_send_open(struct inode *inode, struct file *file, int isdir,
+                         struct fuse_open_out *outargp)
 {
        struct fuse_conn *fc = get_fuse_conn(inode);
-       struct fuse_req *req;
        struct fuse_open_in inarg;
+       struct fuse_req *req;
+       int err;
+
+       req = fuse_get_request(fc);
+       if (!req)
+               return -EINTR;
+
+       memset(&inarg, 0, sizeof(inarg));
+       inarg.flags = file->f_flags & ~(O_CREAT | O_EXCL | O_NOCTTY | O_TRUNC);
+       req->in.h.opcode = isdir ? FUSE_OPENDIR : FUSE_OPEN;
+       req->in.h.nodeid = get_node_id(inode);
+       req->inode = inode;
+       req->in.numargs = 1;
+       req->in.args[0].size = sizeof(inarg);
+       req->in.args[0].value = &inarg;
+       req->out.numargs = 1;
+       req->out.args[0].size = sizeof(*outargp);
+       req->out.args[0].value = outargp;
+       request_send(fc, req);
+       err = req->out.h.error;
+       fuse_put_request(fc, req);
+
+       return err;
+}
+
+struct fuse_file *fuse_file_alloc(void)
+{
+       struct fuse_file *ff;
+       ff = kmalloc(sizeof(struct fuse_file), GFP_KERNEL);
+       if (ff) {
+               ff->release_req = fuse_request_alloc();
+               if (!ff->release_req) {
+                       kfree(ff);
+                       ff = NULL;
+               }
+       }
+       return ff;
+}
+
+void fuse_file_free(struct fuse_file *ff)
+{
+       fuse_request_free(ff->release_req);
+       kfree(ff);
+}
+
+void fuse_finish_open(struct inode *inode, struct file *file,
+                     struct fuse_file *ff, struct fuse_open_out *outarg)
+{
+       if (outarg->open_flags & FOPEN_DIRECT_IO)
+               file->f_op = &fuse_direct_io_file_operations;
+       if (!(outarg->open_flags & FOPEN_KEEP_CACHE))
+#ifdef KERNEL_2_6
+               invalidate_inode_pages(inode->i_mapping);
+#else
+               invalidate_inode_pages(inode);
+#endif
+       ff->fh = outarg->fh;
+       file->private_data = ff;
+}
+
+int fuse_open_common(struct inode *inode, struct file *file, int isdir)
+{
        struct fuse_open_out outarg;
        struct fuse_file *ff;
        int err;
@@ -38,77 +100,53 @@ int fuse_open_common(struct inode *inode, struct file *file, int isdir)
        /* If opening the root node, no lookup has been performed on
           it, so the attributes must be refreshed */
        if (get_node_id(inode) == FUSE_ROOT_ID) {
-               int err = fuse_do_getattr(inode);
+               err = fuse_do_getattr(inode);
                if (err)
                        return err;
        }
 
-       req = fuse_get_request(fc);
-       if (!req)
-               return -EINTR;
-
-       err = -ENOMEM;
-       ff = kmalloc(sizeof(struct fuse_file), GFP_KERNEL);
+       ff = fuse_file_alloc();
        if (!ff)
-               goto out_put_request;
-
-       ff->release_req = fuse_request_alloc();
-       if (!ff->release_req) {
-               kfree(ff);
-               goto out_put_request;
-       }
+               return -ENOMEM;
 
-       memset(&inarg, 0, sizeof(inarg));
-       inarg.flags = file->f_flags & ~(O_CREAT | O_EXCL | O_NOCTTY | O_TRUNC);
-       req->in.h.opcode = isdir ? FUSE_OPENDIR : FUSE_OPEN;
-       req->in.h.nodeid = get_node_id(inode);
-       req->inode = inode;
-       req->in.numargs = 1;
-       req->in.args[0].size = sizeof(inarg);
-       req->in.args[0].value = &inarg;
-       req->out.numargs = 1;
-       req->out.args[0].size = sizeof(outarg);
-       req->out.args[0].value = &outarg;
-       request_send(fc, req);
-       err = req->out.h.error;
-       if (err) {
-               fuse_request_free(ff->release_req);
-               kfree(ff);
-       } else {
-               if (!isdir && (outarg.open_flags & FOPEN_DIRECT_IO))
-                       file->f_op = &fuse_direct_io_file_operations;
-               if (!(outarg.open_flags & FOPEN_KEEP_CACHE))
-#ifdef KERNEL_2_6
-                       invalidate_inode_pages(inode->i_mapping);
-#else
-                       invalidate_inode_pages(inode);
-#endif
-               ff->fh = outarg.fh;
-               file->private_data = ff;
+       err = fuse_send_open(inode, file, isdir, &outarg);
+       if (err)
+               fuse_file_free(ff);
+       else {
+               if (isdir)
+                       outarg.open_flags &= ~FOPEN_DIRECT_IO;
+               fuse_finish_open(inode, file, ff, &outarg);
        }
 
- out_put_request:
-       fuse_put_request(fc, req);
        return err;
 }
 
-int fuse_release_common(struct inode *inode, struct file *file, int isdir)
+void fuse_send_release(struct fuse_conn *fc, struct fuse_file *ff,
+                      u64 nodeid, struct inode *inode, int flags, int isdir)
 {
-       struct fuse_conn *fc = get_fuse_conn(inode);
-       struct fuse_file *ff = file->private_data;
-       struct fuse_req *req = ff->release_req;
+       struct fuse_req * req = ff->release_req;
        struct fuse_release_in *inarg = &req->misc.release_in;
 
        inarg->fh = ff->fh;
-       inarg->flags = file->f_flags & ~O_EXCL;
+       inarg->flags = flags;
        req->in.h.opcode = isdir ? FUSE_RELEASEDIR : FUSE_RELEASE;
-       req->in.h.nodeid = get_node_id(inode);
+       req->in.h.nodeid = nodeid;
        req->inode = inode;
        req->in.numargs = 1;
        req->in.args[0].size = sizeof(struct fuse_release_in);
        req->in.args[0].value = inarg;
        request_send_background(fc, req);
        kfree(ff);
+}
+
+int fuse_release_common(struct inode *inode, struct file *file, int isdir)
+{
+       struct fuse_file *ff = file->private_data;
+       if (ff) {
+               struct fuse_conn *fc = get_fuse_conn(inode);
+               u64 nodeid = get_node_id(inode);
+               fuse_send_release(fc, ff, nodeid, inode, file->f_flags, isdir);
+       }
 
        /* Return value is ignored by VFS */
        return 0;
index af9457d146689954ed5088ecfdcec8c12c18d4a0..dced4491d16243a95b17f2e57a44f0d7e965f560 100644 (file)
@@ -343,6 +343,9 @@ struct fuse_conn {
        /** Is access not implemented by fs? */
        unsigned no_access : 1;
 
+       /** Is create not implemented by fs? */
+       unsigned no_create : 1;
+
 #ifdef KERNEL_2_6
        /** Backing dev info */
        struct backing_dev_info bdi;
@@ -420,6 +423,17 @@ size_t fuse_send_read_common(struct fuse_req *req, struct file *file,
  */
 int fuse_open_common(struct inode *inode, struct file *file, int isdir);
 
+struct fuse_file *fuse_file_alloc(void);
+void fuse_file_free(struct fuse_file *ff);
+void fuse_finish_open(struct inode *inode, struct file *file,
+                     struct fuse_file *ff, struct fuse_open_out *outarg);
+
+/**
+ * Send a RELEASE request
+ */
+void fuse_send_release(struct fuse_conn *fc, struct fuse_file *ff,
+                      u64 nodeid, struct inode *inode, int flags, int isdir);
+
 /**
  * Send RELEASE or RELEASEDIR request
  */
index 771020168d1a90454b7574277e4ea36018e82b81..23f83151ecc6a28575d2b15f2ad95cdea002999c 100644 (file)
@@ -128,7 +128,8 @@ enum fuse_opcode {
        FUSE_READDIR       = 28,
        FUSE_RELEASEDIR    = 29,
        FUSE_FSYNCDIR      = 30,
-       FUSE_ACCESS        = 34
+       FUSE_ACCESS        = 34,
+       FUSE_CREATE        = 35
 };
 
 /* Conservative buffer size for the client */
@@ -186,7 +187,7 @@ struct fuse_setattr_in {
 
 struct fuse_open_in {
        __u32   flags;
-       __u32   padding;
+       __u32   mode;
 };
 
 struct fuse_open_out {
index 6b3b6ae56f6efc7c9d8a6ad2b0acaeeb4090be3b..5dad478d055d0ed0bdd75d464d638e5bb3844537 100644 (file)
@@ -994,6 +994,66 @@ static void fuse_link(fuse_req_t req, fuse_ino_t ino, fuse_ino_t newparent,
     reply_entry(req, &e, err);
 }
 
+static void fuse_create(fuse_req_t req, fuse_ino_t parent, const char *name,
+                        mode_t mode, struct fuse_file_info *fi)
+{
+    struct fuse *f = req_fuse_prepare(req);
+    struct fuse_entry_param e;
+    char *path;
+    int err;
+
+    err = -ENOENT;
+    pthread_rwlock_rdlock(&f->tree_lock);
+    path = get_path_name(f, parent, name);
+    if (path != NULL) {
+        err = -ENOSYS;
+        if (f->op.create && f->op.getattr) {
+            err = f->op.create(path, mode, fi);
+            if (!err) {
+                if (f->flags & FUSE_DEBUG) {
+                    printf("CREATE[%lu] flags: 0x%x %s\n", fi->fh, fi->flags,
+                           path);
+                    fflush(stdout);
+                }
+                err = lookup_path(f, parent, name, path, &e);
+                if (err) {
+                    if (f->op.release)
+                        f->op.release(path, fi);
+                } else if (!S_ISREG(e.attr.st_mode)) {
+                    err = -EIO;
+                    if (f->op.release)
+                        f->op.release(path, fi);
+                    forget_node(f, e.ino, 1);
+                }
+            }
+        }
+    }
+
+    if (!err) {
+        if (f->flags & FUSE_DIRECT_IO)
+            fi->direct_io = 1;
+        if (f->flags & FUSE_KERNEL_CACHE)
+            fi->keep_cache = 1;
+
+        pthread_mutex_lock(&f->lock);
+        if (fuse_reply_create(req, &e, fi) == -ENOENT) {
+            /* The open syscall was interrupted, so it must be cancelled */
+            if(f->op.release)
+                f->op.release(path, fi);
+            forget_node(f, e.ino, 1);
+        } else {
+            struct node *node = get_node(f, e.ino);
+            node->open_count ++;
+        }
+        pthread_mutex_unlock(&f->lock);
+    } else
+        reply_err(req, err);
+
+    if (path)
+        free(path);
+    pthread_rwlock_unlock(&f->tree_lock);
+}
+
 static void fuse_open(fuse_req_t req, fuse_ino_t ino,
                       struct fuse_file_info *fi)
 {
@@ -1654,6 +1714,7 @@ static struct fuse_lowlevel_ops fuse_path_ops = {
     .symlink = fuse_symlink,
     .rename = fuse_rename,
     .link = fuse_link,
+    .create = fuse_create,
     .open = fuse_open,
     .read = fuse_read,
     .write = fuse_write,
index 22dc41d9e3fa261532ef2a2fdd445fcb4fdd9dd1..9379968401254ac18ffdaef5507f98e7dd6f98cc 100644 (file)
@@ -70,6 +70,7 @@ static const char *opname(enum fuse_opcode opcode)
     case FUSE_RELEASEDIR:      return "RELEASEDIR";
     case FUSE_FSYNCDIR:                return "FSYNCDIR";
     case FUSE_ACCESS:          return "ACCESS";
+    case FUSE_CREATE:          return "CREATE";
     default:                   return "???";
     }
 }
@@ -265,6 +266,20 @@ int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e)
     return send_reply_ok(req, &arg, sizeof(arg));
 }
 
+int fuse_reply_create(fuse_req_t req, const struct fuse_entry_param *e,
+                      const struct fuse_file_info *f)
+{
+    struct {
+        struct fuse_entry_out e;
+        struct fuse_open_out o;
+    } arg;
+
+    memset(&arg, 0, sizeof(arg));
+    fill_entry(&arg.e, e);
+    fill_open(&arg.o, f);
+    return send_reply_ok(req, &arg, sizeof(arg));
+}
+
 int fuse_reply_attr(fuse_req_t req, const struct stat *attr,
                     double attr_timeout)
 {
@@ -443,6 +458,20 @@ static void do_link(fuse_req_t req, fuse_ino_t nodeid,
         fuse_reply_err(req, ENOSYS);
 }
 
+static void do_create(fuse_req_t req, fuse_ino_t nodeid,
+                      struct fuse_open_in *arg)
+{
+    if (req->f->op.create) {
+        struct fuse_file_info fi;
+
+        memset(&fi, 0, sizeof(fi));
+        fi.flags = arg->flags;
+
+        req->f->op.create(req, nodeid, PARAM(arg), arg->mode, &fi);
+    } else
+        fuse_reply_err(req, ENOSYS);
+}
+
 static void do_open(fuse_req_t req, fuse_ino_t nodeid,
                     struct fuse_open_in *arg)
 {
@@ -825,6 +854,10 @@ static void fuse_ll_process(void *data, const char *buf, size_t len,
         do_access(req, in->nodeid, (struct fuse_access_in *) inarg);
         break;
 
+    case FUSE_CREATE:
+        do_create(req, in->nodeid, (struct fuse_open_in *) inarg);
+        break;
+
     default:
         fuse_reply_err(req, ENOSYS);
     }