// SPDX-License-Identifier: GPL-2.0-or-later
/*
 * Copyright (c) International Business Machines  Corp., 2001
 *	         written by Wayne Boyer
 * Copyright (c) 2013 Markos Chandras
 * Copyright (c) 2013 Cyril Hrubis <chrubis@suse.cz>
 */

/*\
 *
 * [Description]
 *
 * Basic getdents() test that checks if directory listing is correct and
 * complete.
 *
 */

#define _GNU_SOURCE

#include "tst_test.h"
#include "getdents.h"

#include <stdlib.h>

static void reset_flags(void);
static void check_flags(void);
static void set_flag(const char *name);

static int fd;

enum entry_type {
	ENTRY_DIR,
	ENTRY_FILE,
	ENTRY_SYMLINK,
};

struct testcase {
	const char *name;
	enum entry_type type;
	int create:1;
	int found:1;
};

struct testcase testcases[] = {
	{.name = ".",       .create = 0, .type = ENTRY_DIR},
	{.name = "..",      .create = 0, .type = ENTRY_DIR},
	{.name = "dir",     .create = 1, .type = ENTRY_DIR},
	{.name = "file",    .create = 1, .type = ENTRY_FILE},
	{.name = "symlink", .create = 1, .type = ENTRY_SYMLINK},
};

/*
 * Big enough for dirp entires + data, the current size returned
 * by kernel is 128 bytes.
 */
#define BUFSIZE 512

static void *dirp;

static void run(void)
{
	int rval;

	fd = SAFE_OPEN(".", O_RDONLY|O_DIRECTORY);

	rval = tst_getdents(fd, dirp, BUFSIZE);

	if (rval < 0) {
		if (errno == ENOSYS)
			tst_brk(TCONF, "syscall not implemented");
		else {
			tst_res(TFAIL | TERRNO, "getdents failed unexpectedly");
			return;
		}
	}

	if (rval == 0) {
		tst_res(TFAIL, "getdents failed - returned end of directory");
		return;
	}

	reset_flags();

	void *recp = dirp;

	do {
		size_t d_reclen = tst_dirp_reclen(recp);
		const char *d_name = tst_dirp_name(recp);

		set_flag(d_name);

		tst_res(TINFO, "Found '%s'", d_name);

		rval -= d_reclen;
		recp += d_reclen;
	} while (rval > 0);

	check_flags();

	SAFE_CLOSE(fd);
}

static void reset_flags(void)
{
	size_t i;

	for (i = 0; i < ARRAY_SIZE(testcases); i++)
		testcases[i].found = 0;
}

static void check_flags(void)
{
	size_t i;
	int err = 0;

	for (i = 0; i < ARRAY_SIZE(testcases); i++) {
		if (!testcases[i].found) {
			tst_res(TINFO, "Entry '%s' not found", testcases[i].name);
			err++;
		}
	}

	if (err)
		tst_res(TFAIL, "Some entries not found");
	else
		tst_res(TPASS, "All entries found");
}

static void set_flag(const char *name)
{
	size_t i;

	for (i = 0; i < ARRAY_SIZE(testcases); i++) {
		if (!strcmp(name, testcases[i].name)) {

			if (testcases[i].found)
				tst_res(TFAIL, "Duplicate entry %s", name);

			testcases[i].found = 1;
			return;
		}
	}

	tst_res(TFAIL, "Unexpected entry '%s' found", name);
}

static void setup(void)
{
	size_t i;

	getdents_info();

	if (!tst_variant) {
		for (i = 0; i < ARRAY_SIZE(testcases); i++) {
			if (!testcases[i].create)
				continue;

			switch (testcases[i].type) {
			case ENTRY_DIR:
				SAFE_MKDIR(testcases[i].name, 0777);
			break;
			case ENTRY_FILE:
				SAFE_FILE_PRINTF(testcases[i].name, " ");
			break;
			case ENTRY_SYMLINK:
				SAFE_SYMLINK("nonexistent", testcases[i].name);
			break;
			}
		}
	}
}

static struct tst_test test = {
	.needs_tmpdir = 1,
	.test_all = run,
	.setup = setup,
	.bufs = (struct tst_buffers []) {
		{&dirp, .size = BUFSIZE},
		{},
	},
	.test_variants = TEST_VARIANTS,
};
