Viewing: osd_iam_lvar.c

// 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, variable sized keys.
 *
 * Author: Nikita Danilov <nikita@clusterfs.com>
 */

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

/*
 * Leaf operations.
 */

enum {
	IAM_LVAR_LEAF_MAGIC = 0x1973
};

struct lvar_leaf_header {
	__le16 vlh_magic; /* magic number IAM_LVAR_LEAF_MAGIC */
	__le16 vlh_used;  /* used bytes, including header */
};

/*
 * Format of leaf entry:
 *
 * __le16 keysize
 *     u8 key[keysize]
 *     u8 record[rec_size]
 *
 * Entries are ordered in key order.
 */

typedef u32 lvar_hash_t;

struct lvar_leaf_entry {
	__le32 vle_hash;
	__le16 vle_keysize;
	u8 vle_key[];
};

#define PDIFF(ptr0, ptr1) (((char *)(ptr0)) - ((char *)(ptr1)))


static inline int blocksize(const struct iam_leaf *leaf)
{
	return iam_leaf_container(leaf)->ic_object->i_sb->s_blocksize;
}

static inline const char *kchar(const struct iam_key *key)
{
	return (void *)key;
}

static inline struct iam_lentry *lvar_lentry(const struct lvar_leaf_entry *ent)
{
	return (struct iam_lentry *)ent;
}

static inline struct lvar_leaf_entry *lentry_lvar(const struct iam_lentry *lent)
{
	return (struct lvar_leaf_entry *)lent;
}


static inline int e_keysize(const struct lvar_leaf_entry *ent)
{
	return le16_to_cpu(ent->vle_keysize);
}

enum {
	LVAR_PAD   = 4,
	LVAR_ROUND = LVAR_PAD - 1
};

static inline int getsize(const struct iam_leaf *leaf, int namelen, int recsize)
{
	BUILD_BUG_ON((LVAR_PAD & (LVAR_PAD - 1)));

	return (offsetof(struct lvar_leaf_entry, vle_key) +
			namelen + recsize + LVAR_ROUND) & ~LVAR_ROUND;
}

static inline int rec_size(const struct iam_rec *rec)
{
	return *(const char *)rec;
}

static inline struct iam_rec *e_rec(const struct lvar_leaf_entry *ent)
{
	return ((void *)ent) +
		offsetof(struct lvar_leaf_entry, vle_key) + e_keysize(ent);
}

static inline int e_size(const struct iam_leaf *leaf,
			 const struct lvar_leaf_entry *ent)
{
	return getsize(leaf, e_keysize(ent), rec_size(e_rec(ent)));
}

static inline char *e_char(const struct lvar_leaf_entry *ent)
{
	return (char *)&ent->vle_key;
}

static inline struct iam_key *e_key(const struct lvar_leaf_entry *ent)
{
	return (struct iam_key *)e_char(ent);
}

static inline lvar_hash_t e_hash(const struct lvar_leaf_entry *ent)
{
	return le32_to_cpu(ent->vle_hash);
}

static inline struct lvar_leaf_entry *e_next(const struct iam_leaf *leaf,
					     const struct lvar_leaf_entry *ent)
{
	return ((void *)ent) + e_size(leaf, ent);
}

#define LVAR_HASH_SANDWICH  (0)
#define LVAR_HASH_TEA       (1)
#define LVAR_HASH_R5        (0)
#define LVAR_HASH_PREFIX    (0)

#ifdef HAVE_LDISKFSFS_DIRHASH_WITH_DIR
#define e_ldiskfsfs_dirhash(dir, name, len, info) \
		ldiskfsfs_dirhash((dir), (name), (len), (info))
#else
#define e_ldiskfsfs_dirhash(dir, name, len, info) \
		ldiskfsfs_dirhash((name), (len), (info))
#endif

