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