#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>

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

static int
get_repeat(const char *path, const char *name, size_t expected_size)
{
	uint8_t first[1024], second[1024];
	ssize_t first_size, second_size;

	if (expected_size > sizeof(first))
		return (fail("xattr size exceeds probe buffer"));
	memset(first, 0xa5, sizeof(first));
	memset(second, 0x5a, sizeof(second));
	errno = 0;
	first_size = extattr_get_file(path, EXTATTR_NAMESPACE_USER, name,
	    first, sizeof(first));
	if (first_size != (ssize_t)expected_size || errno != 0)
		return (fail("first xattr read returned the wrong size or errno"));
	errno = 0;
	second_size = extattr_get_file(path, EXTATTR_NAMESPACE_USER, name,
	    second, sizeof(second));
	if (second_size != first_size || errno != 0 ||
	    memcmp(first, second, expected_size) != 0)
		return (fail("repeated xattr read changed its result"));
	for (size_t offset = expected_size; offset < sizeof(first); offset++) {
		if (first[offset] != 0xa5 || second[offset] != 0x5a)
			return (fail("xattr read wrote beyond its value"));
	}
	return (0);
}

static int
get_errno(const char *path, const char *name, int expected_errno)
{
	uint8_t value[32];
	ssize_t size;

	memset(value, 0xa5, sizeof(value));
	errno = 0;
	size = extattr_get_file(path, EXTATTR_NAMESPACE_USER, name, value,
	    sizeof(value));
	if (size != -1 || errno != expected_errno) {
		fprintf(stderr, "xattr errno mismatch: size=%zd errno=%d expected=%d\n",
		    size, errno, expected_errno);
		return (fail("xattr returned the wrong positive errno"));
	}
	for (size_t offset = 0; offset < sizeof(value); offset++) {
		if (value[offset] != 0xa5)
			return (fail("failed xattr lookup modified the output"));
	}
	return (0);
}

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

	errno = 0;
	size = extattr_list_file(path, EXTATTR_NAMESPACE_USER, NULL, 0);
	if (size <= 0 || errno != 0)
		return (fail("xattr list sizing failed"));
	buffer = malloc((size_t)size);
	if (buffer == NULL)
		return (fail("xattr list allocation failed"));
	if (extattr_list_file(path, EXTATTR_NAMESPACE_USER, buffer,
	    (size_t)size) != size) {
		free(buffer);
		return (fail("xattr list transfer failed"));
	}
	seen = 0;
	for (offset = 0; offset < (size_t)size;) {
		const char *name;
		uint8_t name_size;

		name_size = buffer[offset++];
		if (name_size == 0 || name_size > (size_t)size - offset) {
			free(buffer);
			return (fail("xattr list record is malformed"));
		}
		name = (const char *)(buffer + offset);
		if (name_size == sizeof("inline") - 1 &&
		    memcmp(name, "inline", name_size) == 0) {
			if ((seen & 1U) != 0) {
				free(buffer);
				return (fail("xattr list duplicated inline"));
			}
			seen |= 1U;
		} else if (name_size == sizeof("shared") - 1 &&
		    memcmp(name, "shared", name_size) == 0) {
			if ((seen & 2U) != 0) {
				free(buffer);
				return (fail("xattr list duplicated shared"));
			}
			seen |= 2U;
		} else {
			free(buffer);
			return (fail("xattr list returned an unexpected name"));
		}
		offset += name_size;
	}
	free(buffer);
	return (seen == 3U ? 0 : fail("xattr list omitted a name"));
}

static int
valid(const char *path)
{
	if (get_repeat(path, "inline", 127) != 0 ||
	    get_repeat(path, "shared", 511) != 0 ||
	    list_user(path) != 0 ||
	    get_errno(path, "missing", ENOATTR) != 0)
		return (1);
	return (0);
}

static int
corrupt(const char *path)
{
	return (get_errno(path, "inline", EINTEGRITY));
}

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

	for (long worker = 0; worker < workers; worker++) {
		child = fork();
		if (child < 0)
			return (fail("xattr worker fork failed"));
		if (child == 0) {
			for (long iteration = 0; iteration < loops; iteration++) {
				if (valid(path) != 0)
					_exit(1);
			}
			_exit(0);
		}
	}
	for (long worker = 0; worker < workers; worker++) {
		if (wait(&status) < 0 || !WIFEXITED(status) ||
		    WEXITSTATUS(status) != 0)
			return (fail("xattr worker failed"));
	}
	return (0);
}

int
main(int argc, char **argv)
{
	char *end;
	long workers, loops;

	if (argc == 3 && strcmp(argv[1], "valid") == 0)
		return (valid(argv[2]));
	if (argc == 3 && strcmp(argv[1], "corrupt") == 0)
		return (corrupt(argv[2]));
	if (argc == 5 && strcmp(argv[1], "concurrent") == 0) {
		errno = 0;
		workers = strtol(argv[3], &end, 10);
		if (errno != 0 || end[0] != '\0' || workers < 1 || workers > 64)
			return (fail("invalid worker count"));
		errno = 0;
		loops = strtol(argv[4], &end, 10);
		if (errno != 0 || end[0] != '\0' || loops < 1 || loops > 100)
			return (fail("invalid loop count"));
		return (concurrent(argv[2], workers, loops));
	}
	return (fail("usage: B19a-xattr-probe valid|corrupt|concurrent ..."));
}
