// SPDX-License-Identifier: GPL-2.0

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

/*
 * This file is part of Lustre, http://www.lustre.org/
 *
 * Implementation of iam format for fixed size records.
 *
 * Author: Wang Di <wangdi@clusterfs.com>
 * Author: Nikita Danilov <nikita@clusterfs.com>
 */

#include <linux/types.h>
#include "osd_internal.h"

/*
 * Leaf operations.
 */

enum {
	IAM_LEAF_HEADER_MAGIC = 0x1976
};

struct iam_leaf_head {
	__le16 ill_magic;
	__le16 ill_count;
};

static inline int iam_lfix_entry_size(const struct iam_leaf *l)
{
	return iam_leaf_descr(l)->id_key_size + iam_leaf_descr(l)->id_rec_size;
}

static inline struct iam_lentry *
iam_lfix_shift(const struct iam_leaf *l, struct iam_lentry *entry, int shift)
{
	return (void *)entry + shift * iam_lfix_entry_size(l);
}

static inline struct iam_key *iam_leaf_key_at(struct iam_lentry *entry)
{
	return (struct iam_key *)entry;
}

static inline int lfix_keycmp(const struct iam_container *c,
			      const struct iam_key *k1,
			      const struct iam_key *k2)
{
	return memcmp(k1, k2, c->ic_descr->id_key_size);
}

static struct iam_leaf_head *iam_get_head(const struct iam_leaf *l)
{
	return (struct iam_leaf_head *)l->il_bh->b_data;
}

static struct iam_lentry *iam_entries(const struct buffer_head *bh)
{
	return (void *)bh->b_data + sizeof(struct iam_leaf_head);
}

static struct iam_lentry *iam_get_lentries(const struct iam_leaf *l)
{
	return iam_entries(l->il_bh);
}

static int leaf_count_limit(const struct iam_leaf *leaf)
{
	int free_space;

	free_space = iam_leaf_container(leaf)->ic_object->i_sb->s_blocksize;
	free_space -= sizeof(struct iam_leaf_head);
	return free_space / iam_lfix_entry_size(leaf);
}

static int lentry_count_get(const struct iam_leaf *leaf)
{
	return le16_to_cpu(iam_get_head(leaf)->ill_count);
}

static void lentry_count_set(struct iam_leaf *leaf, unsigned count)
{
	assert_corr(0 <= count && count <= leaf_count_limit(leaf));
	iam_get_head(leaf)->ill_count = cpu_to_le16(count);
}

static struct iam_lentry *iam_lfix_get_end(const struct iam_leaf *l);

#if LDISKFS_CORRECTNESS_ON || LDISKFS_INVARIANT_ON
static int iam_leaf_at_rec(const struct iam_leaf *folio)
{
	return iam_get_lentries(folio) <= folio->il_at &&
		folio->il_at < iam_lfix_get_end(folio);
}
#endif

static struct iam_ikey *iam_lfix_ikey(const struct iam_leaf *l,
				      struct iam_ikey *key)
{
	void *ie = l->il_at;

	assert_corr(iam_leaf_at_rec(l));
	return (struct iam_ikey *)ie;
}

static struct iam_key *iam_lfix_key(const struct iam_leaf *l)
{
	void *ie = l->il_at;

	assert_corr(iam_leaf_at_rec(l));
	return (struct iam_key *)ie;
}

static int iam_lfix_key_size(const struct iam_leaf *l)
{
	return iam_leaf_descr(l)->id_key_size;
}

static void iam_lfix_start(struct iam_leaf *l)
{
	l->il_at = iam_get_lentries(l);
}

static inline ptrdiff_t iam_lfix_diff(const struct iam_leaf *l,
				      const struct iam_lentry *e1,
				      const struct iam_lentry *e2)
{
	ptrdiff_t diff;
	int esize;

	esize = iam_lfix_entry_size(l);
	diff = (void *)e1 - (void *)e2;
	assert_corr(diff / esize * esize == diff);
	return diff / esize;
}

