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         String_rec      string;
  98         Name            name;
  99         Name            target;
 100         struct  Line    *line;
 101         struct  Recursive *r;
 102         Property        recurse;
 103         wchar_t         strbuf[STRING_BUFFER_LENGTH];
 104         wchar_t         tmpbuf[STRING_BUFFER_LENGTH];
 105 
 106 #ifdef LTEST
 107         printf("In nse_check_cd, nse = %d, nse_did_recursion = %d\n", nse, nse_did_recursion);
 108 #endif
 109         if (!nse_did_recursion || !nse) {
 110 #ifdef LTEST
 111                 printf ("returning,  nse = %d,  nse_did_recursion = %d\n", nse, nse_did_recursion);
 112 #endif
 113                 return;
 114         }
 115         line = &prop->body.line;
 116 #ifdef LTEST
 117         printf("string = %s\n", line->command_template->command_line->string_mb);
 118 #endif
 119 
 120         wscpy(tmpbuf, line->command_template->command_line->string);
 121         our_template = tmpbuf;
 122         cd = false;
 123         while (nse_gettoken(&our_template, tok)) {
 124 #ifdef LTEST
 125                 printf("in gettoken loop\n");
 126 #endif
 127                 if (IS_WEQUAL(tok, (wchar_t *) "cd")) {
 128                         cd = true;
 129                 } else if (cd && tok[0] == '$') {
 130                         nse_backquote_seen = NULL;
 131                         nse_shell_var_used = NULL;
 132                         nse_watch_vars = true;
 133                         INIT_STRING_FROM_STACK(string, strbuf);
 134                         name = GETNAME(tok, FIND_LENGTH);
 135                         expand_value(name, &string, false);
 136                         nse_watch_vars = false;
 137 
 138 #ifdef LTEST
 139                         printf("cd = %d, tok = $\n", cd);
 140 #endif
 141                         /*
 142                          * Try to trim tok to just
 143                          * the variable.
 144                          */
 145                         if (nse_shell_var_used != NULL) {
 146                                 nse_warning();
 147                                 fprintf(stderr, "\tMake invoked recursively by cd'ing to a directory\n\tdefined by the shell environment variable %s\n\tCommand line: %s\n",
 148                                     nse_shell_var_used->string_mb,
 149                                     line->command_template->command_line->string_mb);
 150                         }
 151                         if (nse_backquote_seen != NULL) {
 152                                 nse_warning();
 153                                 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",
 154                                     nse_backquote_seen->string_mb,
 155                                     line->command_template->command_line->string_mb);
 156                         }
 157                         cd = false;
 158                 } else if (cd && nse_backquotes(tok)) {
 159                         nse_warning();
 160                         fprintf(stderr, "\tMake invoked recursively by cd'ing to a directory\n\tspecified by a command in backquotes\n\tCommand line: %s\n",
 161                             line->command_template->command_line->string_mb);
 162                         cd = false;
 163                 } else {
 164                         cd = false;
 165                 }
 166         }
 167 
 168         /*
 169          * Now check for recursion to ".".
 170          */
 171         if (primary_makefile != NULL) {
 172                 target = prop->body.line.target;
 173                 recurse = get_prop(target->prop, recursive_prop);
 174                 while (recurse != NULL) {
 175                         r = &recurse->body.recursive;
 176                         if (IS_WEQUAL(r->directory->string, (wchar_t *) ".") &&
 177                             !IS_WEQUAL(r->makefiles->name->string, 
 178                             primary_makefile->string)) {
 179                                 nse_warning();
 180                                 fprintf(stderr, "\tRecursion to makefile `%s' in the same directory\n\tCommand line: %s\n",
 181                                     r->makefiles->name->string_mb,
 182                                     line->command_template->command_line->string_mb);
 183                         }
 184                         recurse = get_prop(recurse->next, recursive_prop);
 185                 }
 186         }
 187 }
 188 
 189 /*
 190  * Print an NSE make warning line.
 191  * If the -P flag was given then consider this a fatal
 192  * error, otherwise, just a warning.
 193  */
 194 static void
 195 nse_warning(void)
 196 {
 197         if (report_dependencies_level > 0) {
 198                 our_exit_status = 1;
 199         }
 200         if (primary_makefile != NULL) {
 201                 fprintf(stderr, "make: NSE warning from makefile %s/%s:\n",
 202                         get_current_path(), primary_makefile->string_mb);
 203         } else {
 204                 fprintf(stderr, "make: NSE warning from directory %s:\n",
 205                         get_current_path());
 206         }
 207 }
 208 
 209 /*
 210  * Get the next whitespace delimited token pointed to by *cp.
 211  * Return it in tok.
 212  */
 213 static Boolean
 214 nse_gettoken(wchar_t **cp, wchar_t *tok)
 215 {
 216         wchar_t         *to;
 217         wchar_t         *p;
 218 
 219         p = *cp;
 220         while (*p && iswspace(*p)) {
 221                 p++;
 222         }
 223         if (*p == '\0') {
 224                 return false;
 225         }
 226         to = tok;
 227         while (*p && !iswspace(*p)) {
 228                 *to++ = *p++;
 229         }
 230         if (*p == '\0') {
 231                 return false;
 232         }
 233         *to = '\0';
 234         *cp = p;
 235         return true;
 236 }
 237 
 238 /*
 239  * Given a dependency and a target, see if the dependency
 240  * is an SCCS file.  Check for the last component of its name
 241  * beginning with "s." and the component before that being "SCCS".
 242  * The NSE does not consider a source file to be derived from
 243  * an SCCS file.
 244  */
 245 void
 246 nse_check_sccs(wchar_t *targ, wchar_t *dep)
 247 {
 248         wchar_t         *slash;
 249         wchar_t         *p;
 250 
 251         if (!nse) {
 252                 return;
 253         }
 254         slash = wsrchr(dep, (int) slash_char); 
 255         if (slash == NULL) {
 256                 return;
 257         }
 258         if (slash[1] != 's' || slash[2] != '.') {
 259                 return;
 260         }
 261 
 262         /*
 263          * Find the next to last filename component.
 264          */
 265         for (p = slash - 1; p >= dep; p--) {
 266                 if (*p == '/') {
 267                         break;
 268                 }
 269         }
 270         p++;
 271         MBSTOWCS(wcs_buffer, "SCCS/");
 272         if (IS_WEQUALN(p, wcs_buffer, wslen(wcs_buffer))) {
 273                 nse_warning();
 274                 WCSTOMBS(mbs_buffer, targ);
 275                 WCSTOMBS(mbs_buffer2, dep);
 276                 fprintf(stderr, "\tFile `%s' depends upon SCCS file `%s'\n",
 277                         mbs_buffer, mbs_buffer2);
 278         }
 279         return;
 280 }
 281 
 282 /*
 283  * Given a filename check to see if it has 2 backquotes in it.
 284  * Complain about this because the shell expands the backquotes
 285  * but make does not so the files always appear to be out of date.
 286  */
 287 void
 288 nse_check_file_backquotes(wchar_t *file)
 289 {
 290         if (!nse) {
 291                 return;
 292         }
 293         if (nse_backquotes(file)) {
 294                 nse_warning();
 295                 WCSTOMBS(mbs_buffer, file);
 296                 fprintf(stderr, "\tFilename \"%s\" has backquotes in it\n",
 297                         mbs_buffer);
 298         }
 299 }
 300 
 301 /*
 302  * Return true if the string has two backquotes in it.
 303  */
 304 Boolean
 305 nse_backquotes(wchar_t *str)
 306 {
 307         wchar_t         *bq;
 308 
 309         bq = wschr(str, (int) backquote_char);
 310         if (bq) {
 311                 bq = wschr(&bq[1], (int) backquote_char);
 312                 if (bq) {
 313                         return true;
 314                 }
 315         }
 316         return false;
 317 }
 318 
 319 /*
 320  * A macro that was defined on the command-line was found to affect the
 321  * set of dependencies.  The NSE "target explode" will not know about
 322  * this and will not get the same set of dependencies.
 323  */
 324 void
 325 nse_dep_cmdmacro(wchar_t *macro)
 326 {
 327         if (!nse) {
 328                 return;
 329         }
 330         nse_warning();
 331         WCSTOMBS(mbs_buffer, macro);
 332         fprintf(stderr, "\tVariable `%s' is defined on the command-line and\n\taffects dependencies\n",
 333                 mbs_buffer);
 334 }
 335 
 336 /*
 337  * A macro that was defined on the command-line was found to
 338  * be part of the argument to a cd before a recursive make.
 339  * This make cause the make to recurse to different places
 340  * depending upon how it is invoked.
 341  */
 342 void
 343 nse_rule_cmdmacro(wchar_t *macro)
 344 {
 345         if (!nse) {
 346                 return;
 347         }
 348         nse_warning();
 349         WCSTOMBS(mbs_buffer, macro);
 350         fprintf(stderr, "\tMake invoked recursively by cd'ing to a directory\n\tspecified by a variable (%s) defined on the command-line\n",
 351                 mbs_buffer);
 352 }
 353 
 354 /*
 355  * A dependency has been found with a wildcard in it.
 356  * This causes the NSE problems because the set of dependencies
 357  * can change without changing the Makefile.
 358  */
 359 void
 360 nse_wildcard(wchar_t *targ, wchar_t *dep)
 361 {
 362         if (!nse) {
 363                 return;
 364         }
 365         nse_warning();
 366         WCSTOMBS(mbs_buffer, targ);
 367         WCSTOMBS(mbs_buffer2, dep);
 368         fprintf(stderr, "\tFile `%s' has a wildcard in dependency `%s'\n",
 369                 mbs_buffer, mbs_buffer2);
 370 }
 371 
 372 /*
 373  * Read in the list of suffixes that are interpreted as source
 374  * files.
 375  */
 376 void
 377 nse_init_source_suffixes(void)
 378 {
 379         FILE            *fp;
 380         wchar_t         suffix[100];
 381         Nse_suffix      sufx;
 382         Nse_suffix      *bpatch;
 383 
 384         fp = fopen(TARG_SUFX, "r");
 385         if (fp == NULL) {
 386                 return;
 387         }
 388         bpatch = &sufx_hdr;
 389         while (fscanf(fp, "%s %*s", suffix) == 1) {
 390                 sufx = ALLOC(Nse_suffix);  
 391                 sufx->suffix = wscpy(ALLOC_WC(wslen(suffix) + 1), suffix);
 392                 sufx->next = NULL;
 393                 *bpatch = sufx;
 394                 bpatch = &sufx->next;
 395         }
 396         fclose(fp);
 397 }
 398 
 399 /*
 400  * Check if a derived file (something with a dependency) appears
 401  * to be a source file (by its suffix) but has no rule to build it.
 402  * If so, complain.
 403  *
 404  * This generally arises from the old-style of make-depend that
 405  * produces:
 406  *      foo.c:  foo.h
 407  */
 408 void
 409 nse_check_derived_src(Name target, wchar_t *dep, Cmd_line command_template)
 410 {
 411         Nse_suffix      sufx;
 412         wchar_t         *suffix;
 413         wchar_t         *depsufx;
 414 
 415         if (!nse) {
 416                 return;
 417         }
 418         if (target->stat.is_derived_src) {
 419                 return;
 420         }
 421         if (command_template != NULL) {
 422                 return;
 423         }
 424         suffix = wsrchr(target->string, (int) period_char ); 
 425         if (suffix != NULL) {
 426                 for (sufx = sufx_hdr; sufx != NULL; sufx = sufx->next) {
 427                         if (IS_WEQUAL(sufx->suffix, suffix)) {
 428                                 nse_warning();
 429                                 WCSTOMBS(mbs_buffer, dep);
 430                                 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",
 431                                         target->string_mb, mbs_buffer);
 432                                 break;
 433                         }
 434                 }
 435         }
 436 }
 437 
 438 /*
 439  * See if a target is a potential source file and has no
 440  * dependencies and no rule but shows up on the right-hand
 441  * side.  This tends to occur from old "make depend" output.
 442  */
 443 void
 444 nse_check_no_deps_no_rule(Name target, Property line, Property command)
 445 {
 446         Nse_suffix      sufx;
 447         wchar_t         *suffix;
 448 
 449         if (!nse) {
 450                 return;
 451         }
 452         if (target->stat.is_derived_src) {
 453                 return;
 454         }
 455         if (line != NULL && line->body.line.dependencies != NULL) {
 456                 return;
 457         }
 458         if (command->body.line.sccs_command) {
 459                 return;
 460         }
 461         suffix = wsrchr(target->string, (int) period_char); 
 462         if (suffix != NULL) {
 463                 for (sufx = sufx_hdr; sufx != NULL; sufx = sufx->next) {
 464                         if (IS_WEQUAL(sufx->suffix, suffix)) {
 465                                 if (command->body.line.command_template == NULL) {
 466                                         nse_warning();
 467                                         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",
 468                                                 target->string_mb);
 469                                 }
 470                         }
 471                 }
 472         } 
 473 } 
 474 
 475 /*
 476  * Detected a situation where a recursive make derived a file
 477  * without using a makefile.
 478  */
 479 void
 480 nse_no_makefile(Name target)
 481 {
 482         if (!nse) {
 483                 return;
 484         }
 485         nse_warning();
 486         fprintf(stderr, "Recursive make to derive %s did not use a makefile\n",
 487                 target->string_mb);
 488 }
 489 
 490 /*
 491  * Return the NSE exit status.
 492  * If the -P flag was given then a warning is considered fatal
 493  */
 494 int
 495 nse_exit_status(void)
 496 {
 497         return our_exit_status;
 498 }
 499 #endif