/*
 * CDDL HEADER START
 *
 * This file and its contents are supplied under the terms of the
 * Common Development and Distribution License ("CDDL"), version 1.0.
 * You may use this file only in accordance with the terms of version
 * 1.0 of the CDDL.
 *
 * A full copy of the text of the CDDL should have accompanied this
 * source.  A copy of the CDDL is also available via the Internet at
 * http://www.opensource.org/licenses/cddl1.txt
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
/*
 * Copyright 1994 Sun Microsystems, Inc. All rights reserved.
 * Use is subject to license terms.
 */
/*
 * @(#)nse.cc 1.15 06/12/12
 */

#pragma	ident	"@(#)nse.cc	1.15	06/12/12"

/*
 * Copyright 2017-2018 J. Schilling
 *
 * @(#)nse.cc	1.4 21/08/15 2017-2018 J. Schilling
 */
#include <schily/mconfig.h>
#ifndef lint
static	UConst char sccsid[] =
	"@(#)nse.cc	1.4 21/08/15 2017-2018 J. Schilling";
#endif

#ifdef NSE

/*
 * Included files
 */
#include <mk/defs.h>
#include <mksh/macro.h>		/* expand_value() */
#include <mksh/misc.h>		/* get_prop() */

/*
 * This file does some extra checking on behalf of the NSE.
 * It does some stuff that is analogous to lint in that it
 * looks for things which may be legal but that give the NSE
 * trouble.  Currently it looks for:
 *	1) recursion by cd'ing to a directory specified by a 
 *	   make variable that is defined from the shell environment.
 *	2) recursion by cd'ing to a directory specified by a
 *         make variable that has backquotes in it.
 *	3) recursion to another makefile in the same directory.
 *	4) a dependency upon an SCCS file (SCCS/s.*)
 *	5) backquotes in a file name
 *	6) a make variable being defined on the command-line that
 *	   ends up affecting dependencies
 *	7) wildcards (*[) in dependencies
 *	8) recursion to the same directory
 *	9) potential source files on the left-hand-side so
 *	   that they appear as derived files
 *
 * Things it should look for:
 *	1) makefiles that are symlinks (why are these a problem?)
 */

#define TARG_SUFX	"/usr/nse/lib/nse_targ.sufx"

typedef	struct _Nse_suffix	*Nse_suffix, Nse_suffix_rec;
struct _Nse_suffix {
	wchar_t			*suffix;	/* The suffix */
	struct _Nse_suffix	*next;		/* Linked list */
};
static Nse_suffix	sufx_hdr;
static int		our_exit_status;

static void		nse_warning(void);
static Boolean		nse_gettoken(wchar_t **, wchar_t *);

/*
 * Given a command that has just recursed to a sub make
 * try to determine if it cd'ed to a directory that was
 * defined by a make variable imported from the shell
 * environment or a variable with backquotes in it.
 * This routine will find something like:
 *	cd $(DIR); $(MAKE)
 * where DIR is imported from the shell environment.
 * However it well not find:
 *	CD = cd
 *		$(CD) $(DIR); $(MAKE)
 * or
 *	CD = cd $(DIR)
 *		$(CD); $(MAKE)
 *
 * This routine also checks for recursion to the same
 * directory.
 */