static int iam_lfix_init(struct iam_leaf *l)
{
	int result;
	struct iam_leaf_head *ill;
	int count;

	assert_corr(l->il_bh != NULL);

	ill = iam_get_head(l);
	count = le16_to_cpu(ill->ill_count);
	if (le16_to_cpu(ill->ill_magic) == IAM_LEAF_HEADER_MAGIC &&
			0 <= count && count <= leaf_count_limit(l)) {
		l->il_at = l->il_entries = iam_get_lentries(l);
		result = 0;
	} else {
		struct inode *obj;

		result = -EIO;
		obj = iam_leaf_container(l)->ic_object;
		CERROR(
		"Bad magic in node %llu #%lu: %#x != %#x or bad cnt: %d %d: rc = %d\n",
			(unsigned long long)l->il_bh->b_blocknr, obj->i_ino,
			le16_to_cpu(ill->ill_magic), IAM_LEAF_HEADER_MAGIC,
			count, leaf_count_limit(l), result);
	}
	return result;
}

static void iam_lfix_fini(struct iam_leaf *l)
{
	l->il_entries = l->il_at = NULL;
}

static struct iam_lentry *iam_lfix_get_end(const struct iam_leaf *l)
{
	int count = lentry_count_get(l);
	struct iam_lentry *ile = iam_lfix_shift(l, l->il_entries, count);

	return ile;
}

static struct iam_rec *iam_lfix_rec(const struct iam_leaf *l)
{
	void *e = l->il_at;

	assert_corr(iam_leaf_at_rec(l));
	return e + iam_leaf_descr(l)->id_key_size;
}

static void iam_lfix_next(struct iam_leaf *l)
{
	assert_corr(iam_leaf_at_rec(l));
	l->il_at = iam_lfix_shift(l, l->il_at, 1);
}

/*
 * Bug chasing.
 */
int lfix_dump = 0;

static char hdigit(char ch)
{
	static const char d[] = "0123456789abcdef";

	return d[ch & 0xf];
}

static char *hex(char ch, char *area)
{
	area[0] = hdigit(ch >> 4);
	area[1] = hdigit(ch);
	area[2] = 0;
	return area;
}

static void l_print(struct iam_leaf *leaf, struct iam_lentry *entry)
{
	int i;
	char *area;
	char h[3];

	area = (char *)entry;
	printk(KERN_EMERG "[");
	for (i = iam_lfix_key_size(leaf); i > 0; --i, ++area)
		printk("%s", hex(*area, h));
	printk("]-(");
	for (i = iam_leaf_descr(leaf)->id_rec_size; i > 0; --i, ++area)
		printk("%s", hex(*area, h));
	printk(")\n");
}

static void lfix_print(struct iam_leaf *leaf)
{
	struct iam_lentry *entry;
	int count;
	int i;

	entry = leaf->il_entries;
	count = lentry_count_get(leaf);
	printk(KERN_EMERG "lfix: %p %p %d\n", leaf, leaf->il_at, count);
	for (i = 0; i < count; ++i, entry = iam_lfix_shift(leaf, entry, 1))
		l_print(leaf, entry);
}

