/* See LICENSE file for copyright and license details. */
#include "common.h"


char *
libgeome_util_areadlink(struct libgeome_context *ctx, const char *path)
{
	char *ret = NULL, *new;
	size_t size = 0;
	ssize_t r;

	do {
		new = realloc(ret, size += 128U);
		if (!new) {
			ctx->print_error(ctx, "realloc: %s\n", strerror(errno));
			free(ret);
			return NULL;
		}
		ret = new;
		r = readlink(path, ret, size - 1U);
		if (r < 0) {
			ctx->print_error(ctx, "readlink %s: %s\n", path, strerror(errno));
			free(ret);
			return NULL;
		}
	} while ((size_t)r >= size - 2U);

	ret[r] = '\0';
	return ret;
}


int
libgeome_util_safe_pipe(struct libgeome_context *ctx, int fds[2])
{
	int i, old_fd, new_fd, nul_fd = -1;

	if (pipe(fds)) {
		ctx->print_error(ctx, "pipe: %s\n", strerror(errno));
		return -1;
	}

	if (fds[0] > 2 && fds[1] > 2)
		return 0;

	for (i = 0; i < 2; i++) {
		if (fds[i] > 2)
			continue;
		new_fd = fcntl(fds[i], F_DUPFD, 3);
		if (new_fd < 0) {
			ctx->print_error(ctx, "fcntl F_DUPFD 3: %s\n", strerror(errno));
			goto fail;
		}
		old_fd = fds[i];
		close(fds[i]);
		fds[i] = new_fd;
		nul_fd = open("/dev/null", O_RDWR);
		if (nul_fd != old_fd) {
			ctx->print_error(ctx, "open /dev/null O_RDWR: %s\n", strerror(errno));
			goto fail;
		}
	}

	if (old_fd < 2) {
		new_fd = dup(nul_fd);
		if (new_fd < 0) {
			ctx->print_error(ctx, "duo: %s\n", strerror(errno));
			goto fail;
		}
		if (new_fd > 2)
			close(new_fd);
	}

	return 0;

fail:
	close(fds[0]);
	close(fds[1]);
	return -1;
}


void
libgeome_util_spawn_async_kill(struct libgeome_context *ctx, const struct spawn_join_info *info, int signum)
{
	(void) ctx;
	kill(info->pid, signum);
}


int
libgeome_util_spawn_async_read(struct libgeome_context *ctx, const char *path, const char *const *argv,
                               unsigned int alarm_seconds, struct spawn_join_info *info_out, int *fd_out)
{
	struct sigaction sa, old_sa;
	sigset_t mask, old_mask;
	int fds[2], ignore_sigchld;
	pid_t pid;

	if (sigemptyset(&mask)) {
		ctx->print_error(ctx, "sigemptyset: %s\n", strerror(errno));
		return -1;
	}
	if (sigaddset(&mask, SIGCHLD)) {
		ctx->print_error(ctx, "sigaddset SIGCHLD: %s\n", strerror(errno));
		return -1;
	}
	if (sigprocmask(SIG_BLOCK, &mask, &old_mask)) {
		ctx->print_error(ctx, "sigprocmask SIG_BLOCK {SIGCHLD}: %s\n", strerror(errno));
		return -1;
	}

	memset(&sa, 0, sizeof(sa));
	sa.sa_handler = SIG_DFL;
	if (sigaction(SIGCHLD, &sa, &old_sa)) {
		ctx->print_error(ctx, "sigaction SIGCHLD: %s\n", strerror(errno));
		if (sigprocmask(SIG_SETMASK, &old_mask, NULL)) {
			ctx->print_abort(ctx, "sigprocmask SIG_SETMASK <old mask>: %s\n", strerror(errno));
			abort();
		}
		return -1;
	}
	ignore_sigchld = !(old_sa.sa_flags & SA_SIGINFO) && old_sa.sa_handler == SIG_IGN;
	if (!ignore_sigchld && sigaction(SIGCHLD, &old_sa, NULL)) {
		ctx->print_abort(ctx, "sigaction SIGCHLD: %s\n", strerror(errno));
		abort();
	}

	if (sigprocmask(SIG_SETMASK, &old_mask, NULL)) {
		ctx->print_abort(ctx, "sigaction SIG_SETMASK <old mask>: %s\n", strerror(errno));
		abort();
	}

	if (libgeome_util_safe_pipe(ctx, fds))
		goto out;

	pid = fork();
	switch (pid) {
	case -1:
		ctx->print_error(ctx, "fork: %s\n", strerror(errno));
		close(fds[0]);
		close(fds[1]);
		goto out;

	case 0:
		close(fds[0]);
		if (dup2(fds[1], STDOUT_FILENO) != STDOUT_FILENO) {
			ctx->print_error(ctx, "dup2 <pipe> <stdout>: %s\n", strerror(errno));
			_exit(127);
		}
		close(fds[1]);
		alarm(alarm_seconds);
		execvp(path, (const void *)argv);
		ctx->print_error(ctx, "execvp %s: %s\n", path, strerror(errno));
		_exit(127);

	default:
		close(fds[1]);
		break;
	}

	info_out->pid = pid;
	info_out->ignore_sigchld = ignore_sigchld;
	*fd_out = fds[0];
	return 0;

out:
	if (ignore_sigchld) {
		sa.sa_handler = SIG_IGN;
		if (sigaction(SIGCHLD, &sa, NULL)) {
			ctx->print_abort(ctx, "sigaction SIGCHLD: %s\n", strerror(errno));
			abort();
		}
		while (waitpid(-1, NULL, WNOHANG) != -1);
	}
	return -1;
}