static u32 hash_build0(const struct inode *dir, const char *name, int namelen)
{
	u32 result;

	if (namelen == 0)
		return 0;
	if (strncmp(name, ".", 1) == 0 && namelen == 1)
		return 1;
	if (strncmp(name, "..", 2) == 0 && namelen == 2)
		return 2;

	if (LVAR_HASH_PREFIX) {
		result = 0;
		strncpy((void *)&result,
			name, min_t(int, namelen, sizeof(result)));
	} else {
		struct ldiskfs_dx_hash_info hinfo;

		hinfo.hash_version = LDISKFS_DX_HASH_TEA;
		hinfo.seed = NULL;
		e_ldiskfsfs_dirhash(dir, name, namelen, &hinfo);
		result = hinfo.hash;
		if (LVAR_HASH_SANDWICH) {
			u32 result2;

			hinfo.hash_version = LDISKFS_DX_HASH_TEA;
			hinfo.seed = NULL;
			e_ldiskfsfs_dirhash(dir, name, namelen, &hinfo);
			result2 = hinfo.hash;
			result = (0xfc000000 & result2) | (0x03ffffff & result);
		}
	}
	return result;
}

enum {
	HASH_GRAY_AREA = 1024,
	HASH_MAX_SIZE  = 0x7fffffffUL
};

static u32 hash_build(const struct inode *dir, const char *name, int namelen)
{
	u32 hash;

	hash = (hash_build0(dir, name, namelen) << 1) & HASH_MAX_SIZE;
	if (hash > HASH_MAX_SIZE - HASH_GRAY_AREA)
		hash &= HASH_GRAY_AREA - 1;
	return hash;
}

static inline lvar_hash_t get_hash(const struct inode *dir,
				   const char *name, int namelen)
{
	return hash_build(dir, name, namelen);
}

static inline lvar_hash_t iam_get_hash(const struct iam_leaf *leaf,
				       const char *name, int namelen)
{
	struct iam_path *iam_path = iam_leaf_path(leaf);

	return get_hash(iam_path_obj(iam_path), name, namelen);
}

static inline int e_eq(const struct lvar_leaf_entry *ent,
		       const char *name, int namelen)
{
	return namelen == e_keysize(ent) && !memcmp(e_char(ent), name, namelen);
}

static inline int e_cmp(const struct iam_leaf *leaf,
			const struct lvar_leaf_entry *ent, lvar_hash_t hash)
{
	lvar_hash_t ehash;

	ehash = e_hash(ent);
	return ehash == hash ? 0 : (ehash < hash ? -1 : 1);
}

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

static int h_used(const struct lvar_leaf_header *hdr)
{
	return le16_to_cpu(hdr->vlh_used);
}

static void h_used_adj(const struct iam_leaf *leaf,
		       struct lvar_leaf_header *hdr, int adj)
{
	int used;

	used = h_used(hdr) + adj;
	assert_corr(sizeof(*hdr) <= used && used <= blocksize(leaf));
	hdr->vlh_used = cpu_to_le16(used);
}

static struct lvar_leaf_entry *n_start(const struct iam_leaf *leaf)
{
	return (void *)leaf->il_bh->b_data + sizeof(struct lvar_leaf_header);
}

static struct lvar_leaf_entry *n_end(const struct iam_leaf *l)
{
	return (void *)l->il_bh->b_data + h_used(n_head(l));
}

static struct lvar_leaf_entry *n_cur(const struct iam_leaf *l)
{
	return lentry_lvar(l->il_at);
}

#if LDISKFS_CORRECTNESS_ON
static int n_at_rec(const struct iam_leaf *folio)
{
	return n_start(folio) <= lentry_lvar(folio->il_at) &&
		lentry_lvar(folio->il_at) < n_end(folio);
}

