Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 0 additions & 8 deletions fs/fuse/dir.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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 */
Expand Down
196 changes: 128 additions & 68 deletions fs/fuse/file.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand All @@ -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
Expand Down Expand Up @@ -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;
}
Expand All @@ -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)
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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);
/*
Expand Down
13 changes: 0 additions & 13 deletions fs/fuse/fuse_i.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
14 changes: 0 additions & 14 deletions fs/fuse/inode.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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);
Expand Down
Loading