blob: 5966a711677510cfc44083bef38275bc38ea88b7 [file] [edit]
// SPDX-License-Identifier: GPL-2.0
/* Ceph netfs-based file read-write operations.
*
* There are a few funny things going on here.
*
* The page->private field is used to reference a struct ceph_snap_context for
* _every_ dirty page. This indicates which snapshot the page was logically
* dirtied in, and thus which snap context needs to be associated with the osd
* write during writeback.
*
* Similarly, struct ceph_inode_info maintains a set of counters to count dirty
* pages on the inode. In the absence of snapshots, i_wrbuffer_ref ==
* i_wrbuffer_ref_head == the dirty page count.
*
* When a snapshot is taken (that is, when the client receives notification
* that a snapshot was taken), each inode with caps and with dirty pages (dirty
* pages implies there is a cap) gets a new ceph_cap_snap in the i_cap_snaps
* list (which is sorted in ascending order, new snaps go to the tail). The
* i_wrbuffer_ref_head count is moved to capsnap->dirty. (Unless a sync write
* is currently in progress. In that case, the capsnap is said to be
* "pending", new writes cannot start, and the capsnap isn't "finalized" until
* the write completes (or fails) and a final size/mtime for the inode for that
* snap can be settled upon.) i_wrbuffer_ref_head is reset to 0.
*
* On writeback, we must submit writes to the osd IN SNAP ORDER. So, we look
* for the first capsnap in i_cap_snaps and write out pages in that snap
* context _only_. Then we move on to the next capsnap, eventually reaching
* the "live" or "head" context (i.e., pages that are not yet snapped) and are
* writing the most recently dirtied pages.
*
* Invalidate and so forth must take care to ensure the dirty page accounting
* is preserved.
*
* Copyright (C) 2026 Red Hat, Inc. All Rights Reserved.
* Written by David Howells (dhowells@redhat.com)
*/
#include <linux/ceph/ceph_debug.h>
#include <linux/backing-dev.h>
#include <linux/fs.h>
#include <linux/mm.h>
#include <linux/swap.h>
#include <linux/pagemap.h>
#include <linux/slab.h>
#include <linux/folio_batch.h>
#include <linux/task_io_accounting_ops.h>
#include <linux/signal.h>
#include <linux/iversion.h>
#include <linux/ktime.h>
#include <linux/netfs.h>
#include <trace/events/netfs.h>
#include "super.h"
#include "mds_client.h"
#include "cache.h"
#include "metric.h"
#include "crypto.h"
#include <linux/ceph/osd_client.h>
#include <linux/ceph/striper.h>
struct ceph_writeback_ctl
{
loff_t i_size;
u64 truncate_size;
u32 truncate_seq;
bool size_stable;
bool head_snapc;
};
struct kmem_cache *ceph_io_request_cachep;
struct kmem_cache *ceph_io_subrequest_cachep;
static struct ceph_io_subrequest *ceph_sreq2io(struct netfs_io_subrequest *subreq)
{
BUILD_BUG_ON(sizeof(struct ceph_io_request) > NETFS_DEF_IO_REQUEST_SIZE);
BUILD_BUG_ON(sizeof(struct ceph_io_subrequest) > NETFS_DEF_IO_SUBREQUEST_SIZE);
return container_of(subreq, struct ceph_io_subrequest, sreq);
}
/*
* Get the snapc from the group attached to a request
*/
static struct ceph_snap_context *ceph_wreq_snapc(struct netfs_io_request *wreq)
{
struct ceph_snap_context *snapc =
container_of(wreq->group, struct ceph_snap_context, group);
return snapc;
}
#if 0
static void ceph_put_many_snap_context(struct ceph_snap_context *sc, unsigned int nr)
{
if (sc)
netfs_put_group_many(&sc->group, nr);
}
#endif
/*
* Handle the termination of a write to the server.
*/
static void ceph_netfs_write_callback(struct ceph_osd_request *req)
{
struct netfs_io_subrequest *subreq = req->r_subreq;
struct ceph_io_subrequest *csub = ceph_sreq2io(subreq);
struct ceph_io_request *creq = csub->creq;
struct inode *inode = creq->rreq.inode;
struct ceph_inode_info *ci = ceph_inode(inode);
struct ceph_fs_client *fsc = ceph_inode_to_fs_client(inode);
struct ceph_client *cl = ceph_inode_to_client(inode);
size_t wrote = req->r_result ? 0 : subreq->len;
int err = req->r_result;
trace_netfs_sreq(subreq, netfs_sreq_trace_io_progress);
ceph_update_write_metrics(&fsc->mdsc->metric, req->r_start_latency,
req->r_end_latency, wrote, err);
if (err) {
doutc(cl, "sync_write osd write returned %d\n", err);
/* Version changed! Must re-do the rmw cycle */
if ((creq->rmw_assert_version && (err == -ERANGE || err == -EOVERFLOW)) ||
(!creq->rmw_assert_version && err == -EEXIST)) {
/* We should only ever see this on a rmw */
WARN_ON_ONCE(!test_bit(NETFS_RREQ_RMW, &ci->netfs.flags));
/* The version should never go backward */
WARN_ON_ONCE(err == -EOVERFLOW);
/* FIXME: limit number of times we loop? */
set_bit(NETFS_RREQ_REPEAT_RMW, &creq->rreq.flags);
trace_netfs_sreq(subreq, netfs_sreq_trace_need_rmw);
}
ceph_set_error_write(ci);
} else {
ceph_clear_error_write(ci);
}
csub->req = NULL;
ceph_osdc_put_request(req);
netfs_write_subrequest_terminated(subreq, err ?: wrote);
}
/*
* Estimate the maximum amount to accumulate before generating writes to the
* server. Use the end of the object for this as we would like be able to
* generate sparse writes.
*/
static int ceph_estimate_write(struct netfs_io_request *wreq,
struct netfs_io_stream *stream,
struct netfs_write_estimate *estimate)
{
struct ceph_inode_info *ci = ceph_inode(wreq->inode);
size_t max_len;
u64 objnum, objoff;
/* Clamp the length to the next object boundary. */
ceph_calc_file_object_mapping(&ci->i_layout, stream->issue_from,
ULLONG_MAX, &objnum, &objoff, &max_len);
estimate->issue_at = stream->issue_from + max_len;
return 0;
}
/*
* Issue a subrequest to upload to the server.
*/
static void ceph_issue_write(struct netfs_io_subrequest *subreq)
{
struct ceph_io_subrequest *csub = ceph_sreq2io(subreq);
struct ceph_snap_context *snapc = ceph_wreq_snapc(subreq->rreq);
struct ceph_osd_request *req;
struct ceph_io_request *creq = csub->creq;
struct ceph_fs_client *fsc = ceph_inode_to_fs_client(subreq->rreq->inode);
struct ceph_osd_client *osdc = &fsc->client->osdc;
struct inode *inode = subreq->rreq->inode;
struct ceph_inode_info *ci = ceph_inode(inode);
struct ceph_client *cl = ceph_inode_to_client(inode);
unsigned long long len;
unsigned int rmw = test_bit(NETFS_RREQ_RMW, &ci->netfs.flags) ? 1 : 0;
u64 objnum, objoff;
int ret;
doutc(cl, "issue_write R=%08x[%x] ino %llx %lld~%zu -- %srmw\n",
subreq->rreq->debug_id, subreq->debug_index, ci->i_vino.ino,
subreq->start, subreq->len,
rmw ? "" : "no ");
/* Clamp the length to the next object boundary. */
ceph_calc_file_object_mapping(&ci->i_layout, subreq->start, subreq->len,
&objnum, &objoff, &subreq->len);
len = umin(subreq->len, fsc->mount_options->wsize);
req = ceph_osdc_new_request(osdc, &ci->i_layout, ci->i_vino,
subreq->start, &len,
rmw, /* which: 0 or 1 */
rmw + 1, /* num_ops: 1 or 2 */
CEPH_OSD_OP_WRITE,
CEPH_OSD_FLAG_WRITE,
snapc,
ci->i_truncate_seq,
ci->i_truncate_size, false);
if (IS_ERR(req))
return netfs_write_subrequest_terminated(subreq, PTR_ERR(req));
subreq->len = len;
ret = netfs_prepare_write_buffer(subreq, INT_MAX, NULL); /* Need to copy the bvecq */
if (ret < 0)
return netfs_write_subrequest_terminated(subreq, ret);
doutc(cl, "write op %lld~%zu\n", subreq->start, subreq->len);
osd_req_op_extent_osd_bvecq(req, 0, subreq->content.bvecq, subreq->len);
req->r_inode = inode;
req->r_mtime = current_time(inode);
req->r_callback = ceph_netfs_write_callback;
req->r_subreq = subreq;
csub->req = req;
/*
* If we're doing an RMW cycle, set up an assertion that the remote
* data hasn't changed. If we don't have a version number, then the
* object doesn't exist yet. Use an exclusive create instead of a
* version assertion in that case.
*/
if (rmw) {
if (creq->rmw_assert_version) {
osd_req_op_init(req, 0, CEPH_OSD_OP_ASSERT_VER, 0);
req->r_ops[0].assert_ver.ver = creq->rmw_assert_version;
} else {
osd_req_op_init(req, 0, CEPH_OSD_OP_CREATE,
CEPH_OSD_OP_FLAG_EXCL);
}
}
trace_netfs_sreq(subreq, netfs_sreq_trace_submit);
ceph_osdc_start_request(osdc, req);
}
/*
* Mark the caps as dirty
*/
static void ceph_netfs_post_modify(struct inode *inode, void *fs_priv)
{
struct ceph_inode_info *ci = ceph_inode(inode);
struct ceph_cap_flush **prealloc_cf = fs_priv;
int dirty;
spin_lock(&ci->i_ceph_lock);
dirty = __ceph_mark_dirty_caps(ci, CEPH_CAP_FILE_WR, prealloc_cf);
spin_unlock(&ci->i_ceph_lock);
if (dirty)
__mark_inode_dirty(inode, dirty);
}
static void ceph_netfs_expand_readahead(struct netfs_io_request *rreq)
{
struct inode *inode = rreq->inode;
struct ceph_inode_info *ci = ceph_inode(inode);
struct ceph_file_layout *lo = &ci->i_layout;
unsigned long max_pages = inode->i_sb->s_bdi->ra_pages;
loff_t end = rreq->start + rreq->len, new_end;
struct ceph_io_request *priv = container_of(rreq, struct ceph_io_request, rreq);
unsigned long max_len;
u32 blockoff;
if (priv) {
/* Readahead is disabled by posix_fadvise POSIX_FADV_RANDOM */
if (priv->file_ra_disabled)
max_pages = 0;
else
max_pages = priv->file_ra_pages;
}
/* Readahead is disabled */
if (!max_pages)
return;
max_len = max_pages << PAGE_SHIFT;
/*
* Try to expand the length forward by rounding up it to the next
* block, but do not exceed the file size, unless the original
* request already exceeds it.
*/
new_end = umin(round_up(end, lo->stripe_unit), rreq->i_size);
if (new_end > end && new_end <= rreq->start + max_len)
rreq->len = new_end - rreq->start;
/* Try to expand the start downward */
div_u64_rem(rreq->start, lo->stripe_unit, &blockoff);
if (rreq->len + blockoff <= max_len) {
rreq->start -= blockoff;
rreq->len += blockoff;
}
}
static void ceph_netfs_read_callback(struct ceph_osd_request *req)
{
struct inode *inode = req->r_inode;
struct ceph_fs_client *fsc = ceph_inode_to_fs_client(inode);
struct ceph_client *cl = fsc->client;
struct ceph_osd_data *osd_data = osd_req_op_extent_osd_data(req, 0);
struct netfs_io_subrequest *subreq = req->r_priv;
struct ceph_osd_req_op *op = &req->r_ops[0];
bool sparse = (op->op == CEPH_OSD_OP_SPARSE_READ);
int err = req->r_result;
ceph_update_read_metrics(&fsc->mdsc->metric, req->r_start_latency,
req->r_end_latency, osd_data->iter.count, err);
doutc(cl, "result %d subreq->len=%zu i_size=%lld\n", req->r_result,
subreq->len, i_size_read(req->r_inode));
/* no object means success but no data */
if (err == -ENOENT)
err = 0;
else if (err == -EBLOCKLISTED)
fsc->blocklisted = true;
if (err >= 0) {
if (sparse && err > 0)
err = ceph_sparse_ext_map_end(op);
if (err < subreq->len &&
subreq->rreq->origin != NETFS_DIO_READ)
__set_bit(NETFS_SREQ_CLEAR_TAIL, &subreq->flags);
if (IS_ENCRYPTED(inode) && err > 0) {
#if 0
err = ceph_fscrypt_decrypt_extents(inode, osd_data->dbuf,
subreq->start,
op->extent.sparse_ext,
op->extent.sparse_ext_cnt);
if (err > subreq->len)
err = subreq->len;
#else
pr_err("TODO: Content-decrypt currently disabled\n");
err = -EOPNOTSUPP;
#endif
}
}
if (err > 0) {
subreq->transferred = err;
err = 0;
}
subreq->error = err;
trace_netfs_sreq(subreq, netfs_sreq_trace_io_progress);
ceph_dec_osd_stopping_blocker(fsc->mdsc);
netfs_read_subreq_terminated(subreq);
}
static void ceph_rmw_read_done(struct netfs_io_request *wreq, struct netfs_io_request *rreq)
{
struct ceph_io_request *cwreq = container_of(wreq, struct ceph_io_request, rreq);
struct ceph_io_request *crreq = container_of(rreq, struct ceph_io_request, rreq);
cwreq->rmw_assert_version = crreq->rmw_assert_version;
}
static int ceph_netfs_issue_read_inline(struct netfs_io_subrequest *subreq)
{
struct netfs_io_request *rreq = subreq->rreq;
struct inode *inode = rreq->inode;
struct ceph_mds_reply_info_parsed *rinfo;
struct ceph_mds_reply_info_in *iinfo;
struct ceph_mds_request *req;
struct ceph_mds_client *mdsc = ceph_sb_to_mdsc(inode->i_sb);
struct ceph_inode_info *ci = ceph_inode(inode);
struct iov_iter iter;
ssize_t err = 0;
size_t len, copied;
int mode;
__clear_bit(NETFS_SREQ_COPY_TO_CACHE, &subreq->flags);
if (subreq->start >= inode->i_size)
goto out;
/* We need to fetch the inline data. */
mode = ceph_try_to_choose_auth_mds(inode, CEPH_STAT_CAP_INLINE_DATA);
req = ceph_mdsc_create_request(mdsc, CEPH_MDS_OP_GETATTR, mode);
if (IS_ERR(req)) {
err = PTR_ERR(req);
goto out;
}
req->r_ino1 = ci->i_vino;
req->r_args.getattr.mask = cpu_to_le32(CEPH_STAT_CAP_INLINE_DATA);
req->r_num_caps = 2;
trace_netfs_sreq(subreq, netfs_sreq_trace_submit);
err = ceph_mdsc_do_request(mdsc, NULL, req);
if (err < 0)
goto out;
rinfo = &req->r_reply_info;
iinfo = &rinfo->targeti;
if (iinfo->inline_version == CEPH_INLINE_NONE) {
/* The data got uninlined */
ceph_mdsc_put_request(req);
return 1; /* Proceed as uninlined */
}
iov_iter_bvec_queue(&iter, ITER_DEST, subreq->content.bvecq,
subreq->content.slot, subreq->content.offset,
subreq->len);
len = umin(iinfo->inline_len - subreq->start, subreq->len);
copied = copy_to_iter(iinfo->inline_data + subreq->start, len, &iter);
if (copied) {
subreq->transferred += copied;
if (copied == len)
__set_bit(NETFS_SREQ_HIT_EOF, &subreq->flags);
subreq->error = 0;
} else {
subreq->error = -EFAULT;
}
ceph_mdsc_put_request(req);
out:
netfs_read_subreq_terminated(subreq);
return err;
}
static void ceph_netfs_issue_read(struct netfs_io_subrequest *subreq)
{
struct netfs_io_request *rreq = subreq->rreq;
struct inode *inode = rreq->inode;
struct ceph_inode_info *ci = ceph_inode(inode);
struct ceph_fs_client *fsc = ceph_inode_to_fs_client(inode);
struct ceph_client *cl = fsc->client;
struct ceph_osd_request *req = NULL;
struct ceph_vino vino = ceph_vino(inode);
bool sparse = IS_ENCRYPTED(inode) || ceph_test_mount_opt(fsc, SPARSEREAD);
u64 objno, objoff, len, off = subreq->start;
int err = 0, extent_cnt;
err = ceph_inode_is_shutdown(inode);
if (err <= 0)
goto failed;
if (ceph_has_inline_data(ci) && ceph_netfs_issue_read_inline(subreq))
return;
/* Truncate the extent at the end of the current block */
ceph_calc_file_object_mapping(&ci->i_layout, subreq->start, subreq->len,
&objno, &objoff, &subreq->len);
len = umin(subreq->len, fsc->mount_options->rsize);
req = ceph_osdc_new_request(&fsc->client->osdc, &ci->i_layout, vino,
off, &len, 0, 1,
sparse ? CEPH_OSD_OP_SPARSE_READ : CEPH_OSD_OP_READ,
CEPH_OSD_FLAG_READ, /* read_from_replica will be or'd in */
NULL, ci->i_truncate_seq, ci->i_truncate_size, false);
if (IS_ERR(req)) {
err = PTR_ERR(req);
req = NULL;
goto failed;
}
subreq->len = len;
err = netfs_prepare_read_buffer(subreq, INT_MAX); /* TODO: Extract the bvecq */
if (err < 0)
goto failed;
if (sparse) {
extent_cnt = __ceph_sparse_read_ext_count(inode, len);
err = ceph_alloc_sparse_ext_map(&req->r_ops[0], extent_cnt);
if (err)
goto failed;
}
doutc(cl, "%llx.%llx pos=%llu orig_len=%zu len=%llu\n",
ceph_vinop(inode), subreq->start, subreq->len, len);
osd_req_op_extent_osd_bvecq(req, 0, subreq->content.bvecq, subreq->len);
if (!ceph_inc_osd_stopping_blocker(fsc->mdsc)) {
err = -EIO;
goto failed;
}
req->r_callback = ceph_netfs_read_callback;
req->r_priv = subreq;
req->r_inode = inode;
trace_netfs_sreq(subreq, netfs_sreq_trace_submit);
ceph_osdc_start_request(req->r_osdc, req);
ceph_osdc_put_request(req);
doutc(cl, "%llx.%llx queued\n", ceph_vinop(inode));
return;
failed:
subreq->error = err;
if (req)
ceph_osdc_put_request(req);
doutc(cl, "%llx.%llx result %d\n", ceph_vinop(inode), err);
return netfs_read_subreq_terminated(subreq);
}
static int ceph_init_request(struct netfs_io_request *rreq, struct file *file)
{
struct ceph_io_request *priv = container_of(rreq, struct ceph_io_request, rreq);
struct inode *inode = rreq->inode;
struct ceph_client *cl = ceph_inode_to_client(inode);
struct ceph_fs_client *fsc = ceph_inode_to_fs_client(inode);
int got = 0, want = CEPH_CAP_FILE_CACHE;
int ret = 0;
rreq->rsize = 1024 * 1024;
rreq->wsize = umin(i_blocksize(inode), fsc->mount_options->wsize);
switch (rreq->origin) {
case NETFS_READAHEAD:
goto init_readahead;
case NETFS_WRITEBACK:
case NETFS_WRITETHROUGH:
case NETFS_UNBUFFERED_WRITE:
case NETFS_DIO_WRITE:
if (S_ISREG(rreq->inode->i_mode))
rreq->io_streams[0].avail = true;
return 0;
default:
return 0;
}
init_readahead:
/*
* If we are doing readahead triggered by a read, fault-in or
* MADV/FADV_WILLNEED, someone higher up the stack must be holding the
* FILE_CACHE and/or LAZYIO caps.
*/
if (file) {
priv->file_ra_pages = file->f_ra.ra_pages;
priv->file_ra_disabled = file->f_mode & FMODE_RANDOM;
rreq->netfs_priv = priv;
return 0;
}
/*
* readahead callers do not necessarily hold Fcb caps
* (e.g. fadvise, madvise).
*/
ret = ceph_try_get_caps(inode, CEPH_CAP_FILE_RD, want, true, &got);
if (ret < 0) {
doutc(cl, "%llx.%llx, error getting cap\n", ceph_vinop(inode));
goto out;
}
if (!(got & want)) {
doutc(cl, "%llx.%llx, no cache cap\n", ceph_vinop(inode));
ret = -EACCES;
goto out;
}
if (ret > 0)
priv->caps = got;
else
ret = -EACCES;
out:
return ret;
}
static void ceph_netfs_free_request(struct netfs_io_request *rreq)
{
struct ceph_io_request *creq = container_of(rreq, struct ceph_io_request, rreq);
if (creq->caps)
ceph_put_cap_refs(ceph_inode(rreq->inode), creq->caps);
}
const struct netfs_request_ops ceph_netfs_ops = {
.init_request = ceph_init_request,
.free_request = ceph_netfs_free_request,
.expand_readahead = ceph_netfs_expand_readahead,
.issue_read = ceph_netfs_issue_read,
.rmw_read_done = ceph_rmw_read_done,
.post_modify = ceph_netfs_post_modify,
.estimate_write = ceph_estimate_write,
.issue_write = ceph_issue_write,
};
/*
* Get ref for the oldest snapc for an inode with dirty data... that is, the
* only snap context we are allowed to write back.
*/
static struct ceph_snap_context *
ceph_get_oldest_context(struct inode *inode, struct ceph_writeback_ctl *ctl,
struct ceph_snap_context *folio_snapc)
{
struct ceph_snap_context *snapc = NULL;
struct ceph_inode_info *ci = ceph_inode(inode);
struct ceph_cap_snap *capsnap = NULL;
struct ceph_client *cl = ceph_inode_to_client(inode);
spin_lock(&ci->i_ceph_lock);
list_for_each_entry(capsnap, &ci->i_cap_snaps, ci_item) {
doutc(cl, " capsnap %p snapc %p has %d dirty pages\n",
capsnap, capsnap->context, capsnap->dirty_pages);
if (!capsnap->dirty_pages)
continue;
/* get i_size, truncate_{seq,size} for folio_snapc? */
if (snapc && capsnap->context != folio_snapc)
continue;
if (ctl) {
if (capsnap->writing) {
ctl->i_size = i_size_read(inode);
ctl->size_stable = false;
} else {
ctl->i_size = capsnap->size;
ctl->size_stable = true;
}
ctl->truncate_size = capsnap->truncate_size;
ctl->truncate_seq = capsnap->truncate_seq;
ctl->head_snapc = false;
}
if (snapc)
break;
snapc = ceph_get_snap_context(capsnap->context);
if (!folio_snapc ||
folio_snapc == snapc ||
folio_snapc->seq > snapc->seq)
break;
}
if (!snapc && ci->i_wrbuffer_ref_head) {
snapc = ceph_get_snap_context(ci->i_head_snapc);
doutc(cl, " head snapc %p has %d dirty pages\n", snapc,
ci->i_wrbuffer_ref_head);
if (ctl) {
ctl->i_size = i_size_read(inode);
ctl->truncate_size = ci->i_truncate_size;
ctl->truncate_seq = ci->i_truncate_seq;
ctl->size_stable = false;
ctl->head_snapc = true;
}
}
spin_unlock(&ci->i_ceph_lock);
return snapc;
}
/*
* Flush dirty data. We have to start with the oldest snap as that's the only
* one we're allowed to write back.
*/
static int ceph_writepages(struct address_space *mapping,
struct writeback_control *wbc)
{
struct ceph_writeback_ctl ceph_wbc;
struct ceph_snap_context *snapc;
struct ceph_inode_info *ci = ceph_inode(mapping->host);
loff_t actual_start = wbc->range_start, actual_end = wbc->range_end;
int ret;
do {
snapc = ceph_get_oldest_context(mapping->host, &ceph_wbc, NULL);
if (snapc == ci->i_head_snapc) {
wbc->range_start = actual_start;
wbc->range_end = actual_end;
} else {
/* Do not respect wbc->range_{start,end}. Dirty pages
* in that range can be associated with newer snapc.
* They are not writeable until we write all dirty
* pages associated with an older snapc get written.
*/
wbc->range_start = 0;
wbc->range_end = LLONG_MAX;
}
ret = netfs_writepages_group(mapping, wbc, &snapc->group, &ceph_wbc);
ceph_put_snap_context(snapc);
if (snapc == ci->i_head_snapc)
break;
} while (ret == 0 && wbc->nr_to_write > 0);
return ret;
}
const struct address_space_operations ceph_aops = {
.read_folio = netfs_read_folio,
.readahead = netfs_readahead,
.writepages = ceph_writepages,
.dirty_folio = ceph_dirty_folio,
.invalidate_folio = netfs_invalidate_folio,
.release_folio = netfs_release_folio,
.direct_IO = noop_direct_IO,
.migrate_folio = filemap_migrate_folio,
};
/*
* Wrap generic_file_aio_read with checks for cap bits on the inode.
* Atomically grab references, so that those bits are not released
* back to the MDS mid-read.
*
* Hmm, the sync read case isn't actually async... should it be?
*/
ssize_t ceph_netfs_read_iter(struct kiocb *iocb, struct iov_iter *to)
{
struct file *filp = iocb->ki_filp;
struct inode *inode = file_inode(filp);
struct ceph_inode_info *ci = ceph_inode(inode);
struct ceph_file_info *fi = filp->private_data;
struct ceph_client *cl = ceph_inode_to_client(inode);
ssize_t ret;
size_t len = iov_iter_count(to);
bool dio = iocb->ki_flags & IOCB_DIRECT;
int want = 0, got = 0;
doutc(cl, "%llu~%zu trying to get caps on %p %llx.%llx\n",
iocb->ki_pos, len, inode, ceph_vinop(inode));
if (ceph_inode_is_shutdown(inode))
return -ESTALE;
if (dio)
ret = netfs_start_io_direct(inode);
else
ret = netfs_start_io_read(inode);
if (ret < 0)
return ret;
if (!(fi->flags & CEPH_F_SYNC) && !dio)
want |= CEPH_CAP_FILE_CACHE;
if (fi->fmode & CEPH_FILE_MODE_LAZY)
want |= CEPH_CAP_FILE_LAZYIO;
ret = ceph_get_caps(filp, CEPH_CAP_FILE_RD, want, -1, &got);
if (ret < 0)
goto out;
if ((got & (CEPH_CAP_FILE_CACHE|CEPH_CAP_FILE_LAZYIO)) == 0 ||
dio ||
(fi->flags & CEPH_F_SYNC)) {
doutc(cl, "sync %p %llx.%llx %llu~%zu got cap refs on %s\n",
inode, ceph_vinop(inode), iocb->ki_pos, len,
ceph_cap_string(got));
ret = netfs_unbuffered_read_iter(iocb, to);
} else {
doutc(cl, "async %p %llx.%llx %llu~%zu got cap refs on %s\n",
inode, ceph_vinop(inode), iocb->ki_pos, len,
ceph_cap_string(got));
ret = filemap_read(iocb, to, 0);
}
doutc(cl, "%p %llx.%llx dropping cap refs on %s = %zd\n",
inode, ceph_vinop(inode), ceph_cap_string(got), ret);
ceph_put_cap_refs(ci, got);
out:
if (dio)
netfs_end_io_direct(inode);
else
netfs_end_io_read(inode);
return ret;
}
/*
* Get the most recent snap context in the list to which the inode subscribes.
* This is the only one we are allowed to modify. If a folio points to an
* earlier snapshot, it must be flushed first.
*/
static struct ceph_snap_context *ceph_get_most_recent_snapc(struct inode *inode)
{
struct ceph_snap_context *snapc;
struct ceph_inode_info *ci = ceph_inode(inode);
/* Get the snap this write is going to belong to. */
spin_lock(&ci->i_ceph_lock);
if (__ceph_have_pending_cap_snap(ci)) {
struct ceph_cap_snap *capsnap =
list_last_entry(&ci->i_cap_snaps,
struct ceph_cap_snap, ci_item);
snapc = ceph_get_snap_context(capsnap->context);
} else {
BUG_ON(!ci->i_head_snapc);
snapc = ceph_get_snap_context(ci->i_head_snapc);
}
spin_unlock(&ci->i_ceph_lock);
return snapc;
}
/*
* Take cap references to avoid releasing caps to MDS mid-write.
*
* If we are synchronous, and write with an old snap context, the OSD
* may return EOLDSNAPC. In that case, retry the write.. _after_
* dropping our cap refs and allowing the pending snap to logically
* complete _before_ this write occurs.
*
* If we are near ENOSPC, write synchronously.
*/
ssize_t ceph_netfs_write_iter(struct kiocb *iocb, struct iov_iter *from)
{
struct file *file = iocb->ki_filp;
struct inode *inode = file_inode(file);
struct ceph_snap_context *snapc;
struct ceph_inode_info *ci = ceph_inode(inode);
struct ceph_fs_client *fsc = ceph_inode_to_fs_client(inode);
struct ceph_file_info *fi = file->private_data;
struct ceph_osd_client *osdc = &fsc->client->osdc;
struct ceph_cap_flush *prealloc_cf;
struct ceph_client *cl = fsc->client;
ssize_t count, written = 0;
loff_t limit = max(i_size_read(inode), fsc->max_file_size);
loff_t pos;
bool direct_lock = false;
u64 pool_flags;
u32 map_flags;
int err, want = 0, got;
if (ceph_inode_is_shutdown(inode))
return -ESTALE;
if (ceph_snap(inode) != CEPH_NOSNAP)
return -EROFS;
prealloc_cf = ceph_alloc_cap_flush();
if (!prealloc_cf)
return -ENOMEM;
if ((iocb->ki_flags & (IOCB_DIRECT | IOCB_APPEND)) == IOCB_DIRECT)
direct_lock = true;
retry_snap:
if (direct_lock)
netfs_start_io_direct(inode);
else
netfs_start_io_write(inode);
if (iocb->ki_flags & IOCB_APPEND) {
err = ceph_do_getattr(inode, CEPH_STAT_CAP_SIZE, false);
if (err < 0)
goto out;
}
err = generic_write_checks(iocb, from);
if (err <= 0)
goto out;
pos = iocb->ki_pos;
if (unlikely(pos >= limit)) {
err = -EFBIG;
goto out;
} else {
iov_iter_truncate(from, limit - pos);
}
count = iov_iter_count(from);
if (ceph_quota_is_max_bytes_exceeded(inode, pos + count)) {
err = -EDQUOT;
goto out;
}
down_read(&osdc->lock);
map_flags = osdc->osdmap->flags;
pool_flags = ceph_pg_pool_flags(osdc->osdmap, ci->i_layout.pool_id);
up_read(&osdc->lock);
if ((map_flags & CEPH_OSDMAP_FULL) ||
(pool_flags & CEPH_POOL_FLAG_FULL)) {
err = -ENOSPC;
goto out;
}
err = file_remove_privs(file);
if (err)
goto out;
doutc(cl, "%p %llx.%llx %llu~%zd getting caps. i_size %llu\n",
inode, ceph_vinop(inode), pos, count,
i_size_read(inode));
if (!(fi->flags & CEPH_F_SYNC) && !direct_lock)
want |= CEPH_CAP_FILE_BUFFER;
if (fi->fmode & CEPH_FILE_MODE_LAZY)
want |= CEPH_CAP_FILE_LAZYIO;
got = 0;
err = ceph_get_caps(file, CEPH_CAP_FILE_WR, want, pos + count, &got);
if (err < 0)
goto out;
err = file_update_time(file);
if (err)
goto out_caps;
inode_inc_iversion_raw(inode);
doutc(cl, "%p %llx.%llx %llu~%zd got cap refs on %s\n",
inode, ceph_vinop(inode), pos, count, ceph_cap_string(got));
/* Get the snap this write is going to belong to. */
snapc = ceph_get_most_recent_snapc(inode);
if ((got & (CEPH_CAP_FILE_BUFFER|CEPH_CAP_FILE_LAZYIO)) == 0 ||
(iocb->ki_flags & IOCB_DIRECT) || (fi->flags & CEPH_F_SYNC) ||
(ci->i_ceph_flags & CEPH_I_ERROR_WRITE)) {
struct iov_iter data;
/* we might need to revert back to that point */
data = *from;
written = netfs_unbuffered_write_iter_locked(iocb, &data, &snapc->group);
if (direct_lock)
netfs_end_io_direct(inode);
else
netfs_end_io_write(inode);
if (written > 0)
iov_iter_advance(from, written);
ceph_put_snap_context(snapc);
} else {
/*
* No need to acquire the i_truncate_mutex. Because the MDS
* revokes Fwb caps before sending truncate message to us. We
* can't get Fwb cap while there are pending vmtruncate. So
* write and vmtruncate can not run at the same time
*/
written = netfs_perform_write(iocb, from, &snapc->group, &prealloc_cf);
netfs_end_io_write(inode);
}
if (written >= 0) {
int dirty;
spin_lock(&ci->i_ceph_lock);
dirty = __ceph_mark_dirty_caps(ci, CEPH_CAP_FILE_WR,
&prealloc_cf);
spin_unlock(&ci->i_ceph_lock);
if (dirty)
__mark_inode_dirty(inode, dirty);
if (ceph_quota_is_max_bytes_approaching(inode, iocb->ki_pos))
ceph_check_caps(ci, CHECK_CAPS_FLUSH);
}
doutc(cl, "%p %llx.%llx %llu~%u dropping cap refs on %s\n",
inode, ceph_vinop(inode), pos, (unsigned)count,
ceph_cap_string(got));
ceph_put_cap_refs(ci, got);
if (written == -EOLDSNAPC) {
doutc(cl, "%p %llx.%llx %llu~%u" "got EOLDSNAPC, retrying\n",
inode, ceph_vinop(inode), pos, (unsigned)count);
goto retry_snap;
}
if (written >= 0) {
if ((map_flags & CEPH_OSDMAP_NEARFULL) ||
(pool_flags & CEPH_POOL_FLAG_NEARFULL))
iocb->ki_flags |= IOCB_DSYNC;
written = generic_write_sync(iocb, written);
}
goto out_unlocked;
out_caps:
ceph_put_cap_refs(ci, got);
out:
if (direct_lock)
netfs_end_io_direct(inode);
else
netfs_end_io_write(inode);
out_unlocked:
ceph_free_cap_flush(prealloc_cf);
return written ? written : err;
}
vm_fault_t ceph_page_mkwrite(struct vm_fault *vmf)
{
struct ceph_snap_context *snapc;
struct vm_area_struct *vma = vmf->vma;
struct inode *inode = file_inode(vma->vm_file);
struct ceph_client *cl = ceph_inode_to_client(inode);
struct ceph_inode_info *ci = ceph_inode(inode);
struct ceph_file_info *fi = vma->vm_file->private_data;
struct ceph_cap_flush *prealloc_cf;
struct folio *folio = page_folio(vmf->page);
loff_t size = i_size_read(inode);
loff_t off = folio_pos(folio);
size_t len = folio_size(folio);
int want, got, err;
vm_fault_t ret = VM_FAULT_SIGBUS;
if (ceph_inode_is_shutdown(inode))
return ret;
prealloc_cf = ceph_alloc_cap_flush();
if (!prealloc_cf)
return -ENOMEM;
doutc(cl, "%llx.%llx %llu~%zd getting caps i_size %llu\n",
ceph_vinop(inode), off, len, size);
if (fi->fmode & CEPH_FILE_MODE_LAZY)
want = CEPH_CAP_FILE_BUFFER | CEPH_CAP_FILE_LAZYIO;
else
want = CEPH_CAP_FILE_BUFFER;
got = 0;
err = ceph_get_caps(vma->vm_file, CEPH_CAP_FILE_WR, want, off + len, &got);
if (err < 0)
goto out_free;
doutc(cl, "%llx.%llx %llu~%zd got cap refs on %s\n", ceph_vinop(inode),
off, len, ceph_cap_string(got));
/* Get the snap this write is going to belong to. */
snapc = ceph_get_most_recent_snapc(inode);
ret = netfs_page_mkwrite(vmf, &snapc->group, &prealloc_cf);
doutc(cl, "%llx.%llx %llu~%zd dropping cap refs on %s ret %x\n",
ceph_vinop(inode), off, len, ceph_cap_string(got), ret);
ceph_put_cap_refs_async(ci, got);
out_free:
ceph_free_cap_flush(prealloc_cf);
if (err < 0)
ret = vmf_error(err);
return ret;
}