#if LDISKFS_INVARIANT_ON
static int n_invariant(const struct iam_leaf *leaf)
{
	struct iam_path *path;
	struct inode *dir;
	struct lvar_leaf_entry *scan;
	struct lvar_leaf_entry *end;
	lvar_hash_t hash;
	lvar_hash_t nexthash;
	lvar_hash_t starthash;

	end  = n_end(leaf);
	hash = 0;
	path = leaf->il_path;

	if (h_used(n_head(leaf)) > blocksize(leaf))
		return 0;

	dir = iam_path_obj(iam_path);
	/*
	 * Delimiting key in the parent index node. Clear least bit to account
	 * for hash collision marker.
	 */
	starthash = *(lvar_hash_t *)iam_ikey_at(path, path->ip_frame->at) & ~1;
	for (scan = n_start(leaf); scan < end; scan = e_next(leaf, scan)) {
		nexthash = e_hash(scan);
		if (nexthash != get_hash(dir, e_char(scan), e_keysize(scan))) {
			BREAKPOINT();
			return 0;
		}
		if (nexthash < hash) {
			BREAKPOINT();
			return 0;
		}
		hash = nexthash;
	}
	if (scan != end) {
		BREAKPOINT();
		return 0;
	}
	return 1;
}
/* LDISKFS_INVARIANT_ON */
#endif

/* LDISKFS_CORRECTNESS_ON */
#endif

static struct iam_ikey *lvar_ikey(const struct iam_leaf *l,
				  struct iam_ikey *key)
{
	lvar_hash_t *hash;

	assert_corr(n_at_rec(l));

	hash = (void *)key;
	*hash = e_hash(n_cur(l));
	return key;
}

static struct iam_key *lvar_key(const struct iam_leaf *l)
{
	return e_key(n_cur(l));
}

static int lvar_key_size(const struct iam_leaf *l)
{
	return e_keysize(n_cur(l));
}

static void lvar_start(struct iam_leaf *l)
{
	l->il_at = lvar_lentry(n_start(l));
}

static int lvar_init(struct iam_leaf *l)
{
	int result;
	int used;
	struct lvar_leaf_header *head;

	assert_corr(l->il_bh != NULL);

	head = n_head(l);
	used = h_used(head);
	if (le16_to_cpu(head->vlh_magic) == IAM_LVAR_LEAF_MAGIC &&
			used <= blocksize(l)) {
		l->il_at = l->il_entries = lvar_lentry(n_start(l));
		result = 0;
	} else {
		struct inode *obj;

		obj = iam_leaf_container(l)->ic_object;
		CERROR(
		"Bad magic in node %llu (#%lu): %#x != %#x or wrong used: %d\n",
		(unsigned long long)l->il_bh->b_blocknr, obj->i_ino,
		le16_to_cpu(head->vlh_magic), IAM_LVAR_LEAF_MAGIC,
		used);
		result = -EIO;
	}
	return result;
}

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

static struct iam_rec *lvar_rec(const struct iam_leaf *l)
{
	assert_corr(n_at_rec(l));
	return e_rec(n_cur(l));
}

static void lvar_next(struct iam_leaf *l)
{
	assert_corr(n_at_rec(l));
	assert_corr(iam_leaf_is_locked(l));
	l->il_at = lvar_lentry(e_next(l, n_cur(l)));
}

static int lvar_lookup(struct iam_leaf *leaf, const struct iam_key *k)
{
	struct lvar_leaf_entry *found;
	struct lvar_leaf_entry *scan;
	struct lvar_leaf_entry *end;
	int result;
	const char *name;
	int namelen;
	int found_equal;
	lvar_hash_t hash;
	int last;

	assert_inv(n_invariant(leaf));
	end = n_end(leaf);

	name = kchar(k);
	namelen = strlen(name);
	hash = iam_get_hash(leaf, name, namelen);
	found = NULL;
	found_equal = 0;
	last = 1;

	for (scan = n_start(leaf); scan < end; scan = e_next(leaf, scan)) {
		lvar_hash_t scan_hash;

		scan_hash = e_hash(scan);
		if (scan_hash < hash)
			found = scan;
		else if (scan_hash == hash) {
			if (e_eq(scan, name, namelen)) {
				/*
				 * perfect match
				 */
				leaf->il_at = lvar_lentry(scan);
				return IAM_LOOKUP_EXACT;
			} else if (!found_equal) {
				found = scan;
				found_equal = 1;
			}
		} else {
			last = 0;
			break;
		}
	}
	if (found == NULL) {
		/*
		 * @k is less than all hashes in the leaf.
		 */
		lvar_start(leaf);
		result = IAM_LOOKUP_BEFORE;
	} else {
		leaf->il_at = lvar_lentry(found);
		result = IAM_LOOKUP_OK;
		assert_corr(n_at_rec(leaf));
	}
	if (last)
		result |= IAM_LOOKUP_LAST;
	assert_inv(n_invariant(leaf));

	return result;
}