static int iam_lfix_lookup(struct iam_leaf *l, const struct iam_key *k)
{
	struct iam_lentry *p, *q, *m, *t;
	struct iam_container *c;
	int count;
	int result;

	count = lentry_count_get(l);
	if (count == 0)
                return IAM_LOOKUP_EMPTY;

	result = IAM_LOOKUP_OK;
	c = iam_leaf_container(l);

	p = l->il_entries;
	q = iam_lfix_shift(l, p, count - 1);
	if (lfix_keycmp(c, k, iam_leaf_key_at(p)) < 0) {
		/*
		 * @k is less than the least key in the leaf
		 */
		l->il_at = p;
		result = IAM_LOOKUP_BEFORE;
	} else if (lfix_keycmp(c, iam_leaf_key_at(q), k) <= 0) {
		l->il_at = q;
	} else {
		/*
		 * EWD1293
		 */
		while (iam_lfix_shift(l, p, 1) != q) {
			m = iam_lfix_shift(l, p, iam_lfix_diff(l, q, p) / 2);
			assert_corr(p < m && m < q);
			if (lfix_keycmp(c, iam_leaf_key_at(m), k) <= 0)
				p = m;
			else
				q = m;
		}
		assert_corr(lfix_keycmp(c, iam_leaf_key_at(p), k) <= 0 &&
			    lfix_keycmp(c, k, iam_leaf_key_at(q)) < 0);
		/*
		 * skip over records with duplicate keys.
		 */
		while (p > l->il_entries) {
			t = iam_lfix_shift(l, p, -1);
			if (lfix_keycmp(c, iam_leaf_key_at(t), k) == 0)
				p = t;
			else
				break;
		}
		l->il_at = p;
	}
	assert_corr(iam_leaf_at_rec(l));

	if (lfix_keycmp(c, iam_leaf_key_at(l->il_at), k) == 0)
                result = IAM_LOOKUP_EXACT;

	if (lfix_dump)
                lfix_print(l);

	return result;
}

static int iam_lfix_ilookup(struct iam_leaf *l, const struct iam_ikey *ik)
{
	return iam_lfix_lookup(l, (const struct iam_key *)ik);
}

static void iam_lfix_key_set(struct iam_leaf *l, const struct iam_key *k)
{
	assert_corr(iam_leaf_at_rec(l));
	memcpy(iam_leaf_key_at(l->il_at), k, iam_leaf_descr(l)->id_key_size);
}

static int iam_lfix_key_cmp(const struct iam_leaf *l, const struct iam_key *k)
{
	return lfix_keycmp(iam_leaf_container(l), iam_leaf_key_at(l->il_at), k);
}

static int iam_lfix_key_eq(const struct iam_leaf *l, const struct iam_key *k)
{
	return !lfix_keycmp(iam_leaf_container(l),
			    iam_leaf_key_at(l->il_at), k);
}

static void iam_lfix_rec_set(struct iam_leaf *l, const struct iam_rec *r)
{
	assert_corr(iam_leaf_at_rec(l));
	memcpy(iam_lfix_rec(l), r, iam_leaf_descr(l)->id_rec_size);
}

static inline int lfix_reccmp(const struct iam_container *c,
			      const struct iam_rec *r1,
			      const struct iam_rec *r2)
{
	return memcmp(r1, r2, c->ic_descr->id_rec_size);
}

static int iam_lfix_rec_eq(const struct iam_leaf *l, const struct iam_rec *r)
{
	return !lfix_reccmp(iam_leaf_container(l), iam_lfix_rec(l), r);
}

static void iam_lfix_rec_get(const struct iam_leaf *l, struct iam_rec *r)
{
	assert_corr(iam_leaf_at_rec(l));
	memcpy(r, iam_lfix_rec(l), iam_leaf_descr(l)->id_rec_size);
}

static void iam_lfix_rec_add(struct iam_leaf *leaf,
			     const struct iam_key *k, const struct iam_rec *r)
{
	struct iam_lentry *end;
	struct iam_lentry *cur;
	struct iam_lentry *start;
	ptrdiff_t diff;
	int count;

	assert_corr(iam_leaf_can_add(leaf, k, r));

	count = lentry_count_get(leaf);
	/*
	 * This branch handles two exceptional cases:
	 *
	 *   - leaf positioned beyond last record, and
	 *
	 *   - empty leaf.
	 */
	if (!iam_leaf_at_end(leaf)) {
		end   = iam_lfix_get_end(leaf);
		cur   = leaf->il_at;
		if (lfix_keycmp(iam_leaf_container(leaf),
				k, iam_leaf_key_at(cur)) >= 0)
			iam_lfix_next(leaf);
		else
			/*
			 * Another exceptional case: insertion with the key
			 * less than least key in the leaf.
			 */
			assert_corr(cur == leaf->il_entries);

		start = leaf->il_at;
		diff  = (void *)end - (void *)start;
		assert_corr(diff >= 0);
		memmove(iam_lfix_shift(leaf, start, 1), start, diff);
	}
	lentry_count_set(leaf, count + 1);
	iam_lfix_key_set(leaf, k);
	iam_lfix_rec_set(leaf, r);
	assert_corr(iam_leaf_at_rec(leaf));
}

