#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <grp.h>
#include <limits.h>
#include <errno.h>
#include <error.h>
#include <string.h>
#include <fcntl.h>
#include "attr-sets.h"
#include "process.h"

/*
 * require_group - ensure a collected attribute set contains a specific GID
 * @set: attribute set generated by read_proc_status()
 * @gid: numeric group identifier expected in the set
 * @label: description for error reporting when the lookup fails
 */
static void require_group(attr_sets_entry_t *set, unsigned int gid,
			  const char *label)
{
	if (!attr_set_check_int(set, (int64_t)gid))
		error(1, 0, "%s group %u not found", label, gid);
}


/*
 * check_split_groups_status - verify parser consumes fragmented Groups lines
 *
 * Return: none; exits through error() if parsing misses expected gids.
 */
static void check_split_groups_status(void)
{
	char status_template[] = "/tmp/fapolicyd-status-XXXXXX";
	char long_groups[512];
	char content[1024];
	int fd;
	int len;
	unsigned int gid_values[] = {
		1000, 1001, 1002, 1003, 1004,
		1234567890, 1006, 1007, 1008, 1009,
		1010, 1011, 1012, 1013, 1014,
		1015, 1016, 1017, 1018, 1019,
	};
	struct proc_status_info info = {
		.ppid = -1,
		.uid = NULL,
		.groups = NULL,
		.comm = NULL,
	};
	attr_sets_entry_t *groups;
	size_t i;

	len = snprintf(long_groups, sizeof(long_groups),
		"Groups: 1000 1001 1002 1003 1004 1234567890 "
		"1006 1007 1008 1009 1010 1011 1012 1013 "
		"1014 1015 1016 1017 1018 1019\n");
	if (len < 0 || (size_t)len >= sizeof(long_groups))
		error(1, 0, "Unable to format Groups line");

	len = snprintf(content, sizeof(content),
		"Name:	testproc\n"
		"Gid:	1000 1001 1002 1003\n"
		"%s",
		long_groups);
	if (len < 0 || (size_t)len >= sizeof(content))
		error(1, 0, "Unable to format synthetic status content");

	fd = mkstemp(status_template);
	if (fd < 0)
		error(1, errno, "mkstemp failed");

	if (write(fd, content, (size_t)len) != len)
		error(1, errno, "write synthetic status failed");

	if (lseek(fd, 0, SEEK_SET) == (off_t)-1)
		error(1, errno, "lseek synthetic status failed");

	if (read_proc_status_fd(fd, PROC_STAT_GID, &info) != 0)
		error(1, 0, "Unable to parse synthetic status file");

	close(fd);
	unlink(status_template);

	groups = info.groups;
	info.groups = NULL;
	if (!groups)
		error(1, 0, "Synthetic gid set not available");

	for (i = 0; i < sizeof(gid_values) / sizeof(gid_values[0]); i++) {
		if (!attr_set_check_int(groups, (int64_t)gid_values[i])) {
			error(1, 0, "Synthetic group %u not found",
				gid_values[i]);
		}
	}

	attr_set_destroy(groups);
}

/*
 * check_truncated_status_cleanup - reject a partial Groups line.
 *
 * Return: none; exits through error() if incomplete status data succeeds or
 * leaves a partially populated result behind.
 */
static void check_truncated_status_cleanup(void)
{
	char status_template[] = "/tmp/fapolicyd-status-XXXXXX";
	const char content[] =
		"Name:\ttestproc\n"
		"Uid:\t1000 1000 1000 1000\n"
		"Gid:\t1000 1000 1000 1000\n"
		"Groups:\t1000 1001";
	struct proc_status_info info = {
		.ppid = -1,
		.uid = NULL,
		.groups = NULL,
		.comm = NULL,
	};
	int fd;

	fd = mkstemp(status_template);
	if (fd < 0)
		error(1, errno, "mkstemp failed");
	if (write(fd, content, sizeof(content) - 1) !=
	    (ssize_t)(sizeof(content) - 1))
		error(1, errno, "write truncated status failed");
	if (lseek(fd, 0, SEEK_SET) == (off_t)-1)
		error(1, errno, "lseek truncated status failed");

	if (read_proc_status_fd(fd,
		    PROC_STAT_UID | PROC_STAT_GID | PROC_STAT_COMM, &info) == 0)
		error(1, 0, "Truncated status unexpectedly succeeded");

	close(fd);
	unlink(status_template);
	if (info.uid || info.groups || info.comm)
		error(1, 0, "Truncated status left partial data behind");
}