static int lvar_ilookup(struct iam_leaf *leaf, const struct iam_ikey *ik)
{
	struct lvar_leaf_entry *scan;
	struct lvar_leaf_entry *end;
	lvar_hash_t hash;

	assert_inv(n_invariant(leaf));
	end  = n_end(leaf);
	hash = *(const lvar_hash_t *)ik;

	lvar_start(leaf);
	for (scan = n_start(leaf); scan < end; scan = e_next(leaf, scan)) {
		lvar_hash_t scan_hash;

		scan_hash = e_hash(scan);
		if (scan_hash > hash)
			return scan == n_start(leaf) ?
				IAM_LOOKUP_BEFORE : IAM_LOOKUP_OK;
		leaf->il_at = lvar_lentry(scan);
		if (scan_hash == hash)
			return IAM_LOOKUP_EXACT;
	}
	assert_inv(n_invariant(leaf));
	/*
	 * @ik is greater than any key in the node. Return last record in the
	 * node.
	 */
	return IAM_LOOKUP_OK;
}

static void __lvar_key_set(struct iam_leaf *l, const struct iam_key *k)
{
	memcpy(e_key(n_cur(l)), k, e_keysize(n_cur(l)));
}

static void lvar_key_set(struct iam_leaf *l, const struct iam_key *k)
{
	assert_corr(n_at_rec(l));
	assert_corr(strlen(kchar(k)) == e_keysize(n_cur(l)));
	assert_corr(iam_leaf_is_locked(l));
	__lvar_key_set(l, k);
	assert_inv(n_invariant(l));
}

static int lvar_key_cmp(const struct iam_leaf *l, const struct iam_key *k)
{
	lvar_hash_t hash;
	const char *name;

	name = kchar(k);

	hash = iam_get_hash(l, name, strlen(name));
	return e_cmp(l, n_cur(l), hash);
}

static int lvar_key_eq(const struct iam_leaf *l, const struct iam_key *k)
{
	const char *name;

	name = kchar(k);
	return e_eq(n_cur(l), name, strlen(name));
}

static void __lvar_rec_set(struct iam_leaf *l, const struct iam_rec *r)
{
	memcpy(e_rec(n_cur(l)), r, rec_size(r));
}

static void lvar_rec_set(struct iam_leaf *l, const struct iam_rec *r)
{
	assert_corr(n_at_rec(l));
	assert_corr(iam_leaf_is_locked(l));
	__lvar_rec_set(l, r);
	assert_inv(n_invariant(l));
}

static int lvar_rec_eq(const struct iam_leaf *l, const struct iam_rec *r)
{
	struct iam_rec *rec = e_rec(n_cur(l));

	if (rec_size(rec) != rec_size(r))
		return 0;
	return !memcmp(rec, r, rec_size(r));
}

static void lvar_rec_get(const struct iam_leaf *l, struct iam_rec *r)
{
	struct iam_rec *rec;

	rec = e_rec(n_cur(l));
	assert_corr(n_at_rec(l));
	assert_corr(iam_leaf_is_locked(l));
	memcpy(r, rec, rec_size(rec));
	assert_inv(n_invariant(l));
}

static int lvar_can_add(const struct iam_leaf *l,
			const struct iam_key *k, const struct iam_rec *r)
{
	assert_corr(iam_leaf_is_locked(l));
	return h_used(n_head(l)) +
		getsize(l, strlen(kchar(k)), rec_size(r)) <= blocksize(l);
}

