Viewing: osd_io.c
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (c) 2025-2026, DDN/Whamcloud, Inc.
*/
/*
* Author: Yingjin Qian <qian@ddn.com>
*/
#define DEBUG_SUBSYSTEM S_OSD
#include <linux/mm.h>
#include <linux/swap.h>
#include <lustre_compat.h>
#include <obd_support.h>
#include "osd_internal.h"
/* Copied from osd-ldiskfs */
static int osd_map_remote_to_local(loff_t offset, ssize_t len, int *nrpages,
struct niobuf_local *lnb, int maxlnb)
{
int rc = 0;
ENTRY;
*nrpages = 0;
while (len > 0) {
int poff = offset & (PAGE_SIZE - 1);
int plen = PAGE_SIZE - poff;
if (*nrpages >= maxlnb) {
rc = -EOVERFLOW;
break;
}
if (plen > len)
plen = len;
lnb->lnb_file_offset = offset;
lnb->lnb_page_offset = poff;
lnb->lnb_len = plen;
lnb->lnb_flags = 0;
lnb->lnb_page = NULL;
lnb->lnb_rc = 0;
lnb->lnb_guard_rpc = 0;
lnb->lnb_guard_disk = 0;
lnb->lnb_locked = 0;
lnb->lnb_hole = 0;
LASSERTF(plen <= len, "plen %u, len %lld\n", plen,
(long long) len);
offset += plen;
len -= plen;
lnb++;
(*nrpages)++;
}
RETURN(rc);
}
static int osd_get_page(const struct lu_env *env, struct dt_object *dt,
struct niobuf_local *lnb, gfp_t gfp_mask, bool write)
{
struct inode *inode = osd_dt_obj(dt)->oo_inode;
struct page *page;
pgoff_t index;
LASSERT(inode);
index = lnb->lnb_file_offset >> PAGE_SHIFT;
if (write) {
page = find_or_create_page(inode->i_mapping, index, gfp_mask);
if (page == NULL)
return -ENOMEM;
LASSERT(!PagePrivate2(page));
} else {
/*
* Specially handling for hole in the memory FS during read.
* It does not allocate pages for holes, just records them and
* free them after reading.
* Otherwise, reading on a large sparse file may hit OOM.
*/
page = find_lock_page(inode->i_mapping, index);
/* fallocated page? */
if (page && !PageUptodate(page)) {
unlock_page(page);
put_page(page);
page = NULL;
}
if (page == NULL) {
page = alloc_page(gfp_mask);
if (!page)
return -ENOMEM;
SetPagePrivate2(page);
lock_page(page);
ClearPageUptodate(page);
page_folio(page)->index = index;
lnb->lnb_hole = 1;
}
}
lnb->lnb_page = page;
lnb->lnb_locked = 1;
if (!lnb->lnb_hole)
mark_page_accessed(page);
return 0;
}
/*
* Unlock and release pages loaded by @osd_bufs_get().
*
* Unlock \a npages pages from \a lnb and drop the refcount on them.
*/
static int osd_bufs_put(const struct lu_env *env, struct dt_object *dt,
struct niobuf_local *lnb, int npages)
{
struct folio_batch fbatch;
int i;
ll_folio_batch_init(&fbatch);
for (i = 0; i < npages; i++) {
struct page *page = lnb[i].lnb_page;
if (page == NULL)
continue;
/* If the page is not cached in the memory FS, then free it. */
if (PagePrivate2(page)) {
LASSERT(lnb[i].lnb_hole);
LASSERT(PageLocked(page));
ClearPagePrivate2(page);
unlock_page(page);
__free_page(page);
} else {
if (lnb[i].lnb_locked)
unlock_page(page);
if (folio_batch_add_page(&fbatch, page) == 0)
folio_batch_release(&fbatch);
}
lnb[i].lnb_page = NULL;
}
folio_batch_release(&fbatch);
return 0;
}
/**
* osd_bufs_get() - Load and lock pages undergoing IO
* @env: thread execution environment
* @dt: dt object undergoing IO (OSD object + methods)
* @pos: byte offset of IO start
* @len: number of bytes of IO
* @lnb: array of extents undergoing IO
* @maxlnb: maximum lnb
* @rw: read or write operation, and other flags
*
* Pages as described in the \a lnb array are fetched (from disk or cache)
* and locked for IO by the caller.
*
* Returns:
* %pages - (zero or more) loaded successfully
* %-ENOMEM - on memory/page allocation error
*/
static int osd_bufs_get(const struct lu_env *env, struct dt_object *dt,
loff_t pos, ssize_t len, struct niobuf_local *lnb,
int maxlnb, enum dt_bufs_type rw)
{
struct osd_object *obj = osd_dt_obj(dt);
gfp_t gfp_mask;
int npages;
int rc;
int i;
LASSERT(obj->oo_inode);
if (unlikely(obj->oo_destroyed))
RETURN(-ENOENT);
rc = osd_map_remote_to_local(pos, len, &npages, lnb, maxlnb);
if (rc)
RETURN(rc);
/* this could also try less hard for DT_BUFS_TYPE_READAHEAD pages */
gfp_mask = rw & DT_BUFS_TYPE_LOCAL ? (GFP_NOFS | __GFP_HIGHMEM) :
GFP_HIGHUSER;
for (i = 0; i < npages; i++, lnb++) {
rc = osd_get_page(env, dt, lnb, gfp_mask,
rw & DT_BUFS_TYPE_WRITE);
if (rc)
GOTO(cleanup, rc);
}
RETURN(i);
cleanup:
if (i > 0)
osd_bufs_put(env, dt, lnb - i, i);
return rc;
}
static ssize_t osd_read(const struct lu_env *env, struct dt_object *dt,
struct lu_buf *buf, loff_t *pos)
{
struct osd_object *obj = osd_dt_obj(dt);
struct osd_device *dev = osd_obj2dev(obj);
struct inode *inode = obj->oo_inode;
struct file *file;
ssize_t result;
ENTRY;
/* TODO: Specially handling for symlink. */
if (S_ISLNK(dt->do_lu.lo_header->loh_attr))
RETURN(-EOPNOTSUPP);
file = osd_alloc_file_pseudo(inode, dev->od_mnt, "/",
O_NOATIME | O_RDONLY, inode->i_fop);
if (IS_ERR(file))
RETURN(PTR_ERR(file));
result = kernel_read(file, buf->lb_buf, buf->lb_len, pos);
ihold(inode);
fput(file);
RETURN(result);
}
static ssize_t osd_write(const struct lu_env *env, struct dt_object *dt,
const struct lu_buf *buf, loff_t *pos,
struct thandle *th)
{
struct osd_object *obj = osd_dt_obj(dt);
struct osd_device *dev = osd_obj2dev(obj);
struct inode *inode = obj->oo_inode;
struct file *file;
ssize_t result;
ENTRY;
/* TODO: Specially handling for symlink. */
if (S_ISLNK(dt->do_lu.lo_header->loh_attr))
RETURN(-EOPNOTSUPP);
file = osd_alloc_file_pseudo(inode, dev->od_mnt, "/",
O_NOATIME | O_WRONLY, inode->i_fop);
if (IS_ERR(file))
RETURN(PTR_ERR(file));
result = kernel_write(file, buf->lb_buf, buf->lb_len, pos);
ihold(inode);
fput(file);
RETURN(result);
}
/* Can we move all osd_read_prep() codes into osd_bufs_get() ? */
static int osd_read_prep(const struct lu_env *env, struct dt_object *dt,
struct niobuf_local *lnb, int npages)
{
struct inode *inode = osd_dt_obj(dt)->oo_inode;
loff_t isize;
int i;
ENTRY;
LASSERT(inode);
isize = i_size_read(inode);
for (i = 0; i < npages; i++) {
/*
* If there is no more data, abort early.
* lnb->lnb_rc == 0, so it is easy to detect later.
*/
if (isize <= lnb[i].lnb_file_offset)
break;
/*
* Instead of looking if we go beyond isize, send complete
* pages all the time.
*/
lnb[i].lnb_rc = lnb[i].lnb_len;
if (lnb[i].lnb_hole) {
void *kaddr;
LASSERT(PagePrivate2(lnb[i].lnb_page));
kaddr = kmap(lnb[i].lnb_page);
memset(kaddr, 0, PAGE_SIZE);
kunmap(lnb[i].lnb_page);
SetPageUptodate(lnb[i].lnb_page);
} else {
/*
* The page in cache for MemFS should be always
* in uptodate state.
*/
LASSERT(PageUptodate(lnb[i].lnb_page));
unlock_page(lnb[i].lnb_page);
/*
* No need to unlock in osd_bufs_put(). The sooner page
* is unlocked, the earlier another client can access
* it.
*/
lnb[i].lnb_locked = 0;
}
}
RETURN(0);
}
static int osd_write_prep(const struct lu_env *env, struct dt_object *dt,
struct niobuf_local *lnb, int npages)
{
struct inode *inode = osd_dt_obj(dt)->oo_inode;
ssize_t isize;
__s64 maxidx;
int i;
ENTRY;
LASSERT(inode);
isize = i_size_read(inode);
maxidx = ((isize + PAGE_SIZE - 1) >> PAGE_SHIFT) - 1;
for (i = 0; i < npages; i++) {
/*
* Till commit the content of the page is undefined
* we will set it uptodate once bulk is done. Otherwise
* subsequent reads can access non-stable data.
*/
ClearPageUptodate(lnb[i].lnb_page);
if (lnb[i].lnb_len == PAGE_SIZE)
continue;
if (maxidx < folio_index_page(lnb[i].lnb_page)) {
long off;
char *p = kmap(lnb[i].lnb_page);
off = lnb[i].lnb_page_offset;
if (off)
memset(p, 0, off);
off = (lnb[i].lnb_page_offset + lnb[i].lnb_len) &
~PAGE_MASK;
if (off)
memset(p + off, 0, PAGE_SIZE - off);
kunmap(lnb[i].lnb_page);
}
}
RETURN(0);
}
static int osd_write_commit(const struct lu_env *env, struct dt_object *dt,
struct niobuf_local *lnb, int npages,
struct thandle *th, __u64 user_size)
{
struct inode *inode = osd_dt_obj(dt)->oo_inode;
struct address_space *mapping = inode->i_mapping;
size_t isize;
int i;
ENTRY;
LASSERT(inode);
for (i = 0; i < npages; i++) {
if (lnb[i].lnb_rc) { /* ENOSPC, network RPC error, etc. */
LASSERT(lnb[i].lnb_page);
generic_error_remove_folio(inode->i_mapping,
page_folio(lnb[i].lnb_page));
continue;
}
/*
* TODO: @lnb array is a sorted array according to the file
* offset, thus it just needs to check the last @lnb for
* file size.
*/
if (user_size < lnb[i].lnb_file_offset + lnb[i].lnb_len)
user_size = lnb[i].lnb_file_offset + lnb[i].lnb_len;
LASSERT(PageLocked(lnb[i].lnb_page));
LASSERT(!PageWriteback(lnb[i].lnb_page));
/* LASSERT(!PageDirty(lnb[i].lnb_page)); */
SetPageUptodate(lnb[i].lnb_page);
#ifdef HAVE_DIRTY_FOLIO
mapping->a_ops->dirty_folio(mapping,
page_folio(lnb[i].lnb_page));
#else
mapping->a_ops->set_page_dirty(lnb[i].lnb_page);
#endif
}
spin_lock(&inode->i_lock);
isize = i_size_read(inode);
if (isize < user_size)
i_size_write(inode, user_size);
spin_unlock(&inode->i_lock);
CDEBUG(D_INFO, "Size after write: i_size=%lld user_size=%llu\n",
i_size_read(inode), user_size);
/* No transno is needed for in-memory FS. */
th->th_local = 1;
RETURN(0);
}
/* TODO: Implement punch operation. */
static int osd_punch(const struct lu_env *env, struct dt_object *dt,
__u64 start, __u64 end, struct thandle *th)
{
RETURN(0);
}
/* TODO: Implemented lseek operation. */
static loff_t osd_lseek(const struct lu_env *env, struct dt_object *dt,
loff_t offset, int whence)
{
RETURN(0);
}
const struct dt_body_operations osd_wbcfs_body_ops = {
.dbo_read = osd_read,
.dbo_write = osd_write,
.dbo_bufs_get = osd_bufs_get,
.dbo_bufs_put = osd_bufs_put,
.dbo_write_prep = osd_write_prep,
.dbo_write_commit = osd_write_commit,
.dbo_read_prep = osd_read_prep,
.dbo_punch = osd_punch,
.dbo_lseek = osd_lseek,
};