static void iam_lfix_rec_del(struct iam_leaf *leaf, int shift)
{
	struct iam_lentry *next, *end;
	int count;
	ptrdiff_t diff;

	assert_corr(iam_leaf_at_rec(leaf));

	count = lentry_count_get(leaf);
	end = iam_lfix_get_end(leaf);
	next = iam_lfix_shift(leaf, leaf->il_at, 1);
	diff = (void *)end - (void *)next;
	memmove(leaf->il_at, next, diff);

	lentry_count_set(leaf, count - 1);
}

static int iam_lfix_can_add(const struct iam_leaf *l,
			    const struct iam_key *k, const struct iam_rec *r)
{
	return lentry_count_get(l) < leaf_count_limit(l);
}

static int iam_lfix_at_end(const struct iam_leaf *folio)
{
	return folio->il_at == iam_lfix_get_end(folio);
}

static void iam_lfix_init_new(struct iam_container *c, struct buffer_head *bh)
{
	struct iam_leaf_head *hdr;

	hdr = (struct iam_leaf_head *)bh->b_data;
	hdr->ill_magic = cpu_to_le16(IAM_LEAF_HEADER_MAGIC);
	hdr->ill_count = cpu_to_le16(0);
}

static void iam_lfix_split(struct iam_leaf *l, struct buffer_head **bh,
			   iam_ptr_t new_blknr)
{
	struct iam_path *path;
	struct iam_leaf_head *hdr;
	const struct iam_ikey *pivot;
	struct buffer_head *new_leaf;

	unsigned int count;
	unsigned int split;

	void *start;
	void *finis;

	new_leaf = *bh;
	path = iam_leaf_path(l);

	hdr = (void *)new_leaf->b_data;

	count = lentry_count_get(l);
	split = count / 2;

	start = iam_lfix_shift(l, iam_get_lentries(l), split);
	finis = iam_lfix_shift(l, iam_get_lentries(l), count);

	pivot = (const struct iam_ikey *)iam_leaf_key_at(start);

	memmove(iam_entries(new_leaf), start, finis - start);
	hdr->ill_count = cpu_to_le16(count - split);
	lentry_count_set(l, split);
	if ((void *)l->il_at >= start) {
		/*
		 * insertion point moves into new leaf.
		 */
		int shift;

		shift = iam_lfix_diff(l, l->il_at, start);
		*bh = l->il_bh;
		l->il_bh = new_leaf;
		l->il_curidx = new_blknr;
		iam_lfix_init(l);
		/*
		 * init cannot fail, as node was just initialized.
		 */
		assert_corr(result == 0);
		l->il_at = iam_lfix_shift(l, iam_get_lentries(l), shift);
	}
	/*
	 * Insert pointer to the new node (together with the least key in
	 * the node) into index node.
	 */
	iam_insert_key_lock(path, path->ip_frame, pivot, new_blknr);
}

static int iam_lfix_leaf_empty(struct iam_leaf *leaf)
{
	return lentry_count_get(leaf) == 0;
}

