summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--fs/smb/client/file.c147
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);