Viewing: osd_io.c

// SPDX-License-Identifier: GPL-2.0

/*
 * Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved.
 * Use is subject to license terms.
 *
 * Copyright (c) 2012, 2017, Intel Corporation.
 */

/*
 * This file is part of Lustre, http://www.lustre.org/
 *
 * body operations
 *
 * Author: Nikita Danilov <nikita@clusterfs.com>
 * Author: Alex Zhuravlev <bzzz@whamcloud.com>
 */

#define DEBUG_SUBSYSTEM	S_OSD

/* prerequisite for linux/xattr.h */
#include <linux/types.h>
/* prerequisite for linux/xattr.h */
#include <linux/fs.h>
#include <linux/mm.h>
#include <linux/swap.h>
#include <linux/pagevec.h>
#include <linux/blk_types.h>

/*
 * struct OBD_{ALLOC,FREE}*()
 */
#include <obd_support.h>

#include "osd_internal.h"
/* ext_depth() */
#include <ldiskfs/ldiskfs_extents.h>
#include <ldiskfs/ldiskfs.h>

#ifndef SECTOR_SHIFT
#define SECTOR_SHIFT 9
#endif

struct kmem_cache *biop_cachep;

static void dio_complete_routine(struct bio *bio);

static int osd_bio_init(struct bio *bio, struct osd_iobuf *iobuf,
			int start_page_idx)
{
	struct osd_bio_private *bio_private = NULL;
	ENTRY;

	OBD_SLAB_ALLOC_GFP(bio_private, biop_cachep, sizeof(*bio_private),
			   GFP_NOIO);
	if (bio_private == NULL)
		RETURN(-ENOMEM);

	bio->bi_end_io = dio_complete_routine;
	bio->bi_private = bio_private;
	bio_private->obp_start_page_idx = start_page_idx;
	bio_private->obp_iobuf = iobuf;
	bio_private->obp_bio = bio;

	RETURN(0);
}

static void osd_bio_uninit(struct bio *bio)
{
	struct osd_bio_private *bio_private;

	if (!bio)
		return;
	bio_private = bio->bi_private;
	bio_put(bio);
	if (bio_private->obp_integrity_buf != NULL)
		kfree(bio_private->obp_integrity_buf);
	OBD_SLAB_FREE(bio_private, biop_cachep, sizeof(*bio_private));
}

static inline bool osd_use_page_cache(struct osd_device *d)
{
	/* do not use pagecache if write and read caching are disabled */
	if (d->od_writethrough_cache + d->od_read_cache == 0)
		return false;
	/* use pagecache by default */
	return true;
}

static int __osd_init_iobuf(struct osd_device *d, struct osd_iobuf *iobuf,
			    struct inode *inode,
			    int rw, const short line, int pages)
{
	int blocks, i;

	LASSERTF(iobuf->dr_elapsed_valid == 0,
		 "iobuf %px, reqs %d, rw %d, line %d\n", iobuf,
		 atomic_read(&iobuf->dr_numreqs), iobuf->dr_rw,
		 iobuf->dr_init_at);
	LASSERT(pages <= PTLRPC_MAX_BRW_PAGES);

	init_waitqueue_head(&iobuf->dr_wait);
	atomic_set(&iobuf->dr_numreqs, 0);
	iobuf->dr_npages = 0;
	iobuf->dr_lextents = 0;
	iobuf->dr_pextents = 0;

	iobuf->dr_error = 0;
	iobuf->dr_dev = d;
	iobuf->dr_frags = 0;
	iobuf->dr_elapsed = ktime_set(0, 0);
	/* must be counted before, so assert */
	iobuf->dr_rw = rw;
	iobuf->dr_init_at = line;
	iobuf->dr_inode = inode;

	/* Init dr_start_pg_wblks to 0 for osd_read/write_prep().
	 * For osd_write_commit() need to keep the value assigned in
	 * osd_ldiskfs_map_inode_pages() during retries, and before it ,
	 * init dr_start_pg_wblks to 0 in osd_write_prep() is sufficient.
	 */
	if (rw == 0)
		iobuf->dr_start_pg_wblks = 0;

	blocks = pages * (PAGE_SIZE >> osd_sb(d)->s_blocksize_bits);
	if (iobuf->dr_bl_buf.lb_len >= blocks * sizeof(iobuf->dr_blocks[0])) {
		return 0;
	}

	/* start with 1MB for 4K blocks */
	i = 256;
	while (i <= PTLRPC_MAX_BRW_PAGES && i < pages)
		i <<= 1;

	CDEBUG(D_OTHER, "realloc %u for %u (%u) pages\n",
	       (unsigned int)(pages * sizeof(iobuf->dr_lnbs[0])), i, pages);
	pages = i;
	blocks = pages * (PAGE_SIZE >> osd_sb(d)->s_blocksize_bits);
	iobuf->dr_max_pages = 0;
	CDEBUG(D_OTHER, "realloc %u for %u blocks\n",
	       (unsigned int)(blocks * sizeof(iobuf->dr_blocks[0])), blocks);

	lu_buf_realloc(&iobuf->dr_bl_buf, blocks * sizeof(iobuf->dr_blocks[0]));
	iobuf->dr_blocks = iobuf->dr_bl_buf.lb_buf;
	if (unlikely(iobuf->dr_blocks == NULL))
		return -ENOMEM;

	lu_buf_realloc(&iobuf->dr_lnb_buf,
		       pages * sizeof(iobuf->dr_lnbs[0]));
	iobuf->dr_lnbs = iobuf->dr_lnb_buf.lb_buf;
	if (unlikely(iobuf->dr_lnbs == NULL))
		return -ENOMEM;

	iobuf->dr_max_pages = pages;

	return 0;
}

#define osd_init_iobuf(dev, iobuf, inode, rw, pages)			\
({									\
	int __r;							\
	BUILD_BUG_ON(__LINE__ >= (1 << 16));				\
	__r = __osd_init_iobuf(dev, iobuf, inode, rw, __LINE__, pages);	\
	__r;								\
})

static void osd_iobuf_add_page(struct osd_iobuf *iobuf,
			       struct niobuf_local *lnb)
{
	LASSERT(iobuf->dr_npages < iobuf->dr_max_pages);
	iobuf->dr_lnbs[iobuf->dr_npages] = lnb;
	iobuf->dr_npages++;
}

void osd_fini_iobuf(struct osd_device *d, struct osd_iobuf *iobuf)
{
	struct brw_stats *bs = &d->od_brw_stats;
	struct obd_hist_pcpu *stats = NULL;
	unsigned int latency_us;
	int page_count = iobuf->dr_npages;
	int idx = fls(page_count) - 1;
	int rw = iobuf->dr_rw;

	iobuf->dr_error = 0;

	if (!iobuf->dr_elapsed_valid)
		return;
	if (unlikely(idx < 0)) {
		CDEBUG(D_PAGE, "%s: histogram index %d < 0\n",
		       d->od_svname, idx);
		idx = 0;
	}
	iobuf->dr_elapsed_valid = 0;

	lprocfs_oh_tally_pcpu(&bs->bs_hist[BRW_R_DIO_FRAGS + rw],
			      iobuf->dr_frags);
	lprocfs_oh_tally_log2_pcpu(&bs->bs_hist[BRW_R_IO_TIME + rw],
				   ktime_to_ms(iobuf->dr_elapsed));
	if (unlikely(idx >= IO_LATENCY_BUCKETS))
		idx = IO_LATENCY_BUCKETS - 1;
	latency_us = ktime_to_ns(iobuf->dr_elapsed) >> 10;

	if (rw == READ)
		stats = bs->bs_read_io_latency_by_size;
	else if (rw == WRITE)
		stats = bs->bs_write_io_latency_by_size;
	if (likely(stats && stats[idx].oh_initialized))
		lprocfs_oh_tally_log2_pcpu(stats + idx, latency_us);
}

void osd_bio_fini(struct bio *bio)
{
	struct osd_bio_private *bio_private = bio->bi_private;
	struct osd_iobuf *iobuf = bio_private->obp_iobuf;

	/*
	 * set dr_elapsed before dr_numreqs turns to 0, otherwise
	 * it's possible that service thread will see dr_numreqs
	 * is zero, but dr_elapsed is not set yet, leading to lost
	 * data in this processing and an assertion in a subsequent
	 * call to OSD.
	 */
	if (atomic_read(&iobuf->dr_numreqs) == 1) {
		ktime_t now = ktime_get();

		iobuf->dr_elapsed = ktime_sub(now, iobuf->dr_start_time);
		iobuf->dr_elapsed_valid = 1;
	}
	if (atomic_dec_and_test(&iobuf->dr_numreqs))
		wake_up(&iobuf->dr_wait);

	/* Completed bios used to be chained off iobuf->dr_bios and freed in
	 * filter_clear_dreq().  It was then possible to exhaust the biovec-256
	 * mempool when serious on-disk fragmentation was encountered,
	 * deadlocking the OST.  The bios are now released as soon as complete
	 * so the pool cannot be exhausted while IOs are competing. b=10076
	 */
	osd_bio_uninit(bio);
}

static void dio_complete_routine(struct bio *bio)
{
	int error = blk_status_to_errno(bio->bi_status);
	struct osd_bio_private *bio_private = bio->bi_private;
	struct osd_iobuf *iobuf = bio_private->obp_iobuf;
	struct bio_vec *bvl;


	/* CAVEAT EMPTOR: possibly in IRQ context
	 * DO NOT record procfs stats here!!!
	 */
	if (unlikely(iobuf == NULL)) {
		CERROR("***** bio->bi_private is NULL! Dump the bio contents to the console. Please report this to <https://jira.whamcloud.com/>, and probably have to reboot this node: rc = %d\n",
		       -EIO);
		CERROR("bi_next: %p, bi_flags: %lx, " __stringify(bi_opf)
		       ": %x, bi_vcnt: %d, bi_idx: %d, bi->size: %d, bi_end_io: %p, bi_cnt: %d, bi_private: %p\n",
		       bio->bi_next, (unsigned long)bio->bi_flags,
		       (unsigned int)bio->bi_opf, bio->bi_vcnt,
		       bio->bi_iter.bi_idx,
		       bio_sectors(bio) << 9, bio->bi_end_io,
		       atomic_read(&bio->__bi_cnt), bio->bi_private);
		return;
	}

	/* the check is outside of the cycle for performance reason -bzzz */
	if (!bio_data_dir(bio)) {
		DECLARE_BVEC_ITER_ALL(iter_all);

		bio_for_each_segment_all(bvl, bio, iter_all) {
			if (likely(error == 0))
				SetPageUptodate(bvl->bv_page);
			LASSERT(PageLocked(bvl->bv_page));
		}
		atomic_dec(&iobuf->dr_dev->od_r_in_flight);
	} else {
		atomic_dec(&iobuf->dr_dev->od_w_in_flight);
	}

	/* any real error is good enough -bzzz */
	if (error != 0 && iobuf->dr_error == 0)
		iobuf->dr_error = error;

	if (bio_data_dir(bio) == READ && iobuf->dr_error == 0 &&
	    bio_private->obp_integrity_buf != NULL &&
	    bdev_integrity_enabled(osd_sb(iobuf->dr_dev)->s_bdev,
				   iobuf->dr_rw)) {
		INIT_WORK(&bio_private->obp_work, osd_bio_integrity_verify_fn);
		queue_work(iobuf->dr_dev->od_integrityd_wq,
			   &bio_private->obp_work);
		return;
	}

	osd_bio_fini(bio);
}

static void record_start_io(struct osd_iobuf *iobuf, int size)
{
	struct osd_device *osd = iobuf->dr_dev;
	struct brw_stats *h = &osd->od_brw_stats;

	iobuf->dr_frags++;
	atomic_inc(&iobuf->dr_numreqs);

	if (iobuf->dr_rw == 0) {
		atomic_inc(&osd->od_r_in_flight);
		if (h->bs_inflight_io_log2)
			lprocfs_oh_tally_log2_pcpu(
				&h->bs_hist[BRW_R_RPC_HIST],
				atomic_read(&osd->od_r_in_flight));
		else
			lprocfs_oh_tally_pcpu(
				&h->bs_hist[BRW_R_RPC_HIST],
				atomic_read(&osd->od_r_in_flight));
		lprocfs_oh_tally_log2_pcpu(&h->bs_hist[BRW_R_DISK_IOSIZE],
					   size);
	} else if (iobuf->dr_rw == 1) {
		atomic_inc(&osd->od_w_in_flight);
		if (h->bs_inflight_io_log2)
			lprocfs_oh_tally_log2_pcpu(
				&h->bs_hist[BRW_W_RPC_HIST],
				atomic_read(&osd->od_w_in_flight));
		else
			lprocfs_oh_tally_pcpu(
				&h->bs_hist[BRW_W_RPC_HIST],
				atomic_read(&osd->od_w_in_flight));
		lprocfs_oh_tally_log2_pcpu(&h->bs_hist[BRW_W_DISK_IOSIZE],
					   size);
	} else {
		LBUG();
	}
}

static int osd_submit_bio(struct osd_device *osd,
			  struct osd_iobuf *iobuf,
			  struct bio *bio)
{
	struct request_queue *q;
	unsigned int bi_size;
	int rc = 0;

	if (bio == NULL)
		return 0;