static const struct iam_leaf_operations iam_lfix_leaf_ops = {
	.init           = iam_lfix_init,
	.init_new       = iam_lfix_init_new,
	.fini           = iam_lfix_fini,
	.start          = iam_lfix_start,
	.next           = iam_lfix_next,
	.key            = iam_lfix_key,
	.ikey           = iam_lfix_ikey,
	.rec            = iam_lfix_rec,
	.key_set        = iam_lfix_key_set,
	.key_cmp        = iam_lfix_key_cmp,
	.key_eq         = iam_lfix_key_eq,
	.key_size       = iam_lfix_key_size,
	.rec_set        = iam_lfix_rec_set,
	.rec_eq         = iam_lfix_rec_eq,
	.rec_get        = iam_lfix_rec_get,
	.lookup         = iam_lfix_lookup,
	.ilookup        = iam_lfix_ilookup,
	.at_end         = iam_lfix_at_end,
	.rec_add        = iam_lfix_rec_add,
	.rec_del        = iam_lfix_rec_del,
	.can_add        = iam_lfix_can_add,
	.split          = iam_lfix_split,
	.leaf_empty     = iam_lfix_leaf_empty,
};

/*
 * Index operations.
 */

enum {
	/*
	 * Then shalt thou see the dew-BEDABBLED wretch
	 * Turn, and return, indenting with the way;
	 * Each envious brier his weary legs doth scratch,
	 * Each shadow makes him stop, each murmur stay:
	 * For misery is trodden on by many,
	 * And being low never relieved by any.
	 */
	IAM_LFIX_ROOT_MAGIC = 0xbedabb1edULL /* d01efull */
};

struct iam_lfix_root {
	__le64  ilr_magic;
	__le16  ilr_keysize;
	__le16  ilr_recsize;
	__le16  ilr_ptrsize;
	u8      ilr_indirect_levels;
	u8      ilr_padding;
};

static __u32 iam_lfix_root_ptr(struct iam_container *c)
{
	return 0;
}

static int iam_lfix_node_init(struct iam_container *c, struct buffer_head *bh,
                              int root)
{
	return 0;
}

static struct iam_entry *iam_lfix_root_inc(struct iam_container *c,
					   struct iam_path *path,
					   struct iam_frame *frame)
{
	struct iam_lfix_root *root;
	struct iam_entry *entries;

	entries = frame->entries;

	dx_set_count(entries, 2);
	assert_corr(dx_get_limit(entries) == dx_root_limit(path));

	root = (void *)frame->bh->b_data;
	assert_corr(le64_to_cpu(root->ilr_magic) == IAM_LFIX_ROOT_MAGIC);
	root->ilr_indirect_levels++;
	frame->at = entries = iam_entry_shift(path, entries, 1);
	memset(iam_ikey_at(path, entries), 0,
	       iam_path_descr(path)->id_ikey_size);
	return entries;
}

static int iam_lfix_node_check(struct iam_path *path, struct iam_frame *frame)
{
	unsigned int count;
	unsigned int limit;
	unsigned int limit_correct;
	struct iam_entry *entries;

	entries = dx_node_get_entries(path, frame);

	if (frame == path->ip_frames) {
		struct iam_lfix_root *root;

		root = (void *)frame->bh->b_data;
		if (le64_to_cpu(root->ilr_magic) != IAM_LFIX_ROOT_MAGIC)
			return -EIO;
		limit_correct = dx_root_limit(path);
	} else {
		limit_correct = dx_node_limit(path);
	}
	count = dx_get_count(entries);
	limit = dx_get_limit(entries);
	if (count > limit)
		return -EIO;
	if (limit != limit_correct)
		return -EIO;
	return 0;
}

static int iam_lfix_node_load(struct iam_path *path, struct iam_frame *frame)
{
	struct iam_entry *entries;
	void *data;

	entries = dx_node_get_entries(path, frame);
	data = frame->bh->b_data;

	if (frame == path->ip_frames) {
		struct iam_lfix_root *root;

		root = data;
		path->ip_indirect = root->ilr_indirect_levels;
		if (path->ip_ikey_target == NULL)
			path->ip_ikey_target =
				(struct iam_ikey *)path->ip_key_target;
	}
	frame->entries = frame->at = entries;
	return 0;
}

