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