static int lvar_at_end(const struct iam_leaf *folio)
{
	assert_corr(iam_leaf_is_locked(folio));
	return n_cur(folio) == n_end(folio);
}

static void lvar_rec_add(struct iam_leaf *leaf,
			 const struct iam_key *k, const struct iam_rec *r)
{
	const char *key;
	int ksize;
	int shift;
	void *end;
	void *start;
	ptrdiff_t diff;

	assert_corr(lvar_can_add(leaf, k, r));
	assert_inv(n_invariant(leaf));
	assert_corr(iam_leaf_is_locked(leaf));

	key   = kchar(k);
	ksize = strlen(key);
	shift = getsize(leaf, ksize, rec_size(r));

	if (!lvar_at_end(leaf)) {
		assert_corr(n_cur(leaf) < n_end(leaf));
		end = n_end(leaf);
		if (lvar_key_cmp(leaf, k) <= 0)
			lvar_next(leaf);
		else
			/*
			 * Another exceptional case: insertion with the key
			 * less than least key in the leaf.
			 */
			assert_corr(leaf->il_at == leaf->il_entries);

		start = leaf->il_at;
		diff  = PDIFF(end, start);
		assert_corr(diff >= 0);
		memmove(start + shift, start, diff);
	}
	h_used_adj(leaf, n_head(leaf), shift);
	n_cur(leaf)->vle_keysize = cpu_to_le16(ksize);
	n_cur(leaf)->vle_hash = cpu_to_le32(iam_get_hash(leaf, key, ksize));
	__lvar_key_set(leaf, k);
	__lvar_rec_set(leaf, r);
	assert_corr(n_at_rec(leaf));
	assert_inv(n_invariant(leaf));
}

static void lvar_rec_del(struct iam_leaf *leaf, int shift)
{
	void *next;
	void *end;
	int nob;

	assert_corr(n_at_rec(leaf));
	assert_inv(n_invariant(leaf));
	assert_corr(iam_leaf_is_locked(leaf));

	end  = n_end(leaf);
	next = e_next(leaf, n_cur(leaf));
	nob  = e_size(leaf, n_cur(leaf));
	memmove(leaf->il_at, next, end - next);
	h_used_adj(leaf, n_head(leaf), -nob);
	assert_inv(n_invariant(leaf));
}

static void lvar_init_new(struct iam_container *c, struct buffer_head *bh)
{
	struct lvar_leaf_header *hdr;

	hdr = (struct lvar_leaf_header *)bh->b_data;
	hdr->vlh_magic = cpu_to_le16(IAM_LVAR_LEAF_MAGIC);
	hdr->vlh_used  = sizeof(*hdr);
}

static struct lvar_leaf_entry *find_pivot(const struct iam_leaf *leaf,
					  struct lvar_leaf_entry **prev)
{
	void *scan;
	void *start;
	int threshold;

	*prev = NULL;
	threshold = blocksize(leaf) / 2;
	for (scan = start = n_start(leaf); scan - start <= threshold;
			*prev = scan, scan = e_next(leaf, scan)) {
		;
	}
	return scan;
}

