CIFS: Separate page search from readpages
[deliverable/linux.git] / fs / cifs / file.c
index 0064d38fdb76b4cf039d6b5014f9bb00bc7ebc53..bec48f1bc3cab7d8c29092bb1f7462b6c79096a1 100644 (file)
@@ -1670,8 +1670,8 @@ cifs_write(struct cifsFileInfo *open_file, __u32 pid, const char *write_data,
                                        break;
                        }
 
-                       len = min((size_t)cifs_sb->wsize,
-                                 write_size - total_written);
+                       len = min(server->ops->wp_retry_size(dentry->d_inode),
+                                 (unsigned int)write_size - total_written);
                        /* iov[0] is reserved for smb header */
                        iov[1].iov_base = (char *)write_data + total_written;
                        iov[1].iov_len = len;
@@ -2009,19 +2009,17 @@ wdata_send_pages(struct cifs_writedata *wdata, unsigned int nr_pages,
                        (loff_t)PAGE_CACHE_SIZE);
        wdata->bytes = ((nr_pages - 1) * PAGE_CACHE_SIZE) + wdata->tailsz;
 
-       do {
-               if (wdata->cfile != NULL)
-                       cifsFileInfo_put(wdata->cfile);
-               wdata->cfile = find_writable_file(CIFS_I(mapping->host), false);
-               if (!wdata->cfile) {
-                       cifs_dbg(VFS, "No writable handles for inode\n");
-                       rc = -EBADF;
-                       break;
-               }
+       if (wdata->cfile != NULL)
+               cifsFileInfo_put(wdata->cfile);
+       wdata->cfile = find_writable_file(CIFS_I(mapping->host), false);
+       if (!wdata->cfile) {
+               cifs_dbg(VFS, "No writable handles for inode\n");
+               rc = -EBADF;
+       } else {
                wdata->pid = wdata->cfile->pid;
                server = tlink_tcon(wdata->cfile->tlink)->ses->server;
                rc = server->ops->async_writev(wdata, cifs_writedata_release);
-       } while (wbc->sync_mode == WB_SYNC_ALL && rc == -EAGAIN);
+       }
 
        for (i = 0; i < nr_pages; ++i)
                unlock_page(wdata->pages[i]);
