1 /*
2  * CDDL HEADER START
3  *
4  * This file and its contents are supplied under the terms of the
5  * Common Development and Distribution License ("CDDL"), version 1.0.
6  * You may use this file only in accordance with the terms of version
7  * 1.0 of the CDDL.
8  *
9  * A full copy of the text of the CDDL should have accompanied this
10  * source.  A copy of the CDDL is also available via the Internet at
11  * http://www.opensource.org/licenses/cddl1.txt
12  * See the License for the specific language governing permissions
13  * and limitations under the License.
14  *
15  * When distributing Covered Code, include this CDDL HEADER in each
16  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
17  * If applicable, add the following below this CDDL HEADER, with the
18  * fields enclosed by brackets "[]" replaced with your own identifying
19  * information: Portions Copyright [yyyy] [name of copyright owner]
20  *
21  * CDDL HEADER END
22  */
23 /*
24  * Copyright 1994 Sun Microsystems, Inc. All rights reserved.
25  * Use is subject to license terms.
26  */
27 /*
28  * @(#)nse.cc 1.15 06/12/12
29  */
30 
31 #pragma	ident	"@(#)nse.cc	1.15	06/12/12"
32 
33 /*
34  * Copyright 2017-2018 J. Schilling
35  *
36  * @(#)nse.cc	1.4 21/08/15 2017-2018 J. Schilling
37  */
38 #include <schily/mconfig.h>
39 #ifndef lint
40 static	UConst char sccsid[] =
41 	"@(#)nse.cc	1.4 21/08/15 2017-2018 J. Schilling";
42 #endif
43 
44 #ifdef NSE
45 
46 /*
47  * Included files
48  */
49 #include <mk/defs.h>
50 #include <mksh/macro.h>		/* expand_value() */
51 #include <mksh/misc.h>		/* get_prop() */
52 
53 /*
54  * This file does some extra checking on behalf of the NSE.
55  * It does some stuff that is analogous to lint in that it
56  * looks for things which may be legal but that give the NSE
57  * trouble.  Currently it looks for:
58  *	1) recursion by cd'ing to a directory specified by a
59  *	   make variable that is defined from the shell environment.
60  *	2) recursion by cd'ing to a directory specified by a
61  *         make variable that has backquotes in it.
62  *	3) recursion to another makefile in the same directory.
63  *	4) a dependency upon an SCCS file (SCCS/s.*)
64  *	5) backquotes in a file name
65  *	6) a make variable being defined on the command-line that
66  *	   ends up affecting dependencies
67  *	7) wildcards (*[) in dependencies
68  *	8) recursion to the same directory
69  *	9) potential source files on the left-hand-side so
70  *	   that they appear as derived files
71  *
72  * Things it should look for:
73  *	1) makefiles that are symlinks (why are these a problem?)
74  */
75 
76 #define TARG_SUFX	"/usr/nse/lib/nse_targ.sufx"
77 
78 typedef	struct _Nse_suffix	*Nse_suffix, Nse_suffix_rec;
79 struct _Nse_suffix {
80 	wchar_t			*suffix;	/* The suffix */
81 	struct _Nse_suffix	*next;		/* Linked list */
82 };
83 static Nse_suffix	sufx_hdr;
84 static int		our_exit_status;
85 
86 static void		nse_warning(void);
87 static Boolean		nse_gettoken(wchar_t **, wchar_t *);
88 
89 /*
90  * Given a command that has just recursed to a sub make
91  * try to determine if it cd'ed to a directory that was
92  * defined by a make variable imported from the shell
93  * environment or a variable with backquotes in it.
94  * This routine will find something like:
95  *	cd $(DIR); $(MAKE)
96  * where DIR is imported from the shell environment.
97  * However it well not find:
98  *	CD = cd
99  *		$(CD) $(DIR); $(MAKE)
100  * or
101  *	CD = cd $(DIR)
102  *		$(CD); $(MAKE)
103  *
104  * This routine also checks for recursion to the same
105  * directory.
106  */
107 void
nse_check_cd(Property prop)108 nse_check_cd(Property prop)
109 {
110 	wchar_t		tok[512];
111 	wchar_t		*p;
112 	wchar_t		*our_template;
113 	int		len;
114 	Boolean		cd;
115 #ifdef SUNOS4_AND_AFTER
116 	String_rec	string;
117 #else
118 	String		string;
119 #endif
120 	Name		name;
121 	Name		target;
122 	struct	Line	*line;
123 	struct	Recursive *r;
124 	Property	recurse;
125 	wchar_t		strbuf[STRING_BUFFER_LENGTH];
126 	wchar_t		tmpbuf[STRING_BUFFER_LENGTH];
127 
128 #ifdef LTEST
129 	printf("In nse_check_cd, nse = %d, nse_did_recursion = %d\n", nse, nse_did_recursion);
130 #endif
131 #ifdef SUNOS4_AND_AFTER
132 	if (!nse_did_recursion || !nse) {
133 #else
134 	if (is_false(nse_did_recursion) || is_false(flag.nse)) {
135 #endif
136 #ifdef LTEST
137 		printf ("returning,  nse = %d,  nse_did_recursion = %d\n", nse, nse_did_recursion);
138 #endif
139 		return;
140 	}
141 	line = &prop->body.line;
142 #ifdef LTEST
143 	printf("string = %s\n", line->command_template->command_line->string_mb);
144 #endif
145 
146 	wcscpy(tmpbuf, line->command_template->command_line->string);
147 	our_template = tmpbuf;
148 	cd = false;
149 	while (nse_gettoken(&our_template, tok)) {
150 #ifdef LTEST
151 		printf("in gettoken loop\n");
152 #endif
153 #ifdef SUNOS4_AND_AFTER
154 		if (IS_WEQUAL(tok, (wchar_t *) "cd")) {
155 #else
156 		if (is_equal(tok, "cd")) {
157 #endif
158 			cd = true;
159 		} else if (cd && tok[0] == '$') {
160 			nse_backquote_seen = NULL;
161 			nse_shell_var_used = NULL;
162 			nse_watch_vars = true;
163 #ifdef SUNOS4_AND_AFTER
164 			INIT_STRING_FROM_STACK(string, strbuf);
165 			name = GETNAME(tok, FIND_LENGTH);
166 #else
167 			init_string_from_stack(string, strbuf);
168 			name = getname(tok, FIND_LENGTH);
169 #endif
170 			expand_value(name, &string, false);
171 			nse_watch_vars = false;
172 
173 #ifdef LTEST
174 			printf("cd = %d, tok = $\n", cd);
175 #endif
176 			/*
177 			 * Try to trim tok to just
178 			 * the variable.
179 			 */
180 			if (nse_shell_var_used != NULL) {
181 				nse_warning();
182 				fprintf(stderr, "\tMake invoked recursively by cd'ing to a directory\n\tdefined by the shell environment variable %s\n\tCommand line: %s\n",
183 				    nse_shell_var_used->string_mb,
184 				    line->command_template->command_line->string_mb);
185 			}
186 			if (nse_backquote_seen != NULL) {
187 				nse_warning();
188 				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",
189 				    nse_backquote_seen->string_mb,
190 				    line->command_template->command_line->string_mb);
191 			}
192 			cd = false;
193 		} else if (cd && nse_backquotes(tok)) {
194 			nse_warning();
195 			fprintf(stderr, "\tMake invoked recursively by cd'ing to a directory\n\tspecified by a command in backquotes\n\tCommand line: %s\n",
196 			    line->command_template->command_line->string_mb);
197 			cd = false;
198 		} else {
199 			cd = false;
200 		}
201 	}
202 
203 	/*
204 	 * Now check for recursion to ".".
205 	 */
206 	if (primary_makefile != NULL) {
207 		target = prop->body.line.target;
208 		recurse = get_prop(target->prop, recursive_prop);
209 		while (recurse != NULL) {
210 			r = &recurse->body.recursive;
211 #ifdef SUNOS4_AND_AFTER
212 			if (IS_WEQUAL(r->directory->string, (wchar_t *) ".") &&
213 			    !IS_WEQUAL(r->makefiles->name->string,
214                             primary_makefile->string)) {
215 #else
216 			if (is_equal(r->directory->string, ".") &&
217 			    !is_equal(r->makefiles->name->string,
218 			    primary_makefile->string)) {
219 #endif
220 				nse_warning();
221 				fprintf(stderr, "\tRecursion to makefile `%s' in the same directory\n\tCommand line: %s\n",
222 				    r->makefiles->name->string_mb,
223 				    line->command_template->command_line->string_mb);
224 			}
225 			recurse = get_prop(recurse->next, recursive_prop);
226 		}
227 	}
228 }
229 
230 /*
231  * Print an NSE make warning line.
232  * If the -P flag was given then consider this a fatal
233  * error, otherwise, just a warning.
234  */
235 static void
236 nse_warning(void)
237 {
238 #ifdef SUNOS4_AND_AFTER
239 	if (report_dependencies_level > 0) {
240 #else
241 	if (is_true(flag.report_dependencies)) {
242 #endif
243 		our_exit_status = 1;
244 	}
245 	if (primary_makefile != NULL) {
246 		fprintf(stderr, "make: NSE warning from makefile %s/%s:\n",
247 			get_current_path(), primary_makefile->string_mb);
248 	} else {
249 		fprintf(stderr, "make: NSE warning from directory %s:\n",
250 			get_current_path());
251 	}
252 }
253 
254 /*
255  * Get the next whitespace delimited token pointed to by *cp.
256  * Return it in tok.
257  */
258 static Boolean
259 nse_gettoken(wchar_t **cp, wchar_t *tok)
260 {
261 	wchar_t		*to;
262 	wchar_t		*p;
263 
264 	p = *cp;
265 	while (*p && iswspace(*p)) {
266 		p++;
267 	}
268 	if (*p == '\0') {
269 		return false;
270 	}
271 	to = tok;
272 	while (*p && !iswspace(*p)) {
273 		*to++ = *p++;
274 	}
275 	if (*p == '\0') {
276 		return false;
277 	}
278 	*to = '\0';
279 	*cp = p;
280 	return true;
281 }
282 
283 /*
284  * Given a dependency and a target, see if the dependency
285  * is an SCCS file.  Check for the last component of its name
286  * beginning with "s." and the component before that being "SCCS".
287  * The NSE does not consider a source file to be derived from
288  * an SCCS file.
289  */
290 void
291 nse_check_sccs(wchar_t *targ, wchar_t *dep)
292 {
293 	wchar_t		*slash;
294 	wchar_t		*p;
295 
296 #ifdef SUNOS4_AND_AFTER
297 	if (!nse) {
298 #else
299 	if (is_false(flag.nse)) {
300 #endif
301 		return;
302 	}
303 #ifdef SUNOS4_AND_AFTER
304 	slash = wcsrchr(dep, (int) slash_char);
305 #else
306 	slash = rindex(dep, '/');
307 #endif
308 	if (slash == NULL) {
309 		return;
310 	}
311 	if (slash[1] != 's' || slash[2] != '.') {
312 		return;
313 	}
314 
315 	/*
316 	 * Find the next to last filename component.
317 	 */
318 	for (p = slash - 1; p >= dep; p--) {
319 		if (*p == '/') {
320 			break;
321 		}
322 	}
323 	p++;
324 #ifdef SUNOS4_AND_AFTER
325 	MBSTOWCS(wcs_buffer, "SCCS/");
326 	if (IS_WEQUALN(p, wcs_buffer, wcslen(wcs_buffer))) {
327 #else
328 	if (is_equaln(p, "SCCS/", 5)) {
329 #endif
330 		nse_warning();
331 		WCSTOMBS(mbs_buffer, targ);
332 		WCSTOMBS(mbs_buffer2, dep);
333 		fprintf(stderr, "\tFile `%s' depends upon SCCS file `%s'\n",
334 			mbs_buffer, mbs_buffer2);
335 	}
336 	return;
337 }
338 
339 /*
340  * Given a filename check to see if it has 2 backquotes in it.
341  * Complain about this because the shell expands the backquotes
342  * but make does not so the files always appear to be out of date.
343  */
344 void
345 nse_check_file_backquotes(wchar_t *file)
346 {
347 #ifdef SUNOS4_AND_AFTER
348 	if (!nse) {
349 #else
350 	if (is_false(flag.nse)) {
351 #endif
352 		return;
353 	}
354 	if (nse_backquotes(file)) {
355 		nse_warning();
356 		WCSTOMBS(mbs_buffer, file);
357 		fprintf(stderr, "\tFilename \"%s\" has backquotes in it\n",
358 			mbs_buffer);
359 	}
360 }
361 
362 /*
363  * Return true if the string has two backquotes in it.
364  */
365 Boolean
366 nse_backquotes(wchar_t *str)
367 {
368 	wchar_t		*bq;
369 
370 #ifdef SUNOS4_AND_AFTER
371 	bq = wcschr(str, (int) backquote_char);
372 	if (bq) {
373 		bq = wcschr(&bq[1], (int) backquote_char);
374 #else
375 	bq = index(str, '`');
376 	if (bq) {
377 		bq = index(&bq[1], '`');
378 #endif
379 		if (bq) {
380 			return true;
381 		}
382 	}
383 	return false;
384 }
385 
386 /*
387  * A macro that was defined on the command-line was found to affect the
388  * set of dependencies.  The NSE "target explode" will not know about
389  * this and will not get the same set of dependencies.
390  */
391 void
392 nse_dep_cmdmacro(wchar_t *macro)
393 {
394 #ifdef SUNOS4_AND_AFTER
395 	if (!nse) {
396 #else
397 	if (is_false(flag.nse)) {
398 #endif
399 		return;
400 	}
401 	nse_warning();
402 	WCSTOMBS(mbs_buffer, macro);
403 	fprintf(stderr, "\tVariable `%s' is defined on the command-line and\n\taffects dependencies\n",
404 		mbs_buffer);
405 }
406 
407 /*
408  * A macro that was defined on the command-line was found to
409  * be part of the argument to a cd before a recursive make.
410  * This make cause the make to recurse to different places
411  * depending upon how it is invoked.
412  */
413 void
414 nse_rule_cmdmacro(wchar_t *macro)
415 {
416 #ifdef SUNOS4_AND_AFTER
417 	if (!nse) {
418 #else
419 	if (is_false(flag.nse)) {
420 #endif
421 		return;
422 	}
423 	nse_warning();
424 	WCSTOMBS(mbs_buffer, macro);
425 	fprintf(stderr, "\tMake invoked recursively by cd'ing to a directory\n\tspecified by a variable (%s) defined on the command-line\n",
426 		mbs_buffer);
427 }
428 
429 /*
430  * A dependency has been found with a wildcard in it.
431  * This causes the NSE problems because the set of dependencies
432  * can change without changing the Makefile.
433  */
434 void
435 nse_wildcard(wchar_t *targ, wchar_t *dep)
436 {
437 #ifdef SUNOS4_AND_AFTER
438 	if (!nse) {
439 #else
440 	if (is_false(flag.nse)) {
441 #endif
442 		return;
443 	}
444 	nse_warning();
445 	WCSTOMBS(mbs_buffer, targ);
446 	WCSTOMBS(mbs_buffer2, dep);
447 	fprintf(stderr, "\tFile `%s' has a wildcard in dependency `%s'\n",
448 		mbs_buffer, mbs_buffer2);
449 }
450 
451 /*
452  * Read in the list of suffixes that are interpreted as source
453  * files.
454  */
455 void
456 nse_init_source_suffixes(void)
457 {
458 	FILE		*fp;
459 	wchar_t		suffix[100];
460 	Nse_suffix	sufx;
461 	Nse_suffix	*bpatch;
462 
463 	fp = fopen(TARG_SUFX, "r");
464 	if (fp == NULL) {
465 		return;
466 	}
467 	bpatch = &sufx_hdr;
468 	while (fscanf(fp, "%s %*s", suffix) == 1) {
469 #ifdef SUNOS4_AND_AFTER
470 		sufx = ALLOC(Nse_suffix);
471 		sufx->suffix = wcscpy(ALLOC_WC(wcslen(suffix) + 1), suffix);
472 #else
473 		sufx = alloc(Nse_suffix);
474 		sufx->suffix = strcpy(malloc(strlen(suffix) + 1), suffix);
475 #endif
476 		sufx->next = NULL;
477 		*bpatch = sufx;
478 		bpatch = &sufx->next;
479 	}
480 	fclose(fp);
481 }
482 
483 /*
484  * Check if a derived file (something with a dependency) appears
485  * to be a source file (by its suffix) but has no rule to build it.
486  * If so, complain.
487  *
488  * This generally arises from the old-style of make-depend that
489  * produces:
490  *	foo.c:	foo.h
491  */
492 void
493 nse_check_derived_src(Name target, wchar_t *dep, Cmd_line command_template)
494 {
495 	Nse_suffix	sufx;
496 	wchar_t		*suffix;
497 	wchar_t		*depsufx;
498 
499 #ifdef SUNOS4_AND_AFTER
500 	if (!nse) {
501 #else
502 	if (is_false(flag.nse)) {
503 #endif
504 		return;
505 	}
506 #ifdef SUNOS4_AND_AFTER
507 	if (target->stat.is_derived_src) {
508 #else
509 	if (is_true(target->stat.is_derived_src)) {
510 #endif
511 		return;
512 	}
513 	if (command_template != NULL) {
514 		return;
515 	}
516 #ifdef SUNOS4_AND_AFTER
517 	suffix = wcsrchr(target->string, (int) period_char );
518 #else
519 	suffix = rindex(target->string, '.');
520 #endif
521 	if (suffix != NULL) {
522 		for (sufx = sufx_hdr; sufx != NULL; sufx = sufx->next) {
523 #ifdef SUNOS4_AND_AFTER
524 			if (IS_WEQUAL(sufx->suffix, suffix)) {
525 #else
526 			if (is_equal(sufx->suffix, suffix)) {
527 #endif
528 				nse_warning();
529 				WCSTOMBS(mbs_buffer, dep);
530 				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",
531 					target->string_mb, mbs_buffer);
532 				break;
533 			}
534 		}
535 	}
536 }
537 
538 /*
539  * See if a target is a potential source file and has no
540  * dependencies and no rule but shows up on the right-hand
541  * side.  This tends to occur from old "make depend" output.
542  */
543 void
544 nse_check_no_deps_no_rule(Name target, Property line, Property command)
545 {
546 	Nse_suffix	sufx;
547 	wchar_t		*suffix;
548 
549 #ifdef SUNOS4_AND_AFTER
550 	if (!nse) {
551 #else
552 	if (is_false(flag.nse)) {
553 #endif
554 		return;
555 	}
556 #ifdef SUNOS4_AND_AFTER
557 	if (target->stat.is_derived_src) {
558 #else
559 	if (is_true(target->stat.is_derived_src)) {
560 #endif
561 		return;
562 	}
563 	if (line != NULL && line->body.line.dependencies != NULL) {
564 		return;
565 	}
566 #ifdef SUNOS4_AND_AFTER
567 	if (command->body.line.sccs_command) {
568 #else
569 	if (is_true(command->body.line.sccs_command)) {
570 #endif
571 		return;
572 	}
573 #ifdef SUNOS4_AND_AFTER
574 	suffix = wcsrchr(target->string, (int) period_char);
575 #else
576 	suffix = rindex(target->string, '.');
577 #endif
578 	if (suffix != NULL) {
579 		for (sufx = sufx_hdr; sufx != NULL; sufx = sufx->next) {
580 #ifdef SUNOS4_AND_AFTER
581 			if (IS_WEQUAL(sufx->suffix, suffix)) {
582 #else
583 			if (is_equal(sufx->suffix, suffix)) {
584 #endif
585 				if (command->body.line.command_template == NULL) {
586 					nse_warning();
587 					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",
588 						target->string_mb);
589 				}
590 			}
591 		}
592 	}
593 }
594 
595 /*
596  * Detected a situation where a recursive make derived a file
597  * without using a makefile.
598  */
599 void
600 nse_no_makefile(Name target)
601 {
602 #ifdef SUNOS4_AND_AFTER
603 	if (!nse) {
604 #else
605 	if (is_false(flag.nse)) {
606 #endif
607 		return;
608 	}
609 	nse_warning();
610 	fprintf(stderr, "Recursive make to derive %s did not use a makefile\n",
611 		target->string_mb);
612 }
613 
614 /*
615  * Return the NSE exit status.
616  * If the -P flag was given then a warning is considered fatal
617  */
618 int
619 nse_exit_status(void)
620 {
621 	return our_exit_status;
622 }
623 #endif
624