	q = bio_get_queue(bio);
	bi_size = bio_sectors(bio) << SECTOR_SHIFT;
	/* Dang! I have to fragment this I/O */
	CDEBUG(D_INODE,
	       "bio++ sz %d vcnt %d(%d) sectors %d(%d) psg %d(%d)\n",
	       bi_size, bio->bi_vcnt, bio->bi_max_vecs,
	       bio_sectors(bio),
	       queue_max_sectors(q),
	       osd_bio_nr_segs(bio),
	       queue_max_segments(q));

	rc = osd_bio_integrity_handle(osd, bio, iobuf);
	if (rc)
		goto out;

	record_start_io(iobuf, bi_size);

	bio->bi_opf |= iobuf->dr_rw;
	submit_bio(bio);
out:
	return rc;
}

static int can_be_merged(struct bio *bio, sector_t sector)
{

	return bio_end_sector(bio) == sector ? 1 : 0;
}


static void osd_mark_page_io_done(struct osd_iobuf *iobuf,
				  struct inode *inode,
				  sector_t start_blocks,
				  sector_t count)
{
	struct niobuf_local **lnbs = iobuf->dr_lnbs;
	int blocks_per_page = PAGE_SIZE >> inode->i_blkbits;
	int i, end;

	i = start_blocks / blocks_per_page;
	end = (start_blocks + count) / blocks_per_page;
	for ( ; i < end; i++)
		lnbs[i]->lnb_flags |= OBD_BRW_DONE;
}

/*
 * Linux v5.12-rc1-20-ga8affc03a9b3
 *  block: rename BIO_MAX_PAGES to BIO_MAX_VECS
 */
#ifndef BIO_MAX_VECS
#define BIO_MAX_VECS	BIO_MAX_PAGES
#endif

static int osd_do_bio(struct osd_device *osd, struct inode *inode,
		      struct osd_iobuf *iobuf, sector_t start_blocks,
		      sector_t count)
{
	int blocks_per_page = PAGE_SIZE >> inode->i_blkbits;
	struct niobuf_local **lnbs = iobuf->dr_lnbs;
	int npages = iobuf->dr_npages;
	sector_t *blocks = iobuf->dr_blocks;
	struct super_block *sb = inode->i_sb;
	int sector_bits = sb->s_blocksize_bits - SECTOR_SHIFT;
	unsigned int blocksize = sb->s_blocksize;
	struct block_device *bdev = sb->s_bdev;
	struct bio *bio = NULL;
	int bio_start_page_idx = 0;
	struct page *page;
	unsigned int page_offset;
	sector_t sector;
	int nblocks;
	int block_idx, block_idx_end;
	int page_idx, page_idx_start;
	int i;
	int rc = 0;
	struct blk_plug plug;
	int blocks_left_page;

	ENTRY;

	LASSERT(iobuf->dr_npages == npages);
	iobuf->dr_start_time = ktime_get();

	if (!count)
		count = npages * blocks_per_page;
	block_idx_end = start_blocks + count;

	blk_start_plug(&plug);

	page_idx_start = start_blocks / blocks_per_page;
	for (page_idx = page_idx_start, block_idx = start_blocks;
	     block_idx < block_idx_end; page_idx++,
	     block_idx += blocks_left_page) {
		/* For cases where the filesystems blocksize is not the
		 * same as PAGE_SIZE (e.g. ARM with PAGE_SIZE=64KB and
		 * blocksize=4KB), there will be multiple blocks to
		 * read/write per page. Also, the start and end block may
		 * not be aligned to the start and end of the page, so the
		 * first page may skip some blocks at the start ("i != 0",
		 * "blocks_left_page" is reduced), and the last page may
		 * skip some blocks at the end (limited by "count").
		 */
		page = lnbs[page_idx]->lnb_page;
		LASSERT(page_idx < iobuf->dr_npages);

		i = block_idx % blocks_per_page;
		blocks_left_page = blocks_per_page - i;
		if (block_idx + blocks_left_page > block_idx_end)
			blocks_left_page = block_idx_end - block_idx;
		page_offset = i * blocksize;
		for (i = 0; i < blocks_left_page;
		     i += nblocks, page_offset += blocksize * nblocks) {
			nblocks = 1;

			if (blocks[block_idx + i] == 0) {  /* hole */
				void *addr;
				struct niobuf_local *lnb =
					iobuf->dr_lnbs[page_idx];

				CDEBUG(D_INODE,
				       "hole at page_idx %d, block_idx %d, at offset %llu\n",
				       page_idx, block_idx,
				       lnb->lnb_file_offset);
				lnb->lnb_hole = 1;
				LASSERTF(iobuf->dr_rw == 0,
					 "page_idx %u, block_idx %u, i %u,"
					 "start_blocks: %llu, count: %llu, npages: %d\n",
					 page_idx, block_idx, i,
					 (unsigned long long)start_blocks,
					 (unsigned long long)count, npages);
				addr = kmap_local_page(page);
				memset(addr + page_offset, 0, blocksize);
				kunmap_local(addr);
				continue;
			}

			sector = (sector_t)blocks[block_idx + i] << sector_bits;

			/* Additional contiguous file blocks? */
			while (i + nblocks < blocks_left_page &&
			       (sector + (nblocks << sector_bits)) ==
			       ((sector_t)blocks[block_idx + i + nblocks] <<
				 sector_bits))
				nblocks++;

			if (bio && can_be_merged(bio, sector) &&
			    bio_add_page(bio, page, blocksize * nblocks,
					 page_offset) != 0)
				continue;       /* added this frag OK */

			rc = osd_submit_bio(osd, iobuf, bio);
			if (rc)
				goto out;

			bio_start_page_idx = page_idx;
			/* allocate new bio */
			bio = cfs_bio_alloc(bdev,
					    min_t(unsigned short, BIO_MAX_VECS,
						  (block_idx_end - block_idx +
						   blocks_left_page - 1)),
					    iobuf->dr_rw ? REQ_OP_WRITE
							 : REQ_OP_READ,
					    GFP_NOIO);
			if (!bio) {
				rc = -ENOMEM;
				CERROR("%s: cannot allocate bio %u pages: rc = %d\n",
				       osd->od_svname, block_idx_end -
					  block_idx + blocks_left_page - 1, rc);
				goto out;
			}
			bio->bi_iter.bi_sector = sector;
			rc = osd_bio_init(bio, iobuf, bio_start_page_idx);
			if (rc)
				goto out;

			rc = bio_add_page(bio, page,
					  blocksize * nblocks, page_offset);
			LASSERT(rc != 0);
		}
	}
	rc = osd_submit_bio(osd, iobuf, bio);
	if (rc)
		goto out;
out:
	blk_finish_plug(&plug);

	/* in order to achieve better IO throughput, we don't wait for writes
	 * completion here. instead we proceed with transaction commit in
	 * parallel and wait for IO completion once transaction is stopped
	 * see osd_trans_stop() for more details -bzzz
	 */
	if (iobuf->dr_rw == 0 || CFS_FAIL_CHECK(OBD_FAIL_OST_INTEGRITY_FAULT)) {
		io_wait_event(iobuf->dr_wait,
			      atomic_read(&iobuf->dr_numreqs) == 0);
	}

	if (rc == 0)
		rc = iobuf->dr_error;
	else
		osd_bio_uninit(bio);

	if (iobuf->dr_rw == 0 || CFS_FAIL_CHECK(OBD_FAIL_OST_INTEGRITY_FAULT))
		osd_fini_iobuf(osd, iobuf);

	/* Write only now */
	if (rc == 0 && iobuf->dr_rw)
		osd_mark_page_io_done(iobuf, inode,
				      start_blocks, count);

	RETURN(rc);
}

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 = rnb->rnb_flags; */
		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 struct page *osd_get_page(const struct lu_env *env, struct dt_object *dt,
				 loff_t offset, gfp_t gfp_mask, bool cache)
{
	struct osd_thread_info *oti = osd_oti_get(env);
	struct inode *inode = osd_dt_obj(dt)->oo_inode;
	struct osd_device *d = osd_obj2dev(osd_dt_obj(dt));
	struct folio *folio;
	int cur;

	LASSERT(inode);

	if (cache) {
		folio = get_folio_create(inode->i_mapping, offset >> PAGE_SHIFT,
					 FGP_LOCK | FGP_ACCESSED | FGP_CREAT,
					 gfp_mask);
		if (!IS_ERR_OR_NULL(folio)) {
			LASSERT(!folio_test_private_2(folio));
			folio_wait_writeback(folio);
			return fpgptr(folio);
		} else {
			lprocfs_counter_add(d->od_stats, LPROC_OSD_NO_PAGE, 1);
			return NULL;
		}
	}

	if (inode->i_mapping->nrpages) {
		/* consult with pagecache, but do not create new pages */
		/* this is normally used once */
		folio = get_folio_lock(inode->i_mapping, offset >> PAGE_SHIFT,
					FGP_LOCK, gfp_mask);
		if (!IS_ERR_OR_NULL(folio)) {
			folio_wait_writeback(folio);
			return fpgptr(folio);
		}
	}

	LASSERT(oti->oti_dio_folios);
	cur = oti->oti_dio_pages_used;
	folio = oti->oti_dio_folios[cur];
	if (IS_ERR_OR_NULL(folio)) {
		LASSERT(cur < PTLRPC_MAX_BRW_PAGES);
		folio = folio_alloc(gfp_mask, 0);
		if (IS_ERR_OR_NULL(folio))
			return NULL;
		oti->oti_dio_folios[cur] = folio;
		folio_set_private_2(folio);
		folio_lock(folio);
	}

	folio_clear_uptodate(folio);
	folio->index = offset >> PAGE_SHIFT;
	oti->oti_dio_pages_used++;

	return fpgptr(folio);
}

/*
 * there are following "locks":
 * journal_start
 * i_mutex
 * page lock
 *
 * osd write path:
 *  - lock page(s)
 *  - journal_start
 *  - truncate_sem
 *
 * ext4 vmtruncate:
 *  - lock pages, unlock
 *  - journal_start
 *  - lock partial page
 *  - i_data_sem
 *
 */

/**
 * Unlock and release pages loaded by osd_bufs_get()
 *
 * Unlock \a npages pages from \a lnb and drop the refcount on them.
 *
 * \param env		thread execution environment
 * \param dt		dt object undergoing IO (OSD object + methods)
 * \param lnb		array of pages undergoing IO
 * \param npages	number of pages in \a lnb
 *
 * \retval 0		always
 */
static int osd_bufs_put(const struct lu_env *env, struct dt_object *dt,
			struct niobuf_local *lnb, int npages)
{
	struct osd_device *osd = osd_obj2dev(osd_dt_obj(dt));
	struct osd_thread_info *oti = osd_oti_get(env);
	struct osd_iobuf *iobuf = &oti->oti_iobuf;
	struct folio_batch fbatch;
	int i;

	osd_brw_stats_update(osd, iobuf);
	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 isn't cached, then reset uptodate
		 * to prevent reuse
		 */
		if (PagePrivate2(page)) {
			oti->oti_dio_pages_used--;
			lnb[i].lnb_dio = 0;
		} 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;
	}

	LASSERTF(oti->oti_dio_pages_used == 0, "%d\n", oti->oti_dio_pages_used);

	/* Release any partial folio_batch */
	folio_batch_release(&fbatch);

	RETURN(0);
}

/**
 * Load and lock pages undergoing IO
 *
 * Pages as described in the \a lnb array are fetched (from disk or cache)
 * and locked for IO by the caller.
 *
 * DLM locking protects us from write and truncate competing for same region,
 * but partial-page truncate can leave dirty pages in the cache for ldiskfs.
 * It's possible the writeout on a such a page is in progress when we access
 * it. It's also possible that during this writeout we put new (partial) data
 * into the page, but won't be able to proceed in filter_commitrw_write().
 * Therefore, just wait for writeout completion as it should be rare enough.
 *
 * \param env		thread execution environment
 * \param dt		dt object undergoing IO (OSD object + methods)
 * \param pos		byte offset of IO start
 * \param len		number of bytes of IO
 * \param lnb		array of extents undergoing IO
 * \param maxlnb	max pages could be loaded
 * \param rw		read or write operation, and other flags
 *
 * \retval pages	(zero or more) loaded successfully
 * \retval -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_thread_info *oti = osd_oti_get(env);
	struct osd_object *obj = osd_dt_obj(dt);
	struct osd_device *osd   = osd_obj2dev(obj);
	int npages, i, iosize, rc = 0;
	bool cache, write;
	loff_t fsize;
	gfp_t gfp_mask;

	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);

	write = rw & DT_BUFS_TYPE_WRITE;

	fsize = lnb[npages - 1].lnb_file_offset + lnb[npages - 1].lnb_len;
	iosize = fsize - lnb[0].lnb_file_offset;
	fsize = max(fsize, i_size_read(obj->oo_inode));

	cache = rw & DT_BUFS_TYPE_READAHEAD;
	if (cache)
		goto bypass_checks;

	cache = osd_use_page_cache(osd);
	while (cache) {
		if (write) {
			if (!osd->od_writethrough_cache) {
				cache = false;
				break;
			}
			if (iosize >= osd->od_writethrough_max_iosize) {
				cache = false;
				break;
			}
		} else {
			if (!osd->od_read_cache) {
				cache = false;
				break;
			}
			if (iosize >= osd->od_readcache_max_iosize) {
				cache = false;
				break;
			}
		}
		/* don't use cache on large files */
		if (osd->od_readcache_max_filesize &&
		    fsize > osd->od_readcache_max_filesize)
			cache = false;
		break;
	}

