#include <sys/types.h>
#include <sys/extattr.h>
#include <sys/wait.h>

#include <errno.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#define VALUE_SIZE 96

static int
fail(const char *message)
{
	fprintf(stderr, "%s\n", message);
	return (1);
}

static int
parse_long(const char *text, long minimum, long maximum, long *valuep)
{
	char *end;
	long value;

	errno = 0;
	value = strtol(text, &end, 10);
	if (errno != 0 || text[0] == '\0' || end[0] != '\0' ||
	    value < minimum || value > maximum)
		return (-1);
	*valuep = value;
	return (0);
}

static int
namespace_id(const char *name)
{
	if (strcmp(name, "user") == 0)
		return (EXTATTR_NAMESPACE_USER);
	if (strcmp(name, "system") == 0)
		return (EXTATTR_NAMESPACE_SYSTEM);
	return (-1);
}

static void
expected_value(unsigned int index, uint8_t *value)
{
	char prefix[16];
	size_t length;

	(void)snprintf(prefix, sizeof(prefix), "value-%02u-", index);
	length = strlen(prefix);
	for (size_t offset = 0; offset < VALUE_SIZE; offset++)
		value[offset] = prefix[offset % length];
}

static int
get_hit(const char *path, const char *name, unsigned int index)
{
	uint8_t actual[VALUE_SIZE + 16], expected[VALUE_SIZE];
	ssize_t length;

	memset(actual, 0xa5, sizeof(actual));
	expected_value(index, expected);
	errno = 0;
	length = extattr_get_file(path, EXTATTR_NAMESPACE_USER, name, actual,
	    sizeof(actual));
	if (length != VALUE_SIZE || errno != 0)
		return (fail("extattr hit returned the wrong length or errno"));
	if (memcmp(actual, expected, sizeof(expected)) != 0)
		return (fail("extattr hit returned the wrong value"));
	for (size_t offset = VALUE_SIZE; offset < sizeof(actual); offset++) {
		if (actual[offset] != 0xa5)
			return (fail("extattr hit wrote beyond the value"));
	}
	return (0);
}

static int
get_errno(const char *path, int attrnamespace, const char *name,
    int expected_errno)
{
	uint8_t actual[VALUE_SIZE + 16];
	ssize_t length;

	memset(actual, 0xa5, sizeof(actual));
	errno = 0;
	length = extattr_get_file(path, attrnamespace, name, actual,
	    sizeof(actual));
	if (length != -1 || errno != expected_errno)
		return (fail("extattr failure returned the wrong positive errno"));
	for (size_t offset = 0; offset < sizeof(actual); offset++) {
		if (actual[offset] != 0xa5)
			return (fail("failed extattr lookup modified the output"));
	}
	return (0);
}

static int
list_user(const char *path)
{
	uint8_t *buffer;
	ssize_t length;
	size_t offset;
	unsigned int seen;

	errno = 0;
	length = extattr_list_file(path, EXTATTR_NAMESPACE_USER, NULL, 0);
	if (length <= 0 || errno != 0)
		return (fail("extattr list sizing failed"));
	buffer = malloc((size_t)length);
	if (buffer == NULL)
		return (fail("extattr list allocation failed"));
	if (extattr_list_file(path, EXTATTR_NAMESPACE_USER, buffer,
	    (size_t)length) != length) {
		free(buffer);
		return (fail("extattr list transfer failed"));
	}
	seen = 0;
	for (offset = 0; offset < (size_t)length;) {
		char expected[16];
		uint8_t name_length;
		unsigned int index;

		name_length = buffer[offset++];
		if (name_length == 0 || name_length > (size_t)length - offset) {
			free(buffer);
			return (fail("extattr list record is malformed"));
		}
		for (index = 0; index < 8; index++) {
			(void)snprintf(expected, sizeof(expected), "attr%02u", index);
			if (strlen(expected) == name_length &&
			    memcmp(buffer + offset, expected, name_length) == 0)
				break;
		}
		if (index == 8 || (seen & (1U << index)) != 0) {
			free(buffer);
			return (fail("extattr list returned an unknown or duplicate name"));
		}
		seen |= 1U << index;
		offset += name_length;
	}
	free(buffer);
	if (seen != 0xff)
		return (fail("extattr list omitted a user attribute"));
	return (0);
}

static int
concurrent(const char *path, const char *hit, const char *miss,
    const char *collision, long workers, long loops)
{
	long worker;
	int status;
	pid_t child;

	for (worker = 0; worker < workers; worker++) {
		child = fork();
		if (child < 0)
			return (fail("fork failed"));
		if (child == 0) {
			for (long iteration = 0; iteration < loops; iteration++) {
				if (get_hit(path, hit, 0) != 0 ||
				    get_errno(path, EXTATTR_NAMESPACE_USER, miss, ENOATTR) != 0 ||
				    get_errno(path, EXTATTR_NAMESPACE_USER, collision, ENOATTR) != 0)
					_exit(1);
			}
			_exit(0);
		}
	}
	for (worker = 0; worker < workers; worker++) {
		if (wait(&status) < 0 || !WIFEXITED(status) ||
		    WEXITSTATUS(status) != 0)
			return (fail("concurrent extattr worker failed"));
	}
	return (0);
}

int
main(int argc, char **argv)
{
	long first, second;
	int attrnamespace;

	if (argc == 5 && strcmp(argv[1], "hit") == 0) {
		if (parse_long(argv[4], 0, 7, &first) != 0)
			return (fail("invalid hit index"));
		return (get_hit(argv[2], argv[3], (unsigned int)first));
	}
	if (argc == 6 && strcmp(argv[1], "errno") == 0) {
		attrnamespace = namespace_id(argv[3]);
		if (attrnamespace < 0 || parse_long(argv[5], 1, 255, &first) != 0)
			return (fail("invalid errno arguments"));
		return (get_errno(argv[2], attrnamespace, argv[4], (int)first));
	}
	if (argc == 3 && strcmp(argv[1], "list") == 0)
		return (list_user(argv[2]));
	if (argc == 8 && strcmp(argv[1], "concurrent") == 0) {
		if (parse_long(argv[6], 1, 64, &first) != 0 ||
		    parse_long(argv[7], 1, 1000, &second) != 0)
			return (fail("invalid concurrency arguments"));
		return (concurrent(argv[2], argv[3], argv[4], argv[5], first,
		    second));
	}
	return (fail("usage: B19b-xattr-probe MODE PATH ARG..."));
}
