From d9079a75b14b73e7953adf4958709b1e5ab3804c Mon Sep 17 00:00:00 2001 From: Miklos Szeredi Date: Wed, 26 Oct 2005 15:29:06 +0000 Subject: [PATCH] atomic open+create added --- ChangeLog | 6 +- example/fusexmp_fh.c | 12 ++++ include/fuse.h | 28 ++++++-- include/fuse_lowlevel.h | 71 ++++++++++++++++++++ kernel/configure.ac | 8 +++ kernel/dev.c | 7 ++ kernel/dir.c | 105 ++++++++++++++++++++++++++++++ kernel/file.c | 140 +++++++++++++++++++++++++--------------- kernel/fuse_i.h | 14 ++++ kernel/fuse_kernel.h | 5 +- lib/fuse.c | 61 +++++++++++++++++ lib/fuse_lowlevel.c | 33 ++++++++++ 12 files changed, 430 insertions(+), 60 deletions(-) diff --git a/ChangeLog b/ChangeLog index d5def4f..a92cf6f 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,11 +1,15 @@ 2005-10-26 Miklos Szeredi * 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 * lib: optimize buffer reallocation in fill_dir. diff --git a/example/fusexmp_fh.c b/example/fusexmp_fh.c index d17699e..262a868 100644 --- a/example/fusexmp_fh.c +++ b/example/fusexmp_fh.c @@ -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, diff --git a/include/fuse.h b/include/fuse.h index 4a099bc..290e9d3 100644 --- a/include/fuse.h +++ b/include/fuse.h @@ -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 diff --git a/include/fuse_lowlevel.h b/include/fuse_lowlevel.h index 73f57f0..e9c64ce 100644 --- a/include/fuse_lowlevel.h +++ b/include/fuse_lowlevel.h @@ -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 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 * diff --git a/kernel/configure.ac b/kernel/configure.ac index 5a1f80d..41ebafc 100644 --- a/kernel/configure.ac +++ b/kernel/configure.ac @@ -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= diff --git a/kernel/dev.c b/kernel/dev.c index 4df10f4..30bed76 100644 --- a/kernel/dev.c +++ b/kernel/dev.c @@ -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); diff --git a/kernel/dir.c b/kernel/dir.c index 20a6aae..bc792cb 100644 --- a/kernel/dir.c +++ b/kernel/dir.c @@ -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); } diff --git a/kernel/file.c b/kernel/file.c index 19bd296..c047c84 100644 --- a/kernel/file.c +++ b/kernel/file.c @@ -18,11 +18,73 @@ #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; diff --git a/kernel/fuse_i.h b/kernel/fuse_i.h index af9457d..dced449 100644 --- a/kernel/fuse_i.h +++ b/kernel/fuse_i.h @@ -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 */ diff --git a/kernel/fuse_kernel.h b/kernel/fuse_kernel.h index 7710201..23f8315 100644 --- a/kernel/fuse_kernel.h +++ b/kernel/fuse_kernel.h @@ -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 { diff --git a/lib/fuse.c b/lib/fuse.c index 6b3b6ae..5dad478 100644 --- a/lib/fuse.c +++ b/lib/fuse.c @@ -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, diff --git a/lib/fuse_lowlevel.c b/lib/fuse_lowlevel.c index 22dc41d..9379968 100644 --- a/lib/fuse_lowlevel.c +++ b/lib/fuse_lowlevel.c @@ -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); } -- 2.30.2