bypass_checks:
	if (!cache && unlikely(!oti->oti_dio_folios)) {
		OBD_ALLOC_PTR_ARRAY_LARGE(oti->oti_dio_folios,
					  PTLRPC_MAX_BRW_PAGES);
		if (!oti->oti_dio_folios)
			return -ENOMEM;
	}

	/* 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++) {
		lnb->lnb_page = osd_get_page(env, dt, lnb->lnb_file_offset,
					     gfp_mask, cache);
		if (lnb->lnb_page == NULL)
			GOTO(cleanup, rc = -ENOMEM);

		lnb->lnb_locked = 1;
		lnb->lnb_dio = !!cache;
		if (cache)
			mark_page_accessed(lnb->lnb_page);
	}

#if 0
	/* XXX: this version doesn't invalidate cached pages, but use them */
	if (!cache && write && obj->oo_inode->i_mapping->nrpages) {
		/* do not allow data aliasing, invalidate pagecache */
		/* XXX: can be quite expensive in mixed case */
		invalidate_mapping_pages(obj->oo_inode->i_mapping,
				lnb[0].lnb_file_offset >> PAGE_SHIFT,
				lnb[npages - 1].lnb_file_offset >> PAGE_SHIFT);
	}
#endif

	RETURN(i);

cleanup:
	if (i > 0)
		osd_bufs_put(env, dt, lnb - i, i);
	return rc;
}

#ifdef HAVE_LDISKFS_JOURNAL_ENSURE_CREDITS
static int osd_extend_trans(handle_t *handle, int needed,
				    struct inode *inode)
{
	int rc;

	rc = __ldiskfs_journal_ensure_credits(handle, needed, 2 * needed,
		ldiskfs_trans_default_revoke_credits(inode->i_sb));

	return rc;
}
#else
static int osd_extend_trans(handle_t *handle, int needed,
				    struct inode *inode)
{
	int rc;

	if (ldiskfs_handle_has_enough_credits(handle, needed))
		return 0;
	rc = ldiskfs_journal_extend(handle,
				needed - handle->h_buffer_credits);
	return rc;
}
#endif /* HAVE_LDISKFS_JOURNAL_ENSURE_CREDITS */

static int osd_ldiskfs_map_write(struct inode *inode, struct osd_iobuf *iobuf,
				 struct osd_device *osd, sector_t start_blocks,
				 sector_t count, loff_t *disk_size,
				 __u64 user_size)
{
	/* if file has grown, take user_size into account */
	if (user_size && *disk_size > user_size)
		*disk_size = user_size;

	spin_lock(&inode->i_lock);
	if (*disk_size > i_size_read(inode)) {
		i_size_write(inode, *disk_size);
		LDISKFS_I(inode)->i_disksize = *disk_size;
		spin_unlock(&inode->i_lock);
		osd_dirty_inode(inode, I_DIRTY_DATASYNC);
	} else {
		spin_unlock(&inode->i_lock);
	}

	/*
	 * We don't do stats here as in read path because
	 * write is async: we'll do this in osd_put_bufs()
	 */
	return osd_do_bio(osd, inode, iobuf, start_blocks, count);
}

static unsigned int osd_extent_bytes(const struct osd_device *o)
{
	unsigned int *extent_bytes_ptr =
			raw_cpu_ptr(o->od_extent_bytes_percpu);

	if (likely(*extent_bytes_ptr))
		return *extent_bytes_ptr;

	/* initialize on first access or CPU hotplug */
	if (!ldiskfs_has_feature_extents(osd_sb(o)))
		*extent_bytes_ptr = 1 << osd_sb(o)->s_blocksize_bits;
	else
		*extent_bytes_ptr = OSD_DEFAULT_EXTENT_BYTES;

	return *extent_bytes_ptr;
}

#define EXTENT_BYTES_DECAY 64
static void osd_decay_extent_bytes(struct osd_device *osd,
				   unsigned int new_bytes)
{
	unsigned int old_bytes;

	if (!ldiskfs_has_feature_extents(osd_sb(osd)))
		return;

	old_bytes = osd_extent_bytes(osd);
	*raw_cpu_ptr(osd->od_extent_bytes_percpu) =
		(old_bytes * (EXTENT_BYTES_DECAY - 1) +
		 min(new_bytes, OSD_DEFAULT_EXTENT_BYTES) +
		 EXTENT_BYTES_DECAY - 1) / EXTENT_BYTES_DECAY;
}

static int osd_ldiskfs_map_inode_pages(struct inode *inode,
				       struct osd_iobuf *iobuf,
				       struct osd_device *osd,
				       const int create,
				       __u64 user_size,
				       int check_credits,
				       struct thandle *thandle)
{
	int blocks_per_page = PAGE_SIZE >> inode->i_blkbits;
	int rc = 0, i = 0, mapped_index = 0;
	struct page *fp = NULL;
	int clen = 0;
	pgoff_t max_page_index;
	handle_t *handle = NULL;
	sector_t start_blocks = 0, count = 0;
	loff_t disk_size = 0;
	struct niobuf_local **lnbs = iobuf->dr_lnbs;
	int pages = iobuf->dr_npages;
	sector_t *blocks = iobuf->dr_blocks;
	struct niobuf_local *lnb1, *lnb2;
	loff_t size1, size2;
	bool compressed = false;
	int flags = 0;

	max_page_index = inode->i_sb->s_maxbytes >> PAGE_SHIFT;

	CDEBUG(D_OTHER, "inode %lu: map %d pages from %lu\n",
		inode->i_ino, pages, folio_index_page((*lnbs)->lnb_page));

	if (osd->od_extents_dense)
		compressed = iobuf->dr_lnbs[0]->lnb_flags & OBD_BRW_COMPRESSED;

	if (create) {
		flags = LDISKFS_GET_BLOCKS_CREATE;
		handle = ldiskfs_journal_current_handle();
		LASSERT(handle != NULL);
		rc = osd_attach_jinode(inode);
		if (rc)
			return rc;
		disk_size = i_size_read(inode);
		/* if disk_size is already bigger than specified user_size,
		 * ignore user_size
		 */
		if (disk_size > user_size)
			user_size = 0;
	}
	/* pages are sorted already. so, we just have to find
	 * contig. space and process them properly
	 */
	while (i < pages) {
		long blen, total = 0, previous_total = 0;
		struct ldiskfs_map_blocks map = { 0 };
		ktime_t time;

		if (fp == NULL) { /* start new extent */
			fp = (*lnbs)->lnb_page;
			lnbs++;
			clen = 1;
			iobuf->dr_lextents++;
			if (++i != pages)
				continue;
		} else if (folio_index_page(fp) + clen ==
			   folio_index_page((*lnbs)->lnb_page)) {
			/* continue the extent */
			lnbs++;
			clen++;
			if (++i != pages)
				continue;
		}
		if (folio_index_page(fp) + clen > max_page_index)
			GOTO(cleanup, rc = -EFBIG);
		/* process found extent */
		map.m_lblk = folio_index_page(fp) * blocks_per_page;
		map.m_len = blen = clen * blocks_per_page;

		/*
		 * Skip already written blocks of the start page.
		 * Note that this branch will not go into for 4K PAGE_SIZE.
		 * Because dr_start_pg_wblks is always 0 for 4K PAGE_SIZE.
		 * iobuf->dr_start_pg_wblks = (start_blocks + count) %
		 * blocks_per_page.
		 */
		if (iobuf->dr_start_pg_wblks > 0) {
			total = previous_total = start_blocks =
				iobuf->dr_start_pg_wblks;
			map.m_lblk = folio_index_page(fp) * blocks_per_page +
				total;
			map.m_len = blen - total;
			iobuf->dr_start_pg_wblks = 0;
		}

cont_map:
		/**
		 * We might restart transaction for block allocations,
		 * in order to make sure data ordered mode, issue IO, disk
		 * size update and block allocations need be within same
		 * transaction to make sure consistency.
		 */
		if (handle && check_credits) {
			struct osd_thandle *oh;

			LASSERT(thandle != NULL);
			oh = container_of(thandle, struct osd_thandle,
					  ot_super);
			/*
			 * only issue IO if restart transaction needed,
			 * as update disk size need hold inode lock, we
			 * want to avoid that as much as possible.
			 */
			if (oh->oh_declared_ext <= 0) {
				rc = osd_ldiskfs_map_write(inode,
					iobuf, osd, start_blocks,
					count, &disk_size, user_size);
				if (rc)
					GOTO(cleanup, rc);
				thandle->th_restart_tran = 1;
				iobuf->dr_start_pg_wblks = (start_blocks +
						count) % blocks_per_page;
				GOTO(cleanup, rc = -EAGAIN);
			}

			if (CFS_FAIL_CHECK(OBD_FAIL_OST_RESTART_IO))
				oh->oh_declared_ext = 0;
			else
				oh->oh_declared_ext--;
		}
#ifdef LDISKFS_GET_BLOCKS_VERY_DENSE
		if (osd->od_extents_dense) {
			if (CFS_FAIL_CHECK(OBD_FAIL_OSD_MARK_COMPRESSED))
				flags |= LDISKFS_GET_BLOCKS_VERY_DENSE;
			if (compressed)
				flags |= LDISKFS_GET_BLOCKS_VERY_DENSE;
		}
#endif

		time = ktime_get();
		rc = ldiskfs_map_blocks(handle, inode, &map, flags);
		time = ktime_sub(ktime_get(), time);

		if (rc >= 0) {
			struct brw_stats *h = &osd->od_brw_stats;
			int idx, c = 0;

			iobuf->dr_pextents++;

			idx = map.m_flags & LDISKFS_MAP_NEW ?
				BRW_ALLOC_TIME : BRW_MAP_TIME;
			lprocfs_oh_tally_log2_pcpu(&h->bs_hist[idx],
						   ktime_to_ms(time));

			for (; total < blen && c < map.m_len; c++, total++) {
				if (rc == 0) {
					*(blocks + total) = 0;
					total++;
					break;
				}
				if ((map.m_flags & LDISKFS_MAP_UNWRITTEN) &&
				    !create) {
					/* don't try to read allocated, but
					 * unwritten blocks, instead fill the
					 * patches with zeros in osd_do_bio() */
					*(blocks + total) = 0;
					continue;
				}
				*(blocks + total) = map.m_pblk + c;
				/* unmap any possible underlying
				 * metadata from the block device
				 * mapping.  b=6998.
				 */
				if ((map.m_flags & LDISKFS_MAP_NEW) &&
				    create)
					clean_bdev_aliases(inode->i_sb->s_bdev,
							   map.m_pblk + c, 1);
			}
			rc = 0;
		}

		if (rc == 0 && create) {
			count += (total - previous_total);
			mapped_index = (start_blocks + count + blocks_per_page -
					1) / blocks_per_page - 1;
			lnb1 = iobuf->dr_lnbs[i - clen];
			lnb2 = iobuf->dr_lnbs[mapped_index];
			size1 = lnb1->lnb_file_offset -
				(lnb1->lnb_file_offset % PAGE_SIZE) +
				(total << inode->i_blkbits);
			size2 = lnb2->lnb_file_offset + lnb2->lnb_len;

			if (size1 > size2)
				size1 = size2;
			if (size1 > disk_size)
				disk_size = size1;
		}

		if (rc == 0 && total < blen) {
			/*
			 * decay extent blocks if we could not
			 * allocate extent once.
			 */
			osd_decay_extent_bytes(osd,
				(total - previous_total) << inode->i_blkbits);
			map.m_lblk = folio_index_page(fp) * blocks_per_page +
				     total;
			map.m_len = blen - total;
			previous_total = total;
			goto cont_map;
		}
		if (rc != 0)
			GOTO(cleanup, rc);
		/*
		 * decay extent blocks if we could allocate
		 * good large extent.
		 */
		if (total - previous_total >=
		    osd_extent_bytes(osd) >> inode->i_blkbits)
			osd_decay_extent_bytes(osd,
				(total - previous_total) << inode->i_blkbits);
		/* look for next extent */
		fp = NULL;
		blocks += blocks_per_page * clen;
	}
cleanup:
	if (rc == 0 && create &&
	    start_blocks < pages * blocks_per_page) {
		rc = osd_ldiskfs_map_write(inode, iobuf, osd, start_blocks,
					   count, &disk_size, user_size);
		LASSERT(start_blocks + count == pages * blocks_per_page);
	}
	return rc;
}

static int osd_write_prep(const struct lu_env *env, struct dt_object *dt,
			  struct niobuf_local *lnb, int npages)
{
	struct osd_thread_info *oti   = osd_oti_get(env);
	struct osd_iobuf       *iobuf = &oti->oti_iobuf;
	struct inode           *inode = osd_dt_obj(dt)->oo_inode;
	struct osd_device      *osd   = osd_obj2dev(osd_dt_obj(dt));
	ktime_t start, end;
	s64 timediff;
	ssize_t isize;
	__s64  maxidx;
	int i, rc = 0;

	LASSERT(inode);

	rc = osd_init_iobuf(osd, iobuf, inode, 0, npages);
	if (unlikely(rc != 0))
		RETURN(rc);

	isize = i_size_read(inode);
	maxidx = ((isize + PAGE_SIZE - 1) >> PAGE_SHIFT) - 1;

