diff --git a/fs/fuse/dir.c b/fs/fuse/dir.c index 99af2777432201..ce398f117ef593 100644 --- a/fs/fuse/dir.c +++ b/fs/fuse/dir.c @@ -2105,7 +2105,6 @@ int fuse_do_setattr(struct mnt_idmap *idmap, struct dentry *dentry, if (fc->dlm && fc->writeback_cache) fuse_dlm_cache_release_locks(fi); spin_lock(&fi->lock); - fi->server_size = 0; i_size_write(inode, 0); spin_unlock(&fi->lock); truncate_pagecache(inode, 0); @@ -2201,13 +2200,6 @@ int fuse_do_setattr(struct mnt_idmap *idmap, struct dentry *dentry, /* see the comment in fuse_change_attributes() */ if (!is_wb || is_truncate) i_size_write(inode, outarg.attr.size); - /* - * A truncate settles the size on the server; only shrink the - * server-materialized bound: growing just exposes zeros, which the - * bound need not cover (see fuse_write_begin()). - */ - if (is_truncate && (loff_t) outarg.attr.size < fi->server_size) - fi->server_size = outarg.attr.size; if (is_truncate) { /* NOTE: this may release/reacquire fi->lock */ diff --git a/fs/fuse/file.c b/fs/fuse/file.c index a9198a7eb283e2..48247876177b5d 100644 --- a/fs/fuse/file.c +++ b/fs/fuse/file.c @@ -333,7 +333,6 @@ static void fuse_truncate_update_attr(struct inode *inode, struct file *file) spin_lock(&fi->lock); fi->attr_version = atomic64_inc_return(&fc->attr_version); - fi->server_size = 0; i_size_write(inode, 0); spin_unlock(&fi->lock); file_update_time(file); @@ -1301,15 +1300,6 @@ bool fuse_write_update_attr(struct inode *inode, loff_t pos, ssize_t written) spin_lock(&fi->lock); fi->attr_version = atomic64_inc_return(&fc->attr_version); - if (written > 0 && S_ISREG(inode->i_mode)) { - /* - * The server acknowledged data up to @pos, keep the - * server-materialized bound in sync for the expansion - * zero-fill in fuse_write_begin(). - */ - if (pos > fi->server_size) - fi->server_size = pos; - } if (written > 0 && pos > inode->i_size) { i_size_write(inode, pos); ret = true; @@ -1454,7 +1444,8 @@ static inline unsigned int fuse_wr_pages(loff_t pos, size_t len, max_pages); } -static ssize_t fuse_perform_write(struct kiocb *iocb, struct iov_iter *ii) +static ssize_t fuse_perform_write(struct kiocb *iocb, struct iov_iter *ii, + bool cache) { struct address_space *mapping = iocb->ki_filp->f_mapping; struct inode *inode = mapping->host; @@ -1484,6 +1475,14 @@ static ssize_t fuse_perform_write(struct kiocb *iocb, struct iov_iter *ii) if (count <= 0) { err = count; } else { + /* + * On behalf of a buffered write whose bytes bypass + * the page cache (DLM unaligned edges): the server + * must classify them like the writeback they + * replace. + */ + if (cache) + ia.write.in.write_flags |= FUSE_WRITE_CACHE; err = fuse_send_write_pages(&ia, iocb, inode, pos, count); if (!err) { @@ -1646,6 +1645,88 @@ static int fuse_cache_wr_dlm_lock(struct file *file, loff_t pos, size_t len, return 0; } +/* + * Write @len bytes of @from at the current iocb position, either straight + * through to the server (@through, for an unaligned edge) or into the page + * cache (@through == false, for the aligned interior). Both primitives + * consume @len bytes from @from and advance iocb->ki_pos; the iterator is + * temporarily capped to @len so the unconsumed tail stays available for the + * next chunk. Returns bytes written (< @len means a short write, the caller + * stops) or a negative error. + */ +static ssize_t fuse_dlm_write_chunk(struct kiocb *iocb, struct iov_iter *from, + size_t len, bool through) +{ + size_t hidden; + ssize_t res; + + if (!len) + return 0; + + /* Cap the iterator to this chunk, keeping the tail for later chunks. */ + hidden = iov_iter_count(from) - len; + iov_iter_truncate(from, len); + res = through ? fuse_perform_write(iocb, from, true) + : generic_perform_write(iocb, from); + /* Restore from the iterator's own residue, so short writes/errors + * (which leave it partly advanced) reexpand to the exact remainder. */ + iov_iter_reexpand(from, iov_iter_count(from) + hidden); + + return res; +} + +/* + * Buffered write under DLM. A partial folio dirtied for writeback would + * have to be completed by reading the untouched remainder back from the + * server, and for a write past the server EOF that READ can only return + * zero bytes: a wasted round trip per unaligned edge. So cache only the + * page-aligned interior, whole folios need no read-modify-write, and + * route the unaligned head and tail straight through to the server. The + * writethrough path writes just those bytes and leaves the folio + * non-uptodate, doing no read, and each edge lands as an independent + * FUSE_WRITE carrying FUSE_WRITE_CACHE like the writeback it replaces, + * so writers sharing a boundary folio accumulate their bytes on the + * server. Aligned writes take the interior path whole; a + * sub-page write with no aligned interior goes fully through. + */ +static ssize_t fuse_dlm_buffered_write(struct kiocb *iocb, + struct iov_iter *from) +{ + loff_t pos = iocb->ki_pos; + loff_t end = pos + iov_iter_count(from); + loff_t mid_start = round_up(pos, PAGE_SIZE); + loff_t mid_end = round_down(end, PAGE_SIZE); + ssize_t res, total = 0; + + /* No whole folio inside the write: nothing cacheable, all through. */ + if (mid_end <= mid_start) + return fuse_perform_write(iocb, from, true); + + /* Unaligned head [pos, mid_start): through. */ + res = fuse_dlm_write_chunk(iocb, from, mid_start - pos, true); + if (res < 0) + return total ? total : res; + total += res; + if (res < mid_start - pos) + return total; + + /* Aligned interior [mid_start, mid_end): cached whole folios. */ + res = fuse_dlm_write_chunk(iocb, from, mid_end - mid_start, false); + if (res < 0) + return total ? total : res; + total += res; + if (res < mid_end - mid_start) + return total; + + /* Unaligned tail [mid_end, end): through. */ + res = fuse_dlm_write_chunk(iocb, from, end - mid_end, true); + if (res < 0) + return total ? total : res; + total += res; + + return total; +} + static ssize_t fuse_cache_write_iter(struct kiocb *iocb, struct iov_iter *from) { struct file *file = iocb->ki_filp; @@ -1769,9 +1850,8 @@ static ssize_t fuse_cache_write_iter(struct kiocb *iocb, struct iov_iter *from) /* * An append write lands at the current EOF no matter * what ki_pos holds: generic_write_checks() rewrites - * ki_pos to i_size for IOCB_APPEND, and i_size is - * stable here because append writes hold the inode lock - * exclusive. Lock where the data will land. + * ki_pos to i_size for IOCB_APPEND. Lock where the + * data will land. */ dlm_pos = i_size_read(inode); dlm_len = iov_iter_count(from); @@ -1786,6 +1866,26 @@ static ssize_t fuse_cache_write_iter(struct kiocb *iocb, struct iov_iter *from) if (written <= 0) goto wb_out; + /* + * The exclusive inode lock does not pin i_size for the append: + * attribute replies move it under fi->lock alone, so + * generic_write_checks() may have put ki_pos past the granted + * range. Re-lock where the write really lands; dlm_pos tracks + * it so the in-gate re-validation below guards the same range. + */ + if (fc->dlm && (iocb->ki_flags & IOCB_APPEND) && + iocb->ki_pos != dlm_pos) { + dlm_pos = iocb->ki_pos; + dlm_len = iov_iter_count(from); + + err = fuse_cache_wr_dlm_lock(file, dlm_pos, dlm_len, + &dlm_unrecorded); + if (err) { + written = err; + goto wb_out; + } + } + /* * Kill suid/sgid and stamp the timestamps here, before the * gate, instead of leaving them to @@ -1830,6 +1930,7 @@ static ssize_t fuse_cache_write_iter(struct kiocb *iocb, struct iov_iter *from) /* The gate is already dropped; funnel * the failure through the one audited * exit. */ + written = err; wb_guard = false; goto wb_out; } @@ -1843,7 +1944,17 @@ static ssize_t fuse_cache_write_iter(struct kiocb *iocb, struct iov_iter *from) written = direct_write_fallback(iocb, from, written, generic_perform_write(iocb, from)); } else { - written = generic_perform_write(iocb, from); + /* + * Under DLM the unaligned edges go through to the + * server instead of being completed by a + * read-modify-write READ (see + * fuse_dlm_buffered_write()); only whole folios are + * cached for writeback. + */ + if (fc->dlm) + written = fuse_dlm_buffered_write(iocb, from); + else + written = generic_perform_write(iocb, from); } wb_out: if (wb_guard) @@ -1905,9 +2016,9 @@ static ssize_t fuse_cache_write_iter(struct kiocb *iocb, struct iov_iter *from) if (written < 0 || !iov_iter_count(from)) goto out; written = direct_write_fallback(iocb, from, written, - fuse_perform_write(iocb, from)); + fuse_perform_write(iocb, from, false)); } else { - written = fuse_perform_write(iocb, from); + written = fuse_perform_write(iocb, from, false); } out: if (wb_guard) @@ -2364,20 +2475,6 @@ static void fuse_writepage_end(struct fuse_mount *fm, struct fuse_args *args, if (!fc->writeback_cache) fuse_invalidate_attr_mask(inode, FUSE_STATX_MODIFY); spin_lock(&fi->lock); - if (!error) { - struct fuse_write_in *inarg = &wpa->ia.write.in; - - /* - * The server acknowledged this writeback, so data up to the - * end of the request is materialized on the server. Advance - * the bound before the pages end writeback below, i.e. before - * they can go clean and be reclaimed, so that - * fuse_write_begin() can never zero-fill a reclaimed range - * the server holds data in. - */ - if ((loff_t) (inarg->offset + inarg->size) > fi->server_size) - fi->server_size = inarg->offset + inarg->size; - } fi->writectr--; fuse_writepage_finish(wpa); spin_unlock(&fi->lock); @@ -2740,42 +2837,6 @@ static int fuse_write_begin(struct file *file, struct address_space *mapping, goto success; } - /* - * The folio is inside i_size but may still sit in a range the server - * holds no data for: a shared-lock writer extends i_size past regions - * it has not written yet (see fuse_write_end()), and every such folio - * would otherwise be read back from the server although it cannot - * contain data. Zero-fill locally instead when the server is known to - * hold nothing in the range and we hold the DLM write lock covering - * it: - * - * - fi->server_size bounds the data materialized on the server - * (writeback and direct write acknowledgements, server - * attributes), - * - local data not yet acknowledged sits in uptodate folios, which - * are already handled above, - * - the page-granular DLM write lock excludes data written by other - * nodes, re-checked against the live lock tree so a revoked lock - * falls back to reading. - */ - if (fc->dlm) { - struct fuse_inode *fi = get_fuse_inode(mapping->host); - loff_t fpos = folio_pos(folio); - size_t fsz = folio_size(folio); - bool hole; - - spin_lock(&fi->lock); - hole = fpos >= fi->server_size; - spin_unlock(&fi->lock); - - if (hole && fuse_dlm_range_is_locked(fi, fpos, fpos + fsz - 1, - FUSE_PAGE_LOCK_WRITE)) { - folio_zero_range(folio, 0, fsz); - folio_mark_uptodate(folio); - goto success; - } - } - err = fuse_do_readpage(file, &folio->page); if (err) goto cleanup; @@ -3854,7 +3915,6 @@ void fuse_init_file_inode(struct inode *inode, unsigned int flags) fuse_dlm_cache_init(fi); fi->writectr = 0; fi->iocachectr = 0; - fi->server_size = 0; init_waitqueue_head(&fi->page_waitq); init_waitqueue_head(&fi->direct_io_waitq); /* diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h index c6e9791105671d..8dc81ff1f27452 100644 --- a/fs/fuse/fuse_i.h +++ b/fs/fuse/fuse_i.h @@ -189,19 +189,6 @@ struct fuse_inode { /* dlm locked areas we have sent lock requests for */ struct fuse_dlm_cache dlm_locked_areas; - /* - * Server-materialized size: an upper bound for how far - * the server holds file data. Seeded from - * server-reported attributes, advanced when the server - * acknowledges data (writeback completion, - * fuse_write_update_attr()), lowered again on - * truncate. A read-modify-write of a folio starting - * at or past this bound needs no READ request under a - * held DLM write lock: the server has no data there - * (see fuse_write_begin()). Protected by fi->lock. - */ - loff_t server_size; - /* * Per-inode read/write coherency gate for the * forced-direct-IO feature. Cache-serving buffered reads diff --git a/fs/fuse/inode.c b/fs/fuse/inode.c index b4b4cec2d65671..69694ed4ee9105 100644 --- a/fs/fuse/inode.c +++ b/fs/fuse/inode.c @@ -571,12 +571,10 @@ static void fuse_change_attributes_i(struct inode *inode, struct fuse_attr *attr loff_t oldsize; struct timespec64 old_mtime; bool have_size = !sx || (sx->mask & STATX_SIZE); - u64 srv_size; cache_mask = fuse_attr_cache_mask(inode, attr, have_size); spin_lock(&fi->lock); - srv_size = attr->size; if (cache_mask & STATX_SIZE) attr->size = i_size_read(inode); @@ -596,18 +594,6 @@ static void fuse_change_attributes_i(struct inode *inode, struct fuse_attr *attr return; } - /* - * srv_size is the size the server reported before the writeback - * cache_mask above replaced attr->size with the local value. It - * bounds how far the server can hold data, letting the buffered write - * path zero-fill expansion read-modify-writes instead of sending READ - * requests, see fuse_write_begin(). Only ever grow it here: stale - * attributes were rejected above and truncation lowers it directly. - */ - if (have_size && S_ISREG(inode->i_mode) && - (loff_t) srv_size > fi->server_size) - fi->server_size = srv_size; - old_mtime = inode_get_mtime(inode); fuse_change_attributes_common(inode, attr, sx, attr_valid, cache_mask, evict_ctr);