From: Miklos Szeredi Date: Fri, 16 Jul 2004 16:17:02 +0000 (+0000) Subject: fixes and improvements X-Git-Tag: fuse_1_9~28 X-Git-Url: http://git.maquefel.me/?a=commitdiff_plain;h=069c950a5e9c4df16be9fd5ab5f26fdacdc5e72a;p=qemu-gpiodev%2Flibfuse.git fixes and improvements --- diff --git a/ChangeLog b/ChangeLog index 95d0e2c..55769c8 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,16 @@ +2004-07-16 Miklos Szeredi + + * Separate directory entry and inode attribute validity timer + + * New write semaphore to stop page writeback during truncate + + * Fsync now waits for all writes to complete before sending the + request + + * Optimization: if a page is completely written by + fuse_commit_write(), clear the dirty flag and set the uptodate + flag for that page + 2004-07-13 Miklos Szeredi * Add FUSE_HARD_REMOVE flag, and '-i' option to fuse main, which diff --git a/kernel/dev.c b/kernel/dev.c index a7dacb8..bc59d99 100644 --- a/kernel/dev.c +++ b/kernel/dev.c @@ -96,6 +96,16 @@ static int get_unique(struct fuse_conn *fc) return fc->reqctr; } +void fuse_reset_request(struct fuse_req *req) +{ + int preallocated = req->preallocated; + + memset(req, 0, sizeof(*req)); + INIT_LIST_HEAD(&req->list); + init_waitqueue_head(&req->waitq); + req->preallocated = preallocated; +} + static struct fuse_req *do_get_request(struct fuse_conn *fc) { struct fuse_req *req; @@ -105,12 +115,7 @@ static struct fuse_req *do_get_request(struct fuse_conn *fc) req = list_entry(fc->unused_list.next, struct fuse_req, list); list_del_init(&req->list); spin_unlock(&fuse_lock); - - memset(req, 0, sizeof(*req)); - INIT_LIST_HEAD(&req->list); - init_waitqueue_head(&req->waitq); - req->preallocated = 1; - + fuse_reset_request(req); return req; } @@ -422,7 +427,7 @@ static int fuse_invalidate(struct fuse_conn *fc, struct fuse_user_header *uh) struct inode *inode = iget(fc->sb, uh->ino); int err = -ENOENT; if (inode) { - if (inode->u.generic_ip) { + if (FUSE_FI(inode)) { invalidate_inode_pages(inode); err = 0; } @@ -577,6 +582,7 @@ static struct fuse_conn *new_conn(void) free_conn(fc); return NULL; } + req->preallocated = 1; list_add(&req->list, &fc->unused_list); } fc->reqctr = 1; diff --git a/kernel/dir.c b/kernel/dir.c index 519e044..7a8567c 100644 --- a/kernel/dir.c +++ b/kernel/dir.c @@ -87,14 +87,14 @@ struct inode *fuse_iget(struct super_block *sb, ino_t ino, int generation, inode = iget(sb, ino); if (inode) { - if (!inode->u.generic_ip) { - struct fuse_req *req = fuse_request_alloc(); - if (!req) { + if (!INO_FI(inode)) { + struct fuse_inode *fi = fuse_inode_alloc(); + if (!fi) { iput(inode); inode = NULL; goto out; } - inode->u.generic_ip = req; + INO_FI(inode) = fi; inode->i_generation = generation; fuse_init_inode(inode, attr); } else if (inode->i_generation != generation) @@ -182,25 +182,23 @@ static int fuse_lookup_iget(struct inode *dir, struct dentry *entry, if (err && err != -ENOENT) return err; - if (inode) - entry->d_time = time_to_jiffies(outarg.entry_valid, + if (inode) { + struct fuse_inode *fi = INO_FI(inode); + entry->d_time = time_to_jiffies(outarg.entry_valid, outarg.entry_valid_nsec); + fi->i_time = time_to_jiffies(outarg.attr_valid, + outarg.attr_valid_nsec); + } entry->d_op = &fuse_dentry_operations; *inodep = inode; return 0; } -static void uncache_dir(struct inode *dir) +static void fuse_invalidate_attr(struct inode *inode) { - struct dentry *entry = d_find_alias(dir); - if (!entry) - dir->i_nlink = 0; - else { - /* FIXME: this should reset the _attribute_ timeout */ - entry->d_time = jiffies - 1; - dput(entry); - } + struct fuse_inode *fi = INO_FI(inode); + fi->i_time = jiffies - 1; } static int lookup_new_entry(struct fuse_conn *fc, struct fuse_req *req, @@ -209,6 +207,7 @@ static int lookup_new_entry(struct fuse_conn *fc, struct fuse_req *req, int mode) { struct inode *inode; + struct fuse_inode *fi; inode = fuse_iget(dir->i_sb, outarg->ino, outarg->generation, &outarg->attr, version); if (!inode) { @@ -227,8 +226,12 @@ static int lookup_new_entry(struct fuse_conn *fc, struct fuse_req *req, entry->d_time = time_to_jiffies(outarg->entry_valid, outarg->entry_valid_nsec); + fi = INO_FI(inode); + fi->i_time = time_to_jiffies(outarg->attr_valid, + outarg->attr_valid_nsec); + d_instantiate(entry, inode); - uncache_dir(dir); + fuse_invalidate_attr(dir); return 0; } @@ -360,12 +363,14 @@ static int fuse_unlink(struct inode *dir, struct dentry *entry) request_send(fc, req); err = req->out.h.error; if (!err) { + struct inode *inode = entry->d_inode; + /* Set nlink to zero so the inode can be cleared, if the inode does have more links this will be discovered at the next lookup/getattr */ - /* FIXME: mark inode "not uptodate" */ - entry->d_inode->i_nlink = 0; - uncache_dir(dir); + inode->i_nlink = 0; + fuse_invalidate_attr(inode); + fuse_invalidate_attr(dir); } fuse_put_request(fc, req); return err; @@ -389,7 +394,7 @@ static int fuse_rmdir(struct inode *dir, struct dentry *entry) err = req->out.h.error; if (!err) { entry->d_inode->i_nlink = 0; - uncache_dir(dir); + fuse_invalidate_attr(dir); } fuse_put_request(fc, req); return err; @@ -421,9 +426,9 @@ static int fuse_rename(struct inode *olddir, struct dentry *oldent, err = req->out.h.error; fuse_put_request(fc, req); if (!err) { - uncache_dir(olddir); + fuse_invalidate_attr(olddir); if (olddir != newdir) - uncache_dir(newdir); + fuse_invalidate_attr(newdir); } return err; } @@ -467,6 +472,7 @@ static int fuse_link(struct dentry *entry, struct inode *newdir, int fuse_do_getattr(struct inode *inode) { + struct fuse_inode *fi = INO_FI(inode); struct fuse_conn *fc = INO_FC(inode); struct fuse_req *req = fuse_get_request(fc); struct fuse_attr_out arg; @@ -482,8 +488,11 @@ int fuse_do_getattr(struct inode *inode) req->out.args[0].value = &arg; request_send(fc, req); err = req->out.h.error; - if (!err) + if (!err) { change_attributes(inode, &arg.attr); + fi->i_time = time_to_jiffies(arg.attr_valid, + arg.attr_valid_nsec); + } fuse_put_request(fc, req); return err; } @@ -491,13 +500,14 @@ int fuse_do_getattr(struct inode *inode) static int fuse_revalidate(struct dentry *entry) { struct inode *inode = entry->d_inode; + struct fuse_inode *fi = INO_FI(inode); struct fuse_conn *fc = INO_FC(inode); if (inode->i_ino == FUSE_ROOT_INO) { if (!(fc->flags & FUSE_ALLOW_OTHER) && current->fsuid != fc->uid) return -EACCES; - } else if (!entry->d_time || time_before_eq(jiffies, entry->d_time)) + } else if (!fi->i_time || time_before_eq(jiffies, fi->i_time)) return 0; return fuse_do_getattr(inode); @@ -745,19 +755,33 @@ static int fuse_setattr(struct dentry *entry, struct iattr *attr) { struct inode *inode = entry->d_inode; struct fuse_conn *fc = INO_FC(inode); + struct fuse_inode *fi = INO_FI(inode); struct fuse_req *req; struct fuse_setattr_in inarg; struct fuse_attr_out outarg; int err; + int is_truncate = 0; + + + if (attr->ia_valid & ATTR_SIZE) { + unsigned long limit; + is_truncate = 1; - /* FIXME: need to fix race between truncate and writepage */ - if (attr->ia_valid & ATTR_SIZE) - fuse_sync_inode(inode); + limit = current->rlim[RLIMIT_FSIZE].rlim_cur; + if (limit != RLIM_INFINITY && attr->ia_size > limit) { + send_sig(SIGXFSZ, current, 0); + return -EFBIG; + } + //fuse_sync_inode(inode); + } req = fuse_get_request(fc); if (!req) return -ERESTARTSYS; + if (is_truncate) + down_write(&fi->write_sem); + memset(&inarg, 0, sizeof(inarg)); inarg.valid = iattr_to_fattr(attr, &inarg.attr); req->in.h.opcode = FUSE_SETATTR; @@ -770,14 +794,22 @@ static int fuse_setattr(struct dentry *entry, struct iattr *attr) req->out.args[0].value = &outarg; request_send(fc, req); err = req->out.h.error; - if (!err) { - if (attr->ia_valid & ATTR_SIZE && - outarg.attr.size < i_size_read(inode)) - vmtruncate(inode, outarg.attr.size); + fuse_put_request(fc, req); + if (!err) { + if (is_truncate) { + loff_t origsize = i_size_read(inode); + i_size_write(inode, outarg.attr.size); + up_write(&fi->write_sem); + if (origsize > outarg.attr.size) + vmtruncate(inode, outarg.attr.size); + } change_attributes(inode, &outarg.attr); - } - fuse_put_request(fc, req); + fi->i_time = time_to_jiffies(outarg.attr_valid, + outarg.attr_valid_nsec); + } else if (is_truncate) + up_write(&fi->write_sem); + return err; } @@ -787,6 +819,7 @@ static int _fuse_dentry_revalidate(struct dentry *entry) return 0; else if (entry->d_time && time_after(jiffies, entry->d_time)) { struct inode *inode = entry->d_inode; + struct fuse_inode *fi = INO_FI(inode); struct fuse_entry_out outarg; int version; int ret; @@ -803,6 +836,8 @@ static int _fuse_dentry_revalidate(struct dentry *entry) inode->i_version = version; entry->d_time = time_to_jiffies(outarg.entry_valid, outarg.entry_valid_nsec); + fi->i_time = time_to_jiffies(outarg.attr_valid, + outarg.attr_valid_nsec); } return 1; } diff --git a/kernel/file.c b/kernel/file.c index 8e32f45..2a6fb4b 100644 --- a/kernel/file.c +++ b/kernel/file.c @@ -145,6 +145,7 @@ static int fuse_flush(struct file *file) static int fuse_fsync(struct file *file, struct dentry *de, int datasync) { struct inode *inode = de->d_inode; + struct fuse_inode *fi = INO_FI(inode); struct fuse_conn *fc = INO_FC(inode); struct fuse_req *req; struct fuse_fsync_in inarg; @@ -156,7 +157,12 @@ static int fuse_fsync(struct file *file, struct dentry *de, int datasync) req = fuse_get_request(fc); if (!req) return -ERESTARTSYS; - + + /* Make sure all writes to this inode are completed before + issuing the FSYNC request */ + down_write(&fi->write_sem); + up_write(&fi->write_sem); + memset(&inarg, 0, sizeof(inarg)); inarg.datasync = datasync; req->in.h.opcode = FUSE_FSYNC; @@ -172,10 +178,6 @@ static int fuse_fsync(struct file *file, struct dentry *de, int datasync) } fuse_put_request(fc, req); return err; - - /* FIXME: need to ensure, that all write requests issued - before this request are completed. Should userspace take - care of this? */ } static ssize_t fuse_send_read(struct inode *inode, char *buf, loff_t pos, @@ -392,34 +394,29 @@ static ssize_t fuse_file_read(struct file *file, char *buf, struct fuse_conn *fc = INO_FC(inode); ssize_t res; - down(&inode->i_sem); if (fc->flags & FUSE_DIRECT_IO) { res = fuse_read(file, buf, count, ppos); } else { - if (fc->flags & FUSE_LARGE_READ) + if (fc->flags & FUSE_LARGE_READ) { + down(&inode->i_sem); fuse_file_bigread(inode, *ppos, count); - + up(&inode->i_sem); + } res = generic_file_read(file, buf, count, ppos); } - up(&inode->i_sem); return res; } -static ssize_t fuse_send_write(struct inode *inode, const char *buf, - loff_t pos, size_t count) +static ssize_t fuse_send_write(struct fuse_req *req, struct inode *inode, + const char *buf, loff_t pos, size_t count) { struct fuse_conn *fc = INO_FC(inode); - struct fuse_req *req; struct fuse_write_in inarg; struct fuse_write_out outarg; ssize_t res; - req = fuse_get_request(fc); - if (!req) - return -ERESTARTSYS; - memset(&inarg, 0, sizeof(inarg)); inarg.offset = pos; inarg.size = count; @@ -436,21 +433,28 @@ static ssize_t fuse_send_write(struct inode *inode, const char *buf, request_send(fc, req); res = req->out.h.error; if (!res) - res = outarg.size; - fuse_put_request(fc, req); - return res; + return outarg.size; + else + return res; } static int write_buffer(struct inode *inode, struct page *page, unsigned offset, size_t count) { + struct fuse_conn *fc = INO_FC(inode); char *buffer; ssize_t res; loff_t pos; + struct fuse_req *req; + req = fuse_get_request(fc); + if (!req) + return -ERESTARTSYS; + pos = ((unsigned long long) page->index << PAGE_CACHE_SHIFT) + offset; buffer = kmap(page); - res = fuse_send_write(inode, buffer + offset, pos, count); + res = fuse_send_write(req, inode, buffer + offset, pos, count); + fuse_put_request(fc, req); if (res >= 0) { if (res < count) { printk("fuse: short write\n"); @@ -481,11 +485,53 @@ static int get_write_count(struct inode *inode, struct page *page) return count; } + +static int write_page_block(struct inode *inode, struct page *page) +{ + struct fuse_conn *fc = INO_FC(inode); + struct fuse_inode *fi = INO_FI(inode); + char *buffer; + ssize_t res; + loff_t pos; + unsigned count; + struct fuse_req *req; + + req = fuse_get_request(fc); + if (!req) + return -ERESTARTSYS; + + down_read(&fi->write_sem); + count = get_write_count(inode, page); + res = 0; + if (count) { + pos = ((unsigned long long) page->index << PAGE_CACHE_SHIFT); + buffer = kmap(page); + res = fuse_send_write(req, inode, buffer, pos, count); + if (res >= 0) { + if (res < count) { + printk("fuse: short write\n"); + res = -EPROTO; + } else + res = 0; + } + } + up_read(&fi->write_sem); + fuse_put_request(fc, req); + kunmap(page); + if (res) + SetPageError(page); + return res; +} + + #ifdef KERNEL_2_6 -static void write_buffer_end(struct fuse_conn *fc, struct fuse_req *req) + +static void write_page_nonblock_end(struct fuse_conn *fc, struct fuse_req *req) { struct page *page = (struct page *) req->data; + struct inode *inode = page->mapping->host; + struct fuse_inode *fi = INO_FI(inode); struct fuse_write_out *outarg = req->out.args[0].value; if (!req->out.h.error && outarg->size != req->in.args[1].size) { printk("fuse: short write\n"); @@ -499,26 +545,23 @@ static void write_buffer_end(struct fuse_conn *fc, struct fuse_req *req) else set_bit(AS_EIO, &page->mapping->flags); } + up_read(&fi->write_sem); + end_page_writeback(page); kunmap(page); fuse_put_request(fc, req); } -static int write_buffer_nonblock(struct inode *inode, struct page *page, - unsigned offset, size_t count) +static void send_write_nonblock(struct fuse_req *req, struct inode *inode, + struct page *page, unsigned count) { struct fuse_conn *fc = INO_FC(inode); - struct fuse_req *req; struct fuse_write_in *inarg; char *buffer; - req = fuse_get_request_nonblock(fc); - if (!req) - return -EWOULDBLOCK; - inarg = &req->misc.write.in; buffer = kmap(page); - inarg->offset = ((unsigned long long) page->index << PAGE_CACHE_SHIFT) + offset; + inarg->offset = ((unsigned long long) page->index << PAGE_CACHE_SHIFT); inarg->size = count; req->in.h.opcode = FUSE_WRITE; req->in.h.ino = inode->i_ino; @@ -526,36 +569,52 @@ static int write_buffer_nonblock(struct inode *inode, struct page *page, req->in.args[0].size = sizeof(struct fuse_write_in); req->in.args[0].value = inarg; req->in.args[1].size = count; - req->in.args[1].value = buffer + offset; + req->in.args[1].value = buffer; req->out.numargs = 1; req->out.args[0].size = sizeof(struct fuse_write_out); req->out.args[0].value = &req->misc.write.out; - request_send_nonblock(fc, req, write_buffer_end, page); - return 0; + request_send_nonblock(fc, req, write_page_nonblock_end, page); } -static int fuse_writepage(struct page *page, struct writeback_control *wbc) +static int write_page_nonblock(struct inode *inode, struct page *page) { + struct fuse_conn *fc = INO_FC(inode); + struct fuse_inode *fi = INO_FI(inode); + struct fuse_req *req; int err; - struct inode *inode = page->mapping->host; - unsigned count = get_write_count(inode, page); - err = -EINVAL; - if (count) { - /* FIXME: check sync_mode, and wait for previous writes (or - signal userspace to do this) */ - if (wbc->nonblocking) { - SetPageWriteback(page); - err = write_buffer_nonblock(inode, page, 0, count); - if (err) - ClearPageWriteback(page); - if (err == -EWOULDBLOCK) { - redirty_page_for_writepage(wbc, page); - err = 0; + err = -EWOULDBLOCK; + req = fuse_get_request_nonblock(fc); + if (req) { + if (down_read_trylock(&fi->write_sem)) { + unsigned count; + err = 0; + count = get_write_count(inode, page); + if (count) { + SetPageWriteback(page); + send_write_nonblock(req, inode, page, count); + return 0; } - } else - err = write_buffer(inode, page, 0, count); + up_read(&fi->write_sem); + } + fuse_put_request(fc, req); } + return err; +} + +static int fuse_writepage(struct page *page, struct writeback_control *wbc) +{ + int err; + struct inode *inode = page->mapping->host; + + if (wbc->nonblocking) { + err = write_page_nonblock(inode, page); + if (err == -EWOULDBLOCK) { + redirty_page_for_writepage(wbc, page); + err = 0; + } + } else + err = write_page_block(inode, page); unlock_page(page); return err; @@ -563,13 +622,7 @@ static int fuse_writepage(struct page *page, struct writeback_control *wbc) #else static int fuse_writepage(struct page *page) { - int err; - struct inode *inode = page->mapping->host; - int count = get_write_count(inode, page); - err = -EINVAL; - if (count) - err = write_buffer(inode, page, 0, count); - + int err = write_page_block(page->mapping->host, page); unlock_page(page); return err; } @@ -593,6 +646,12 @@ static int fuse_commit_write(struct file *file, struct page *page, loff_t pos = (page->index << PAGE_CACHE_SHIFT) + to; if (pos > i_size_read(inode)) i_size_write(inode, pos); + + if (offset == 0 && to == PAGE_CACHE_SIZE) { + clear_page_dirty(page); + SetPageUptodate(page); + } + } return err; } @@ -605,11 +664,18 @@ static ssize_t fuse_write(struct file *file, const char *buf, size_t count, char *tmpbuf; ssize_t res = 0; loff_t pos = *ppos; + struct fuse_req *req; + + req = fuse_get_request(fc); + if (!req) + return -ERESTARTSYS; tmpbuf = kmalloc(count < fc->max_write ? count : fc->max_write, GFP_KERNEL); - if (!tmpbuf) + if (!tmpbuf) { + fuse_put_request(fc, req); return -ENOMEM; + } while (count) { size_t nbytes = count < fc->max_write ? count : fc->max_write; @@ -618,7 +684,7 @@ static ssize_t fuse_write(struct file *file, const char *buf, size_t count, res = -EFAULT; break; } - res1 = fuse_send_write(inode, tmpbuf, pos, nbytes); + res1 = fuse_send_write(req, inode, tmpbuf, pos, nbytes); if (res1 < 0) { res = res1; break; @@ -629,8 +695,12 @@ static ssize_t fuse_write(struct file *file, const char *buf, size_t count, pos += res1; if (res1 < nbytes) break; + + if (count) + fuse_reset_request(req); } kfree(tmpbuf); + fuse_put_request(fc, req); if (res > 0) { if (pos > i_size_read(inode)) diff --git a/kernel/fuse_i.h b/kernel/fuse_i.h index fdd4625..4b03990 100644 --- a/kernel/fuse_i.h +++ b/kernel/fuse_i.h @@ -65,6 +65,13 @@ permission checking is done in the kernel */ /** Bypass the page cache for read and write operations */ #define FUSE_DIRECT_IO (1 << 4) +/** FUSE specific inode data */ +struct fuse_inode { + struct fuse_req *forget_req; + struct rw_semaphore write_sem; + unsigned long i_time; +}; + /** One input argument of a request */ struct fuse_in_arg { unsigned int size; @@ -223,7 +230,8 @@ struct fuse_getdir_out_i { #define SB_FC(sb) ((sb)->u.generic_sbp) #endif #define INO_FC(inode) SB_FC((inode)->i_sb) -#define DEV_FC(file) ((struct fuse_conn *) (file)->private_data) +#define DEV_FC(file) ((file)->private_data) +#define INO_FI(inode) ((inode)->u.generic_ip) /** @@ -292,6 +300,11 @@ struct fuse_req *fuse_request_alloc(void); */ void fuse_request_free(struct fuse_req *req); +/** + * Reinitialize a request, the preallocated flag is left unmodified + */ +void fuse_reset_request(struct fuse_req *req); + /** * Reserve a preallocated request */ @@ -333,6 +346,11 @@ int fuse_do_getattr(struct inode *inode); */ void fuse_sync_inode(struct inode *inode); +/** + * Allocate fuse specific inode data + */ +struct fuse_inode *fuse_inode_alloc(void); + /* * Local Variables: * indent-tabs-mode: t diff --git a/kernel/inode.c b/kernel/inode.c index 51eed45..8c1cba3 100644 --- a/kernel/inode.c +++ b/kernel/inode.c @@ -24,6 +24,7 @@ static int user_allow_other; +static kmem_cache_t *fuse_inode_cachep; #ifdef KERNEL_2_6 #include @@ -53,6 +54,29 @@ struct fuse_mount_data { unsigned int max_read; }; +struct fuse_inode *fuse_inode_alloc(void) +{ + struct fuse_inode *fi; + + fi = kmem_cache_alloc(fuse_inode_cachep, SLAB_KERNEL); + if (fi) { + memset(fi, 0, sizeof(*fi)); + fi->forget_req = fuse_request_alloc(); + if (!fi->forget_req) { + kmem_cache_free(fuse_inode_cachep, fi); + fi = NULL; + } else + init_rwsem(&fi->write_sem); + } + + return fi; +} + +static void fuse_inode_free(struct fuse_inode *fi) +{ + kmem_cache_free(fuse_inode_cachep, fi); +} + static void fuse_read_inode(struct inode *inode) { /* No op */ @@ -74,15 +98,16 @@ void fuse_send_forget(struct fuse_conn *fc, struct fuse_req *req, ino_t ino, static void fuse_clear_inode(struct inode *inode) { struct fuse_conn *fc = INO_FC(inode); - struct fuse_req *req = inode->u.generic_ip; + struct fuse_inode *fi = INO_FI(inode); - if (fc == NULL) { - if (req) - fuse_request_free(req); - return; + if (fi) { + if (fc == NULL) + fuse_request_free(fi->forget_req); + else + fuse_send_forget(fc, fi->forget_req, inode->i_ino, + inode->i_version); + fuse_inode_free(fi); } - if (req != NULL) - fuse_send_forget(fc, req, inode->i_ino, inode->i_version); } static void fuse_put_super(struct super_block *sb) @@ -409,18 +434,28 @@ static DECLARE_FSTYPE(fuse_fs_type, "fuse", fuse_read_super_compat, 0); int fuse_fs_init() { - int res; + int err; - res = register_filesystem(&fuse_fs_type); - if (res) + err = register_filesystem(&fuse_fs_type); + if (err) printk("fuse: failed to register filesystem\n"); + else { + fuse_inode_cachep = kmem_cache_create("fuse_inode", + sizeof(struct fuse_inode), + 0, 0, NULL, NULL); + if (!fuse_inode_cachep) { + unregister_filesystem(&fuse_fs_type); + err = -ENOMEM; + } + } - return res; + return err; } void fuse_fs_cleanup() { unregister_filesystem(&fuse_fs_type); + kmem_cache_destroy(fuse_inode_cachep); } /*