	start = ktime_get();
	for (i = 0; i < npages; i++) {

		/*
		 * till commit the content of the page is undefined
		 * we'll 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)) {
			osd_iobuf_add_page(iobuf, &lnb[i]);
		} else {
			long off;
			char *p = kmap_local_page(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_local(p);
		}
	}
	end = ktime_get();
	timediff = ktime_us_delta(end, start);
	lprocfs_counter_add(osd->od_stats, LPROC_OSD_GET_PAGE, timediff);

	if (iobuf->dr_npages) {
		rc = osd_ldiskfs_map_inode_pages(inode, iobuf, osd, 0,
						 0, 0, NULL);
		if (likely(rc == 0)) {
			rc = osd_do_bio(osd, inode, iobuf, 0, 0);
			/* do IO stats for preparation reads */
			osd_fini_iobuf(osd, iobuf);
		}
	}
	RETURN(rc);
}

#ifdef KERNEL_DS
#define DECLARE_MM_SEGMENT_T(name)             mm_segment_t name
#define access_set_kernel(saved_fs, fei)				\
do {									\
	saved_fs = get_fs();						\
	set_fs(KERNEL_DS);						\
} while (0)
#define access_unset_kernel(saved_fs, fei)             set_fs((saved_fs))
#else
#define DECLARE_MM_SEGMENT_T(name)
#define access_set_kernel(saved_fs, fei)				\
	(fei)->fi_flags |= LDISKFS_FIEMAP_FLAG_MEMCPY
#define access_unset_kernel(saved_fs, fei) \
	(fei)->fi_flags &= ~(LDISKFS_FIEMAP_FLAG_MEMCPY)
#endif /* KERNEL_DS */

static int osd_is_mapped(struct dt_object *dt, __u64 offset,
			 struct ldiskfs_map_blocks *map)
{
	struct inode *inode = osd_dt_obj(dt)->oo_inode;
	int mapped;
	sector_t block = osd_i_blocks(inode, offset);
	sector_t end;

	if (i_size_read(inode) == 0)
		return 0;

	/* Beyond EOF, must not be mapped */
	if ((i_size_read(inode) - 1) < offset)
		return 0;

	end = map->m_lblk + map->m_len;
	if (block >= map->m_lblk && block < end)
		return map->m_flags & LDISKFS_MAP_MAPPED;

	map->m_lblk = block;
	map->m_len = INT_MAX;

	mapped = ldiskfs_map_blocks(NULL, inode, map, 0);
	if (mapped < 0) {
		map->m_len = 0;
		return 0;
	}

	return map->m_flags & LDISKFS_MAP_MAPPED;
}

#define MAX_EXTENTS_PER_WRITE 100
static int osd_declare_write_commit(const struct lu_env *env,
				    struct dt_object *dt,
				    struct niobuf_local *lnb, int npages,
				    struct thandle *handle)
{
	const struct osd_device	*osd = osd_obj2dev(osd_dt_obj(dt));
	struct inode		*inode = osd_dt_obj(dt)->oo_inode;
	struct osd_thandle	*oh;
	int			extents = 0, new_meta = 0;
	int			depth, new_blocks = 0;
	int			i;
	int			dirty_groups = 0;
	int			rc = 0;
	int			credits = 0;
	long long		quota_space = 0;
	struct ldiskfs_map_blocks map;
	enum osd_quota_local_flags local_flags = 0;
	enum osd_qid_declare_flags declare_flags = OSD_QID_BLK;
	unsigned int		extent_bytes;
	loff_t extent_start = 0;
	loff_t extent_end = 0;
	ENTRY;

	LASSERT(handle != NULL);
	oh = container_of(handle, struct osd_thandle, ot_super);
	LASSERT(oh->ot_handle == NULL);

	/*
	 * We track a decaying average extent blocks per filesystem,
	 * for most of time, it will be 1M, with filesystem becoming
	 * heavily-fragmented, it will be reduced to 4K at the worst.
	 */
	extent_bytes = osd_extent_bytes(osd);
	LASSERT(extent_bytes >= osd_sb(osd)->s_blocksize);

	/* calculate number of extents (probably better to pass nb) */
	for (i = 0; i < npages; i++) {
		/* ignore quota for the whole request if any page is from
		 * client cache or written by root.
		 *
		 * XXX we could handle this on per-lnb basis as done by
		 * grant.
		 */
		if ((lnb[i].lnb_flags & OBD_BRW_NOQUOTA) ||
		    (lnb[i].lnb_flags & OBD_BRW_SYS_RESOURCE) ||
		    !(lnb[i].lnb_flags & OBD_BRW_SYNC))
			declare_flags |= OSD_QID_FORCE;
		/* ASYNC means that the page comes from the cache - it must be
		 * written anyway.
		 */
		if (lnb[i].lnb_flags & OBD_BRW_ASYNC)
			declare_flags |= OSD_QID_IGNORE_ROOT_PRJ;
		/*
		 * Convert unwritten extent might need split extents, could
		 * not skip it.
		 */
		if (osd_is_mapped(dt, lnb[i].lnb_file_offset, &map) &&
		    !(map.m_flags & LDISKFS_MAP_UNWRITTEN)) {
			lnb[i].lnb_flags |= OBD_BRW_MAPPED;
			continue;
		}

		if (lnb[i].lnb_flags & OBD_BRW_DONE) {
			lnb[i].lnb_flags |= OBD_BRW_MAPPED;
			continue;
		}

		/* count only unmapped changes */
		new_blocks++;
		if (lnb[i].lnb_file_offset != extent_end || extent_end == 0) {
			if (extent_end != 0)
				extents += (extent_end - extent_start +
					    extent_bytes - 1) / extent_bytes;
			extent_start = lnb[i].lnb_file_offset;
			extent_end = lnb[i].lnb_file_offset + lnb[i].lnb_len;
		} else {
			extent_end += lnb[i].lnb_len;
		}

		quota_space += PAGE_SIZE;
	}

	credits++; /* inode */
	/*
	 * overwrite case, no need to modify tree and
	 * allocate blocks.
	 */
	if (!extent_end)
		goto out_declare;

	extents += (extent_end - extent_start +
		    extent_bytes - 1) / extent_bytes;
	/**
	 * with system space usage growing up, mballoc codes won't
	 * try best to scan block group to align best free extent as
	 * we can. So extent bytes per extent could be decayed to a
	 * very small value, this could make us reserve too many credits.
	 * We could be more optimistic in the credit reservations, even
	 * in a case where the filesystem is nearly full, it is extremely
	 * unlikely that the worst case would ever be hit.
	 */
	if (extents > MAX_EXTENTS_PER_WRITE)
		extents = MAX_EXTENTS_PER_WRITE;

	/**
	 * If we add a single extent, then in the worse case, each tree
	 * level index/leaf need to be changed in case of the tree split.
	 * If more extents are inserted, they could cause the whole tree
	 * split more than once, but this is really rare.
	 */
	if (LDISKFS_I(inode)->i_flags & LDISKFS_EXTENTS_FL) {
		/*
		 * many concurrent threads may grow tree by the time
		 * our transaction starts. so, consider 2 is a min depth.
		 */
		depth = ext_depth(inode);
		depth = min(max(depth, 1) + 1, LDISKFS_MAX_EXTENT_DEPTH);
		if (extents <= 1) {
			credits += depth * 2 * extents;
			new_meta = depth;
		} else {
			credits += depth * 3 * extents;
			new_meta = depth * 2 * extents;
		}
	} else {
		/*
		 * With N contiguous data blocks, we need at most
		 * N/EXT4_ADDR_PER_BLOCK(inode->i_sb) + 1 indirect blocks,
		 * 2 dindirect blocks, and 1 tindirect block
		 */
		new_meta = DIV_ROUND_UP(new_blocks,
				LDISKFS_ADDR_PER_BLOCK(inode->i_sb)) + 4;
		credits += new_meta;
	}
	dirty_groups += (extents + new_meta);

	oh->oh_declared_ext = extents;

	/* quota space for metadata blocks */
	quota_space += new_meta * LDISKFS_BLOCK_SIZE(osd_sb(osd));

	/* quota space should be reported in 1K blocks */
	quota_space = stoqb(quota_space);

	/* each new block can go in different group (bitmap + gd) */

	/* we can't dirty more bitmap blocks than exist */
	if (dirty_groups > LDISKFS_SB(osd_sb(osd))->s_groups_count)
		credits += LDISKFS_SB(osd_sb(osd))->s_groups_count;
	else
		credits += dirty_groups;

	/* we can't dirty more gd blocks than exist */
	if (dirty_groups > LDISKFS_SB(osd_sb(osd))->s_gdb_count)
		credits += LDISKFS_SB(osd_sb(osd))->s_gdb_count;
	else
		credits += dirty_groups;

	CDEBUG(D_INODE,
	       "%s: inode #%lu extent_bytes %u extents %d credits %d\n",
	       osd_ino2name(inode), inode->i_ino, extent_bytes, extents,
	       credits);

out_declare:
	osd_trans_declare_op(env, oh, OSD_OT_WRITE, credits);

	/* make sure the over quota flags were not set */
	lnb[0].lnb_flags &= ~OBD_BRW_OVER_ALLQUOTA;

	rc = osd_declare_inode_qid(env, i_uid_read(inode), i_gid_read(inode),
				   i_projid_read(inode), quota_space, oh,
				   osd_dt_obj(dt), &local_flags, declare_flags);

	/* we need only to store the overquota flags in the first lnb for
	 * now, once we support multiple objects BRW, this code needs be
	 * revised.
	 */
	if (local_flags & QUOTA_FL_OVER_USRQUOTA)
		lnb[0].lnb_flags |= OBD_BRW_OVER_USRQUOTA;
	if (local_flags & QUOTA_FL_OVER_GRPQUOTA)
		lnb[0].lnb_flags |= OBD_BRW_OVER_GRPQUOTA;
	if (local_flags & QUOTA_FL_OVER_PRJQUOTA)
		lnb[0].lnb_flags |= OBD_BRW_OVER_PRJQUOTA;
	if (local_flags & QUOTA_FL_ROOT_PRJQUOTA)
		lnb[0].lnb_flags |= OBD_BRW_ROOT_PRJQUOTA;

	if (rc == 0)
		rc = osd_trunc_lock(osd_dt_obj(dt), oh, true);

	RETURN(rc);
}

/* Check if a block is allocated or not */
static int osd_write_commit(const struct lu_env *env, struct dt_object *dt,
			    struct niobuf_local *lnb, int npages,
			    struct thandle *thandle, __u64 user_size)
{
	struct osd_thread_info *oti = osd_oti_get(env);
	struct osd_iobuf *iobuf = &oti->oti_iobuf;
	struct inode *inode = osd_dt_obj(dt)->oo_inode;
	struct osd_device  *osd = osd_obj2dev(osd_dt_obj(dt));
	int rc = 0, i, check_credits = 0;

	LASSERT(inode);

	rc = osd_init_iobuf(osd, iobuf, inode, 1, npages);
	if (unlikely(rc != 0))
		RETURN(rc);

	dquot_initialize(inode);

	for (i = 0; i < npages; i++) {
		if (lnb[i].lnb_rc == -ENOSPC &&
		    (lnb[i].lnb_flags & OBD_BRW_MAPPED)) {
			/* Allow the write to proceed if overwriting an
			 * existing block
			 */
			lnb[i].lnb_rc = 0;
		}

		if (lnb[i].lnb_rc) { /* ENOSPC, network RPC error, etc. */
			CDEBUG(D_INODE, "Skipping [%d] == %d\n", i,
			       lnb[i].lnb_rc);
			LASSERT(lnb[i].lnb_page);
			generic_error_remove_folio(inode->i_mapping,
						   page_folio(lnb[i].lnb_page));
			continue;
		}

		if (lnb[i].lnb_flags & OBD_BRW_DONE)
			continue;

		if (!(lnb[i].lnb_flags & OBD_BRW_MAPPED))
			check_credits = 1;

		LASSERT(PageLocked(lnb[i].lnb_page));
		LASSERT(!PageWriteback(lnb[i].lnb_page));

		/*
		 * Since write and truncate are serialized by dd_sem, even
		 * partial-page truncate should not leave dirty pages in the
		 * page cache.
		 */
		LASSERT(!PageDirty(lnb[i].lnb_page));

		SetPageUptodate(lnb[i].lnb_page);

		osd_iobuf_add_page(iobuf, &lnb[i]);
	}

	osd_trans_exec_op(env, thandle, OSD_OT_WRITE);

	if (CFS_FAIL_CHECK(OBD_FAIL_OST_MAPBLK_ENOSPC)) {
		rc = -ENOSPC;
	} else if (iobuf->dr_npages > 0) {
		rc = osd_ldiskfs_map_inode_pages(inode, iobuf, osd,
						 1, user_size,
						 check_credits,
						 thandle);
	} else {
		/* no pages to write, no transno is needed */
		thandle->th_local = 1;
	}

	if (rc != 0 && !thandle->th_restart_tran)
		osd_fini_iobuf(osd, iobuf);

	osd_trans_exec_check(env, thandle, OSD_OT_WRITE);

	if (unlikely(rc != 0 && !thandle->th_restart_tran)) {
		/* if write fails, we should drop pages from the cache */
		for (i = 0; i < npages; i++) {
			if (lnb[i].lnb_page == NULL)
				continue;
			if (!PagePrivate2(lnb[i].lnb_page)) {
				LASSERT(PageLocked(lnb[i].lnb_page));
				generic_error_remove_folio(inode->i_mapping,
						page_folio(lnb[i].lnb_page));
			}
		}
	}

	RETURN(rc);
}

static int osd_read_prep(const struct lu_env *env, struct dt_object *dt,
			 struct niobuf_local *lnb, int npages)
{
	struct osd_thread_info *oti = osd_oti_get(env);
	struct osd_iobuf *iobuf = &oti->oti_iobuf;
	struct inode *inode = osd_dt_obj(dt)->oo_inode;
	struct osd_device *osd = osd_obj2dev(osd_dt_obj(dt));
	int rc = 0, i, cache_hits = 0, cache_misses = 0;
	ktime_t start, end;
	s64 timediff;
	loff_t isize;

