// SPDX-License-Identifier: GPL-2.0

/*
 * Copyright (c) 2008, 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/
 *
 * Lustre user identity mapping.
 *
 * Author: Fan Yong <fanyong@clusterfs.com>
 */

#define DEBUG_SUBSYSTEM S_SEC

#include <linux/user_namespace.h>
#include <linux/uidgid.h>

#include <lustre_idmap.h>
#include <upcall_cache.h>
#include <md_object.h>
#include <obd_support.h>

/*
 * groups_search() is copied from linux kernel!
 * A simple bsearch.
 */
int lustre_groups_search(struct group_info *group_info, gid_t grp)
{
	int left, right;

	if (!group_info)
		return 0;

	left = 0;
	right = group_info->ngroups;
	while (left < right) {
		int mid = (left + right) / 2;
		int cmp = grp -
			from_kgid(&init_user_ns, CFS_GROUP_AT(group_info, mid));

		if (cmp > 0)
			left = mid + 1;
		else if (cmp < 0)
			right = mid;
		else
			return 1;
	}
	return 0;
}
EXPORT_SYMBOL(lustre_groups_search);

/* groups_sort() is copied from linux kernel! */
/* a simple shell-metzner sort */
void lustre_groups_sort(struct group_info *group_info)
{
	int base, max, stride;
	int gidsetsize = group_info->ngroups;

	for (stride = 1; stride < gidsetsize; stride = 3 * stride + 1)
		; /* nothing */
	stride /= 3;

	while (stride) {
		max = gidsetsize - stride;
		for (base = 0; base < max; base++) {
			int left = base;
			int right = left + stride;
			gid_t tmp = from_kgid(&init_user_ns,
					      CFS_GROUP_AT(group_info, right));

			while (left >= 0 &&
			       tmp < from_kgid(&init_user_ns,
					       CFS_GROUP_AT(group_info, left))) {
				CFS_GROUP_AT(group_info, right) =
					CFS_GROUP_AT(group_info, left);
				right = left;
				left -= stride;
			}
			CFS_GROUP_AT(group_info, right) =
						make_kgid(&init_user_ns, tmp);
		}
		stride /= 3;
	}
}
EXPORT_SYMBOL(lustre_groups_sort);

int lustre_in_group_p(struct lu_ucred *mu, gid_t grp)
{
	int rc = 1;

	if (grp != mu->uc_fsgid) {
		struct group_info *group_info = NULL;

		if (mu->uc_ginfo || !mu->uc_identity ||
		    mu->uc_valid == UCRED_OLD)
			if (grp == mu->uc_suppgids[0] ||
			    grp == mu->uc_suppgids[1])
				return 1;

		if (mu->uc_ginfo)
			group_info = mu->uc_ginfo;
		else if (mu->uc_identity)
			group_info = mu->uc_identity->mi_ginfo;

		if (!group_info)
			return 0;

		get_group_info(group_info);
		rc = lustre_groups_search(group_info, grp);
		put_group_info(group_info);
	}
	return rc;
}
EXPORT_SYMBOL(lustre_in_group_p);

/* make sure fsgid is one of primary or supplementary groups
 * fetched from identity upcall
 */
int has_proper_groups(struct lu_ucred *ucred)
{
	struct group_info *group_info = NULL;
	int rc;

	if (!ucred->uc_identity)
		return 1;

	if (ucred->uc_fsgid == ucred->uc_identity->mi_gid)
		return 1;

	group_info = ucred->uc_identity->mi_ginfo;
	if (!group_info)
		return 0;

	get_group_info(group_info);
	rc = lustre_groups_search(group_info, ucred->uc_fsgid);
	put_group_info(group_info);

	return rc;
}
EXPORT_SYMBOL(has_proper_groups);

int lustre_print_groups(struct group_info *group_info)
{
	int g;

	if (!group_info)
		RETURN(-ENOENT);

	get_group_info(group_info);
	CDEBUG(D_INFO, "ngoups=%u\n", group_info->ngroups);
	for (g = 0; g < group_info->ngroups; g++)
		CDEBUG(D_INFO, "group[%u] = %u\n", g,
		       from_kgid(&init_user_ns, group_info->gid[g]));
	put_group_info(group_info);

	RETURN(0);
}
EXPORT_SYMBOL(lustre_print_groups);

int lustre_print_ucred(struct lu_ucred *ucred)
{
	struct group_info *group_info = ucred->uc_ginfo;

	if (!group_info && ucred->uc_identity)
		group_info = ucred->uc_identity->mi_ginfo;

	CDEBUG(D_INFO, "uid=%u gid=%u fsuid=%u fsgid=%u supp=%u/%u ginfo=%px\n",
	       ucred->uc_uid, ucred->uc_gid, ucred->uc_fsuid, ucred->uc_fsgid,
	       ucred->uc_suppgids[0], ucred->uc_suppgids[1], group_info);

	return lustre_print_groups(group_info);
}
EXPORT_SYMBOL(lustre_print_ucred);
