// SPDX-License-Identifier: GPL-2.0

/*
 * Copyright (C) 2013, Trustees of Indiana University
 *
 * Copyright (c) 2017, Intel Corporation.
 *
 * Author: Joshua Walgenbach <jjw@iu.edu>
 */

#include <linux/rbtree.h>
#include <lustre_net.h>
#include "nodemap_internal.h"

/**
 * idmap_create() - Allocate the lu_idmap structure
 * @client_id: client uid or gid
 * @fs_id: filesystem uid or gid
 *
 * Return alloated lu_idmap structure on success, NULL otherwise
 */
struct lu_idmap *idmap_create(__u32 client_id, __u32 fs_id)
{
	struct lu_idmap	*idmap;

	OBD_ALLOC_PTR(idmap);
	if (idmap == NULL) {
		CERROR("cannot allocate lu_idmap of size %zu bytes\n",
		       sizeof(idmap));
		return NULL;
	}

	idmap->id_client = client_id;
	idmap->id_fs = fs_id;
	RB_CLEAR_NODE(&idmap->id_client_to_fs);
	RB_CLEAR_NODE(&idmap->id_fs_to_client);
	return idmap;
}

static void idmap_destroy(struct lu_idmap *idmap)

{
	LASSERT(RB_EMPTY_NODE(&idmap->id_fs_to_client) == 0);
	LASSERT(RB_EMPTY_NODE(&idmap->id_client_to_fs) == 0);
	OBD_FREE_PTR(idmap);
}

/**
 * idmap_insert() - Insert idmap into the proper trees
 * @id_type: NODEMAP_UID or NODEMAP_GID or NODEMAP_PROJID
 * @idmap: lu_idmap structure to insert
 * @nodemap: nodemap to associate with the map
 *
 * Return:
 * * %NULL		 on success
 * * %ERR_PTR(-EEXIST) if this idmap already exists
 * * %struct lu_idmap	 if only id_client or id_fs of this idmap
 *			 is matched, return the matched idmap.
 *			 The caller will delete this old idmap and
 *			 its index before insert the new idmap again.
 */
struct lu_idmap *idmap_insert(enum nodemap_id_type id_type,
			      struct lu_idmap *idmap,
			      struct lu_nodemap *nodemap)
{
	struct lu_idmap		*fwd_cur = NULL;
	struct lu_idmap		*bck_cur = NULL;
	struct rb_node		*fwd_parent = NULL;
	struct rb_node		*bck_parent = NULL;
	struct rb_node		**fwd_node;
	struct rb_node		**bck_node;
	struct rb_root		*fwd_root;
	struct rb_root		*bck_root;
	bool			fwd_found = false;
	bool			bck_found = false;

	ENTRY;

	/* for purposes in idmap client to fs is forward
	 * mapping, fs to client is backward mapping
	 */
	if (id_type == NODEMAP_UID) {
		fwd_root = &nodemap->nm_client_to_fs_uidmap;
		bck_root = &nodemap->nm_fs_to_client_uidmap;
	} else if (id_type == NODEMAP_GID) {
		fwd_root = &nodemap->nm_client_to_fs_gidmap;
		bck_root = &nodemap->nm_fs_to_client_gidmap;
	} else if (id_type == NODEMAP_PROJID) {
		fwd_root = &nodemap->nm_client_to_fs_projidmap;
		bck_root = &nodemap->nm_fs_to_client_projidmap;
	} else {
		RETURN(ERR_PTR(-EINVAL));
	}

	fwd_node = &fwd_root->rb_node;
	bck_node = &bck_root->rb_node;

	/* find fwd and bck idmap nodes before insertion or
	 * replacing to prevent split brain idmaps
	 */
	while (*fwd_node) {
		fwd_parent = *fwd_node;
		fwd_cur = rb_entry(*fwd_node, struct lu_idmap,
				   id_client_to_fs);

		if (idmap->id_client < fwd_cur->id_client) {
			fwd_node = &((*fwd_node)->rb_left);
		} else if (idmap->id_client > fwd_cur->id_client) {
			fwd_node = &((*fwd_node)->rb_right);
		} else {
			fwd_found = true;
			break;
		}
	}