	LASSERT(inode);

	rc = osd_init_iobuf(osd, iobuf, inode, 0, npages);
	if (unlikely(rc != 0))
		RETURN(rc);

	isize = i_size_read(inode);

	start = ktime_get();
	for (i = 0; i < npages; i++) {

		if (isize <= lnb[i].lnb_file_offset)
			/* If there's no more data, abort early.
			 * lnb->lnb_rc == 0, so it's easy to detect later.
			 */
			break;

		/* instead of looking if we go beyong isize, send complete
		 * pages all the time
		 */
		lnb[i].lnb_rc = lnb[i].lnb_len;

		/* Bypass disk read if fail_loc is set properly */
		if (CFS_FAIL_CHECK_QUIET(OBD_FAIL_OST_FAKE_RW))
			SetPageUptodate(lnb[i].lnb_page);

		if (PageUptodate(lnb[i].lnb_page)) {
			cache_hits++;
			unlock_page(lnb[i].lnb_page);
		} else {
			cache_misses++;
			osd_iobuf_add_page(iobuf, &lnb[i]);
		}
		/* no need to unlock in osd_bufs_put(), the sooner page is
		 * unlocked, the earlier another client can access it.
		 * notice real unlock_page() can be called few lines
		 * below after osd_do_bio(). lnb is a per-thread, so it's
		 * fine to have PG_locked and lnb_locked inconsistent here
		 */
		lnb[i].lnb_locked = 0;
	}
	end = ktime_get();
	timediff = ktime_us_delta(end, start);
	lprocfs_counter_add(osd->od_stats, LPROC_OSD_GET_PAGE, timediff);

	if (cache_hits != 0)
		lprocfs_counter_add(osd->od_stats, LPROC_OSD_CACHE_HIT,
				    cache_hits);
	if (cache_misses != 0)
		lprocfs_counter_add(osd->od_stats, LPROC_OSD_CACHE_MISS,
				    cache_misses);
	if (cache_hits + cache_misses != 0)
		lprocfs_counter_add(osd->od_stats, LPROC_OSD_CACHE_ACCESS,
				    cache_hits + cache_misses);

	if (iobuf->dr_npages) {
		rc = osd_ldiskfs_map_inode_pages(inode, iobuf, osd, 0,
						 0, 0, NULL);
		if (!rc)
			rc = osd_do_bio(osd, inode, iobuf, 0, 0);

		/* IO stats will be done in osd_bufs_put() */

		/* early release to let others read data during the bulk */
		for (i = 0; i < iobuf->dr_npages; i++) {
			struct page *page = iobuf->dr_lnbs[i]->lnb_page;
			LASSERT(PageLocked(page));
			if (!PagePrivate2(page))
				unlock_page(page);
		}
	}

	RETURN(rc);
}

/*
 * XXX: Another layering violation for now.
 *
 * We don't want to use ->f_op->read methods, because generic file write
 *
 *         - serializes on ->i_sem, and
 *
 *         - does a lot of extra work like balance_dirty_pages(),
 *
 * which doesn't work for globally shared files like /last_rcvd.
 */
static int osd_ldiskfs_readlink(struct inode *inode, char *buffer,
				size_t buflen)
{
	struct ldiskfs_inode_info *ei = LDISKFS_I(inode);

	memcpy(buffer, (char *)ei->i_data, buflen);

	return  buflen;
}

int osd_ldiskfs_read(struct inode *inode, void *buf, int size, loff_t *offs)
{
	struct buffer_head *bh;
	unsigned long block;
	int osize;
	int blocksize;
	int csize;
	int boffs;

	/* prevent reading after eof */
	spin_lock(&inode->i_lock);
	if (i_size_read(inode) < *offs + size) {
		loff_t diff = i_size_read(inode) - *offs;

		spin_unlock(&inode->i_lock);
		if (diff < 0) {
			CDEBUG(D_OTHER,
			       "size %llu is too short to read @%llu\n",
			       i_size_read(inode), *offs);
			return -EBADR;
		} else if (diff == 0) {
			return 0;
		} else {
			size = diff;
		}
	} else {
		spin_unlock(&inode->i_lock);
	}

	blocksize = 1 << inode->i_blkbits;
	osize = size;
	while (size > 0) {
		block = *offs >> inode->i_blkbits;
		boffs = *offs & (blocksize - 1);
		csize = min(blocksize - boffs, size);
		bh = __ldiskfs_bread(NULL, inode, block, 0);
		if (IS_ERR(bh)) {
			CERROR("%s: can't read %u@%llu on ino %lu: rc = %ld\n",
			       osd_ino2name(inode), csize, *offs, inode->i_ino,
			       PTR_ERR(bh));
			return PTR_ERR(bh);
		}

		if (bh != NULL) {
			memcpy(buf, bh->b_data + boffs, csize);
			brelse(bh);
		} else {
			memset(buf, 0, csize);
		}

		*offs += csize;
		buf += csize;
		size -= csize;
	}
	return osize;
}

static ssize_t osd_read(const struct lu_env *env, struct dt_object *dt,
			struct lu_buf *buf, loff_t *pos)
{
	struct inode *inode = osd_dt_obj(dt)->oo_inode;
	int rc;

	/* Read small symlink from inode body as we need to maintain correct
	 * on-disk symlinks for ldiskfs.
	 */
	if (S_ISLNK(dt->do_lu.lo_header->loh_attr)) {
		loff_t size = i_size_read(inode);

		if (buf->lb_len < size)
			return -EOVERFLOW;

		if (size < sizeof(LDISKFS_I(inode)->i_data))
			rc = osd_ldiskfs_readlink(inode, buf->lb_buf, size);
		else
			rc = osd_ldiskfs_read(inode, buf->lb_buf, size, pos);
	} else {
		rc = osd_ldiskfs_read(inode, buf->lb_buf, buf->lb_len, pos);
	}

	return rc;
}

static inline int osd_extents_enabled(struct super_block *sb,
				      struct inode *inode)
{
	if (inode != NULL) {
		if (LDISKFS_I(inode)->i_flags & LDISKFS_EXTENTS_FL)
			return 1;
	} else if (ldiskfs_has_feature_extents(sb)) {
		return 1;
	}
	return 0;
}

int osd_calc_bkmap_credits(struct super_block *sb, struct inode *inode,
			   const loff_t size, const loff_t pos,
			   const int blocks)
{
	int credits, bits, bs, i;

	bits = sb->s_blocksize_bits;
	bs = 1 << bits;

	/* legacy blockmap: 3 levels * 3 (bitmap,gd,itself)
	 * we do not expect blockmaps on the large files,
	 * so let's shrink it to 2 levels (4GB files)
	 */

	/* this is default reservation: 2 levels */
	credits = (blocks + 2) * 3;

	/* actual offset is unknown, hard to optimize */
	if (pos == -1)
		return credits;

	/* now check for few specific cases to optimize */
	if (pos + size <= LDISKFS_NDIR_BLOCKS * bs) {
		/* no indirects */
		credits = blocks;
		/* allocate if not allocated */
		if (inode == NULL) {
			credits += blocks * 2;
			return credits;
		}
		for (i = (pos >> bits); i < (pos >> bits) + blocks; i++) {
			LASSERT(i < LDISKFS_NDIR_BLOCKS);
			if (LDISKFS_I(inode)->i_data[i] == 0)
				credits += 2;
		}
	} else if (pos + size <= (LDISKFS_NDIR_BLOCKS + 1024) * bs) {
		/* single indirect */
		credits = blocks * 3;
		if (inode == NULL ||
		    LDISKFS_I(inode)->i_data[LDISKFS_IND_BLOCK] == 0)
			credits += 3;
		else
			/* The indirect block may be modified. */
			credits += 1;
	}

	return credits;
}

static struct osd_block_ready_map *osd_brm_init(struct osd_object *o)
{
	struct ldiskfs_inode_info *ei = LDISKFS_I(o->oo_inode);
	struct osd_block_ready_map *brm = o->oo_brm;
	int i;

	if (brm)
		return brm;

	down(&ei->i_append_sem);

	if (o->oo_brm)
		GOTO(out, brm = o->oo_brm);
	OBD_ALLOC_PTR_ARRAY(brm, OSD_BRM_MAX);
	if (brm == NULL)
		GOTO(out, brm);
	o->oo_brm = brm;
	for (i = 0; i < OSD_BRM_MAX; i++) {
		brm[i].start = 1UL << 31;
		brm[i].end = 1UL << 31;
	}

out:
	up(&ei->i_append_sem);
	return brm;
}

static inline bool osd_brm_lookup(struct osd_block_ready_map *brm,
				  unsigned long block)
{
	int i;

	for (i = 0; i < OSD_BRM_MAX; i++) {
		if (block >= brm[i].start && block <= brm[i].end) {
			brm[i].time = jiffies;
			return true;
		}
	}
	return false;
}

static ssize_t osd_declare_write(const struct lu_env *env, struct dt_object *dt,
				 const struct lu_buf *buf, loff_t _pos,
				 struct thandle *handle)
{
	int rc = 0, bits, bs, depth, size, credits, blocks;
	struct osd_object *obj  = osd_dt_obj(dt);
	struct super_block *sb = osd_sb(osd_obj2dev(obj));
	struct inode *inode = obj->oo_inode;
	struct osd_thandle *oh;
	loff_t pos;
	ENTRY;

	LASSERT(buf != NULL);
	LASSERT(handle != NULL);

	oh = container_of(handle, struct osd_thandle, ot_super);
	LASSERT(oh->ot_handle == NULL);

	size = buf->lb_len;
	bits = sb->s_blocksize_bits;
	bs = 1 << bits;

	if (osd_tx_was_declared(env, oh, dt, DTO_WRITE_BASE, _pos))
		RETURN(0);

	if (_pos == -1) {
		/* if this is an append, then we
		 * should expect cross-block record
		 */
		pos = 0;
		/*
		 * likely this is a llog file, use multiblock alloc
		 * to improve concurrent writes.
		 * XXX: locking?
		 */
		if (!obj->oo_prealloc_writes && osd_extents_enabled(sb, inode))
			obj->oo_prealloc_writes = 1;
	} else {
		pos = _pos;
	}

	/* blocks to modify */
	blocks = ((pos + size + bs - 1) >> bits) - (pos >> bits);
	LASSERT(blocks > 0);

	if (obj->oo_brm && _pos != -1) {
		unsigned long i;
		credits = 0;
		for (i = (pos >> bits); i < (pos >> bits) + blocks; i++) {
			/* check if block has been allocated already, then
			 * just count it. but if we meet blocks we're not
			 * sure about - go and calculate the worst case */
			if (!osd_brm_lookup(obj->oo_brm, i))
				goto calculate;
			credits++;
		}
		goto out;
	}

calculate:
	credits = 0;
	if (osd_extents_enabled(sb, inode)) {
		/*
		 * many concurrent threads may grow tree by the time
		 * our transaction starts. so, consider 2 is a min depth
		 * for every level we may need to allocate a new block
		 * and take some entries from the old one. so, 3 blocks
		 * to allocate (bitmap, gd, itself) + old block - 4 per
		 * level.
		 */
		depth = inode != NULL ? ext_depth(inode) : 0;
		depth = min(max(depth, 1) + 1, LDISKFS_MAX_EXTENT_DEPTH);
		/* may need to allocate few extent tree index blocks:
		 * gdb+bitmap+block itself except inode level */
		credits = (depth - 1) * 3;
		/* if not append, then split may need to modify
		 * existing blocks moving entries into the new ones
		 */
		if (_pos != -1)
			credits += depth;
		/* blocks to store data: bitmap,gd,itself */
		credits += blocks * 3;
		if (obj->oo_prealloc_writes) {
			/* in case of preallocation we do a single allocation
			 * always, so 1 * 3 (gd, bitmap, block). this is taken
			 * into account a line above, then in the allocation
			 * we could get upto OSD_BRM_ALLOC_SIZE blocks, so add
			 * credits for these blocks as well, plus we may need
			 * to put the inode onto superblock's orphan list */
			credits += OSD_BRM_ALLOC_SIZE;
		}
	} else {
		credits = osd_calc_bkmap_credits(sb, inode, size, _pos, blocks);
	}
	/* if inode is created as part of the transaction,
	 * then it's counted already by the creation method
	 */
	if (inode != NULL)
		credits++;

out:
	osd_trans_declare_op(env, oh, OSD_OT_WRITE, credits);

	/* dt_declare_write() is usually called for system objects, such
	 * as llog or last_rcvd files. We needn't enforce quota on those
	 * objects, so always set the lqi_space as 0.
	 */
	if (inode != NULL)
		rc = osd_declare_inode_qid(env, i_uid_read(inode),
					   i_gid_read(inode),
					   i_projid_read(inode), 0,
					   oh, obj, NULL, OSD_QID_BLK);

	if (rc == 0)
		rc = osd_trunc_lock(obj, oh, true);

	RETURN(rc);
}

static int osd_ldiskfs_writelink(struct inode *inode, char *buffer, int buflen)
{
	/* LU-2634: clear the extent format for fast symlink */
	ldiskfs_clear_inode_flag(inode, LDISKFS_INODE_EXTENTS);

	/* Copying the NUL byte terminating the link target as well */
	memcpy((char *)&LDISKFS_I(inode)->i_data, (char *)buffer, buflen + 1);
	spin_lock(&inode->i_lock);
	LDISKFS_I(inode)->i_disksize = buflen;
	i_size_write(inode, buflen);
	spin_unlock(&inode->i_lock);
	osd_dirty_inode(inode, I_DIRTY_DATASYNC);

	return 0;
}

