diff options
Diffstat (limited to 'fs/fuse/file.c')
| -rw-r--r-- | fs/fuse/file.c | 132 | 
1 files changed, 78 insertions, 54 deletions
| diff --git a/fs/fuse/file.c b/fs/fuse/file.c index e573b0cd2737..83d917f7e542 100644 --- a/fs/fuse/file.c +++ b/fs/fuse/file.c @@ -18,6 +18,7 @@  #include <linux/swap.h>  #include <linux/falloc.h>  #include <linux/uio.h> +#include <linux/fs.h>  static struct page **fuse_pages_alloc(unsigned int npages, gfp_t flags,  				      struct fuse_page_desc **desc) @@ -1586,7 +1587,6 @@ static void fuse_writepage_finish(struct fuse_conn *fc,  	struct backing_dev_info *bdi = inode_to_bdi(inode);  	int i; -	rb_erase(&wpa->writepages_entry, &fi->writepages);  	for (i = 0; i < ap->num_pages; i++) {  		dec_wb_stat(&bdi->wb, WB_WRITEBACK);  		dec_node_page_state(ap->pages[i], NR_WRITEBACK_TEMP); @@ -1637,6 +1637,7 @@ __acquires(fi->lock)   out_free:  	fi->writectr--; +	rb_erase(&wpa->writepages_entry, &fi->writepages);  	fuse_writepage_finish(fc, wpa);  	spin_unlock(&fi->lock); @@ -1674,7 +1675,8 @@ __acquires(fi->lock)  	}  } -static void tree_insert(struct rb_root *root, struct fuse_writepage_args *wpa) +static struct fuse_writepage_args *fuse_insert_writeback(struct rb_root *root, +						struct fuse_writepage_args *wpa)  {  	pgoff_t idx_from = wpa->ia.write.in.offset >> PAGE_SHIFT;  	pgoff_t idx_to = idx_from + wpa->ia.ap.num_pages - 1; @@ -1697,11 +1699,17 @@ static void tree_insert(struct rb_root *root, struct fuse_writepage_args *wpa)  		else if (idx_to < curr_index)  			p = &(*p)->rb_left;  		else -			return (void) WARN_ON(true); +			return curr;  	}  	rb_link_node(&wpa->writepages_entry, parent, p);  	rb_insert_color(&wpa->writepages_entry, root); +	return NULL; +} + +static void tree_insert(struct rb_root *root, struct fuse_writepage_args *wpa) +{ +	WARN_ON(fuse_insert_writeback(root, wpa));  }  static void fuse_writepage_end(struct fuse_conn *fc, struct fuse_args *args, @@ -1714,6 +1722,7 @@ static void fuse_writepage_end(struct fuse_conn *fc, struct fuse_args *args,  	mapping_set_error(inode->i_mapping, error);  	spin_lock(&fi->lock); +	rb_erase(&wpa->writepages_entry, &fi->writepages);  	while (wpa->next) {  		struct fuse_conn *fc = get_fuse_conn(inode);  		struct fuse_write_in *inarg = &wpa->ia.write.in; @@ -1952,14 +1961,14 @@ static void fuse_writepages_send(struct fuse_fill_wb_data *data)  }  /* - * First recheck under fi->lock if the offending offset is still under - * writeback.  If yes, then iterate auxiliary write requests, to see if there's + * Check under fi->lock if the page is under writeback, and insert it onto the + * rb_tree if not. Otherwise iterate auxiliary write requests, to see if there's   * one already added for a page at this offset.  If there's none, then insert   * this new request onto the auxiliary list, otherwise reuse the existing one by - * copying the new page contents over to the old temporary page. + * swapping the new temp page with the old one.   */ -static bool fuse_writepage_in_flight(struct fuse_writepage_args *new_wpa, -				     struct page *page) +static bool fuse_writepage_add(struct fuse_writepage_args *new_wpa, +			       struct page *page)  {  	struct fuse_inode *fi = get_fuse_inode(new_wpa->inode);  	struct fuse_writepage_args *tmp; @@ -1967,17 +1976,15 @@ static bool fuse_writepage_in_flight(struct fuse_writepage_args *new_wpa,  	struct fuse_args_pages *new_ap = &new_wpa->ia.ap;  	WARN_ON(new_ap->num_pages != 0); +	new_ap->num_pages = 1;  	spin_lock(&fi->lock); -	rb_erase(&new_wpa->writepages_entry, &fi->writepages); -	old_wpa = fuse_find_writeback(fi, page->index, page->index); +	old_wpa = fuse_insert_writeback(&fi->writepages, new_wpa);  	if (!old_wpa) { -		tree_insert(&fi->writepages, new_wpa);  		spin_unlock(&fi->lock); -		return false; +		return true;  	} -	new_ap->num_pages = 1;  	for (tmp = old_wpa->next; tmp; tmp = tmp->next) {  		pgoff_t curr_index; @@ -2006,7 +2013,41 @@ static bool fuse_writepage_in_flight(struct fuse_writepage_args *new_wpa,  		fuse_writepage_free(new_wpa);  	} -	return true; +	return false; +} + +static bool fuse_writepage_need_send(struct fuse_conn *fc, struct page *page, +				     struct fuse_args_pages *ap, +				     struct fuse_fill_wb_data *data) +{ +	WARN_ON(!ap->num_pages); + +	/* +	 * Being under writeback is unlikely but possible.  For example direct +	 * read to an mmaped fuse file will set the page dirty twice; once when +	 * the pages are faulted with get_user_pages(), and then after the read +	 * completed. +	 */ +	if (fuse_page_is_writeback(data->inode, page->index)) +		return true; + +	/* Reached max pages */ +	if (ap->num_pages == fc->max_pages) +		return true; + +	/* Reached max write bytes */ +	if ((ap->num_pages + 1) * PAGE_SIZE > fc->max_write) +		return true; + +	/* Discontinuity */ +	if (data->orig_pages[ap->num_pages - 1]->index + 1 != page->index) +		return true; + +	/* Need to grow the pages array?  If so, did the expansion fail? */ +	if (ap->num_pages == data->max_pages && !fuse_pages_realloc(data)) +		return true; + +	return false;  }  static int fuse_writepages_fill(struct page *page, @@ -2019,7 +2060,6 @@ static int fuse_writepages_fill(struct page *page,  	struct fuse_inode *fi = get_fuse_inode(inode);  	struct fuse_conn *fc = get_fuse_conn(inode);  	struct page *tmp_page; -	bool is_writeback;  	int err;  	if (!data->ff) { @@ -2029,25 +2069,9 @@ static int fuse_writepages_fill(struct page *page,  			goto out_unlock;  	} -	/* -	 * Being under writeback is unlikely but possible.  For example direct -	 * read to an mmaped fuse file will set the page dirty twice; once when -	 * the pages are faulted with get_user_pages(), and then after the read -	 * completed. -	 */ -	is_writeback = fuse_page_is_writeback(inode, page->index); - -	if (wpa && ap->num_pages && -	    (is_writeback || ap->num_pages == fc->max_pages || -	     (ap->num_pages + 1) * PAGE_SIZE > fc->max_write || -	     data->orig_pages[ap->num_pages - 1]->index + 1 != page->index)) { +	if (wpa && fuse_writepage_need_send(fc, page, ap, data)) {  		fuse_writepages_send(data);  		data->wpa = NULL; -	} else if (wpa && ap->num_pages == data->max_pages) { -		if (!fuse_pages_realloc(data)) { -			fuse_writepages_send(data); -			data->wpa = NULL; -		}  	}  	err = -ENOMEM; @@ -2085,12 +2109,6 @@ static int fuse_writepages_fill(struct page *page,  		ap->args.end = fuse_writepage_end;  		ap->num_pages = 0;  		wpa->inode = inode; - -		spin_lock(&fi->lock); -		tree_insert(&fi->writepages, wpa); -		spin_unlock(&fi->lock); - -		data->wpa = wpa;  	}  	set_page_writeback(page); @@ -2098,26 +2116,25 @@ static int fuse_writepages_fill(struct page *page,  	ap->pages[ap->num_pages] = tmp_page;  	ap->descs[ap->num_pages].offset = 0;  	ap->descs[ap->num_pages].length = PAGE_SIZE; +	data->orig_pages[ap->num_pages] = page;  	inc_wb_stat(&inode_to_bdi(inode)->wb, WB_WRITEBACK);  	inc_node_page_state(tmp_page, NR_WRITEBACK_TEMP);  	err = 0; -	if (is_writeback && fuse_writepage_in_flight(wpa, page)) { +	if (data->wpa) { +		/* +		 * Protected by fi->lock against concurrent access by +		 * fuse_page_is_writeback(). +		 */ +		spin_lock(&fi->lock); +		ap->num_pages++; +		spin_unlock(&fi->lock); +	} else if (fuse_writepage_add(wpa, page)) { +		data->wpa = wpa; +	} else {  		end_page_writeback(page); -		data->wpa = NULL; -		goto out_unlock;  	} -	data->orig_pages[ap->num_pages] = page; - -	/* -	 * Protected by fi->lock against concurrent access by -	 * fuse_page_is_writeback(). -	 */ -	spin_lock(&fi->lock); -	ap->num_pages++; -	spin_unlock(&fi->lock); -  out_unlock:  	unlock_page(page); @@ -2149,10 +2166,8 @@ static int fuse_writepages(struct address_space *mapping,  	err = write_cache_pages(mapping, wbc, fuse_writepages_fill, &data);  	if (data.wpa) { -		/* Ignore errors if we can write at least one page */  		WARN_ON(!data.wpa->ia.ap.num_pages);  		fuse_writepages_send(&data); -		err = 0;  	}  	if (data.ff)  		fuse_file_put(data.ff, false, false); @@ -2761,7 +2776,16 @@ long fuse_do_ioctl(struct file *file, unsigned int cmd, unsigned long arg,  		struct iovec *iov = iov_page;  		iov->iov_base = (void __user *)arg; -		iov->iov_len = _IOC_SIZE(cmd); + +		switch (cmd) { +		case FS_IOC_GETFLAGS: +		case FS_IOC_SETFLAGS: +			iov->iov_len = sizeof(int); +			break; +		default: +			iov->iov_len = _IOC_SIZE(cmd); +			break; +		}  		if (_IOC_DIR(cmd) & _IOC_WRITE) {  			in_iov = iov; | 