static void lvar_split(struct iam_leaf *leaf, struct buffer_head **bh,
		       iam_ptr_t new_blknr)
{
	struct lvar_leaf_entry *first_to_move;
	struct lvar_leaf_entry *last_to_stay;
	struct iam_path *path;
	struct lvar_leaf_header *hdr;
	struct buffer_head *new_leaf;
	ptrdiff_t tomove;
	lvar_hash_t hash;

	assert_inv(n_invariant(leaf));
	assert_corr(iam_leaf_is_locked(leaf));

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

	hdr = (void *)new_leaf->b_data;

	first_to_move = find_pivot(leaf, &last_to_stay);
	assert_corr(last_to_stay != NULL);
	assert_corr(e_next(leaf, last_to_stay) == first_to_move);

	hash = e_hash(first_to_move);
	if (hash == e_hash(last_to_stay))
		/*
		 * Duplicate hash.
		 */
		hash |= 1;

	tomove = PDIFF(n_end(leaf), first_to_move);
	memmove(hdr + 1, first_to_move, tomove);

	h_used_adj(leaf, hdr, tomove);
	h_used_adj(leaf, n_head(leaf), -tomove);

	assert_corr(n_end(leaf) == first_to_move);

	if (n_cur(leaf) >= first_to_move) {
		/*
		 * insertion point moves into new leaf.
		 */
		ptrdiff_t shift;

		shift = PDIFF(leaf->il_at, first_to_move);
		*bh = leaf->il_bh;
		leaf->il_bh = new_leaf;
		leaf->il_curidx = new_blknr;

		assert_corr(iam_leaf_is_locked(leaf));
		lvar_init(leaf);
		/*
		 * init cannot fail, as node was just initialized.
		 */
		assert_corr(result == 0);
		leaf->il_at = ((void *)leaf->il_at) + 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, (struct iam_ikey *)&hash,
			    new_blknr);
	assert_corr(n_cur(leaf) < n_end(leaf));
	assert_inv(n_invariant(leaf));
}

static int lvar_leaf_empty(struct iam_leaf *leaf)
{
	return h_used(n_head(leaf)) == sizeof(struct lvar_leaf_header);
}

static const struct iam_leaf_operations lvar_leaf_ops = {
	.init           = lvar_init,
	.init_new       = lvar_init_new,
	.fini           = lvar_fini,
	.start          = lvar_start,
	.next           = lvar_next,
	.key            = lvar_key,
	.ikey           = lvar_ikey,
	.rec            = lvar_rec,
	.key_set        = lvar_key_set,
	.key_cmp        = lvar_key_cmp,
	.key_eq         = lvar_key_eq,
	.key_size       = lvar_key_size,
	.rec_set        = lvar_rec_set,
	.rec_eq         = lvar_rec_eq,
	.rec_get        = lvar_rec_get,
	.lookup         = lvar_lookup,
	.ilookup        = lvar_ilookup,
	.at_end         = lvar_at_end,
	.rec_add        = lvar_rec_add,
	.rec_del        = lvar_rec_del,
	.can_add        = lvar_can_add,
	.split          = lvar_split,
	.leaf_empty     = lvar_leaf_empty,
};

/*
 * Index operations.
 */

enum {
	/* grep -E -i '^o?x?[olabcdef]*$' /usr/share/dict/words */
	IAM_LVAR_ROOT_MAGIC = 0xb01dface
};

struct lvar_root {
	__le32 vr_magic;
	__le16 vr_recsize;
	__le16 vr_ptrsize;
	u8 vr_indirect_levels;
	u8 vr_padding0;
	__le16 vr_padding1;
};

static u32 lvar_root_ptr(struct iam_container *c)
{
	return 0;
}

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

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

	assert_corr(iam_frame_is_locked(path, frame));
	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->vr_magic) == IAM_LVAR_ROOT_MAGIC);
	root->vr_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 lvar_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 lvar_root *root;

		root = (void *)frame->bh->b_data;
		if (le32_to_cpu(root->vr_magic) != IAM_LVAR_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 lvar_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 lvar_root *root;
		const char *name;

		root = data;
		name = kchar(path->ip_key_target);
		path->ip_indirect = root->vr_indirect_levels;
		if (path->ip_ikey_target == NULL) {
			path->ip_ikey_target = iam_path_ikey(path, 4);
			*(lvar_hash_t *)path->ip_ikey_target =
				get_hash(iam_path_obj(path), name,
					 strlen(name));
		}
	}
	frame->entries = frame->at = entries;
	return 0;
}