	while (*bck_node) {
		bck_parent = *bck_node;
		bck_cur = rb_entry(*bck_node, struct lu_idmap,
				   id_fs_to_client);

		if (idmap->id_fs < bck_cur->id_fs) {
			bck_node = &((*bck_node)->rb_left);
		} else if (idmap->id_fs > bck_cur->id_fs) {
			bck_node = &((*bck_node)->rb_right);
		} else {
			bck_found = true;
			break;
		}
	}

	/* Already exists */
	if (fwd_found && bck_found)
		RETURN(ERR_PTR(-EEXIST));

	/* Insert a new idmap */
	if (!fwd_found && !bck_found) {
		CDEBUG(D_INFO, "Insert a new idmap %d:%d\n",
		       idmap->id_client, idmap->id_fs);
		rb_link_node(&idmap->id_client_to_fs, fwd_parent, fwd_node);
		rb_insert_color(&idmap->id_client_to_fs, fwd_root);
		rb_link_node(&idmap->id_fs_to_client, bck_parent, bck_node);
		rb_insert_color(&idmap->id_fs_to_client, bck_root);
		RETURN(NULL);
	}

	/* Only id_client or id_fs is matched */
	RETURN(fwd_found ? fwd_cur : bck_cur);
}

/**
 * idmap_delete() - Delete idmap from the correct nodemap tree
 * @id_type: 0 for UID and 1 for GID
 * @idmap: idmap to delete
 * @nodemap: assoicated idmap
 */
void idmap_delete(enum nodemap_id_type id_type, struct lu_idmap *idmap,
		  struct lu_nodemap *nodemap)
{
	struct rb_root *fwd_root;
	struct rb_root *bck_root;

	if (id_type == NODEMAP_UID) {
		fwd_root = &nodemap->nm_client_to_fs_uidmap;
		bck_root = &nodemap->nm_fs_to_client_uidmap;
	} else if (id_type == NODEMAP_GID) {
		fwd_root = &nodemap->nm_client_to_fs_gidmap;
		bck_root = &nodemap->nm_fs_to_client_gidmap;
	} else if (id_type == NODEMAP_PROJID) {
		fwd_root = &nodemap->nm_client_to_fs_projidmap;
		bck_root = &nodemap->nm_fs_to_client_projidmap;
	} else {
		return;
	}

	rb_erase(&idmap->id_client_to_fs, fwd_root);
	rb_erase(&idmap->id_fs_to_client, bck_root);

	idmap_destroy(idmap);
}

/**
 * idmap_search() - Search for an existing id in the nodemap trees.
 * @nodemap: nodemap trees to search
 * @tree_type: 0 for filesystem to client maps
 *             1 for client to filesystem maps
 * @id_type: 0 for UID or 1 for GID
 * @id: numeric id for which to search
 *
 * Returns lu_idmap structure with the map on success
 */
struct lu_idmap *idmap_search(struct lu_nodemap *nodemap,
			      enum nodemap_tree_type tree_type,
			      enum nodemap_id_type id_type,
			      const __u32 id)
{
	struct rb_node	*node;
	struct rb_root	*root = NULL;
	struct lu_idmap	*idmap;

	ENTRY;

	if (id_type == NODEMAP_UID && tree_type == NODEMAP_FS_TO_CLIENT)
		root = &nodemap->nm_fs_to_client_uidmap;
	else if (id_type == NODEMAP_UID && tree_type == NODEMAP_CLIENT_TO_FS)
		root = &nodemap->nm_client_to_fs_uidmap;
	else if (id_type == NODEMAP_GID && tree_type == NODEMAP_FS_TO_CLIENT)
		root = &nodemap->nm_fs_to_client_gidmap;
	else if (id_type == NODEMAP_GID && tree_type == NODEMAP_CLIENT_TO_FS)
		root = &nodemap->nm_client_to_fs_gidmap;
	else if (id_type == NODEMAP_PROJID && tree_type == NODEMAP_FS_TO_CLIENT)
		root = &nodemap->nm_fs_to_client_projidmap;
	else if (id_type == NODEMAP_PROJID && tree_type == NODEMAP_CLIENT_TO_FS)
		root = &nodemap->nm_client_to_fs_projidmap;

	node = root->rb_node;

	if (tree_type == NODEMAP_FS_TO_CLIENT) {
		while (node) {
			idmap = rb_entry(node, struct lu_idmap,
					 id_fs_to_client);
			if (id < idmap->id_fs)
				node = node->rb_left;
			else if (id > idmap->id_fs)
				node = node->rb_right;
			else
				RETURN(idmap);
		}
	} else {
		while (node) {
			idmap = rb_entry(node, struct lu_idmap,
					 id_client_to_fs);
			if (id < idmap->id_client)
				node = node->rb_left;
			else if (id > idmap->id_client)
				node = node->rb_right;
			else
				RETURN(idmap);
		}
	}

	RETURN(NULL);
}

