/* $NetBSD: generic-transform-cc.c,v 1.5 2021/11/07 12:38:12 christos Exp $ */

/*-
 * Copyright (c) 2009 Joerg Sonnenberger <joerg@NetBSD.org>.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
 * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING,
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

#if defined(WRAPPER_LIBTOOL)
#include <sys/stat.h>
#endif

#include <nbcompat.h>
#include <nbcompat/err.h>
#include <stdlib.h>
#include <string.h>
#include "common.h"

#if defined(WRAPPER_LD)
#define generic_transform_cc generic_transform_ld
#endif

#define	OPT_HASH 256

struct transform_rule {
	TAILQ_ENTRY(transform_rule) link;
	char *src;
	char *dst;
	size_t src_len;
};

struct lib_transform_rule {
	TAILQ_ENTRY(lib_transform_rule) link;
	char *src;
	char **dst;
	size_t src_len;
};

TAILQ_HEAD(transform_ruleset, transform_rule);
TAILQ_HEAD(lib_transform_ruleset, lib_transform_rule);

static struct transform_ruleset opt_rules[OPT_HASH];

#if !defined(WRAPPER_LD)
static struct transform_ruleset include_rules =
    TAILQ_HEAD_INITIALIZER(include_rules);
#endif
static struct transform_ruleset lib_rules =
    TAILQ_HEAD_INITIALIZER(lib_rules);
static struct transform_ruleset rpath_rules =
    TAILQ_HEAD_INITIALIZER(rpath_rules);
#if defined(WRAPPER_LIBTOOL)
static struct transform_ruleset libpath_rules =
    TAILQ_HEAD_INITIALIZER(libpath_rules);
#endif

static struct lib_transform_ruleset libname_rules =
    TAILQ_HEAD_INITIALIZER(libname_rules);

void
init_generic_transform(void)
{
	size_t i;

	for (i = 0; i < OPT_HASH; ++i)
		TAILQ_INIT(opt_rules + i);
}

static void
register_generic_transform_path(const char *orig_rule, const char *rule,
    struct transform_ruleset *ruleset)
{
	const char *sep;
	struct transform_rule *r;

	sep = strchr(rule, ':');
	if (sep == NULL)
		goto failure;
	if (strchr(sep + 1, ':') != NULL)
		goto failure;

	r = xmalloc(sizeof(*r));
	r->src = xstrndup(rule, sep - rule);
	if (sep[1])
		r->dst = xstrdup(sep + 1);
	else
		r->dst = NULL;
	r->src_len = sep - rule;

	TAILQ_INSERT_TAIL(ruleset, r, link);

	return;

failure:
	errx(255, "Invalid transform rule: %s", orig_rule);
}

static void
register_generic_transform_lib(const char *orig_rule, const char *rule)
{
	struct lib_transform_rule *r;
	const char *sep;
	size_t count;

	sep = strchr(rule, ':');
	if (sep == NULL)
		goto failure;

	r = xmalloc(sizeof(*r));
	r->src = concat2("-l", rule, sep - rule);
	r->src_len = sep - rule + 2;
	rule = sep;
	for (count = 0; sep != NULL; ++count)
		sep = strchr(sep + 1, ':');

	r->dst = xmalloc(sizeof(char *) * (count + 1));
	for (count = 0; rule != NULL; ++count, rule = sep) {
		++rule;
		sep = strchr(rule, ':');
		if (sep == rule || *rule == '\0')
			goto failure;
		if (sep)
			r->dst[count] = concat2("-l", rule, sep - rule);
		else
			r->dst[count] = concat("-l", rule);
	}
	r->dst[count] = NULL;	

	TAILQ_INSERT_TAIL(&libname_rules, r, link);

	return;
failure:
	errx(255, "Invalid transform rule: %s", orig_rule);
}

static void
register_opt_transform(const char *orig_rule, const char *rule)
{
	const char *sep;
	struct transform_rule *r;
	size_t i;

	sep = strchr(rule, ':');
	if (sep == NULL)
		goto failure;
	if (strchr(sep + 1, ':') != NULL)
		goto failure;

	r = xmalloc(sizeof(*r));
	r->src = xstrndup(rule, sep - rule);
	if (sep[1])
		r->dst = xstrdup(sep + 1);
	else
		r->dst = NULL;
	r->src_len = sep - rule;

	i = wrapper_hash(r->src) & (OPT_HASH - 1);

	TAILQ_INSERT_TAIL(opt_rules + i, r, link);

	return;

failure:
	errx(255, "Invalid transform rule: %s", orig_rule);
}

static void
register_rm_transform(const char *orig_rule, const char *rule)
{
	struct transform_rule *r;
	size_t i;

	if (strchr(rule, ':') != NULL)
		goto failure;

	r = xmalloc(sizeof(*r));
	r->src = xstrdup(rule);
	r->dst = NULL;
	r->src_len = strlen(r->src);

	i = wrapper_hash(r->src) & (OPT_HASH - 1);

	TAILQ_INSERT_TAIL(opt_rules + i, r, link);

	return;

failure:
	errx(255, "Invalid transform rule: %s", orig_rule);
}

void
register_generic_transform(const char *rule)
{
	if (strncmp(rule, "I:", 2) == 0) {
#if !defined(WRAPPER_LD)
		register_generic_transform_path(rule, rule + 2,
		    &include_rules);
#endif
	} else if (strncmp(rule, "L:", 2) == 0) {
		register_generic_transform_path(rule, rule + 2,
		    &lib_rules);
	} else if (strncmp(rule, "R:", 2) == 0) {
		register_generic_transform_path(rule, rule + 2,
		    &rpath_rules);
	} else if (strncmp(rule, "l:", 2) == 0) {
		register_generic_transform_lib(rule, rule + 2);
	} else if (strncmp(rule, "P:", 2) == 0) {
#if defined(WRAPPER_LIBTOOL)
		register_generic_transform_path(rule, rule + 2,
		    &libpath_rules);
#endif
	} else if (strncmp(rule, "opt:", 4) == 0) {
		register_opt_transform(rule, rule + 4);
	} else if (strncmp(rule, "rm:", 3) == 0) {
		register_rm_transform(rule, rule + 3);
	} else
		errx(255, "Unknown transform rule: %s", rule);
}

#if defined(WRAPPER_LIBTOOL)
static void
generic_transform_cc_absolute(struct arglist *args, struct argument *arg)
{
	struct transform_rule *rule;
	const char *fname, *iter;
	size_t len;

	fname = strrchr(arg->val, '/');
	if (strncmp(fname + 1, "lib", 3))
		return;
	iter = fname + 4;
	len = strspn(iter, library_name_chars);
	if (len == 0)
		return;
	iter += len;
	if (strcmp(iter, ".la") && strcmp(iter, ".a"))
		return;

	len = fname - arg->val;

	TAILQ_FOREACH(rule, &libpath_rules, link) {
		if (rule->src_len > len)
			continue;
		if (!isabs(arg->val[rule->src_len]))
			continue;
		if (strncmp(arg->val, rule->src, rule->src_len))
			continue;
		argument_update(arg, concat(rule->dst, arg->val + rule->src_len));
		break;
	}
}

static int
generic_transform_libtool_lib(struct arglist *args, struct argument *arg)
{
	struct argument *arg2;
	struct stat sb;
	char *fname;
	int rv;
	size_t len;

	TAILQ_FOREACH(arg2, args, link) {
		if (arg == arg2)
			break;
		if (strncmp(arg2->val, "-L", 2))
			continue;
		len = strlen(arg2->val);
		if (len >= 6 && strcmp(arg2->val + len - 6, "/.libs") == 0)
			continue;
		fname = xasprintf("%s/lib%s.la", arg2->val + 2, arg->val + 2);
		rv = stat(fname, &sb);
		if (rv == -1 ||
		    !(S_ISREG(sb.st_mode) || S_ISLNK(sb.st_mode))) {
			free(fname);
			continue;
		}
		if (isabs(arg2->val[2])) {
			if (wrksrc == NULL)
				return 0;
			if (strncmp(arg2->val + 2, wrksrc, len))
				return 0;
		}
		argument_update(arg, fname);
		return 1;
	}
	return 0;
}
#endif

void
generic_transform_cc(struct arglist *args)
{
	struct argument *arg, *arg2, *opt_arg;
	struct transform_rule *rule;
	struct transform_ruleset *ruleset;
	struct lib_transform_rule *lib_rule;
	const char *path, *prefix;
	char *transformed;
	int rpath_mode;
	size_t i, len;

	TAILQ_FOREACH_SAFE(arg, args, link, arg2) {
		len = strlen(arg->val);
		i = wrapper_hash2(arg->val, len) & (OPT_HASH - 1);
		rpath_mode = 0;
		TAILQ_FOREACH(rule, opt_rules + i, link) {
			if (rule->src_len == len &&
			    strcmp(rule->src, arg->val) == 0)
				break;
		}
		if (rule != NULL) {
			if (rule->dst == NULL) {
				argument_unlink(args, &arg);
				continue;
			}
			argument_update(arg, xstrdup(rule->dst));
			len = strlen(rule->dst);
		}

#if defined(WRAPPER_LIBTOOL)
		if (strncmp(arg->val, "-l", 2) == 0 &&
		    generic_transform_libtool_lib(args, arg))
			continue;
#endif

		if (isabs(arg->val[0])) {
#if defined(WRAPPER_LIBTOOL)
			generic_transform_cc_absolute(args, arg);
#endif
			continue;
		}
#if !defined(WRAPPER_LD)
		else if (strncmp(arg->val, "-I", 2) == 0) {
			len -= 2;
			prefix = "-I";
			path = arg->val + 2;
			ruleset = &include_rules;
			opt_arg = NULL;
		}
		else if (strcmp(arg->val, "-isystem") == 0) {
			opt_arg = arg;
			arg = arg2;
			if (arg == NULL || arg->val[0] == '-') {
				errx(255, "Missing argument for %s",
				    opt_arg->val);
			}
			ruleset = &include_rules;
			arg2 = TAILQ_NEXT(arg, link);
			len = strlen(arg->val);
			prefix = "";
			path = arg->val;
		}
#endif
		else if (strncmp(arg->val, "-L", 2) == 0) {
			len -= 2;
			prefix = "-L";
			path = arg->val + 2;
			ruleset = &lib_rules;
			opt_arg = NULL;
		}
#if !defined(WRAPPER_LD)
		else if (strncmp(arg->val, "-Wl,-rpath,", 11) == 0) {
			len -= 11;
			prefix = "-Wl,-rpath,";
			path = arg->val + 11;
			ruleset = &rpath_rules;
			opt_arg = NULL;
			rpath_mode = 1;
		} else if (strncmp(arg->val, "-Wl,-rpath-link,", 15) == 0) {
			len -= 15;
			prefix = "-Wl,-rpath-link,";
			path = arg->val + 15;
			ruleset = &rpath_rules;
			opt_arg = NULL;
			rpath_mode = 1;
		}
#else
		else if (strcmp(arg->val, "-rpath") == 0 ||
			 strcmp(arg->val, "-rpath-link") == 0) {
			opt_arg = arg;
			arg = arg2;
			if (arg == NULL || arg->val[0] == '-') {
				errx(255, "Missing argument for %s",
				    opt_arg->val);
			}
			ruleset = &rpath_rules;
			arg2 = TAILQ_NEXT(arg, link);
			len = strlen(arg->val);
			prefix = "";
			path = arg->val;
			rpath_mode = 1;
		}
#endif
		else if (strncmp(arg->val, "-l", 2) == 0) {
			TAILQ_FOREACH(lib_rule, &libname_rules, link) {
				if (lib_rule->src_len != len)
					continue;
				if (strcmp(lib_rule->src, arg->val) == 0)
					break;
			}
			if (lib_rule == NULL)
				continue;
			argument_unlink(args, &arg);
			for (i = 0; lib_rule->dst[i] != NULL; ++i) {
				arg = argument_copy(lib_rule->dst[i]);
				if (arg2 == NULL)
					TAILQ_INSERT_TAIL(args, arg, link);
				else
					TAILQ_INSERT_BEFORE(arg2, arg, link);
			}
			continue;
		} else
			continue;

		if (!isabs(*path))
			continue;

		while (len > 1 && isabs(path[len - 1]))
			--len;

		TAILQ_FOREACH(rule, ruleset, link) {
			if (isabs(rule->src[rule->src_len - 1])) {
				if (rule->src_len - 1 != len)
					continue;
				if (memcmp(path, rule->src, len) != 0)
					continue;
				break;
			}
			if (rule->src_len > len)
				continue;
			if (path[rule->src_len] != '\0' &&
			    !isabs(path[rule->src_len]))
				continue;
			if (strncmp(path, rule->src, rule->src_len) == 0)
				break;
		}
		if (rule == NULL || rule->dst == NULL) {
			if (rpath_mode) {
				if (len >= 6 &&
				    strcmp(path + len - 6, "/.libs") == 0)
					continue;
			}
			if (opt_arg)
				argument_unlink(args, &opt_arg);
			argument_unlink(args, &arg);
			continue;
		}
		transformed = xasprintf("%s%s%s", prefix, rule->dst,
		    path + rule->src_len);
		argument_update(arg, transformed);
	}
}