static struct buffer_head *osd_getnblk(handle_t *handle, struct osd_object *o,
				       ldiskfs_lblk_t block, int nr)
{
	struct osd_block_ready_map *brm = o->oo_brm;
	struct buffer_head *bh, *ret = NULL;
	struct inode *inode = o->oo_inode;
	struct ldiskfs_map_blocks map;
	unsigned long start, end, old;
	ldiskfs_fsblk_t pblk;
	int lru, i, err;

	/* first of all, we map/allocate few blocks */
	map.m_lblk = block;
	map.m_len = nr;
	err = ldiskfs_map_blocks(handle, inode, &map,
				 LDISKFS_GET_BLOCKS_CREATE);
	if (err < 0)
		return ERR_PTR(err);
	LASSERT(map.m_lblk == block);

	/* XXX: save bh's in obj so many subsequent getblk() can be saved */

	/* initialize bh's */
	if (map.m_flags & LDISKFS_MAP_NEW) {
		/*
		 * new blocks should be filled with zeros under the semaphore
		 * and before brm refill, so any concurrent access can't find
		 * them allocated but not-initialized yet.
		 */
		if (!o->oo_on_orphan_list) {
			inode_lock(inode);
			ldiskfs_orphan_add(handle, inode);
			inode_unlock(inode);
			o->oo_on_orphan_list = 1;
		}
		for (pblk = map.m_pblk; pblk < map.m_pblk + map.m_len; pblk++) {
			bh = sb_getblk(inode->i_sb, pblk);
			if (unlikely(!bh))
				return ERR_PTR(-ENOMEM);
			if (ret == NULL) {
				get_bh(bh);
				ret = bh;
			}

			lock_buffer(bh);
			err = osd_ldiskfs_journal_get_create_access(handle,
								    inode->i_sb,
								    bh);
			if (err) {
				unlock_buffer(bh);
				brelse(bh);
				return ERR_PTR(err);
			}
			/*
			 * always reset data, the block can
			 * be uptodate but reallocated.
			 */
			memset(bh->b_data, 0, inode->i_sb->s_blocksize);
			set_buffer_uptodate(bh);
			unlock_buffer(bh);
			err = ldiskfs_handle_dirty_metadata(handle, inode, bh);
			brelse(bh);
			if (err)
				return ERR_PTR(err);

		}
	} else {
		/*
		 * the block have been already allocated and
		 * initialized, just read from the disk if needed.
		 */
		ret = sb_bread(inode->i_sb, map.m_pblk);
		if (unlikely(!ret))
			ret = ERR_PTR(-ENOMEM);
		if (!buffer_uptodate(ret)) {
			brelse(ret);
			ret = ERR_PTR(-EIO);
		}
	}

	/* fill brm so next lookups for the block don't need the semaphore */
	start = block;
	end = block + map.m_len - 1;
	old = jiffies + 1;
	lru = -1;

	for (i = 0; i < OSD_BRM_MAX; i++) {
		/* find the least used slot */
		if (brm[i].time < old) {
			old = brm[i].time;
			lru = i;
		}
		if (start >= brm[i].start && start <= brm[i].end + 1 &&
		    end > brm[i].end) {
			brm[i].end = end;
			brm[i].time = jiffies;
			goto out;
		}
		if (end >= brm[i].start - 1 && end <= brm[i].end &&
		    start < brm[i].start) {
			brm[i].start = start;
			brm[i].time = jiffies;
			goto out;
		}
	}

	/* reuse the least used slot */
	LASSERT(lru >= 0);
	brm[lru].start = start;
	brm[lru].end = end;
	brm[lru].time = jiffies;

out:
	return ret;
}

static struct buffer_head *osd_brm_getblk(handle_t *handle,
					  struct osd_object *o, int block)
{
	struct inode *inode = o->oo_inode;
	struct buffer_head *bh;

	if (osd_brm_lookup(o->oo_brm, block)) {
		/* supposed to be allocated and initialized */
		bh = __ldiskfs_bread(handle, inode, block, 0);
		return bh;
	}

	down(&LDISKFS_I(o->oo_inode)->i_append_sem);
	if (osd_brm_lookup(o->oo_brm, block)) {
		up(&LDISKFS_I(o->oo_inode)->i_append_sem);
		bh = __ldiskfs_bread(handle, inode, block, 0);
	} else {
		bh = osd_getnblk(handle, o, block, OSD_BRM_ALLOC_SIZE);
		up(&LDISKFS_I(o->oo_inode)->i_append_sem);
	}

	return bh;
}

static int osd_ldiskfs_write_fast(struct osd_object *o,  void *buf, int bufsize,
				  loff_t *offs, handle_t *handle)
{
	struct inode *inode = o->oo_inode;
	int blocksize = 1 << inode->i_blkbits;
	loff_t new_size  = i_size_read(inode);
	struct ldiskfs_inode_info *ei = LDISKFS_I(inode);
	struct osd_block_ready_map *brm;
	struct buffer_head *bh;
	loff_t offset = *offs;
	int rc, dirty_inode;

	/* only the first flag-set matters */
	dirty_inode = !test_and_set_bit(LDISKFS_INODE_JOURNAL_DATA,
					&ei->i_flags);

	rc = osd_attach_jinode(inode);
	if (rc)
		return rc;

	brm = osd_brm_init(o);
	if (unlikely(!brm))
		return -ENOMEM;

	while (bufsize > 0) {
		unsigned long block;
		int size, boffs;

		block = offset >> inode->i_blkbits;
		boffs = offset & (blocksize - 1);
		size = min(blocksize - boffs, bufsize);

		bh = osd_brm_getblk(handle, o, block);
		if (IS_ERR_OR_NULL(bh)) {
			rc = -EIO;
			break;
		}

		rc = osd_ldiskfs_journal_get_write_access(handle, inode->i_sb,
							   bh,
							   LDISKFS_JTR_NONE);
		if (rc) {
			CERROR("journal_get_write_access() error %d\n", rc);
			break;
		}
		LASSERTF(boffs + size <= bh->b_size,
			 "boffs %d size %d bh->b_size %lu\n",
			 boffs, size, (unsigned long)bh->b_size);
		memcpy(bh->b_data + boffs, buf, size);
		rc = ldiskfs_handle_dirty_metadata(handle, NULL, bh);
		if (rc)
			break;

		if (offset + size > new_size)
			new_size = offset + size;
		offset += size;
		bufsize -= size;
		buf += size;

		brelse(bh);
	}

	/* correct in-core and on-disk sizes */
	if (new_size > i_size_read(inode)) {
		spin_lock(&inode->i_lock);
		if (new_size > i_size_read(inode))
			i_size_write(inode, new_size);
		if (i_size_read(inode) > ei->i_disksize) {
			ei->i_disksize = i_size_read(inode);
			dirty_inode = 1;
		}
		spin_unlock(&inode->i_lock);
	}
	if (dirty_inode)
		osd_dirty_inode(inode, I_DIRTY_DATASYNC);

	if (rc == 0)
		*offs = offset;
	return rc;
}

int osd_ldiskfs_write(struct osd_device *osd, struct inode *inode, void *buf,
		      int bufsize, int write_NUL, loff_t *offs,
		      handle_t *handle)
{
	struct buffer_head *bh = NULL;
	loff_t offset = *offs;
	loff_t new_size = i_size_read(inode);
	unsigned long block;
	int blocksize = 1 << inode->i_blkbits;
	struct ldiskfs_inode_info *ei = LDISKFS_I(inode);
	bool create, sparse, sync = false;
	int size;
	int boffs;
	int dirty_inode = 0;
	int err = 0;

	if (write_NUL) {
		/*
		 * long symlink write does not count the NUL terminator in
		 * bufsize, we write it, and the inode's file size does not
		 * count the NUL terminator as well.
		 */
		((char *)buf)[bufsize] = '\0';
		++bufsize;
	}

	/* only the first flag-set matters */
	dirty_inode = !test_and_set_bit(LDISKFS_INODE_JOURNAL_DATA,
				       &ei->i_flags);

	/* sparse checking is racy, but sparse is very rare case, leave as is */
	sparse = (new_size > 0 && (inode->i_blocks >> (inode->i_blkbits - 9)) <
		  ((new_size - 1) >> inode->i_blkbits) + 1);

	while (bufsize > 0) {
		int credits = handle->h_buffer_credits;
		unsigned long last_block = (new_size == 0) ? 0 :
					   (new_size - 1) >> inode->i_blkbits;

		if (bh)
			brelse(bh);

		block = offset >> inode->i_blkbits;
		boffs = offset & (blocksize - 1);
		size = min(blocksize - boffs, bufsize);
		sync = (block > last_block || new_size == 0 || sparse);

		if (sync)
			down(&ei->i_append_sem);

		bh = __ldiskfs_bread(handle, inode, block, 0);

		if (unlikely(IS_ERR_OR_NULL(bh) && !sync)) {
			/* the block is not allocated yet, so we need
			 * to serialize allocation and memset(0) */
			down(&ei->i_append_sem);
			sync = true;
		}

		if (IS_ERR_OR_NULL(bh)) {
			int flags = LDISKFS_GET_BLOCKS_CREATE;

			/* while the file system is being mounted, avoid
			 * preallocation otherwise mount can take a long
			 * time as mballoc cache is cold.
			 * XXX: this is a workaround until we have a proper
			 *	fix in mballoc
			 * XXX: works with extent-based files only */
			if (!osd->od_dt_dev.dd_cl_seq)
				flags |= LDISKFS_GET_BLOCKS_NO_NORMALIZE;
			bh = __ldiskfs_bread(handle, inode, block, flags);
			create = true;
		} else {
			if (sync) {
				up(&ei->i_append_sem);
				sync = false;
			}
			create = false;
		}
		if (IS_ERR_OR_NULL(bh)) {
			if (bh == NULL) {
				err = -EIO;
			} else {
				err = PTR_ERR(bh);
				bh = NULL;
			}

			CERROR("%s: error reading offset %llu (block %lu, size %d, offs %llu), credits %d/%d: rc = %d\n",
			       osd_ino2name(inode), offset, block, bufsize,
			       *offs, credits, handle->h_buffer_credits, err);
			break;
		}

		err = osd_ldiskfs_journal_get_write_access(handle, inode->i_sb,
							   bh,
							   LDISKFS_JTR_NONE);
		if (err) {
			CERROR("%s: journal_get_write_access() error: rc = %d\n",
			       osd_ino2name(inode), err);
			break;
		}
		LASSERTF(boffs + size <= bh->b_size,
			 "boffs %d size %d bh->b_size %lu\n",
			 boffs, size, (unsigned long)bh->b_size);
		if (create)
			memset(bh->b_data, 0, bh->b_size);
		if (sync) {
			up(&ei->i_append_sem);
			sync = false;
		}
		memcpy(bh->b_data + boffs, buf, size);
		err = ldiskfs_handle_dirty_metadata(handle, NULL, bh);
		if (err)
			break;

		if (offset + size > new_size)
			new_size = offset + size;
		offset += size;
		bufsize -= size;
		buf += size;
	}
	if (sync)
		up(&ei->i_append_sem);

	if (bh)
		brelse(bh);

	if (write_NUL)
		--new_size;
	/* correct in-core and on-disk sizes */
	if (new_size > i_size_read(inode)) {
		spin_lock(&inode->i_lock);
		if (new_size > i_size_read(inode))
			i_size_write(inode, new_size);
		if (i_size_read(inode) > ei->i_disksize) {
			ei->i_disksize = i_size_read(inode);
			dirty_inode = 1;
		}
		spin_unlock(&inode->i_lock);
	}
	if (dirty_inode)
		osd_dirty_inode(inode, I_DIRTY_DATASYNC);

	if (err == 0)
		*offs = offset;
	return err;
}

static ssize_t osd_write(const struct lu_env *env, struct dt_object *dt,
			 const struct lu_buf *buf, loff_t *pos,
			 struct thandle *handle)
{
	struct osd_object	*obj = osd_dt_obj(dt);
	struct inode		*inode = obj->oo_inode;
	struct osd_thandle	*oh;
	ssize_t			result;
	int			is_link;

	LASSERT(dt_object_exists(dt));

	LASSERT(handle != NULL);
	LASSERT(inode != NULL);
	dquot_initialize(inode);

	/* XXX: don't check: one declared chunk can be used many times */
	/* osd_trans_exec_op(env, handle, OSD_OT_WRITE); */

	oh = container_of(handle, struct osd_thandle, ot_super);
	LASSERT(oh->ot_handle->h_transaction != NULL);
	osd_trans_exec_op(env, handle, OSD_OT_WRITE);

	/* Write small symlink to inode body as we need to maintain correct
	 * on-disk symlinks for ldiskfs.
	 * Note: the buf->lb_buf contains a NUL terminator while buf->lb_len
	 * does not count it in.
	 */
	is_link = S_ISLNK(dt->do_lu.lo_header->loh_attr);
	if (is_link && (buf->lb_len < sizeof(LDISKFS_I(inode)->i_data)))
		result = osd_ldiskfs_writelink(inode, buf->lb_buf, buf->lb_len);
	else if (obj->oo_prealloc_writes)
		result = osd_ldiskfs_write_fast(obj, buf->lb_buf, buf->lb_len,
						pos, oh->ot_handle);
	else
		result = osd_ldiskfs_write(osd_obj2dev(obj), inode, buf->lb_buf,
					   buf->lb_len, is_link, pos,
					   oh->ot_handle);
	if (result == 0)
		result = buf->lb_len;