/*
 * delete all idmap trees from a nodemap
 *
 * \param	nodemap		nodemap to delete trees from
 *
 * This uses the postorder safe traversal code that is committed
 * in a later kernel. Each lu_idmap strucuture is destroyed.
 */
void idmap_delete_tree(struct lu_nodemap *nodemap)
{
	struct lu_idmap		*idmap;
	struct lu_idmap		*temp;
	struct rb_root		root;

	root = nodemap->nm_fs_to_client_uidmap;
	rbtree_postorder_for_each_entry_safe(idmap, temp, &root,
					     id_fs_to_client) {
		idmap_destroy(idmap);
	}

	root = nodemap->nm_client_to_fs_gidmap;
	rbtree_postorder_for_each_entry_safe(idmap, temp, &root,
					     id_client_to_fs) {
		idmap_destroy(idmap);
	}

	root = nodemap->nm_client_to_fs_projidmap;
	rbtree_postorder_for_each_entry_safe(idmap, temp, &root,
					     id_client_to_fs) {
		idmap_destroy(idmap);
	}
}

/*
 * copy all idmap trees from a source nodemap to a dest nodemap
 *
 * \param	dst		nodemap to copy trees to
 * \param	src		nodemap to copy trees from
 *
 * \retval	0 on success, error code otherwise
 *
 * This uses the postorder safe traversal code that is committed
 * in a later kernel. Each lu_idmap structure is copied.
 * No need for this function to hold nm_idmap_lock, as it is called
 * only when a sub-nodemap is first attached to a parent.
 */
int idmap_copy_tree(struct lu_nodemap *dst, struct lu_nodemap *src)
{
	struct lu_idmap *idmap, *temp, *idmap_new, *err;
	struct rb_root root;
	int rc = 0;

	root = src->nm_fs_to_client_uidmap;
	rbtree_postorder_for_each_entry_safe(idmap, temp, &root,
					     id_fs_to_client) {
		idmap_new = idmap_create(idmap->id_client, idmap->id_fs);
		if (!idmap_new)
			GOTO(out_copy_tree, rc = -ENOMEM);

		err = idmap_insert(NODEMAP_UID, idmap_new, dst);
		if (err) {
			OBD_FREE_PTR(idmap);
			GOTO(out_copy_tree,
			     rc = IS_ERR(err) ? PTR_ERR(err) : -EEXIST);
		}
	}

	root = src->nm_client_to_fs_gidmap;
	rbtree_postorder_for_each_entry_safe(idmap, temp, &root,
					     id_client_to_fs) {
		idmap_new = idmap_create(idmap->id_client, idmap->id_fs);
		if (!idmap_new)
			GOTO(out_copy_tree, rc = -ENOMEM);

		err = idmap_insert(NODEMAP_GID, idmap_new, dst);
		if (err) {
			OBD_FREE_PTR(idmap);
			GOTO(out_copy_tree,
			     rc = IS_ERR(err) ? PTR_ERR(err) : -EEXIST);
		}
	}

	root = src->nm_client_to_fs_projidmap;
	rbtree_postorder_for_each_entry_safe(idmap, temp, &root,
					     id_client_to_fs) {
		idmap_new = idmap_create(idmap->id_client, idmap->id_fs);
		if (!idmap_new)
			GOTO(out_copy_tree, rc = -ENOMEM);

		err = idmap_insert(NODEMAP_PROJID, idmap_new, dst);
		if (err) {
			OBD_FREE_PTR(idmap);
			GOTO(out_copy_tree,
			     rc = IS_ERR(err) ? PTR_ERR(err) : -EEXIST);
		}
	}

out_copy_tree:
	if (rc)
		CDEBUG(D_INFO,
		       "Copying idmap %d:%d from %s to %s failed: rc=%d\n",
		       idmap->id_client, idmap->id_fs,
		       src->nm_name, dst->nm_name, rc);
	return rc;
}