static int lvar_ikeycmp(const struct iam_container *c,
			const struct iam_ikey *k1, const struct iam_ikey *k2)
{
	lvar_hash_t p1 = le32_to_cpu(*(lvar_hash_t *)k1);
	lvar_hash_t p2 = le32_to_cpu(*(lvar_hash_t *)k2);

	return p1 > p2 ? 1 : (p1 < p2 ? -1 : 0);
}

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

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

	isize = sizeof(lvar_hash_t) + ptrsize;
	root = buf;
	*root = (typeof(*root)) {
		.vr_magic            = cpu_to_le32(IAM_LVAR_ROOT_MAGIC),
		.vr_recsize          = cpu_to_le16(recsize),
		.vr_ptrsize          = cpu_to_le16(ptrsize),
		.vr_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 lvar_root), blocksize,
					sizeof(lvar_hash_t) + 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) + isize;

	/*
	 * Entry format is <key> followed by <ptr>. In the minimal tree
	 * consisting of a root and single node, <key> is a minimal possible
	 * key.
	 */
	*(lvar_hash_t *)entry = 0;
	entry += sizeof(lvar_hash_t);
	/* now @entry points to <ptr> */
	if (ptrsize == 4)
		*(u_int32_t *)entry = cpu_to_le32(1);
	else
		*(u_int64_t *)entry = cpu_to_le64(1);
}

static int lvar_esize(int namelen, int recsize)
{
	return (offsetof(struct lvar_leaf_entry, vle_key) +
			namelen + recsize + LVAR_ROUND) & ~LVAR_ROUND;
}

static void lvar_leaf(void *buf,
		      int blocksize, int keysize, int ptrsize, int recsize)
{
	struct lvar_leaf_header *head;
	struct lvar_leaf_entry *entry;

	/* form leaf */
	head = buf;
	*head = (typeof(*head)) {
		.vlh_magic = cpu_to_le16(IAM_LVAR_LEAF_MAGIC),
		.vlh_used  = cpu_to_le16(sizeof(*head) + lvar_esize(0, recsize))
	};
	entry = (void *)(head + 1);
	*entry = (typeof(*entry)) {
		.vle_hash    = 0,
		.vle_keysize = 0
	};
	memset(e_rec(entry), 0, recsize);
	*(char *)e_rec(entry) = recsize;
}

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

	lvar_root(root_node->b_data, bsize, keysize, ptrsize, recsize);
	lvar_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;
}

static const struct iam_operations lvar_ops = {
	.id_root_ptr    = lvar_root_ptr,
	.id_node_read   = iam_node_read,
	.id_node_init   = lvar_node_init,
	.id_node_check  = lvar_node_check,
	.id_node_load   = lvar_node_load,
	.id_ikeycmp     = lvar_ikeycmp,
	.id_root_inc    = lvar_root_inc,
	.id_ipd_alloc   = lvar_ipd_alloc,
	.id_ipd_free    = iam_ipd_free,
	.id_name        = "lvar"
};

int iam_lvar_guess(struct iam_container *c)
{
	int result;
	struct buffer_head *bh;
	const struct lvar_root *root;

	assert_corr(c->ic_object != NULL);

	result = iam_node_read(c, lvar_root_ptr(c), NULL, &bh);
	if (result == 0) {
		root = (void *)bh->b_data;

		if (le32_to_cpu(root->vr_magic) == IAM_LVAR_ROOT_MAGIC) {
			struct iam_descr *descr;

			descr = c->ic_descr;
			descr->id_key_size  = LDISKFS_NAME_LEN;
			descr->id_ikey_size = sizeof(lvar_hash_t);
			descr->id_rec_size  = le16_to_cpu(root->vr_recsize);
			descr->id_ptr_size  = le16_to_cpu(root->vr_ptrsize);
			descr->id_root_gap  = sizeof(*root);
			descr->id_node_gap  = 0;
			descr->id_ops       = &lvar_ops;
			descr->id_leaf_ops  = &lvar_leaf_ops;
			c->ic_root_bh = bh;
		} else {
			result = -EBADF;
			brelse(bh);
		}
	}
	return result;
}