	osd_trans_exec_check(env, handle, OSD_OT_WRITE);

	return result;
}

static int osd_declare_fallocate(const struct lu_env *env,
				 struct dt_object *dt, struct lu_attr *attr,
				 __u64 start, __u64 end, int mode,
				 struct thandle *th,
				 enum dt_fallocate_error_t *error_code)
{
	struct osd_thandle *oh = container_of(th, struct osd_thandle, ot_super);
	struct osd_device *osd = osd_obj2dev(osd_dt_obj(dt));
	struct inode *inode = osd_dt_obj(dt)->oo_inode;
	long long quota_space = 0;
	qid_t uid, gid, projid;
	/* 5 is max tree depth. (inode + 4 index blocks) */
	int depth = 5;
	int rc;

	ENTRY;

	/*
	 * mode == 0 (which is standard prealloc) and PUNCH/ZERO are supported
	 * Rest of mode options is not supported yet.
	 */
	if (mode & ~(FALLOC_FL_KEEP_SIZE | FALLOC_FL_PUNCH_HOLE |
		     FALLOC_FL_ZERO_RANGE))
		RETURN(-EOPNOTSUPP);

	/* disable fallocate completely */
	if (osd_dev(dt->do_lu.lo_dev)->od_fallocate_zero_blocks < 0)
		RETURN(-EOPNOTSUPP);

	/* 'Enabled' in another code paths, try that again */
	if ((mode & FALLOC_FL_ZERO_RANGE) &&
	    !ldiskfs_test_inode_flag(inode, LDISKFS_INODE_EXTENTS)) {
		LASSERT(error_code);
		*error_code = DT_FALLOC_ERR_NEED_ZERO;
		RETURN(-EOPNOTSUPP);
	}

	LASSERT(th);
	LASSERT(inode);

	if ((mode & FALLOC_FL_PUNCH_HOLE) == 0) {
		/* quota space for metadata blocks
		 * approximate metadata estimate should be good enough.
		 */
		quota_space += PAGE_SIZE;
		quota_space += depth * LDISKFS_BLOCK_SIZE(osd_sb(osd));

		/* quota space should be reported in 1K blocks */
		quota_space = stoqb(quota_space) + stoqb(end - start) +
			LDISKFS_META_TRANS_BLOCKS(inode->i_sb);
	}

	uid = i_uid_read(inode);
	gid = i_gid_read(inode);
	projid = i_projid_read(inode);
	if (attr) {
		if (attr->la_valid & LA_UID)
			uid = attr->la_uid;
		if (attr->la_valid & LA_GID)
			gid = attr->la_gid;
		if (attr->la_valid & LA_PROJID)
			projid = attr->la_projid;
	}
	rc = osd_declare_inode_qid(env, uid, gid, projid, quota_space, oh,
				   osd_dt_obj(dt), NULL, OSD_QID_BLK);
	if (rc)
		RETURN(rc);

	if ((mode & FALLOC_FL_PUNCH_HOLE) == 0) {
		unsigned int crds_per_ext;
		ldiskfs_lblk_t blen;

		blen = osd_i_blocks(inode, ALIGN(end, 1 << inode->i_blkbits)) -
		       osd_i_blocks(inode, start);

		crds_per_ext = ldiskfs_chunk_trans_blocks(inode, blen);
		/*
		 * allow one more fallocate iteration when num credits
		 * enough to insert one extend + quota/xattrs updates
		 */
		oh->ot_credits_iter = oh->ot_credits + crds_per_ext;
		/* at tx start, reserve tx space for 5 extent inserts */
		oh->ot_credits += 5 * crds_per_ext;
	}

	/*
	 * The both hole punch and allocation may need few transactions
	 * to complete, so we have to avoid concurrent writes/truncates
	 * as we can't release object lock from within ldiskfs.
	 * Notice locking order: transaction start, then lock object
	 * (don't confuse object lock dt_{read|write}_lock() with the
	 * trunc lock.
	 */
	rc = osd_trunc_lock(osd_dt_obj(dt), oh, false);

	RETURN(rc);
}

static int osd_fallocate_preallocate(const struct lu_env *env,
				     struct dt_object *dt,
				     __u64 *start, __u64 end, int mode,
				     struct thandle *th)
{
	struct osd_thandle *oh = container_of(th, struct osd_thandle, ot_super);
	handle_t *handle = ldiskfs_journal_current_handle();
	struct osd_object *obj = osd_dt_obj(dt);
	struct inode *inode = obj->oo_inode;
	struct ldiskfs_map_blocks map;
	ldiskfs_lblk_t blen;
	ldiskfs_lblk_t boff;
	loff_t new_size = 0;
	int depth = 0;
	int flags;
	int rc = 0;

	ENTRY;

	LASSERT(dt_object_exists(dt));
	LASSERT(osd_invariant(obj));
	LASSERT(inode != NULL);

	CDEBUG(D_INODE, "fallocate: inode #%lu: start %llu end %llu mode %d\n",
	       inode->i_ino, *start, end, mode);

	dquot_initialize(inode);

	LASSERT(th);

	boff = osd_i_blocks(inode, *start);
	blen = osd_i_blocks(inode, ALIGN(end, 1 << inode->i_blkbits)) - boff;

	/* Create and mark new extents as either zero or unwritten */
	flags = (osd_dev(dt->do_lu.lo_dev)->od_fallocate_zero_blocks ||
		 !ldiskfs_test_inode_flag(inode, LDISKFS_INODE_EXTENTS)) ?
		LDISKFS_GET_BLOCKS_CREATE_ZERO :
		LDISKFS_GET_BLOCKS_CREATE_UNWRIT_EXT;
#ifdef LDISKFS_GET_BLOCKS_KEEP_SIZE
	if (mode & FALLOC_FL_KEEP_SIZE)
		flags |= LDISKFS_GET_BLOCKS_KEEP_SIZE;
#endif
	inode_lock(inode);

	if (!(mode & FALLOC_FL_KEEP_SIZE) && (end > i_size_read(inode) ||
	    end > LDISKFS_I(inode)->i_disksize)) {
		new_size = end;
		rc = inode_newsize_ok(inode, new_size);
		if (rc)
			GOTO(out, rc);
	}

	inode_dio_wait(inode);

	map.m_lblk = boff;
	map.m_len = blen;

	/* Don't normalize the request if it can fit in one extent so
	 * that it doesn't get unnecessarily split into multiple extents.
	 */
	if (blen <= EXT_UNWRITTEN_MAX_LEN)
		flags |= LDISKFS_GET_BLOCKS_NO_NORMALIZE;

	depth = ext_depth(inode);

	while (rc >= 0 && blen) {
		loff_t epos;

		rc = ldiskfs_map_blocks(handle, inode, &map, flags);
		if (rc <= 0) {
			CDEBUG(D_INODE,
			       "inode #%lu: block %u: len %u: ldiskfs_map_blocks returned %d\n",
			       inode->i_ino, map.m_lblk, map.m_len, rc);
			ldiskfs_mark_inode_dirty(handle, inode);
			break;
		}

		map.m_lblk += rc;
		*start += rc;
		map.m_len = blen = blen - rc;
		epos = (loff_t)map.m_lblk << inode->i_blkbits;
		inode_set_ctime_current(inode);
		if (new_size) {
			if (epos > end)
				epos = end;
			if (ldiskfs_update_inode_size(inode, epos) & 0x1)
				inode_set_mtime_to_ts(inode,
						      inode_get_ctime(inode));
#ifdef LDISKFS_EOFBLOCKS_FL
		} else {
			if (epos > inode->i_size)
				ldiskfs_set_inode_flag(inode,
						       LDISKFS_INODE_EOFBLOCKS);
#endif
		}

		ldiskfs_mark_inode_dirty(handle, inode);

		/* do not attempt to extend an old transaction */
		if (handle->h_transaction->t_state != T_RUNNING)
			GOTO(out, rc = -EAGAIN);

		/*
		 * Recalculate credits when extent tree depth changes.
		 */
		if (depth != ext_depth(inode))
			GOTO(out, rc = -EAGAIN);

		rc = osd_extend_trans(handle, oh->ot_credits_iter, inode);
		if (rc > 0)
			GOTO(out, rc = -EAGAIN);
		if (rc)
			GOTO(out, rc);
}

out:
	inode_unlock(inode);

	RETURN(rc);
}

static int osd_fallocate_advance(const struct lu_env *env, struct dt_object *dt,
				  __u64 *start, __u64 end, int mode,
				  struct thandle *th)
{
	struct osd_object *obj = osd_dt_obj(dt);
	struct inode *inode = obj->oo_inode;
	struct osd_access_lock *al;
	struct osd_thandle *oh;
	int rc = 0, found = 0;

	ENTRY;

	LASSERT(dt_object_exists(dt));
	LASSERT(osd_invariant(obj));
	LASSERT(inode != NULL);

	dquot_initialize(inode);

	LASSERT(th);
	oh = container_of(th, struct osd_thandle, ot_super);
	LASSERT(oh->ot_handle->h_transaction != NULL);

	list_for_each_entry(al, &oh->ot_trunc_locks, tl_list) {
		if (obj != al->tl_obj)
			continue;
		LASSERT(al->tl_shared == 0);
		found = 1;
		/* do actual punch/zero in osd_trans_stop() */
		al->tl_start = *start;
		al->tl_end = end;
		al->tl_mode = mode;
		al->tl_fallocate = true;
		break;
	}

	RETURN(rc);
}

static int osd_fallocate(const struct lu_env *env, struct dt_object *dt,
			 __u64 *start, __u64 end, int mode, struct thandle *th)
{
	int rc;

	ENTRY;

	if (mode & (FALLOC_FL_PUNCH_HOLE | FALLOC_FL_ZERO_RANGE)) {
		/* punch/zero-range */
		rc = osd_fallocate_advance(env, dt, start, end, mode, th);
	} else {
		/* standard preallocate */
		rc = osd_fallocate_preallocate(env, dt, start, end, mode, th);
	}
	RETURN(rc);
}

static int osd_declare_punch(const struct lu_env *env, struct dt_object *dt,
			     __u64 start, __u64 end, struct thandle *th)
{
	struct osd_thandle *oh;
	struct osd_object  *obj = osd_dt_obj(dt);
	struct inode	   *inode;
	int		    rc;
	ENTRY;

	LASSERT(th);
	oh = container_of(th, struct osd_thandle, ot_super);

	/*
	 * we don't need to reserve credits for whole truncate
	 * it's not possible as truncate may need to free too many
	 * blocks and that won't fit a single transaction. instead
	 * we reserve credits to change i_size and put inode onto
	 * orphan list. if needed truncate will extend or restart
	 * transaction
	 */
	osd_trans_declare_op(env, oh, OSD_OT_PUNCH,
			     osd_dto_credits_noquota[DTO_ATTR_SET_BASE] + 3);

	inode = obj->oo_inode;
	LASSERT(inode);

	rc = osd_declare_inode_qid(env, i_uid_read(inode), i_gid_read(inode),
				   i_projid_read(inode), 0, oh, obj,
				   NULL, OSD_QID_BLK);

	/* if object holds encrypted content, we need to make sure we truncate
	 * on an encryption unit boundary, or subsequent reads will get
	 * corrupted content
	 */
	if (rc == 0) {
		if (obj->oo_lma_flags & LUSTRE_ENCRYPT_FL &&
		    start & ~LUSTRE_ENCRYPTION_MASK)
			start = (start & LUSTRE_ENCRYPTION_MASK) +
				LUSTRE_ENCRYPTION_UNIT_SIZE;
		truncate_pagecache(inode, start);
		rc = osd_trunc_lock(obj, oh, false);
	}

	RETURN(rc);
}

static int osd_punch(const struct lu_env *env, struct dt_object *dt,
		     __u64 start, __u64 end, struct thandle *th)
{
	struct osd_object *obj = osd_dt_obj(dt);
	struct osd_device *osd = osd_obj2dev(obj);
	struct inode *inode = obj->oo_inode;
	struct osd_access_lock *al;
	struct osd_thandle *oh;
	int rc = 0, found = 0;
	bool grow = false;
	ENTRY;

	LASSERT(dt_object_exists(dt));
	LASSERT(osd_invariant(obj));
	LASSERT(inode != NULL);
	dquot_initialize(inode);

	LASSERT(th);
	oh = container_of(th, struct osd_thandle, ot_super);
	LASSERT(oh->ot_handle->h_transaction != NULL);

	/* we used to skip truncate to current size to
	 * optimize truncates on OST. with DoM we can
	 * get attr_set to set specific size (MDS_REINT)
	 * and then get truncate RPC which essentially
	 * would be skipped. this is bad.. so, disable
	 * this optimization on MDS till the client stop
	 * to sent MDS_REINT (LU-11033) -bzzz
	 */
	if (osd->od_is_ost && i_size_read(inode) == start)
		RETURN(0);

	osd_trans_exec_op(env, th, OSD_OT_PUNCH);

	spin_lock(&inode->i_lock);
	if (i_size_read(inode) < start)
		grow = true;
	i_size_write(inode, start);
	spin_unlock(&inode->i_lock);

	/* optimize grow case */
	if (grow) {
		osd_execute_truncate(obj);
		GOTO(out, rc);
	}

	if (obj->oo_brm) {
		/* reset block lookup/preallocation cache */
		struct ldiskfs_inode_info *ei = LDISKFS_I(inode);
		struct osd_block_ready_map *brm = obj->oo_brm;
		int i;

		down(&ei->i_append_sem);
		for (i = 0; i < OSD_BRM_MAX; i++) {
			brm[i].start = 1UL << 31;
			brm[i].end = 1UL << 31;
		}
		up(&ei->i_append_sem);
	}

	inode_lock(inode);
	/* add to orphan list to ensure truncate completion
	 * if this transaction succeed. ldiskfs_truncate()
	 * will take the inode out of the list
	 */
	rc = ldiskfs_orphan_add(oh->ot_handle, inode);
	inode_unlock(inode);
	if (rc != 0)
		GOTO(out, rc);

	list_for_each_entry(al, &oh->ot_trunc_locks, tl_list) {
		if (obj != al->tl_obj)
			continue;
		LASSERT(al->tl_shared == 0);
		found = 1;
		/* do actual truncate in osd_trans_stop() */
		al->tl_truncate = 1;
		break;
	}
	LASSERT(found);

out:
	RETURN(rc);
}

