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