static int iam_lfix_ikeycmp(const struct iam_container *c,
			    const struct iam_ikey *k1,
			    const struct iam_ikey *k2)
{
	return memcmp(k1, k2, c->ic_descr->id_ikey_size);
}

static struct iam_path_descr *iam_lfix_ipd_alloc(const struct iam_container *c,
						 void *area)
{
	return iam_ipd_alloc(area, c->ic_descr->id_ikey_size);
}

static const struct iam_operations iam_lfix_ops = {
	.id_root_ptr    = iam_lfix_root_ptr,
	.id_node_read   = iam_node_read,
	.id_node_init   = iam_lfix_node_init,
	.id_node_check  = iam_lfix_node_check,
	.id_node_load   = iam_lfix_node_load,
	.id_ikeycmp     = iam_lfix_ikeycmp,
	.id_root_inc    = iam_lfix_root_inc,
	.id_ipd_alloc   = iam_lfix_ipd_alloc,
	.id_ipd_free    = iam_ipd_free,
	.id_name        = "lfix"
};

int iam_lfix_guess(struct iam_container *c)
{
	int result;
	struct buffer_head *bh;
	const struct iam_lfix_root *root;

	assert_corr(c->ic_object != NULL);

	result = iam_node_read(c, iam_lfix_root_ptr(c), NULL, &bh);
	if (result == 0) {
		root = (void *)bh->b_data;
		if (le64_to_cpu(root->ilr_magic) == IAM_LFIX_ROOT_MAGIC) {
			struct iam_descr *descr;

			descr = c->ic_descr;
			descr->id_key_size  = le16_to_cpu(root->ilr_keysize);
			descr->id_ikey_size = le16_to_cpu(root->ilr_keysize);
			descr->id_rec_size  = le16_to_cpu(root->ilr_recsize);
			descr->id_ptr_size  = le16_to_cpu(root->ilr_ptrsize);
			descr->id_root_gap  = sizeof(struct iam_lfix_root);
			descr->id_node_gap  = 0;
			descr->id_ops       = &iam_lfix_ops;
			descr->id_leaf_ops  = &iam_lfix_leaf_ops;
			c->ic_root_bh = bh;
		} else {
			result = -EBADF;
			brelse(bh);
		}
	}
	return result;
}

/*
 * Debugging aid.
 */

#define KEYSIZE (8)
#define RECSIZE (8)
#define PTRSIZE (4)

#define LFIX_ROOT_RECNO \
        ((4096 - sizeof(struct iam_lfix_root)) / (KEYSIZE + PTRSIZE))

#define LFIX_INDEX_RECNO (4096 / (KEYSIZE + PTRSIZE))

#define LFIX_LEAF_RECNO \
        ((4096 - sizeof(struct iam_leaf_head)) / (KEYSIZE + RECSIZE))

	struct lfix_root {
	struct iam_lfix_root lr_root;
	struct {
		char key[KEYSIZE];
		char ptr[PTRSIZE];
	} lr_entry[LFIX_ROOT_RECNO];
};

	struct lfix_index {
	struct dx_countlimit li_cl;
	char   li_padding[KEYSIZE + PTRSIZE - sizeof(struct dx_countlimit)];
	struct {
		char key[KEYSIZE];
		char ptr[PTRSIZE];
	} li_entry[LFIX_INDEX_RECNO - 1];
};

	struct lfix_leaf {
		struct iam_leaf_head ll_head;
		struct {
			char key[KEYSIZE];
			char rec[RECSIZE];
		} ll_entry[LFIX_LEAF_RECNO];
	};

#define STORE_UNALIGNED(val, dst)			\
({							\
	typeof(val) __val = (val);			\
	BUILD_BUG_ON(sizeof(val) != sizeof(*(dst)));	\
	memcpy(dst, &__val, sizeof(*(dst)));		\
})