int
libgeome_util_spawn_async_join(struct libgeome_context *ctx, const struct spawn_join_info *info, int *status_out)
{
	struct sigaction sa;
	int ret = 0;

	if (waitpid(info->pid, status_out, 0) != info->pid) {
		ctx->print_error(ctx, "waitpid <subprocess> 0: %s\n", strerror(errno));
		ret = -1;
	}

	if (info->ignore_sigchld) {
		memset(&sa, 0, sizeof(sa));
		sa.sa_handler = SIG_IGN;
		if (sigaction(SIGCHLD, &sa, NULL)) {
			ctx->print_abort(ctx, "sigaction SIGCHLD: %s\n", strerror(errno));
			abort();
		}
		while (waitpid(-1, NULL, WNOHANG) != -1);
	}

	return ret;
}


uint64_t
libgeome_util_parse_xml(char *s, struct location *location_out)
{
	uint64_t ret = 0;
	char *p = s;

	errno = 0;

	p = strstr(s, "<lat>");
	if (!p)
		p = strstr(s, "<latitude>");
	if (!p)
		goto skip_latitude;
	p = &strchr(p, '>')[1];
	while (isspace(*p))
		p++;
	location_out->latitude = strtod(p, &p);
	if (errno)
		goto skip_latitude;
	while (isspace(*p))
		p++;
	if (*p != '<')
		goto skip_latitude;
	ret |= LIBGEOME_DATUM_LATITUDE;
skip_latitude:

	p = strstr(s, "<lon>");
	if (!p)
		p = strstr(s, "<long>");
	if (!p)
		p = strstr(s, "<lng>");
	if (!p)
		p = strstr(s, "<longitude>");
	if (!p)
		goto skip_longitude;
	p = &strchr(p, '>')[1];
	while (isspace(*p))
		p++;
	location_out->longitude = strtod(p, &p);
	if (errno)
		goto skip_longitude;
	while (isspace(*p))
		p++;
	if (*p != '<')
		goto skip_longitude;
	ret |= LIBGEOME_DATUM_LONGITUDE;
skip_longitude:

	return ret;
}


uint64_t
libgeome_util_parse_json(char *s, struct location *location_out)
{
	uint64_t ret = 0;
	char *p = s;

	errno = 0;

	p = strstr(s, "\"lat\"");
	if (!p)
		p = strstr(s, "\"latitude\"");
	if (!p)
		goto skip_latitude;
	p = &strchr(&p[1], '"')[1];
	while (isspace(*p))
		p++;
	if (*p++ != ':')
		goto skip_latitude;
	while (isspace(*p))
		p++;
	location_out->latitude = strtod(p, &p);
	if (errno)
		goto skip_latitude;
	while (isspace(*p))
		p++;
	if (*p != ',' && *p != '}')
		goto skip_latitude;
	ret |= LIBGEOME_DATUM_LATITUDE;
skip_latitude:

	p = strstr(s, "\"lon\"");
	if (!p)
		p = strstr(s, "\"long\"");
	if (!p)
		p = strstr(s, "\"lng\"");
	if (!p)
		p = strstr(s, "\"longitude\"");
	if (!p)
		goto skip_longitude;
	p = &strchr(&p[1], '"')[1];
	while (isspace(*p))
		p++;
	if (*p++ != ':')
		goto skip_longitude;
	while (isspace(*p))
		p++;
	location_out->longitude = strtod(p, &p);
	if (errno)
		goto skip_longitude;
	while (isspace(*p))
		p++;
	if (*p != ',' && *p != '}')
		goto skip_longitude;
	ret |= LIBGEOME_DATUM_LONGITUDE;
skip_longitude:

	return ret;
}


uint64_t
libgeome_util_parse_plain(char *s, struct location *location_out)
{
	errno = 0;

	location_out->latitude = strtod(s, &s);
	if (errno)
		return 0;

	while (isspace(*s))
		s++;
	if (*s == ',')
		s++;
	while (isspace(*s))
		s++;

	location_out->longitude = strtod(s, &s);
	if (errno)
		return 0;

	while (isspace(*s))
		s++;

	if (*s)
		return 0;

	return LIBGEOME_DATUM_LATITUDE | LIBGEOME_DATUM_LONGITUDE;
}


uint64_t
libgeome_util_parse_output(char *s, struct location *location_out)
{
	if (s[0] == '<')
		return libgeome_util_parse_xml(s, location_out);
	else if (s[0] == '{')
		return libgeome_util_parse_json(s, location_out);
	else
		return libgeome_util_parse_plain(s, location_out);
}