/*
 * check_status_reader_error_cleanup - reject a status descriptor read error.
 *
 * Return: none; exits through error() if a reader error succeeds or leaves
 * partial result storage allocated.
 */
static void check_status_reader_error_cleanup(void)
{
	struct proc_status_info info = {
		.ppid = -1,
		.uid = NULL,
		.groups = NULL,
		.comm = NULL,
	};
	int fds[2];

	if (pipe(fds))
		error(1, errno, "pipe failed");
	if (fcntl(fds[0], F_SETFL, O_NONBLOCK) == -1)
		error(1, errno, "fcntl O_NONBLOCK failed");

	if (read_proc_status_fd(fds[0],
		    PROC_STAT_UID | PROC_STAT_GID | PROC_STAT_COMM, &info) == 0)
		error(1, 0, "Status reader error unexpectedly succeeded");

	close(fds[0]);
	close(fds[1]);
	if (info.uid || info.groups || info.comm)
		error(1, 0, "Status reader error left partial data behind");
}

/*
 * main - validate GID collection helper captures all credential facets
 *
 * Return: 0 when all expected group IDs are reported, or terminate via error()
 * if any lookup or syscall fails during the exercise.
 */
int main(void)
{
	int res, num, i, check_intersect = 0;
	gid_t gid, gids[NGROUPS_MAX];
	struct proc_status_info info = {
		.ppid = -1,
		.uid = NULL,
		.groups = NULL,
		.comm = NULL
	};
	attr_sets_entry_t *groups;
	FILE *status;
	char buf[4096];
	int saw_gid_line = 0;
	unsigned int missing_gid;

	check_split_groups_status();
	check_truncated_status_cleanup();
	check_status_reader_error_cleanup();

	if (read_proc_status(getpid(), PROC_STAT_GID, &info) != 0)
		error(1, 0, "Unable to obtain gid set");

	groups = info.groups;
	info.groups = NULL;
	if (!groups)
		error(1, 0, "Unable to obtain gid set");

	status = fopen("/proc/self/status", "rt");
	if (!status)
		error(1, errno, "fopen /proc/self/status");

	while (fgets(buf, sizeof(buf), status)) {
		if (memcmp(buf, "Gid:", 4) == 0) {
			unsigned int real_gid = 0, eff_gid = 0;
			unsigned int saved_gid = 0, fs_gid = 0;
			int fields = sscanf(buf, "Gid: %u %u %u %u",
					    &real_gid, &eff_gid,
					    &saved_gid, &fs_gid);

			if (fields >= 1)
				require_group(groups, real_gid, "Real");
			if (fields >= 2)
				require_group(groups, eff_gid, "Effective");
//			if (fields >= 3)
//				require_group(groups, saved_gid, "Saved");
			if (fields >= 4)
				require_group(groups, fs_gid, "Filesystem");
			saw_gid_line = 1;
			break;
		}
	}

	fclose(status);

	if (!saw_gid_line)
		error(1, 0, "Gid line not found in /proc/self/status");

	gid = getgid();

	num = getgroups(NGROUPS_MAX, gids);
	if (num < 0)
		error(1, 0, "Too many groups");

	for (i = 0; i < num; i++) {
		if (gids[i] == gid)
			check_intersect = 1;
		printf("Checking for %u...", (unsigned int)gids[i]);
		res = attr_set_check_int(groups, (int64_t)gids[i]);
		if (!res)
			error(1, 0, "Group %u not found", (unsigned int)gids[i]);
		printf("found\n");
	}

	missing_gid = 0;
	for (; missing_gid < UINT_MAX; missing_gid++) {
		if (!attr_set_check_int(groups, (int64_t)missing_gid))
			break;
	}

	if (missing_gid == UINT_MAX)
		error(1, 0, "Unable to determine missing group for test");

	res = attr_set_check_int(groups, (int64_t)missing_gid);
	if (res)
		error(1, 0, "Found unexpected group");

	if (check_intersect) {
		printf("Doing Negative AVL intersection\n");
		attr_sets_entry_t *g = attr_set_create(NULL, UNSIGNED);
		attr_set_append_int(g, (int64_t)missing_gid);
		attr_set_append_int(g, (int64_t)(missing_gid + 1));
		res = avl_intersection(&(g->tree), &(groups->tree));
		if (res)
			error(1, 0, "Negative AVL intersection failed");

		printf("Doing Positive AVL intersection\n");
		attr_set_append_int(g, (int64_t)gid);
		res = avl_intersection(&(g->tree), &(groups->tree));
		if (!res)
			error(1, 0, "Positive AVL intersection failed");
		attr_set_destroy(g);
	}

	attr_set_destroy(groups);

	return 0;
}