void
nse_check_cd(Property prop)
{
	wchar_t		tok[512];
	wchar_t		*p;
	wchar_t		*our_template;
	int		len;
	Boolean		cd;
#ifdef SUNOS4_AND_AFTER
	String_rec	string;
#else
	String		string;
#endif
	Name		name;
	Name		target;
	struct	Line	*line;
	struct	Recursive *r;
	Property	recurse;
	wchar_t		strbuf[STRING_BUFFER_LENGTH];
	wchar_t		tmpbuf[STRING_BUFFER_LENGTH];

#ifdef LTEST
	printf("In nse_check_cd, nse = %d, nse_did_recursion = %d\n", nse, nse_did_recursion);
#endif
#ifdef SUNOS4_AND_AFTER
	if (!nse_did_recursion || !nse) {
#else
	if (is_false(nse_did_recursion) || is_false(flag.nse)) {
#endif
#ifdef LTEST
		printf ("returning,  nse = %d,  nse_did_recursion = %d\n", nse, nse_did_recursion);
#endif
		return;
	}
	line = &prop->body.line;
#ifdef LTEST
	printf("string = %s\n", line->command_template->command_line->string_mb);
#endif

	wcscpy(tmpbuf, line->command_template->command_line->string);
	our_template = tmpbuf;
	cd = false;
	while (nse_gettoken(&our_template, tok)) {
#ifdef LTEST
		printf("in gettoken loop\n");
#endif
#ifdef SUNOS4_AND_AFTER
		if (IS_WEQUAL(tok, (wchar_t *) "cd")) {
#else
		if (is_equal(tok, "cd")) {
#endif
			cd = true;
		} else if (cd && tok[0] == '$') {
			nse_backquote_seen = NULL;
			nse_shell_var_used = NULL;
			nse_watch_vars = true;
#ifdef SUNOS4_AND_AFTER
			INIT_STRING_FROM_STACK(string, strbuf);
			name = GETNAME(tok, FIND_LENGTH);
#else
			init_string_from_stack(string, strbuf);
			name = getname(tok, FIND_LENGTH);
#endif
			expand_value(name, &string, false);
			nse_watch_vars = false;

#ifdef LTEST
			printf("cd = %d, tok = $\n", cd);
#endif
			/*
			 * Try to trim tok to just
			 * the variable.
			 */
			if (nse_shell_var_used != NULL) {
				nse_warning();
				fprintf(stderr, "\tMake invoked recursively by cd'ing to a directory\n\tdefined by the shell environment variable %s\n\tCommand line: %s\n",
				    nse_shell_var_used->string_mb,
				    line->command_template->command_line->string_mb);
			}
			if (nse_backquote_seen != NULL) {
				nse_warning();
				fprintf(stderr, "\tMake invoked recursively by cd'ing to a directory\n\tdefined by a variable (%s) with backquotes in it\n\tCommand line: %s\n",
				    nse_backquote_seen->string_mb,
				    line->command_template->command_line->string_mb);
			}
			cd = false;
		} else if (cd && nse_backquotes(tok)) {
			nse_warning();
			fprintf(stderr, "\tMake invoked recursively by cd'ing to a directory\n\tspecified by a command in backquotes\n\tCommand line: %s\n",
			    line->command_template->command_line->string_mb);
			cd = false;
		} else {
			cd = false;
		}
	}

	/*
	 * Now check for recursion to ".".
	 */
	if (primary_makefile != NULL) {
		target = prop->body.line.target;
		recurse = get_prop(target->prop, recursive_prop);
		while (recurse != NULL) {
			r = &recurse->body.recursive;
#ifdef SUNOS4_AND_AFTER
			if (IS_WEQUAL(r->directory->string, (wchar_t *) ".") &&
			    !IS_WEQUAL(r->makefiles->name->string, 
                            primary_makefile->string)) {
#else
			if (is_equal(r->directory->string, ".") &&
			    !is_equal(r->makefiles->name->string, 
			    primary_makefile->string)) {
#endif
				nse_warning();
				fprintf(stderr, "\tRecursion to makefile `%s' in the same directory\n\tCommand line: %s\n",
				    r->makefiles->name->string_mb,
				    line->command_template->command_line->string_mb);
			}
			recurse = get_prop(recurse->next, recursive_prop);
		}
	}
}

/*
 * Print an NSE make warning line.
 * If the -P flag was given then consider this a fatal
 * error, otherwise, just a warning.
 */
static void
nse_warning(void)
{
#ifdef SUNOS4_AND_AFTER
	if (report_dependencies_level > 0) {
#else
	if (is_true(flag.report_dependencies)) {
#endif
		our_exit_status = 1;
	}
	if (primary_makefile != NULL) {
		fprintf(stderr, "make: NSE warning from makefile %s/%s:\n",
			get_current_path(), primary_makefile->string_mb);
	} else {
		fprintf(stderr, "make: NSE warning from directory %s:\n",
			get_current_path());
	}
}

/*
 * Get the next whitespace delimited token pointed to by *cp.
 * Return it in tok.
 */
static Boolean
nse_gettoken(wchar_t **cp, wchar_t *tok)
{
	wchar_t		*to;
	wchar_t		*p;

	p = *cp;
	while (*p && iswspace(*p)) {
		p++;
	}
	if (*p == '\0') {
		return false;
	}
	to = tok;
	while (*p && !iswspace(*p)) {
		*to++ = *p++;
	}
	if (*p == '\0') {
		return false;
	}
	*to = '\0';
	*cp = p;
	return true;
}

/*
 * Given a dependency and a target, see if the dependency
 * is an SCCS file.  Check for the last component of its name
 * beginning with "s." and the component before that being "SCCS".
 * The NSE does not consider a source file to be derived from
 * an SCCS file.
 */
void
nse_check_sccs(wchar_t *targ, wchar_t *dep)
{
	wchar_t		*slash;
	wchar_t		*p;

#ifdef SUNOS4_AND_AFTER
	if (!nse) {
#else
	if (is_false(flag.nse)) {
#endif
		return;
	}
#ifdef SUNOS4_AND_AFTER
	slash = wcsrchr(dep, (int) slash_char); 
#else
	slash = rindex(dep, '/');
#endif
	if (slash == NULL) {
		return;
	}
	if (slash[1] != 's' || slash[2] != '.') {
		return;
	}

	/*
	 * Find the next to last filename component.
	 */
	for (p = slash - 1; p >= dep; p--) {
		if (*p == '/') {
			break;
		}
	}
	p++;
#ifdef SUNOS4_AND_AFTER
	MBSTOWCS(wcs_buffer, "SCCS/");
	if (IS_WEQUALN(p, wcs_buffer, wcslen(wcs_buffer))) {
#else
	if (is_equaln(p, "SCCS/", 5)) {
#endif
		nse_warning();
		WCSTOMBS(mbs_buffer, targ);
		WCSTOMBS(mbs_buffer2, dep);
		fprintf(stderr, "\tFile `%s' depends upon SCCS file `%s'\n",
			mbs_buffer, mbs_buffer2);
	}
	return;
}

/*
 * Given a filename check to see if it has 2 backquotes in it.
 * Complain about this because the shell expands the backquotes
 * but make does not so the files always appear to be out of date.
 */
void
nse_check_file_backquotes(wchar_t *file)
{
#ifdef SUNOS4_AND_AFTER
	if (!nse) {
#else
	if (is_false(flag.nse)) {
#endif
		return;
	}
	if (nse_backquotes(file)) {
		nse_warning();
		WCSTOMBS(mbs_buffer, file);
		fprintf(stderr, "\tFilename \"%s\" has backquotes in it\n",
			mbs_buffer);
	}
}

/*
 * Return true if the string has two backquotes in it.
 */
Boolean
nse_backquotes(wchar_t *str)
{
	wchar_t		*bq;

#ifdef SUNOS4_AND_AFTER
	bq = wcschr(str, (int) backquote_char);
	if (bq) {
		bq = wcschr(&bq[1], (int) backquote_char);
#else
	bq = index(str, '`');
	if (bq) {
		bq = index(&bq[1], '`');
#endif
		if (bq) {
			return true;
		}
	}
	return false;
}

/*
 * A macro that was defined on the command-line was found to affect the
 * set of dependencies.  The NSE "target explode" will not know about
 * this and will not get the same set of dependencies.
 */
void
nse_dep_cmdmacro(wchar_t *macro)
{
#ifdef SUNOS4_AND_AFTER
	if (!nse) {
#else
	if (is_false(flag.nse)) {
#endif
		return;
	}
	nse_warning();
	WCSTOMBS(mbs_buffer, macro);
	fprintf(stderr, "\tVariable `%s' is defined on the command-line and\n\taffects dependencies\n",
		mbs_buffer);
}

/*
 * A macro that was defined on the command-line was found to
 * be part of the argument to a cd before a recursive make.
 * This make cause the make to recurse to different places
 * depending upon how it is invoked.
 */
void
nse_rule_cmdmacro(wchar_t *macro)
{
#ifdef SUNOS4_AND_AFTER
	if (!nse) {
#else
	if (is_false(flag.nse)) {
#endif
		return;
	}
	nse_warning();
	WCSTOMBS(mbs_buffer, macro);
	fprintf(stderr, "\tMake invoked recursively by cd'ing to a directory\n\tspecified by a variable (%s) defined on the command-line\n",
		mbs_buffer);
}

/*
 * A dependency has been found with a wildcard in it.
 * This causes the NSE problems because the set of dependencies
 * can change without changing the Makefile.
 */
void
nse_wildcard(wchar_t *targ, wchar_t *dep)
{
#ifdef SUNOS4_AND_AFTER
	if (!nse) {
#else
	if (is_false(flag.nse)) {
#endif
		return;
	}
	nse_warning();
	WCSTOMBS(mbs_buffer, targ);
	WCSTOMBS(mbs_buffer2, dep);
	fprintf(stderr, "\tFile `%s' has a wildcard in dependency `%s'\n",
		mbs_buffer, mbs_buffer2);
}

/*
 * Read in the list of suffixes that are interpreted as source
 * files.
 */
void
nse_init_source_suffixes(void)
{
	FILE		*fp;
	wchar_t		suffix[100];
	Nse_suffix	sufx;
	Nse_suffix	*bpatch;

	fp = fopen(TARG_SUFX, "r");
	if (fp == NULL) {
		return;
	}
	bpatch = &sufx_hdr;
	while (fscanf(fp, "%s %*s", suffix) == 1) {
#ifdef SUNOS4_AND_AFTER
		sufx = ALLOC(Nse_suffix);  
		sufx->suffix = wcscpy(ALLOC_WC(wcslen(suffix) + 1), suffix);
#else
		sufx = alloc(Nse_suffix);
		sufx->suffix = strcpy(malloc(strlen(suffix) + 1), suffix);
#endif
		sufx->next = NULL;
		*bpatch = sufx;
		bpatch = &sufx->next;
	}
	fclose(fp);
}

/*
 * Check if a derived file (something with a dependency) appears
 * to be a source file (by its suffix) but has no rule to build it.
 * If so, complain.
 *
 * This generally arises from the old-style of make-depend that
 * produces:
 *	foo.c:	foo.h
 */
void
nse_check_derived_src(Name target, wchar_t *dep, Cmd_line command_template)
{
	Nse_suffix	sufx;
	wchar_t		*suffix;
	wchar_t		*depsufx;

#ifdef SUNOS4_AND_AFTER
	if (!nse) {
#else
	if (is_false(flag.nse)) {
#endif
		return;
	}
#ifdef SUNOS4_AND_AFTER
	if (target->stat.is_derived_src) {
#else
	if (is_true(target->stat.is_derived_src)) {
#endif
		return;
	}
	if (command_template != NULL) {
		return;
	}
#ifdef SUNOS4_AND_AFTER
	suffix = wcsrchr(target->string, (int) period_char ); 
#else
	suffix = rindex(target->string, '.');
#endif
	if (suffix != NULL) {
		for (sufx = sufx_hdr; sufx != NULL; sufx = sufx->next) {
#ifdef SUNOS4_AND_AFTER
			if (IS_WEQUAL(sufx->suffix, suffix)) {
#else
			if (is_equal(sufx->suffix, suffix)) {
#endif
				nse_warning();
				WCSTOMBS(mbs_buffer, dep);
				fprintf(stderr, "\tProbable source file `%s' appears as a derived file\n\tas it depends upon file `%s', but there is\n\tno rule to build it\n",
					target->string_mb, mbs_buffer);
				break;
			}
		}
	}
}

/*
 * See if a target is a potential source file and has no
 * dependencies and no rule but shows up on the right-hand
 * side.  This tends to occur from old "make depend" output.
 */
void
nse_check_no_deps_no_rule(Name target, Property line, Property command)
{
	Nse_suffix	sufx;
	wchar_t		*suffix;

#ifdef SUNOS4_AND_AFTER
	if (!nse) {
#else
	if (is_false(flag.nse)) {
#endif
		return;
	}
#ifdef SUNOS4_AND_AFTER
	if (target->stat.is_derived_src) {
#else
	if (is_true(target->stat.is_derived_src)) {
#endif
		return;
	}
	if (line != NULL && line->body.line.dependencies != NULL) {
		return;
	}
#ifdef SUNOS4_AND_AFTER
	if (command->body.line.sccs_command) {
#else
	if (is_true(command->body.line.sccs_command)) {
#endif
		return;
	}
#ifdef SUNOS4_AND_AFTER
	suffix = wcsrchr(target->string, (int) period_char); 
#else
	suffix = rindex(target->string, '.');
#endif
	if (suffix != NULL) {
		for (sufx = sufx_hdr; sufx != NULL; sufx = sufx->next) {
#ifdef SUNOS4_AND_AFTER
			if (IS_WEQUAL(sufx->suffix, suffix)) {
#else
			if (is_equal(sufx->suffix, suffix)) {
#endif
				if (command->body.line.command_template == NULL) {
					nse_warning();
					fprintf(stderr, "\tProbable source file `%s' appears as a derived file because\n\tit is on the left-hand side, but it has no dependencies and\n\tno rule to build it\n",
						target->string_mb);
				}
			}
		}
	} 
} 

/*
 * Detected a situation where a recursive make derived a file
 * without using a makefile.
 */
void
nse_no_makefile(Name target)
{
#ifdef SUNOS4_AND_AFTER
	if (!nse) {
#else
	if (is_false(flag.nse)) {
#endif
		return;
	}
	nse_warning();
	fprintf(stderr, "Recursive make to derive %s did not use a makefile\n",
		target->string_mb);
}

/*
 * Return the NSE exit status.
 * If the -P flag was given then a warning is considered fatal
 */
int
nse_exit_status(void)
{
	return our_exit_status;
}
#endif