static void lfix_root(void *buf,
		      int blocksize, int keysize, int ptrsize, int recsize)
{
	struct iam_lfix_root *root;
	struct dx_countlimit *limit;
	void *entry;

	root = buf;
	*root = (typeof(*root)) {
		.ilr_magic           = cpu_to_le64(IAM_LFIX_ROOT_MAGIC),
		.ilr_keysize         = cpu_to_le16(keysize),
		.ilr_recsize         = cpu_to_le16(recsize),
		.ilr_ptrsize         = cpu_to_le16(ptrsize),
		.ilr_indirect_levels = 0
	};

	limit = (void *)(root + 1);
	*limit = (typeof(*limit)){
		/*
		 * limit itself + one pointer to the leaf.
		 */
		.count = cpu_to_le16(2),
		.limit = iam_root_limit(sizeof(struct iam_lfix_root),
					blocksize, keysize + ptrsize)
	};

	/* To guarantee that the padding "keysize + ptrsize"
	 * covers the "dx_countlimit" and the "idle_blocks". */
	LASSERT((keysize + ptrsize) >=
		(sizeof(struct dx_countlimit) + sizeof(__u32)));

	entry = (void *)(limit + 1);
	/* Put "idle_blocks" just after the limit. There was padding after
	 * the limit, the "idle_blocks" re-uses part of the padding, so no
	 * compatibility issues with old layout.
	 */
	*(__u32 *)entry = 0;

	/*
	 * Skip over @limit.
	 */
	entry = (void *)(root + 1) + keysize + ptrsize;

	/*
	 * Entry format is <key> followed by <ptr>. In the minimal tree
	 * consisting of a root and single node, <key> is a minimal possible
	 * key.
	 *
	 * XXX: this key is hard-coded to be a sequence of 0's.
	 */

	memset(entry, 0, keysize);
	entry += keysize;
	/* now @entry points to <ptr> */
	if (ptrsize == 4)
		STORE_UNALIGNED(cpu_to_le32(1), (u_int32_t *)entry);
	else
		STORE_UNALIGNED(cpu_to_le64(1), (u_int64_t *)entry);
}

static void lfix_leaf(void *buf,
		      int blocksize, int keysize, int ptrsize, int recsize)
{
	struct iam_leaf_head *head;
	void *entry;

	/* form leaf */
	head = buf;
	*head = (struct iam_leaf_head) {
		.ill_magic = cpu_to_le16(IAM_LEAF_HEADER_MAGIC),
		/*
		 * Leaf contains an entry with the smallest possible key
		 * (created by zeroing).
		 */
		.ill_count = cpu_to_le16(1),
	};

	entry = (void *)(head + 1);
	memset(entry, 0, keysize + recsize);
}

int iam_lfix_create(struct inode *obj,
		    int keysize, int ptrsize, int recsize, handle_t *handle)
{
	struct buffer_head *root_node;
	struct buffer_head *leaf_node;
	struct super_block *sb;
	u32 blknr;
	int result = 0;
	unsigned long bsize;

	assert_corr(obj->i_size == 0);

	sb = obj->i_sb;
	bsize = sb->s_blocksize;
	root_node = osd_ldiskfs_append(handle, obj, &blknr);
	if (IS_ERR(root_node))
		GOTO(out, result = PTR_ERR(root_node));

	leaf_node = osd_ldiskfs_append(handle, obj, &blknr);
	if (IS_ERR(leaf_node))
		GOTO(out_root, result = PTR_ERR(leaf_node));

	lfix_root(root_node->b_data, bsize, keysize, ptrsize, recsize);
	lfix_leaf(leaf_node->b_data, bsize, keysize, ptrsize, recsize);
	ldiskfs_mark_inode_dirty(handle, obj);
	result = ldiskfs_handle_dirty_metadata(handle, NULL, root_node);
	if (result == 0)
		result = ldiskfs_handle_dirty_metadata(handle, NULL, leaf_node);
	if (result != 0)
		ldiskfs_std_error(sb, result);

	brelse(leaf_node);

	GOTO(out_root, result);

out_root:
	brelse(root_node);
out:
	return result;
}
