#define _POSIX_C_SOURCE 200809L

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

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

#define PROBE_BUFFER_SIZE 2048

static const char pattern[] = "p15-019-safe-target/";

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

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

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

static void
fill_expected(char *buffer, size_t length)
{
	size_t index;

	for (index = 0; index < length; index++)
		buffer[index] = pattern[index % (sizeof(pattern) - 1)];
}

static int
readlink_pass(const char *path, size_t expected_length)
{
	char actual[PROBE_BUFFER_SIZE];
	char expected[PROBE_BUFFER_SIZE];
	ssize_t length;

	if (expected_length + 1 > sizeof(actual))
		return (fail("expected length exceeds probe buffer"));
	memset(actual, 0xa5, sizeof(actual));
	fill_expected(expected, expected_length);
	errno = 0;
	length = readlink(path, actual, sizeof(actual));
	if (length < 0)
		return (fail("readlink-pass returned an error"));
	if ((size_t)length != expected_length)
		return (fail("readlink-pass returned the wrong length"));
	if (memcmp(actual, expected, expected_length) != 0)
		return (fail("readlink-pass returned the wrong target"));
	if ((unsigned char)actual[expected_length] != 0xa5)
		return (fail("readlink-pass appended or copied an extra byte"));
	return (0);
}

static int
readlink_errno(const char *path, int expected_errno)
{
	char actual[PROBE_BUFFER_SIZE];
	ssize_t length;
	size_t index;

	memset(actual, 0xa5, sizeof(actual));
	errno = 0;
	length = readlink(path, actual, sizeof(actual));
	if (length != -1 || errno != expected_errno)
		return (fail("readlink-errno returned the wrong result"));
	for (index = 0; index < sizeof(actual); index++) {
		if ((unsigned char)actual[index] != 0xa5)
			return (fail("failed readlink modified the caller buffer"));
	}
	return (0);
}

static int
follow_errno(const char *path, int expected_errno)
{
	struct stat status;

	errno = 0;
	if (stat(path, &status) != -1 || errno != expected_errno)
		return (fail("follow-errno returned the wrong result"));
	return (0);
}

static int
concurrent_readlink(const char *path, size_t length, 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 (readlink_pass(path, length) != 0)
					_exit(1);
			}
			_exit(0);
		}
	}
	for (worker = 0; worker < workers; worker++) {
		if (wait(&status) < 0 || !WIFEXITED(status) ||
		    WEXITSTATUS(status) != 0)
			return (fail("concurrent readlink worker failed"));
	}
	return (0);
}

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

	if (argc == 4 && strcmp(argv[1], "readlink-pass") == 0) {
		if (parse_positive(argv[3], 1, PROBE_BUFFER_SIZE - 1, &first) != 0)
			return (fail("invalid readlink-pass length"));
		return (readlink_pass(argv[2], (size_t)first));
	}
	if (argc == 4 && strcmp(argv[1], "readlink-errno") == 0) {
		if (parse_positive(argv[3], 1, 255, &first) != 0)
			return (fail("invalid expected errno"));
		return (readlink_errno(argv[2], (int)first));
	}
	if (argc == 4 && strcmp(argv[1], "follow-errno") == 0) {
		if (parse_positive(argv[3], 1, 255, &first) != 0)
			return (fail("invalid expected errno"));
		return (follow_errno(argv[2], (int)first));
	}
	if (argc == 6 && strcmp(argv[1], "concurrent") == 0) {
		if (parse_positive(argv[3], 1, PROBE_BUFFER_SIZE - 1, &first) != 0 ||
		    parse_positive(argv[4], 1, 64, &second) != 0 ||
		    parse_positive(argv[5], 1, 1000, &third) != 0)
			return (fail("invalid concurrent arguments"));
		return (concurrent_readlink(argv[2], (size_t)first, second, third));
	}
	return (fail("usage: B16-symlink-probe MODE PATH ARG..."));
}
