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