diff options
-rw-r--r-- | fs/smb/client/file.c | 147 |
1 files changed, 104 insertions, 43 deletions
diff --git a/fs/smb/client/file.c b/fs/smb/client/file.c index cb75b95efb70..eddd0dab44ed 100644 --- a/fs/smb/client/file.c +++ b/fs/smb/client/file.c @@ -2719,15 +2719,17 @@ static void cifs_extend_writeback(struct address_space *mapping, loff_t start, int max_pages, loff_t max_len, - size_t *_len) + size_t *_len, + int sync_mode) { struct folio_batch batch; struct folio *folio; - unsigned int nr_pages; - pgoff_t index = (start + *_len) / PAGE_SIZE; - size_t len; - bool stop = true; - unsigned int i; + unsigned int nr_pages, i; + pgoff_t idx, index = (start + *_len) / PAGE_SIZE; + size_t len = *_len, flen; + bool stop = true, sync = (sync_mode != WB_SYNC_NONE); + long count = *_count; + int npages = max_pages; folio_batch_init(&batch); @@ -2742,59 +2744,110 @@ static void cifs_extend_writeback(struct address_space *mapping, stop = true; if (xas_retry(xas, folio)) continue; - if (xa_is_value(folio)) - break; - if (folio->index != index) { - xas_reset(xas); + if (xa_is_value(folio)) { + stop = false; break; } + if (folio_index(folio) != index) + goto xareset_next; - if (!folio_try_get(folio)) { - xas_reset(xas); - continue; - } - nr_pages = folio_nr_pages(folio); - if (nr_pages > max_pages) { - xas_reset(xas); - break; - } + if (!folio_try_get(folio)) + goto xareset_next; /* Has the page moved or been split? */ - if (unlikely(folio != xas_reload(xas))) { - folio_put(folio); - xas_reset(xas); - break; + if (unlikely(folio != xas_reload(xas) || folio->mapping != mapping)) { + stop = false; + goto put_next; } - if (!folio_trylock(folio)) { - folio_put(folio); - xas_reset(xas); - break; - } - if (!folio_test_dirty(folio) || - folio_test_writeback(folio)) { - folio_unlock(folio); - folio_put(folio); - xas_reset(xas); - break; + if (!folio_trylock(folio)) + goto put_next; + + nr_pages = folio_nr_pages(folio); + if (nr_pages > npages || nr_pages > count) + goto unlock_next; + + if (folio_test_writeback(folio)) { + /* + * For data-integrity syscalls (fsync(), msync()) we must wait for + * the I/O to complete on the page. + * For other cases (!sync), we can just skip this page, even if + * it's dirty. + */ + if (!sync) { + stop = false; + goto unlock_next; + } else { + folio_wait_writeback(folio); + + /* + * More I/O started meanwhile, bail out and write on the + * next call. + */ + if (WARN_ON_ONCE(folio_test_writeback(folio))) + goto unlock_next; + } } - max_pages -= nr_pages; - len = folio_size(folio); - stop = false; + /* + * We don't really have a boundary for index, so just check for overflow. + */ + if (check_add_overflow(index, nr_pages, &idx)) + goto unlock_next; + + flen = folio_size(folio); - index += nr_pages; - *_count -= nr_pages; - *_len += len; - if (max_pages <= 0 || *_len >= max_len || *_count <= 0) - stop = true; + /* Store sum in @flen so we don't have to undo it in case of failure. */ + if (check_add_overflow(len, flen, &flen) || flen > max_len) + goto unlock_next; + + index = idx; + len = flen; + + /* + * @npages and @count have been checked earlier (and are signed), so we + * can just subtract them here. + */ + npages -= nr_pages; + count -= nr_pages; + /* + * This is not an error; it just means we _did_ add this current folio, but + * can't add any more to this batch, so break out of this loop to start + * processing this batch, but don't stop the outer loop in case there are + * more folios to be processed in @xas. + */ + stop = false; if (!folio_batch_add(&batch, folio)) break; + + /* + * Folios added to the batch must be left locked for the loop below. They + * will be unlocked right away and also folio_batch_release() will take + * care of putting them. + */ + continue; +unlock_next: + folio_unlock(folio); +put_next: + folio_put(folio); +xareset_next: if (stop) break; } + /* + * Only reset @xas if we get here because of one of the stopping conditions above, + * namely: + * - couldn't lock/get a folio (someone else was processing the same folio) + * - folio was in writeback for too long (sync call was writing the same folio) + * - out of bounds index, len, or count (the last processed folio was partial and + * we can't fit it in this write request, so it shall be processed in the next + * write) + */ + if (stop) + xas_reset(xas); + xas_pause(xas); rcu_read_unlock(); @@ -2815,6 +2868,13 @@ static void cifs_extend_writeback(struct address_space *mapping, folio_unlock(folio); } + /* + * By now, data has been updated/written out to @mapping, so from this point of + * view we're done and we can safely update @_len and @_count. + */ + *_len = len; + *_count = count; + folio_batch_release(&batch); cond_resched(); } while (!stop); @@ -2902,7 +2962,8 @@ static ssize_t cifs_write_back_from_locked_folio(struct address_space *mapping, if (max_pages > 0) cifs_extend_writeback(mapping, xas, &count, start, - max_pages, max_len, &len); + max_pages, max_len, &len, + wbc->sync_mode); } } len = min_t(unsigned long long, len, i_size - start); |