static int fiemap_check_ranges(struct inode *inode,
			       u64 start, u64 len, u64 *new_len)
{
	loff_t maxbytes;

	*new_len = len;

	if (len == 0)
		return -EINVAL;

	if (ldiskfs_test_inode_flag(inode, LDISKFS_INODE_EXTENTS))
		maxbytes = inode->i_sb->s_maxbytes;
	else
		maxbytes = LDISKFS_SB(inode->i_sb)->s_bitmap_maxbytes;

	if (start > maxbytes)
		return -EFBIG;

	/*
	 * Shrink request scope to what the fs can actually handle.
	 */
	if (len > maxbytes || (maxbytes - len) < start)
		*new_len = maxbytes - start;

	return 0;
}

/* So that the fiemap access checks can't overflow on 32 bit machines. */
#define FIEMAP_MAX_EXTENTS     (UINT_MAX / sizeof(struct fiemap_extent))

static int osd_fiemap_get(const struct lu_env *env, struct dt_object *dt,
			  struct fiemap *fm)
{
	struct fiemap_extent_info fieinfo = {0, };
	struct inode *inode = osd_dt_obj(dt)->oo_inode;
	u64 len;
	int rc;
	DECLARE_MM_SEGMENT_T(saved_fs);

	LASSERT(inode);
	if (inode->i_op->fiemap == NULL)
		return -EOPNOTSUPP;

	if (fm->fm_extent_count > FIEMAP_MAX_EXTENTS)
		return -EINVAL;

	rc = fiemap_check_ranges(inode, fm->fm_start, fm->fm_length, &len);
	if (rc)
		return rc;

	fieinfo.fi_flags = fm->fm_flags;
	fieinfo.fi_extents_max = fm->fm_extent_count;
	fieinfo.fi_extents_start = fm->fm_extents;

	if (fieinfo.fi_flags & FIEMAP_FLAG_SYNC)
		filemap_write_and_wait(inode->i_mapping);

	access_set_kernel(saved_fs, &fieinfo);
	rc = inode->i_op->fiemap(inode, &fieinfo, fm->fm_start, len);
	access_unset_kernel(saved_fs, &fieinfo);
	fm->fm_flags = fieinfo.fi_flags;
	fm->fm_mapped_extents = fieinfo.fi_extents_mapped;

	return rc;
}

static int osd_ladvise(const struct lu_env *env, struct dt_object *dt,
		       __u64 start, __u64 end, enum lu_ladvise_type advice)
{
	struct osd_object *obj = osd_dt_obj(dt);
	int rc = 0;
	ENTRY;

	switch (advice) {
	case LU_LADVISE_DONTNEED:
		if (end)
			invalidate_mapping_pages(obj->oo_inode->i_mapping,
						 start >> PAGE_SHIFT,
						 (end - 1) >> PAGE_SHIFT);
		break;
	default:
		rc = -ENOTSUPP;
		break;
	}

	RETURN(rc);
}

static loff_t osd_lseek(const struct lu_env *env, struct dt_object *dt,
			loff_t offset, int whence)
{
	struct osd_object *obj = osd_dt_obj(dt);
	struct inode *inode = obj->oo_inode;
	struct file *file;
	loff_t result;

	ENTRY;
	LASSERT(dt_object_exists(dt));
	LASSERT(osd_invariant(obj));
	LASSERT(inode);
	LASSERT(offset >= 0);

	file = osd_get_filp_for_inode(osd_oti_get(env), inode);
	if (IS_ERR(file))
		RETURN(PTR_ERR(file));

	result = file->f_op->llseek(file, offset, whence);
	compat_security_file_free(file);
	/*
	 * If 'offset' is beyond end of object file then treat it as not error
	 * but valid case for SEEK_HOLE and return 'offset' as result.
	 * LOV will decide if it is beyond real end of file or not.
	 */
	if (whence == SEEK_HOLE && result == -ENXIO)
		result = offset;

	CDEBUG(D_INFO, "seek %s from %lld: %lld\n", whence == SEEK_HOLE ?
		       "hole" : "data", offset, result);
	RETURN(result);
}

/*
 * in some cases we may need declare methods for objects being created
 * e.g., when we create symlink
 */
const struct dt_body_operations osd_body_ops_new = {
	.dbo_declare_write = osd_declare_write,
};

const struct dt_body_operations osd_body_ops = {
	.dbo_read			= osd_read,
	.dbo_declare_write		= osd_declare_write,
	.dbo_write			= osd_write,
	.dbo_bufs_get			= osd_bufs_get,
	.dbo_bufs_put			= osd_bufs_put,
	.dbo_write_prep			= osd_write_prep,
	.dbo_declare_write_commit	= osd_declare_write_commit,
	.dbo_write_commit		= osd_write_commit,
	.dbo_read_prep			= osd_read_prep,
	.dbo_declare_punch		= osd_declare_punch,
	.dbo_punch			= osd_punch,
	.dbo_fiemap_get			= osd_fiemap_get,
	.dbo_ladvise			= osd_ladvise,
	.dbo_declare_fallocate		= osd_declare_fallocate,
	.dbo_fallocate			= osd_fallocate,
	.dbo_lseek			= osd_lseek,
};

/**
 * Get a truncate lock
 *
 * In order to take multi-transaction truncate out of main transaction we let
 * the caller grab a lock on the object passed. the lock can be shared (for
 * writes) and exclusive (for truncate). It's not allowed to mix truncate
 * and write in the same transaction handle (do not confuse with big ldiskfs
 * transaction containing lots of handles).
 * The lock must be taken at declaration.
 *
 * \param obj		object to lock
 * \oh			transaction
 * \shared		shared or exclusive
 *
 * \retval 0		lock is granted
 * \retval -NOMEM	no memory to allocate lock
 */
int osd_trunc_lock(struct osd_object *obj, struct osd_thandle *oh, bool shared)
{
	struct osd_access_lock *al, *tmp;

	LASSERT(obj);
	LASSERT(oh);

	list_for_each_entry(tmp, &oh->ot_trunc_locks, tl_list) {
		if (tmp->tl_obj != obj)
			continue;
		LASSERT(tmp->tl_shared == shared);
		/* found same lock */
		return 0;
	}

	OBD_ALLOC_PTR(al);
	if (unlikely(al == NULL))
		return -ENOMEM;
	al->tl_obj = obj;
	al->tl_truncate = false;
	if (shared)
		down_read(&obj->oo_ext_idx_sem);
	else
		down_write(&obj->oo_ext_idx_sem);
	al->tl_shared = shared;
	lu_object_get(&obj->oo_dt.do_lu);

	list_add(&al->tl_list, &oh->ot_trunc_locks);

	return 0;
}

void osd_trunc_unlock_all(const struct lu_env *env, struct list_head *list)
{
	struct osd_access_lock *al, *tmp;

	list_for_each_entry_safe(al, tmp, list, tl_list) {
		if (al->tl_shared)
			up_read(&al->tl_obj->oo_ext_idx_sem);
		else
			up_write(&al->tl_obj->oo_ext_idx_sem);
		osd_object_put(env, al->tl_obj);
		list_del(&al->tl_list);
		OBD_FREE_PTR(al);
	}
}

/* For a partial-page punch, flush punch range to disk immediately */
static void osd_partial_page_flush_punch(struct osd_device *d,
					 struct inode *inode, loff_t start,
					 loff_t end)
{
	if (osd_use_page_cache(d)) {
		filemap_fdatawrite_range(inode->i_mapping, start, end);
	} else {
		/* Notice we use "wait" version to ensure I/O is complete */
		filemap_write_and_wait_range(inode->i_mapping, start,
					     end);
		invalidate_mapping_pages(inode->i_mapping, start >> PAGE_SHIFT,
					 end >> PAGE_SHIFT);
	}
}

static void osd_invalidate_partial_page(struct inode *inode, loff_t offset)
{
	struct address_space *mapping = inode->i_mapping;
	struct ldiskfs_inode_info *ei = LDISKFS_I(inode);
	struct page *page;
	int rc;

	if (!test_bit(LDISKFS_INODE_JOURNAL_DATA, &ei->i_flags))
		return;

	page = find_or_create_page(mapping, i_size_read(inode) >> PAGE_SHIFT,
				   mapping_gfp_constraint(mapping, ~__GFP_FS));
	if (!page)
		return;

	rc = osd_jbd_invalidate_page(LDISKFS_SB(inode->i_sb)->s_journal,
				     page, 0, PAGE_SIZE);
	LASSERTF(rc == 0, "  last page %lu %s%s rc=%d\n",
		 folio_index_page(page),
		 PageChecked(page) ? "C" : "", PageDirty(page) ? "D" : "", rc);
	unlock_page(page);
	put_page(page);
}

/*
 * For a partial-page truncate, flush the page to disk immediately to
 * avoid data corruption during direct disk write.  b=17397
 */
static void osd_partial_page_flush(struct osd_device *d, struct inode *inode,
				   loff_t offset)
{
	if (!(offset & ~PAGE_MASK))
		return;

	if (osd_use_page_cache(d)) {
		filemap_fdatawrite_range(inode->i_mapping, offset, offset + 1);
	} else {
		/* Notice we use "wait" version to ensure I/O is complete */
		filemap_write_and_wait_range(inode->i_mapping, offset,
					     offset + 1);
		invalidate_mapping_pages(inode->i_mapping, offset >> PAGE_SHIFT,
					 offset >> PAGE_SHIFT);
	}

	/* to prevent ldiskfs warning about forgottent page */
	osd_invalidate_partial_page(inode, offset);
}

void osd_execute_truncate(struct osd_object *obj)
{
	struct osd_device *d = osd_obj2dev(obj);
	struct inode *inode = obj->oo_inode;
	__u64 size;

	/* simulate crash before (in the middle) of delayed truncate */
	if (CFS_FAIL_CHECK(OBD_FAIL_OSD_FAIL_AT_TRUNCATE)) {
		struct ldiskfs_inode_info *ei = LDISKFS_I(inode);
		struct ldiskfs_sb_info *sbi = LDISKFS_SB(inode->i_sb);

		mutex_lock(&sbi->s_orphan_lock);
		list_del_init(&ei->i_orphan);
		mutex_unlock(&sbi->s_orphan_lock);
		return;
	}

	size = i_size_read(inode);
	inode_lock(inode);
	/* if object holds encrypted content, we need to make sure we truncate
	 * on an encryption unit boundary, or block content will get corrupted
	 */
	if (obj->oo_lma_flags & LUSTRE_ENCRYPT_FL &&
	    size & ~LUSTRE_ENCRYPTION_MASK)
		inode->i_size = (size & LUSTRE_ENCRYPTION_MASK) +
			LUSTRE_ENCRYPTION_UNIT_SIZE;
	ldiskfs_truncate(inode);
	inode_unlock(inode);
	if (inode->i_size != size) {
		spin_lock(&inode->i_lock);
		i_size_write(inode, size);
		LDISKFS_I(inode)->i_disksize = size;
		spin_unlock(&inode->i_lock);
		osd_dirty_inode(inode, I_DIRTY_DATASYNC);
	}
	osd_partial_page_flush(d, inode, size);
}

static int osd_execute_fallocate(const struct lu_env *env,
				  struct osd_object *obj, loff_t start,
				  loff_t end, int mode)
{
	struct osd_device *d = osd_obj2dev(obj);
	struct inode *inode = obj->oo_inode;
	struct file *file;
	int rc;

	file = osd_get_filp_for_inode(osd_oti_get(env), inode);
	if (IS_ERR(file))
		return PTR_ERR(file);

	file->f_mode |= FMODE_64BITHASH;
	rc = file->f_op->fallocate(file, mode, start, end - start);
	compat_security_file_free(file);
	if (rc == 0)
		osd_partial_page_flush_punch(d, inode, start, end - 1);
	return rc;
}

int osd_process_truncates(const struct lu_env *env, struct list_head *list)
{
	struct osd_access_lock *al;
	int rc = 0;

	LASSERT(!journal_current_handle());

	list_for_each_entry(al, list, tl_list) {
		if (al->tl_shared)
			continue;
		if (al->tl_truncate)
			osd_execute_truncate(al->tl_obj);
		else if (al->tl_fallocate)
			rc = osd_execute_fallocate(env, al->tl_obj,
						   al->tl_start, al->tl_end,
						   al->tl_mode);
	}

	return rc;
}