@@ -2033,6 +2031,7 @@ static int cifs_writepages(struct address_space *mapping,
                           struct writeback_control *wbc)
 {
        struct cifs_sb_info *cifs_sb = CIFS_SB(mapping->host->i_sb);
+       struct TCP_Server_Info *server;
        bool done = false, scanned = false, range_whole = false;
        pgoff_t end, index;
        struct cifs_writedata *wdata;
@@ -2055,23 +2054,30 @@ static int cifs_writepages(struct address_space *mapping,
                        range_whole = true;
                scanned = true;
        }
+       server = cifs_sb_master_tcon(cifs_sb)->ses->server;
 retry:
        while (!done && index <= end) {
-               unsigned int i, nr_pages, found_pages;
-               pgoff_t next = 0, tofind;
+               unsigned int i, nr_pages, found_pages, wsize, credits;
+               pgoff_t next = 0, tofind, saved_index = index;
 
-               tofind = min((cifs_sb->wsize / PAGE_CACHE_SIZE) - 1,
-                               end - index) + 1;
+               rc = server->ops->wait_mtu_credits(server, cifs_sb->wsize,
+                                                  &wsize, &credits);
+               if (rc)
+                       break;
+
+               tofind = min((wsize / PAGE_CACHE_SIZE) - 1, end - index) + 1;
 
                wdata = wdata_alloc_and_fillpages(tofind, mapping, end, &index,
                                                  &found_pages);
                if (!wdata) {
                        rc = -ENOMEM;
+                       add_credits_and_wake_if(server, credits, 0);
                        break;
                }
 
                if (found_pages == 0) {
                        kref_put(&wdata->refcount, cifs_writedata_release);
+                       add_credits_and_wake_if(server, credits, 0);
                        break;
                }
 
@@ -2081,13 +2087,17 @@ retry:
                /* nothing to write? */
                if (nr_pages == 0) {
                        kref_put(&wdata->refcount, cifs_writedata_release);
+                       add_credits_and_wake_if(server, credits, 0);
                        continue;
                }
 
+               wdata->credits = credits;
+
                rc = wdata_send_pages(wdata, nr_pages, mapping, wbc);
 
                /* send failure -- clean up the mess */
                if (rc != 0) {
+                       add_credits_and_wake_if(server, wdata->credits, 0);
                        for (i = 0; i < nr_pages; ++i) {
                                if (rc == -EAGAIN)
                                        redirty_page_for_writepage(wbc,
@@ -2102,6 +2112,11 @@ retry:
                }
                kref_put(&wdata->refcount, cifs_writedata_release);
 
+               if (wbc->sync_mode == WB_SYNC_ALL && rc == -EAGAIN) {
+                       index = saved_index;
+                       continue;
+               }
+
                wbc->nr_to_write -= nr_pages;
                if (wbc->nr_to_write <= 0)
                        done = true;
@@ -2398,123 +2413,109 @@ cifs_uncached_writev_complete(struct work_struct *work)
        kref_put(&wdata->refcount, cifs_uncached_writedata_release);
 }
 
-/* attempt to send write to server, retry on any -EAGAIN errors */
 static int
-cifs_uncached_retry_writev(struct cifs_writedata *wdata)
+wdata_fill_from_iovec(struct cifs_writedata *wdata, struct iov_iter *from,
+                     size_t *len, unsigned long *num_pages)
 {
-       int rc;
-       struct TCP_Server_Info *server;
+       size_t save_len, copied, bytes, cur_len = *len;
+       unsigned long i, nr_pages = *num_pages;
 
-       server = tlink_tcon(wdata->cfile->tlink)->ses->server;
+       save_len = cur_len;
+       for (i = 0; i < nr_pages; i++) {
+               bytes = min_t(const size_t, cur_len, PAGE_SIZE);
+               copied = copy_page_from_iter(wdata->pages[i], 0, bytes, from);
+               cur_len -= copied;
+               /*
+                * If we didn't copy as much as we expected, then that
+                * may mean we trod into an unmapped area. Stop copying
+                * at that point. On the next pass through the big
+                * loop, we'll likely end up getting a zero-length
+                * write and bailing out of it.
+                */
+               if (copied < bytes)
+                       break;
+       }
+       cur_len = save_len - cur_len;
+       *len = cur_len;
 
-       do {
-               if (wdata->cfile->invalidHandle) {
-                       rc = cifs_reopen_file(wdata->cfile, false);
-                       if (rc != 0)
-                               continue;
-               }
-               rc = server->ops->async_writev(wdata,
-                                              cifs_uncached_writedata_release);
-       } while (rc == -EAGAIN);
+       /*
+        * If we have no data to send, then that probably means that
+        * the copy above failed altogether. That's most likely because
+        * the address in the iovec was bogus. Return -EFAULT and let
+        * the caller free anything we allocated and bail out.
+        */
+       if (!cur_len)
+               return -EFAULT;
 
-       return rc;
+       /*
+        * i + 1 now represents the number of pages we actually used in
+        * the copy phase above.
+        */
+       *num_pages = i + 1;
+       return 0;
 }
 
-static ssize_t
-cifs_iovec_write(struct file *file, struct iov_iter *from, loff_t *poffset)
+static int
+cifs_write_from_iter(loff_t offset, size_t len, struct iov_iter *from,
+                    struct cifsFileInfo *open_file,
+                    struct cifs_sb_info *cifs_sb, struct list_head *wdata_list)
 {
-       unsigned long nr_pages, i;
-       size_t bytes, copied, len, cur_len;
-       ssize_t total_written = 0;
-       loff_t offset;
-       struct cifsFileInfo *open_file;
-       struct cifs_tcon *tcon;
-       struct cifs_sb_info *cifs_sb;
-       struct cifs_writedata *wdata, *tmp;
-       struct list_head wdata_list;
-       int rc;
+       int rc = 0;
+       size_t cur_len;
+       unsigned long nr_pages, num_pages, i;
+       struct cifs_writedata *wdata;
+       struct iov_iter saved_from;
+       loff_t saved_offset = offset;
        pid_t pid;
-
-       len = iov_iter_count(from);
-       rc = generic_write_checks(file, poffset, &len, 0);
-       if (rc)
-               return rc;
-
-       if (!len)
-               return 0;
-
-       iov_iter_truncate(from, len);
-
-       INIT_LIST_HEAD(&wdata_list);
-       cifs_sb = CIFS_SB(file->f_path.dentry->d_sb);
-       open_file = file->private_data;
-       tcon = tlink_tcon(open_file->tlink);
-
-       if (!tcon->ses->server->ops->async_writev)
-               return -ENOSYS;
-
-       offset = *poffset;
+       struct TCP_Server_Info *server;
 
        if (cifs_sb->mnt_cifs_flags & CIFS_MOUNT_RWPIDFORWARD)
                pid = open_file->pid;
        else
                pid = current->tgid;
 
+       server = tlink_tcon(open_file->tlink)->ses->server;
+       memcpy(&saved_from, from, sizeof(struct iov_iter));
+
        do {
-               size_t save_len;
+               unsigned int wsize, credits;
+
+               rc = server->ops->wait_mtu_credits(server, cifs_sb->wsize,
+                                                  &wsize, &credits);
+               if (rc)
+                       break;
 
-               nr_pages = get_numpages(cifs_sb->wsize, len, &cur_len);
+               nr_pages = get_numpages(wsize, len, &cur_len);
                wdata = cifs_writedata_alloc(nr_pages,
                                             cifs_uncached_writev_complete);
                if (!wdata) {
                        rc = -ENOMEM;
+                       add_credits_and_wake_if(server, credits, 0);
                        break;
                }
 
                rc = cifs_write_allocate_pages(wdata->pages, nr_pages);
                if (rc) {
                        kfree(wdata);
+                       add_credits_and_wake_if(server, credits, 0);
                        break;
                }
 
-               save_len = cur_len;
-               for (i = 0; i < nr_pages; i++) {
-                       bytes = min_t(size_t, cur_len, PAGE_SIZE);
-                       copied = copy_page_from_iter(wdata->pages[i], 0, bytes,
-                                                    from);
-                       cur_len -= copied;
-                       /*
-                        * If we didn't copy as much as we expected, then that
-                        * may mean we trod into an unmapped area. Stop copying
-                        * at that point. On the next pass through the big
-                        * loop, we'll likely end up getting a zero-length
-                        * write and bailing out of it.
-                        */
-                       if (copied < bytes)
-                               break;
-               }
-               cur_len = save_len - cur_len;
-
-               /*
-                * If we have no data to send, then that probably means that
-                * the copy above failed altogether. That's most likely because
-                * the address in the iovec was bogus. Set the rc to -EFAULT,
-                * free anything we allocated and bail out.
-                */
-               if (!cur_len) {
+               num_pages = nr_pages;
+               rc = wdata_fill_from_iovec(wdata, from, &cur_len, &num_pages);
+               if (rc) {
                        for (i = 0; i < nr_pages; i++)
                                put_page(wdata->pages[i]);
                        kfree(wdata);
-                       rc = -EFAULT;
+                       add_credits_and_wake_if(server, credits, 0);
                        break;
                }
 
                /*
-                * i + 1 now represents the number of pages we actually used in
-                * the copy phase above. Bring nr_pages down to that, and free
-                * any pages that we didn't use.
+                * Bring nr_pages down to the number of pages we actually used,
+                * and free any pages that we didn't use.
                 */
-               for ( ; nr_pages > i + 1; nr_pages--)
+               for ( ; nr_pages > num_pages; nr_pages--)
                        put_page(wdata->pages[nr_pages - 1]);
 
                wdata->sync_mode = WB_SYNC_ALL;
@@ -2525,18 +2526,69 @@ cifs_iovec_write(struct file *file, struct iov_iter *from, loff_t *poffset)
                wdata->bytes = cur_len;
                wdata->pagesz = PAGE_SIZE;
                wdata->tailsz = cur_len - ((nr_pages - 1) * PAGE_SIZE);
-               rc = cifs_uncached_retry_writev(wdata);
+               wdata->credits = credits;
+
+               if (!wdata->cfile->invalidHandle ||
+                   !cifs_reopen_file(wdata->cfile, false))
+                       rc = server->ops->async_writev(wdata,
+                                       cifs_uncached_writedata_release);
                if (rc) {
+                       add_credits_and_wake_if(server, wdata->credits, 0);
                        kref_put(&wdata->refcount,
                                 cifs_uncached_writedata_release);
+                       if (rc == -EAGAIN) {
+                               memcpy(from, &saved_from,
+                                      sizeof(struct iov_iter));
+                               iov_iter_advance(from, offset - saved_offset);
+                               continue;
+                       }
                        break;
                }
 
-               list_add_tail(&wdata->list, &wdata_list);
+               list_add_tail(&wdata->list, wdata_list);
                offset += cur_len;
                len -= cur_len;
        } while (len > 0);
 
+       return rc;
+}
+
+static ssize_t
+cifs_iovec_write(struct file *file, struct iov_iter *from, loff_t *poffset)
+{
+       size_t len;
+       ssize_t total_written = 0;
+       struct cifsFileInfo *open_file;
+       struct cifs_tcon *tcon;
+       struct cifs_sb_info *cifs_sb;
+       struct cifs_writedata *wdata, *tmp;
+       struct list_head wdata_list;
+       struct iov_iter saved_from;
+       int rc;
+
+       len = iov_iter_count(from);
+       rc = generic_write_checks(file, poffset, &len, 0);
+       if (rc)
+               return rc;
+
+       if (!len)
+               return 0;
+
+       iov_iter_truncate(from, len);
+
+       INIT_LIST_HEAD(&wdata_list);
+       cifs_sb = CIFS_SB(file->f_path.dentry->d_sb);
+       open_file = file->private_data;
+       tcon = tlink_tcon(open_file->tlink);
+
+       if (!tcon->ses->server->ops->async_writev)
+               return -ENOSYS;
+
+       memcpy(&saved_from, from, sizeof(struct iov_iter));
+
+       rc = cifs_write_from_iter(*poffset, len, from, open_file, cifs_sb,
+                                 &wdata_list);
+
        /*
         * If at least one write was successfully sent, then discard any rc
         * value from the later writes. If the other write succeeds, then
@@ -2565,7 +2617,25 @@ restart_loop:
 
                        /* resend call if it's a retryable error */
                        if (rc == -EAGAIN) {
-                               rc = cifs_uncached_retry_writev(wdata);
+                               struct list_head tmp_list;
+                               struct iov_iter tmp_from;
+
+                               INIT_LIST_HEAD(&tmp_list);
+                               list_del_init(&wdata->list);
+
+                               memcpy(&tmp_from, &saved_from,
+                                      sizeof(struct iov_iter));
+                               iov_iter_advance(&tmp_from,
+                                                wdata->offset - *poffset);
+
+                               rc = cifs_write_from_iter(wdata->offset,
+                                               wdata->bytes, &tmp_from,
+                                               open_file, cifs_sb, &tmp_list);
+
+                               list_splice(&tmp_list, &wdata_list);
+
+                               kref_put(&wdata->refcount,
+                                        cifs_uncached_writedata_release);
                                goto restart_loop;
                        }
                }
@@ -3270,6 +3340,63 @@ cifs_readpages_read_into_pages(struct TCP_Server_Info *server,
        return total_read > 0 && result != -EAGAIN ? total_read : result;
 }
 
+static int
+readpages_get_pages(struct address_space *mapping, struct list_head *page_list,
+                   unsigned int rsize, struct list_head *tmplist,
+                   unsigned int *nr_pages, loff_t *offset, unsigned int *bytes)
+{
+       struct page *page, *tpage;
+       unsigned int expected_index;
+       int rc;
+
+       page = list_entry(page_list->prev, struct page, lru);
+
+       /*
+        * Lock the page and put it in the cache. Since no one else
+        * should have access to this page, we're safe to simply set
+        * PG_locked without checking it first.
+        */
+       __set_page_locked(page);
+       rc = add_to_page_cache_locked(page, mapping,
+                                     page->index, GFP_KERNEL);
+
+       /* give up if we can't stick it in the cache */
+       if (rc) {
+               __clear_page_locked(page);
+               return rc;
+       }
+
+       /* move first page to the tmplist */
+       *offset = (loff_t)page->index << PAGE_CACHE_SHIFT;
+       *bytes = PAGE_CACHE_SIZE;
+       *nr_pages = 1;
+       list_move_tail(&page->lru, tmplist);
+
+       /* now try and add more pages onto the request */
+       expected_index = page->index + 1;
+       list_for_each_entry_safe_reverse(page, tpage, page_list, lru) {
+               /* discontinuity ? */
+               if (page->index != expected_index)
+                       break;
+
+               /* would this page push the read over the rsize? */
+               if (*bytes + PAGE_CACHE_SIZE > rsize)
+                       break;
+
+               __set_page_locked(page);
+               if (add_to_page_cache_locked(page, mapping, page->index,
+                                                               GFP_KERNEL)) {
+                       __clear_page_locked(page);
+                       break;
+               }
+               list_move_tail(&page->lru, tmplist);
+               (*bytes) += PAGE_CACHE_SIZE;
+               expected_index++;
+               (*nr_pages)++;
+       }
+       return rc;
+}
+
 static int cifs_readpages(struct file *file, struct address_space *mapping,
        struct list_head *page_list, unsigned num_pages)
 {
@@ -3324,57 +3451,15 @@ static int cifs_readpages(struct file *file, struct address_space *mapping,
         * the rdata->pages, then we want them in increasing order.
         */
        while (!list_empty(page_list)) {
-               unsigned int i;
-               unsigned int bytes = PAGE_CACHE_SIZE;
-               unsigned int expected_index;
-               unsigned int nr_pages = 1;
+               unsigned int i, nr_pages, bytes;
                loff_t offset;
                struct page *page, *tpage;
                struct cifs_readdata *rdata;
 
-               page = list_entry(page_list->prev, struct page, lru);
-
-               /*
-                * Lock the page and put it in the cache. Since no one else
-                * should have access to this page, we're safe to simply set
-                * PG_locked without checking it first.
-                */
-               __set_page_locked(page);
-               rc = add_to_page_cache_locked(page, mapping,
-                                             page->index, GFP_KERNEL);
-
-               /* give up if we can't stick it in the cache */
-               if (rc) {
-                       __clear_page_locked(page);
+               rc = readpages_get_pages(mapping, page_list, rsize, &tmplist,
+                                        &nr_pages, &offset, &bytes);
+               if (rc)
                        break;
-               }
-
-               /* move first page to the tmplist */
-               offset = (loff_t)page->index << PAGE_CACHE_SHIFT;
-               list_move_tail(&page->lru, &tmplist);
-
-               /* now try and add more pages onto the request */
-               expected_index = page->index + 1;
-               list_for_each_entry_safe_reverse(page, tpage, page_list, lru) {
-                       /* discontinuity ? */
-                       if (page->index != expected_index)
-                               break;
-
-                       /* would this page push the read over the rsize? */
-                       if (bytes + PAGE_CACHE_SIZE > rsize)
-                               break;
-
-                       __set_page_locked(page);
-                       if (add_to_page_cache_locked(page, mapping,
-                                               page->index, GFP_KERNEL)) {
-                               __clear_page_locked(page);
-                               break;
-                       }
-                       list_move_tail(&page->lru, &tmplist);
-                       bytes += PAGE_CACHE_SIZE;
-                       expected_index++;
-                       nr_pages++;
-               }
 
                rdata = cifs_readdata_alloc(nr_pages, cifs_readv_complete);
                if (!rdata) {
This page took 0.035017 seconds and 5 git commands to generate.