| // SPDX-License-Identifier: GPL-2.0-only |
| /* Network filesystem high-level write support. |
| * |
| * Copyright (C) 2021 Red Hat, Inc. All Rights Reserved. |
| * Written by David Howells (dhowells@redhat.com) |
| */ |
| |
| #include <linux/fs.h> |
| #include <linux/mm.h> |
| #include <linux/pagemap.h> |
| #include <linux/slab.h> |
| #include <linux/writeback.h> |
| #include "internal.h" |
| |
| static void list_excise(struct list_head *first, |
| struct list_head *last) |
| { |
| struct list_head *prev = first->prev, *next = last->next; |
| |
| prev->next = next; |
| next->prev = prev; |
| first->prev = last; |
| last->next = first; |
| } |
| |
| /* |
| * Fix up the dirty list upon completion of write. |
| */ |
| static void netfs_fix_up_dirty_list(struct netfs_writeback *wback) |
| { |
| struct netfs_dirty_region *first, *last, *r; |
| struct netfs_i_context *ctx = netfs_i_context(wback->inode); |
| unsigned long long available_to; |
| struct list_head *lower, *upper, *p; |
| |
| netfs_end_writeback(wback); |
| |
| first = list_first_entry(&wback->regions, struct netfs_dirty_region, flush_link); |
| last = list_last_entry(&wback->regions, struct netfs_dirty_region, flush_link); |
| |
| spin_lock(&ctx->lock); |
| if (wback->coverage.end > ctx->remote_i_size) |
| ctx->remote_i_size = wback->coverage.end; |
| |
| /* Find the bounds of the region we're going to make available. */ |
| lower = &ctx->dirty_regions; |
| r = first; |
| list_for_each_entry_continue_reverse(r, &ctx->dirty_regions, dirty_link) { |
| _debug("- back fix %x", r->debug_id); |
| if (r->state >= NETFS_REGION_IS_DIRTY) { |
| lower = &r->dirty_link; |
| break; |
| } |
| } |
| |
| available_to = ULLONG_MAX; |
| upper = &ctx->dirty_regions; |
| r = last; |
| list_for_each_entry_continue(r, &ctx->dirty_regions, dirty_link) { |
| _debug("- forw fix %x", r->debug_id); |
| if (r->state >= NETFS_REGION_IS_DIRTY) { |
| available_to = r->dirty.start; |
| upper = &r->dirty_link; |
| break; |
| } |
| } |
| |
| /* Remove the sequence of regions we've just written from the dirty |
| * list then poke any waiting writer if they're now cleared to proceed. |
| */ |
| list_excise(&first->dirty_link, &last->dirty_link); |
| |
| for (p = lower->next; p != upper; p = p->next) { |
| r = list_entry(p, struct netfs_dirty_region, dirty_link); |
| if (r->will_modify_to <= available_to) { |
| smp_store_release(&r->state, NETFS_REGION_IS_ACTIVE); |
| trace_netfs_dirty(ctx, r, NULL, netfs_dirty_trace_activate); |
| wake_up_var(&r->state); |
| } |
| } |
| |
| spin_unlock(&ctx->lock); |
| |
| list_for_each_entry(r, &wback->regions, flush_link) { |
| smp_store_release(&r->state, NETFS_REGION_IS_COMPLETE); |
| trace_netfs_dirty(ctx, r, NULL, netfs_dirty_trace_complete); |
| netfs_put_dirty_region(ctx, r, netfs_region_trace_put_written); |
| } |
| } |
| |
| /* |
| * Process a completed write request once all the component operations have |
| * been completed. |
| */ |
| static void netfs_write_completed(struct netfs_writeback *wback, bool was_async) |
| { |
| struct netfs_write_request *wreq; |
| struct netfs_i_context *ctx = netfs_i_context(wback->inode); |
| |
| _enter("%x[]", wback->debug_id); |
| |
| list_for_each_entry(wreq, &wback->writes, wback_link) { |
| if (!wreq->error) |
| continue; |
| switch (wreq->dest) { |
| case NETFS_UPLOAD_TO_SERVER: |
| /* Depending on the type of failure, this may prevent |
| * writeback completion unless we're in disconnected |
| * mode. |
| */ |
| if (!wback->error) |
| wback->error = wreq->error; |
| break; |
| |
| case NETFS_WRITE_TO_CACHE: |
| /* Failure doesn't prevent writeback completion unless |
| * we're in disconnected mode. |
| */ |
| if (wreq->error != -ENOBUFS) |
| ctx->ops->invalidate_cache(wback); |
| break; |
| |
| default: |
| WARN_ON_ONCE(1); |
| if (!wback->error) |
| wback->error = -EIO; |
| return; |
| } |
| } |
| |
| if (wback->error) |
| netfs_redirty_folios(wback); |
| else |
| netfs_fix_up_dirty_list(wback); |
| |
| while ((wreq = list_first_entry_or_null(&wback->writes, |
| struct netfs_write_request, wback_link))) { |
| list_del_init(&wreq->wback_link); |
| netfs_put_write_request(wreq, false, netfs_wback_trace_put_cleanup); |
| } |
| |
| netfs_put_writeback(wback, was_async, netfs_wback_trace_put_for_outstanding); |
| } |
| |
| /* |
| * Deal with the completion of writing the data to the cache. |
| */ |
| void netfs_write_request_completed(void *_op, ssize_t transferred_or_error, |
| bool was_async) |
| { |
| struct netfs_write_request *wreq = _op; |
| struct netfs_writeback *wback = wreq->wback; |
| |
| _enter("%x[%x] %zd", wback->debug_id, wreq->debug_index, transferred_or_error); |
| |
| if (IS_ERR_VALUE(transferred_or_error)) |
| wreq->error = transferred_or_error; |
| switch (wreq->dest) { |
| case NETFS_UPLOAD_TO_SERVER: |
| if (wreq->error) |
| netfs_stat(&netfs_n_wh_upload_failed); |
| else |
| netfs_stat(&netfs_n_wh_upload_done); |
| break; |
| case NETFS_WRITE_TO_CACHE: |
| if (wreq->error) |
| netfs_stat(&netfs_n_wh_write_failed); |
| else |
| netfs_stat(&netfs_n_wh_write_done); |
| break; |
| case NETFS_INVALID_WRITE: |
| break; |
| } |
| |
| trace_netfs_wreq(wreq, netfs_wreq_trace_complete); |
| if (atomic_dec_and_test(&wback->outstanding)) |
| netfs_write_completed(wback, was_async); |
| } |
| EXPORT_SYMBOL(netfs_write_request_completed); |
| |
| static void netfs_write_to_cache_op(struct netfs_write_request *wreq) |
| { |
| struct netfs_cache_resources *cres = &wreq->cache_resources; |
| |
| trace_netfs_wreq(wreq, netfs_wreq_trace_submit); |
| |
| cres->ops->write(cres, wreq->start, &wreq->source, |
| netfs_write_request_completed, wreq); |
| } |
| |
| static void netfs_write_to_cache_op_worker(struct work_struct *work) |
| { |
| struct netfs_write_request *wreq = |
| container_of(work, struct netfs_write_request, work); |
| |
| netfs_write_to_cache_op(wreq); |
| netfs_put_write_request(wreq, false, netfs_wback_trace_put_wreq_work); |
| } |
| |
| /** |
| * netfs_queue_write_request - Queue a write request for attention |
| * @wreq: The write request to be queued |
| * |
| * Queue the specified write request for processing by a worker thread. We |
| * pass the caller's ref on the request to the worker thread. |
| */ |
| void netfs_queue_write_request(struct netfs_write_request *wreq) |
| { |
| if (!queue_work(system_unbound_wq, &wreq->work)) |
| netfs_put_write_request(wreq, false, netfs_wback_trace_put_wip); |
| } |
| EXPORT_SYMBOL(netfs_queue_write_request); |
| |
| /* |
| * Set up a op for writing to the cache. |
| */ |
| static void netfs_set_up_write_to_cache(struct netfs_writeback *wback) |
| { |
| struct netfs_cache_resources *cres; |
| struct netfs_write_request *wreq; |
| struct fscache_cookie *cookie = netfs_i_cookie(wback->inode); |
| loff_t start = wback->coverage.start; |
| size_t len = wback->coverage.end - wback->coverage.start; |
| int ret; |
| |
| if (!fscache_cookie_enabled(cookie)) { |
| clear_bit(NETFS_WBACK_WRITE_TO_CACHE, &wback->flags); |
| return; |
| } |
| |
| wreq = netfs_create_write_request(wback, NETFS_WRITE_TO_CACHE, start, len, |
| netfs_write_to_cache_op_worker); |
| if (!wreq) |
| return; |
| |
| cres = &wreq->cache_resources; |
| ret = fscache_begin_write_operation(cres, cookie); |
| if (ret < 0) { |
| netfs_write_request_completed(wreq, ret, false); |
| netfs_put_write_request(wreq, false, netfs_wback_trace_put_discard); |
| return; |
| } |
| |
| ret = cres->ops->prepare_write(cres, &start, &len, i_size_read(wback->inode), |
| true); |
| if (ret < 0) { |
| netfs_write_request_completed(wreq, ret, false); |
| netfs_put_write_request(wreq, false, netfs_wback_trace_put_discard); |
| return; |
| } |
| |
| netfs_queue_write_request(wreq); |
| } |
| |
| /* |
| * Process a write request. |
| * |
| * All the folios in the bounding box have had a ref taken on them and those |
| * covering the dirty region have been marked as being written back and their |
| * dirty bits provisionally cleared. |
| */ |
| static void netfs_writeback(struct netfs_writeback *wback) |
| { |
| struct netfs_i_context *ctx = netfs_i_context(wback->inode); |
| |
| _enter(""); |
| |
| /* TODO: Encrypt or compress the region as appropriate */ |
| |
| /* ->outstanding > 0 carries a ref */ |
| netfs_get_writeback(wback, netfs_wback_trace_get_for_outstanding); |
| |
| /* We need to write all of the region to the cache */ |
| if (test_bit(NETFS_WBACK_WRITE_TO_CACHE, &wback->flags)) |
| netfs_set_up_write_to_cache(wback); |
| |
| /* However, we don't necessarily write all of the region to the server. |
| * Caching of reads is being managed this way also. |
| */ |
| if (test_bit(NETFS_WBACK_UPLOAD_TO_SERVER, &wback->flags)) |
| ctx->ops->create_write_requests(wback); |
| |
| if (atomic_dec_and_test(&wback->outstanding)) |
| netfs_write_completed(wback, false); |
| } |
| |
| void netfs_writeback_worker(struct work_struct *work) |
| { |
| struct netfs_writeback *wback = |
| container_of(work, struct netfs_writeback, work); |
| |
| netfs_see_writeback(wback, netfs_wback_trace_see_work); |
| netfs_writeback(wback); |
| netfs_put_writeback(wback, false, netfs_wback_trace_put_work); |
| } |
| |
| static void netfs_set_folios_writeback_work(struct work_struct *work) |
| { |
| struct netfs_writeback *wback = |
| container_of(work, struct netfs_writeback, work); |
| |
| netfs_see_writeback(wback, netfs_wback_trace_see_work); |
| if (netfs_lock_folios(wback, true) == 0) { |
| netfs_mark_folios_for_writeback(wback, wback->first, wback->last); |
| netfs_unlock_folios(wback->mapping, wback->first, wback->last); |
| wback->work.func = netfs_writeback_worker; |
| } |
| |
| if (!queue_work(system_unbound_wq, &wback->work)) |
| BUG(); |
| } |
| |
| /* |
| * Begin the process of writing out a chunk of data. |
| * |
| * We are given a write request that holds a series of dirty regions and |
| * (partially) covers a sequence of folios, all of which are present. We need |
| * to perform the following steps: |
| * |
| * (1) Lock all the folios, unmark them as being dirty (if so marked and if not |
| * shared with out-of-scope dirty regions), mark them as undergoing |
| * writeback and unlock them again. |
| * |
| * (2) If encrypting, create an output buffer and encrypt each block of the |
| * data into it, otherwise the output buffer will point to the original |
| * folios. |
| * |
| * (3) If the data is to be cached, set up a write op for the entire output |
| * buffer to the cache, if the cache wants to accept it. |
| * |
| * (4) If the data is to be uploaded (ie. not merely cached): |
| * |
| * (a) If the data is to be compressed, create a compression buffer and |
| * compress the data into it. |
| * |
| * (b) For each destination we want to upload to, set up write ops to write |
| * to that destination. We may need multiple writes if the data is not |
| * contiguous or the span exceeds wsize for a server. |
| */ |
| static int netfs_begin_write(struct netfs_writeback *wback, bool may_wait) |
| { |
| int ret; |
| |
| trace_netfs_wback(wback); |
| |
| ret = netfs_lock_folios(wback, may_wait); |
| if (ret < 0) { |
| wback->work.func = netfs_set_folios_writeback_work; |
| if (!queue_work(system_unbound_wq, &wback->work)) |
| BUG(); |
| } else { |
| netfs_mark_folios_for_writeback(wback, wback->first, wback->last); |
| netfs_unlock_folios(wback->mapping, wback->first, wback->last); |
| if (!queue_work(system_unbound_wq, &wback->work)) |
| BUG(); |
| } |
| _leave(" = %lu", wback->last - wback->first + 1); |
| return wback->last - wback->first + 1; |
| } |
| |
| /* |
| * Split the front off of a dirty region. We don't want to over-modify the |
| * tail region if it's currently active. |
| */ |
| static struct netfs_dirty_region *netfs_split_off_front( |
| struct netfs_i_context *ctx, |
| struct netfs_dirty_region *region, |
| struct netfs_dirty_region **spare, |
| unsigned long long pos) |
| { |
| struct netfs_dirty_region *front = *spare; |
| |
| *spare = NULL; |
| *front = *region; |
| front->dirty.end = pos; |
| region->dirty.start = pos; |
| front->debug_id = atomic_inc_return(&netfs_region_debug_ids); |
| |
| _debug("split D=%x from D=%x", front->debug_id, region->debug_id); |
| |
| refcount_set(&front->ref, 1); |
| netfs_get_flush_group(front->group); |
| spin_lock_init(&front->lock); |
| // TODO: grab cache resources |
| |
| // TODO: need to split the bounding box? |
| if (ctx->ops->split_dirty_region) |
| ctx->ops->split_dirty_region(front); |
| list_add_tail(&front->dirty_link, ®ion->dirty_link); |
| list_add(&front->flush_link, ®ion->flush_link); |
| trace_netfs_dirty(ctx, front, region, netfs_dirty_trace_split); |
| netfs_proc_add_region(front); |
| return front; |
| } |
| |
| /* |
| * Flush some of the dirty queue, transforming a part of a sequence of dirty |
| * regions into a block we can flush. |
| * |
| * A number of things constrain us: |
| * - The region we write out should not be undergoing modification |
| * - We may need to expand or split the region for a number of reasons: |
| * - Filesystem storage block/object size |
| * - Filesystem RPC size (wsize) |
| * - Cache block size |
| * - Cache DIO block size |
| * - Crypto/compression block size |
| * |
| * The caller must hold ctx->lock. |
| */ |
| static long netfs_flush_dirty(struct netfs_i_context *ctx, |
| struct netfs_dirty_region *head, |
| struct netfs_dirty_region *spares[2], |
| struct netfs_range *requested, |
| struct netfs_writeback *wback, |
| struct netfs_dirty_region **_wait_for, |
| loff_t *_wait_to) |
| { |
| struct netfs_dirty_region *tail = NULL, *r, *q; |
| struct netfs_range block; |
| unsigned long long dirty_start, dirty_to, active_from, limit, i_size; |
| unsigned int wsize = ctx->wsize; |
| unsigned int min_bsize = 1U << ctx->min_bshift; |
| long ret; |
| |
| _enter("%llx-%llx", requested->start, requested->end); |
| *_wait_for = NULL; |
| |
| /* Determine where we're going to start and the limits on where we |
| * might end. |
| */ |
| dirty_start = round_down(head->dirty.start, min_bsize); |
| _debug("dirty D=%x start %llx", head->debug_id, dirty_start); |
| |
| if (ctx->obj_bshift) { |
| /* Handle object storage - we limit the write to one object, |
| * but we round down the start if there's more dirty data that |
| * way. |
| */ |
| unsigned long long obj_start; |
| unsigned long long obj_size = 1ULL << ctx->obj_bshift; |
| unsigned long long obj_end; |
| |
| obj_start = max(requested->start, dirty_start); |
| obj_start = round_down(obj_start, obj_size); |
| obj_end = obj_start + obj_size; |
| _debug("object %llx-%llx", obj_start, obj_end); |
| |
| block.start = max(dirty_start, obj_start); |
| limit = min(requested->end, obj_end); |
| if (limit - block.start > wsize) { |
| _debug("size %llx", limit - block.start); |
| block.start = max(block.start, requested->start); |
| limit = min(requested->end, |
| block.start + round_down(wsize, min_bsize)); |
| } |
| _debug("object %llx-%llx", block.start, limit); |
| } else if (min_bsize > 1) { |
| /* There's a block size (cache DIO, crypto). */ |
| block.start = max(dirty_start, requested->start); |
| if (wsize > min_bsize) { |
| /* A single write can encompass several blocks. */ |
| limit = block.start + round_down(wsize, min_bsize); |
| limit = min(limit, requested->end); |
| } else { |
| /* The block will need several writes to send it. */ |
| limit = block.start + min_bsize; |
| } |
| _debug("block %llx-%llx", block.start, limit); |
| } else { |
| /* No blocking factors and no object division. */ |
| block.start = max(dirty_start, requested->start); |
| limit = min(block.start + wsize, requested->end); |
| _debug("plain %llx-%llx", block.start, limit); |
| } |
| |
| i_size = i_size_read(netfs_inode(ctx)); |
| limit = min_t(unsigned long long, limit, i_size); |
| _debug("limit %llx %llx", limit, i_size); |
| |
| /* Determine the subset of dirty regions that are going to contribute. */ |
| r = head; |
| list_for_each_entry_from(r, &ctx->dirty_regions, dirty_link) { |
| _debug("- maybe D=%x s=%llx", r->debug_id, r->dirty.start); |
| if (r->dirty.start >= limit) |
| break; |
| |
| if (min_bsize <= 1 && tail) { |
| /* If we don't need to use a whole block, break at any |
| * discontiguity so that we don't include bridging |
| * blocks in our writeback. |
| */ |
| if (r->dirty.start != tail->dirty.end) { |
| kdebug(" - discontig"); |
| break; |
| } |
| } |
| |
| switch (READ_ONCE(r->state)) { |
| case NETFS_REGION_IS_DIRTY: |
| _debug("- dirty"); |
| tail = r; |
| continue; |
| case NETFS_REGION_IS_FLUSHING: |
| kdebug("- flushing"); |
| limit = round_down(r->dirty.start, min_bsize); |
| goto determined_tail; |
| case NETFS_REGION_IS_ACTIVE: |
| /* We can break off part of a region undergoing active |
| * modification, but assume, for now, that we don't |
| * want to include anything that will change under us |
| * or that's only partially uptodate - especially if |
| * we're going to be encrypting or compressing from it. |
| */ |
| dirty_to = READ_ONCE(r->dirty.end); |
| active_from = round_down(dirty_to, min_bsize); |
| kdebug("active D=%x from %llx", r->debug_id, active_from); |
| if (active_from > limit) { |
| kdebug(" - >limit"); |
| tail = r; |
| goto determined_tail; |
| } |
| |
| limit = active_from; |
| if (r->dirty.start < limit) { |
| kdebug(" - reduce limit"); |
| tail = r; |
| goto determined_tail; |
| } |
| |
| if (limit == block.start || r == head) |
| goto wait_for_active_region; |
| |
| if (limit == r->dirty.start) { |
| kdebug("- active contig"); |
| goto determined_tail; |
| } |
| |
| /* We may need to rewind the subset we're collecting. */ |
| q = r; |
| list_for_each_entry_continue_reverse(q, &ctx->dirty_regions, |
| dirty_link) { |
| kdebug(" - rewind D=%x", q->debug_id); |
| tail = q; |
| if (q->dirty.start < limit) |
| goto determined_tail; |
| if (q == head) { |
| kdebug("over rewound"); |
| return -EIO; |
| } |
| } |
| |
| *_wait_for = r; |
| goto wait_for_active_region; |
| } |
| } |
| |
| determined_tail: |
| if (!tail) { |
| kleave(" = -EAGAIN [no tail]"); |
| return -EAGAIN; |
| } |
| dirty_to = round_up(tail->dirty.end, min_bsize); |
| _debug("dto %llx", dirty_to); |
| block.end = min(dirty_to, limit); |
| _debug("block %llx-%llx", block.start, block.end); |
| |
| /* If the leading and/or trailing edges of the selected regions overlap |
| * the ends of the block, we will need to split those blocks. |
| */ |
| if ((dirty_start < block.start && !spares[0]) || |
| (tail->dirty.end > block.end && !spares[1])) { |
| _leave(" = -ENOBUFS [need spares]"); |
| return -ENOBUFS; |
| } |
| |
| if (dirty_start < block.start) { |
| _debug("eject front"); |
| netfs_split_off_front(ctx, head, &spares[0], block.start); |
| } |
| |
| if (tail->dirty.end > block.end) { |
| _debug("eject back"); |
| r = netfs_split_off_front(ctx, tail, &spares[1], block.end); |
| if (head == tail) |
| head = r; |
| tail = r; |
| } |
| |
| /* Set up the write request */ |
| _debug("wback W=%x", wback->debug_id); |
| wback->coverage = block; |
| wback->first = block.start / PAGE_SIZE; |
| wback->last = (block.end - 1) / PAGE_SIZE; |
| |
| /* Flip the state of all the regions and add them to the request */ |
| r = head; |
| list_for_each_entry_from(r, &ctx->dirty_regions, dirty_link) { |
| if (r->type != NETFS_REGION_CACHE_COPY) |
| __set_bit(NETFS_WBACK_UPLOAD_TO_SERVER, &wback->flags); |
| set_bit(NETFS_REGION_FLUSH_Q, &r->flags); |
| smp_store_release(&r->state, NETFS_REGION_IS_FLUSHING); |
| trace_netfs_dirty(ctx, r, NULL, netfs_dirty_trace_flushing); |
| wake_up_var(&r->state); |
| netfs_get_dirty_region(ctx, r, netfs_region_trace_get_wback); |
| netfs_deduct_write_credit(r, r->dirty.end - r->dirty.start); |
| list_move_tail(&r->flush_link, &wback->regions); |
| if (r == tail) |
| break; |
| } |
| |
| netfs_proc_add_writeback(wback); |
| |
| requested->start = block.end; |
| ret = wback->last - wback->first + 1; |
| _leave(" = %ld", ret); |
| return ret; |
| |
| wait_for_active_region: |
| /* We have to wait for an active region to progress */ |
| _debug("- wait for active %x", r->debug_id); |
| set_bit(NETFS_REGION_FLUSH_Q, &r->flags); |
| *_wait_for = r; |
| *_wait_to = dirty_to; |
| return -EAGAIN; |
| } |
| |
| /* |
| * Flush a range of addresses. |
| */ |
| static int netfs_flush_range(struct address_space *mapping, |
| struct writeback_control *wbc, |
| struct netfs_range *requested) |
| { |
| struct netfs_writeback *wback = NULL; |
| struct netfs_dirty_region *spares[2] = {}, *head, *r, *wait_for; |
| struct netfs_i_context *ctx = netfs_i_context(mapping->host); |
| unsigned int min_bsize = 1U << ctx->min_bshift; |
| loff_t wait_to; |
| int ret; |
| |
| _enter("%llx-%llx", requested->start, requested->end); |
| |
| ret = netfs_sanity_check_ictx(mapping); |
| if (ret < 0) |
| return ret; |
| |
| /* Round the requested region out to the minimum block size (eg. for |
| * crypto purposes). |
| */ |
| requested->start = round_down(requested->start, min_bsize); |
| requested->end = round_up (requested->end, min_bsize); |
| |
| retry: |
| ret = netfs_wait_for_credit(wbc); |
| if (ret < 0) |
| goto out_unlocked; |
| |
| if (!wback) { |
| ret = -ENOMEM; |
| wback = netfs_alloc_writeback(mapping, false); |
| if (!wback) |
| goto out_unlocked; |
| } |
| |
| spin_lock(&ctx->lock); |
| |
| /* Find the first dirty region that overlaps the requested flush region */ |
| ret = 0; |
| head = NULL; |
| list_for_each_entry(r, &ctx->dirty_regions, dirty_link) { |
| //kdebug("query D=%x", r->debug_id); |
| if (r->dirty.end <= requested->start || |
| r->dirty.end == r->dirty.start) |
| continue; |
| if (READ_ONCE(r->state) == NETFS_REGION_IS_FLUSHING) |
| continue; |
| if (r->dirty.start >= requested->end) |
| goto out; |
| head = r; |
| break; |
| } |
| |
| if (!head || head->dirty.start >= requested->end) |
| goto out; |
| |
| ret = netfs_flush_dirty(ctx, head, spares, requested, wback, &wait_for, &wait_to); |
| if (ret == -EAGAIN && wait_for) |
| goto wait_for_active_region; |
| spin_unlock(&ctx->lock); |
| |
| switch (ret) { |
| case -ENOBUFS: |
| goto need_spares; |
| case -EAGAIN: |
| //goto retry; |
| goto out_unlocked; |
| default: |
| goto out_unlocked; |
| case 1 ... INT_MAX: |
| wbc->nr_to_write -= ret; |
| ret = 0; |
| break; |
| } |
| |
| /* Finish preparing the write request. */ |
| //netfs_get_writeback(wback, netfs_wback_trace_get_debug); |
| ret = netfs_begin_write(wback, wbc->sync_mode != WB_SYNC_NONE); |
| |
| wback = NULL; |
| |
| /* TODO: Flush more pieces */ |
| if (wbc->nr_to_write <= 0) |
| goto out_unlocked; |
| if (requested->start < requested->end) |
| goto retry; |
| goto out_unlocked; |
| |
| out: |
| spin_unlock(&ctx->lock); |
| out_unlocked: |
| netfs_free_dirty_region(ctx, spares[0]); |
| netfs_free_dirty_region(ctx, spares[1]); |
| netfs_put_writeback(wback, false, netfs_wback_trace_put_discard); |
| return ret; |
| |
| wait_for_active_region: |
| if (wbc->sync_mode == WB_SYNC_NONE) { |
| spin_unlock(&ctx->lock); |
| ret = -EBUSY; |
| goto out_unlocked; |
| } |
| |
| if (!spares[0] || !spares[1]) { |
| spin_unlock(&ctx->lock); |
| ret = -ENOBUFS; |
| goto out_unlocked; |
| } |
| |
| netfs_get_dirty_region(ctx, r, netfs_region_trace_get_wait_active); |
| spin_unlock(&ctx->lock); |
| |
| wait_var_event(&r->state, (READ_ONCE(r->state) != NETFS_REGION_IS_ACTIVE || |
| READ_ONCE(r->dirty.end) != wait_to)); |
| netfs_put_dirty_region(ctx, r, netfs_region_trace_put_wait_active); |
| need_spares: |
| ret = -ENOMEM; |
| if (!spares[0]) { |
| spares[0] = netfs_alloc_dirty_region(); |
| if (!spares[0]) |
| goto out_unlocked; |
| } |
| if (!spares[1]) { |
| spares[1] = netfs_alloc_dirty_region(); |
| if (!spares[1]) |
| goto out_unlocked; |
| } |
| goto retry; |
| } |
| |
| /** |
| * netfs_writepages - Initiate writeback to the server and cache |
| * @mapping: The pagecache to write from |
| * @wbc: Hints from the VM as to what to write |
| * |
| * This is a helper intended to be called directly from a network filesystem's |
| * address space operations table to perform writeback to the server and the |
| * cache. |
| * |
| * We have to be careful as we can end up racing with setattr() truncating the |
| * pagecache since the caller doesn't take a lock here to prevent it. |
| */ |
| int netfs_writepages(struct address_space *mapping, |
| struct writeback_control *wbc) |
| { |
| struct netfs_range range; |
| unsigned long nr_to_write = wbc->nr_to_write; |
| int ret; |
| |
| _enter("%lx,%llx-%llx,%u,%c%c%c%c,%u,%u", |
| wbc->nr_to_write, |
| wbc->range_start, wbc->range_end, |
| wbc->sync_mode, |
| wbc->for_kupdate ? 'k' : '-', |
| wbc->for_background ? 'b' : '-', |
| wbc->for_reclaim ? 'r' : '-', |
| wbc->for_sync ? 's' : '-', |
| wbc->tagged_writepages, |
| wbc->range_cyclic); |
| |
| //dump_stack(); |
| |
| if (wbc->range_cyclic) { |
| range.start = mapping->writeback_index * PAGE_SIZE; |
| range.end = (unsigned long long)LLONG_MAX + 1; |
| ret = netfs_flush_range(mapping, wbc, &range); |
| if (ret == 0 && range.start > 0 && wbc->nr_to_write > 0) { |
| range.start = 0; |
| range.end = mapping->writeback_index * PAGE_SIZE; |
| ret = netfs_flush_range(mapping, wbc, &range); |
| } |
| mapping->writeback_index = range.start / PAGE_SIZE; |
| } else { |
| range.start = wbc->range_start; |
| range.end = wbc->range_end + 1; |
| ret = netfs_flush_range(mapping, wbc, &range); |
| } |
| |
| _leave(" = %d [%lx/%lx]", ret, wbc->nr_to_write, nr_to_write); |
| if (ret == -EBUSY) |
| ret = 0; |
| return ret; |
| } |
| EXPORT_SYMBOL(netfs_writepages); |