/*******************************************************************/
/*  slibtool: a skinny libtool implementation, written in C        */
/*  Copyright (C) 2016--2018  Z. Gilboa                            */
/*  Released under the Standard MIT License; see COPYING.SLIBTOOL. */
/*******************************************************************/

#include <stdio.h>
#include <limits.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <stdlib.h>
#include <stdbool.h>
#include <sys/wait.h>

#include <slibtool/slibtool.h>
#include "slibtool_spawn_impl.h"

static void slbt_dump_machine_child(
	char *	program,
	int	fd[2])
{
	char *	compiler;
	char *	argv[3];

	close(fd[0]);

	if ((compiler = strrchr(program,'/')))
		compiler++;
	else
		compiler = program;

	argv[0] = compiler;
	argv[1] = "-dumpmachine";
	argv[2] = 0;

	if ((fd[0] = open("/dev/null",O_RDONLY)) >= 0)
		if (dup2(fd[0],0) == 0)
			if (dup2(fd[1],1) == 1)
				execvp(program,argv);

	_exit(EXIT_FAILURE);
}

int slbt_dump_machine(
	const char *	compiler,
	char *		machine,
	size_t		buflen)
{
	ssize_t	ret;
	pid_t	pid;
	pid_t	rpid;
	int	code;
	int	fd[2];
	char *	mark;
	char	program[PATH_MAX];

	/* setup */
	if (!machine || !buflen || !--buflen) {
		errno = EINVAL;
		return -1;
	}

	if ((size_t)snprintf(program,sizeof(program),"%s",
			compiler) >= sizeof(program))
		return -1;

	/* fork */
	if (pipe(fd))
		return -1;

	if ((pid = fork()) < 0) {
		close(fd[0]);
		close(fd[1]);
		return -1;
	}

	/* child */
	if (pid == 0)
		slbt_dump_machine_child(
			program,
			fd);

	/* parent */
	close(fd[1]);

	mark = machine;

	for (; buflen; ) {
		ret = read(fd[0],mark,buflen);

		while ((ret < 0) && (errno == EINTR))
			ret = read(fd[0],mark,buflen);

		if (ret > 0) {
			buflen -= ret;
			mark   += ret;

		} else if (ret == 0) {
			close(fd[0]);
			buflen = 0;

		} else {
			close(fd[0]);
			return -1;
		}
	}

	/* execve verification */
	rpid = waitpid(
		pid,
		&code,
		0);

	if ((rpid != pid) || code) {
		errno = ESTALE;
		return -1;
	}

	/* newline verification */
	if ((mark == machine) || (*--mark != '\n')) {
		errno = ERANGE;
		return -1;
	}

	*mark = 0;

	/* portbld <--> unknown synonym? */
	if ((mark = strstr(machine,"-portbld-")))
		memcpy(mark,"-unknown",8);

	/* all done */
	return 0;
}
