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