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, Version 1.0 only
   6  * (the "License").  You may not use this file except in compliance
   7  * with the License.
   8  *
   9  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
  10  * or http://www.opensolaris.org/os/licensing.
  11  * See the License for the specific language governing permissions
  12  * and limitations under the License.
  13  *
  14  * When distributing Covered Code, include this CDDL HEADER in each
  15  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
  16  * If applicable, add the following below this CDDL HEADER, with the
  17  * fields enclosed by brackets "[]" replaced with your own identifying
  18  * information: Portions Copyright [yyyy] [name of copyright owner]
  19  *
  20  * CDDL HEADER END
  21  */
  22 /*
  23  * Copyright (c) 1995 Sun Microsystems, Inc.  All Rights Reserved
  24  *
  25  * module:
  26  *      rules.c
  27  *
  28  * purpose:
  29  *      to read and write the rules file and manage rules lists
  30  *
  31  * contents:
  32  *      reading rules file
  33  *              read_rules
  34  *              (static) read_command
  35  *      writing rules file
  36  *              write_rules
  37  *              (static) rw_header, rw_base
  38  *      adding rules
  39  *              add_ignore, add_include
  40  *              (static) add_rule
  41  *      adding/checking restrictions
  42  *              add_restr, check_restr
  43  */
  44 #pragma ident   "%Z%%M% %I%     %E% SMI"
  45 
  46 #include <stdio.h>
  47 #include <stdlib.h>
  48 #include <string.h>
  49 #include <time.h>
  50 #include <ctype.h>
  51 
  52 #include "filesync.h"
  53 #include "database.h"
  54 #include "messages.h"
  55 #include "debug.h"
  56 
  57 /*
  58  * routines:
  59  */
  60 static errmask_t rw_base(FILE *file, struct base *bp);
  61 static errmask_t rw_header(FILE *file);
  62 static errmask_t add_rule(struct base *, int, const char *);
  63 static char *read_cmd(char *);
  64 
  65 /*
  66  * globals
  67  */
  68 static int rules_added;
  69 static int restr_added;
  70 
  71 /*
  72  * locals
  73  */
  74 #define RULE_MAJOR      1               /* rules file format major rev  */
  75 #define RULE_MINOR      1               /* rules file format minor rev  */
  76 #define RULE_TAG        "PACKINGRULES"  /* magic string for rules files */
  77 
  78 /*
  79  * routine:
  80  *      read_rules
  81  *
  82  * purpose:
  83  *      to read in the rules file
  84  *
  85  * parameters:
  86  *      name of rules file
  87  *
  88  * returns:
  89  *      error mask
  90  *
  91  * notes:
  92  *      later when I implement a proper (comment preserving) update
  93  *      function I'm going to wish I had figured out how to build the
  94  *      input functions for this function in a way that would make
  95  *      the more usable for that too.
  96  */
  97 errmask_t
  98 read_rules(char *name)
  99 {       FILE *file;
 100         errmask_t errs = 0;
 101         int flags;
 102         int major, minor;
 103         char *s, *s1, *s2;
 104         struct base *bp;
 105         char *errstr = "???";
 106 
 107         file = fopen(name, "r");
 108         if (file == NULL) {
 109                 fprintf(stderr, gettext(ERR_open), gettext(TXT_rules),
 110                         name);
 111                 return (ERR_FILES);
 112         }
 113 
 114         lex_linenum = 0;
 115 
 116         if (opt_debug & DBG_FILES)
 117                 fprintf(stderr, "FILE: READ RULES %s\n", name);
 118 
 119         bp = &omnibase;             /* default base before any others       */
 120 
 121         while (!feof(file)) {
 122                 /* find the first token on the line     */
 123                 s = lex(file);
 124 
 125                 /* skip blank lines and comments        */
 126                 if (s == 0 || *s == 0 || *s == '#' || *s == '*')
 127                         continue;
 128 
 129                 /* see if the first token is a known keyword    */
 130                 if (strcmp(s, "BASE") == 0) {
 131 
 132                         /* get the source & destination tokens      */
 133                         errstr = gettext(TXT_srcdst);
 134                         s1 = lex(0);
 135                         if (s1 == 0)
 136                                 goto bad;
 137                         s1 = strdup(s1);
 138 
 139                         s2 = lex(0);
 140                         if (s2 == 0)
 141                                 goto bad;
 142                         s2 = strdup(s2);
 143 
 144                         /* creat the new base pair              */
 145                         bp = add_base(s1, s2);
 146                         bp->b_flags |= F_LISTED;
 147 
 148                         free(s1);
 149                         free(s2);
 150                         continue;
 151                 }
 152 
 153                 if (strcmp(s, "LIST") == 0) {
 154 
 155                         /* make sure we are associated with a real base */
 156                         if (bp == &omnibase) {
 157                                 errstr = gettext(TXT_nobase);
 158                                 goto bad;
 159                         }
 160 
 161                         /* skip to the next token */
 162                         s = lex(0);
 163                         errstr = gettext(TXT_noargs);
 164                         if (s == 0)
 165                                 goto bad;
 166 
 167                         /* see if it is a program or a name */
 168                         if (*s == '!') {
 169                                 errs |= add_rule(bp, R_PROGRAM,
 170                                                 read_cmd(&s[1]));
 171                         } else {
 172                                 do {
 173                                         flags = wildcards(s) ? R_WILD : 0;
 174                                         errs |= add_rule(bp, flags, s);
 175                                         s = lex(0);
 176                                 } while (s != 0);
 177                         }
 178                         continue;
 179                 }
 180 
 181                 if (strcmp(s, "IGNORE") == 0) {
 182 
 183                         /* skip to the next token */
 184                         s = lex(0);
 185                         errstr = gettext(TXT_noargs);
 186                         if (s == 0)
 187                                 goto bad;
 188 
 189                         flags = R_IGNORE;
 190 
 191                         /* see if it is a program or a name */
 192                         if (*s == '!') {
 193                                 errs |= add_rule(bp, R_PROGRAM|flags,
 194                                                 read_cmd(&s[1]));
 195                         } else {
 196                                 do {
 197                                         if (wildcards(s))
 198                                                 flags |= R_WILD;
 199                                         errs |= add_rule(bp, flags, s);
 200                                         s = lex(0);
 201                                 } while (s != 0);
 202                         }
 203                         continue;
 204                 }
 205 
 206                 if (strcmp(s, "VERSION") == 0 || strcmp(s, RULE_TAG) == 0) {
 207                         s = lex(0);
 208                         errstr = gettext(TXT_noargs);
 209                         if (s == 0)
 210                                 goto bad;
 211 
 212                         major = strtol(s, &s1, 10);
 213                         errstr = gettext(TXT_badver);
 214                         if (*s1 != '.')
 215                                 goto bad;
 216                         minor = strtol(&s1[1], 0, 10);
 217 
 218                         if (major != RULE_MAJOR || minor > RULE_MINOR) {
 219                                 fprintf(stderr, gettext(ERR_badver),
 220                                         major, minor, gettext(TXT_rules), name);
 221                                 errs |= ERR_FILES;
 222                         }
 223                         continue;
 224                 }
 225 
 226         bad:    /* log the error and continue processing to find others */
 227                 fprintf(stderr, gettext(ERR_badinput),
 228                         lex_linenum, errstr, name);
 229                 errs |= ERR_FILES;
 230         }
 231 
 232 
 233         (void) fclose(file);
 234         return (errs);
 235 }
 236 
 237 /*
 238  * routine:
 239  *      read_cmd
 240  *
 241  * purpose:
 242  *      to lex a runnable command (! lines) into a buffer
 243  *
 244  * parameters:
 245  *      first token
 246  *
 247  * returns:
 248  *      pointer to a command line in a static buffer
 249  *      (it is assumed the caller will copy it promptly)
 250  *
 251  * notes:
 252  *      this is necessary because lex has already choped off
 253  *      the first token for us
 254  */
 255 static char *read_cmd(char * s)
 256 {
 257         static char cmdbuf[ MAX_LINE ];
 258 
 259         cmdbuf[0] = 0;
 260 
 261         do {
 262                 if (*s) {
 263                         strcat(cmdbuf, s);
 264                         strcat(cmdbuf, " ");
 265                 }
 266         } while ((s = lex(0)) != 0);
 267 
 268         return (cmdbuf);
 269 }
 270 
 271 /*
 272  * routine:
 273  *      write_rules
 274  *
 275  * purpose:
 276  *      to rewrite the rules file, appending the new rules
 277  *
 278  * parameters:
 279  *      name of output file
 280  *
 281  * returns:
 282  *      error mask
 283  *
 284  */
 285 errmask_t
 286 write_rules(char *name)
 287 {       FILE *newfile;
 288         errmask_t errs = 0;
 289         struct base *bp;
 290         char tmpname[ MAX_PATH ];
 291 
 292         /* if no-touch is specified, we don't update files      */
 293         if (opt_notouch || rules_added == 0)
 294                 return (0);
 295 
 296         /* create a temporary output file                       */
 297         sprintf(tmpname, "%s-TMP", name);
 298 
 299         /* create our output file       */
 300         newfile = fopen(tmpname, "w+");
 301         if (newfile == NULL) {
 302                 fprintf(stderr, gettext(ERR_creat), gettext(TXT_rules),
 303                         name);
 304                 return (ERR_FILES);
 305         }
 306 
 307         if (opt_debug & DBG_FILES)
 308                 fprintf(stderr, "FILE: UPDATE RULES %s\n", name);
 309 
 310         errs |= rw_header(newfile);
 311         errs |= rw_base(newfile, &omnibase);
 312         for (bp = bases; bp; bp = bp->b_next)
 313                 errs |= rw_base(newfile, bp);
 314 
 315         if (ferror(newfile)) {
 316                 fprintf(stderr, gettext(ERR_write), gettext(TXT_rules),
 317                         tmpname);
 318                 errs |= ERR_FILES;
 319         }
 320 
 321         if (fclose(newfile)) {
 322                 fprintf(stderr, gettext(ERR_fclose), gettext(TXT_rules),
 323                         tmpname);
 324                 errs |= ERR_FILES;
 325         }
 326 
 327         /* now switch the new file for the old one      */
 328         if (errs == 0)
 329                 if (rename(tmpname, name) != 0) {
 330                         fprintf(stderr, gettext(ERR_rename),
 331                                 gettext(TXT_rules), tmpname, name);
 332                         errs |= ERR_FILES;
 333                 }
 334 
 335         return (errs);
 336 }
 337 
 338 /*
 339  * routine:
 340  *      rw_header
 341  *
 342  * purpose:
 343  *      to write out a rules header
 344  *
 345  * parameters:
 346  *      FILE* for the output file
 347  *
 348  * returns:
 349  *      error mask
 350  *
 351  * notes:
 352  */
 353 static errmask_t rw_header(FILE *file)
 354 {
 355         time_t now;
 356         struct tm *local;
 357 
 358         /* figure out what time it is   */
 359         (void) time(&now);
 360         local = localtime(&now);
 361 
 362         fprintf(file, "%s %d.%d\n", RULE_TAG, RULE_MAJOR, RULE_MINOR);
 363         fprintf(file, "#\n");
 364         fprintf(file, "# filesync rules, last written by %s, %s",
 365                 cuserid((char *) 0), asctime(local));
 366         fprintf(file, "#\n");
 367 
 368         return (0);
 369 }
 370 
 371 /*
 372  * routine:
 373  *      rw_base
 374  *
 375  * purpose:
 376  *      to write out the summary for one base-pair
 377  *
 378  * parameters:
 379  *      FILE * for the output file
 380  *
 381  * returns:
 382  *      error mask
 383  *
 384  * notes:
 385  */
 386 static errmask_t rw_base(FILE *file, struct base *bp)
 387 {       struct rule *rp;
 388 
 389         fprintf(file, "\n");
 390 
 391         /* global rules don't appear within a base */
 392         if (bp->b_ident)
 393                 fprintf(file, "BASE %s %s\n", noblanks(bp->b_src_spec),
 394                                 noblanks(bp->b_dst_spec));
 395 
 396         for (rp = bp->b_includes; rp; rp = rp->r_next)
 397                 if (rp->r_flags & R_PROGRAM)
 398                         fprintf(file, "LIST !%s\n", rp->r_file);
 399                 else
 400                         fprintf(file, "LIST %s\n", noblanks(rp->r_file));
 401 
 402         for (rp = bp->b_excludes; rp; rp = rp->r_next)
 403                 if (rp->r_flags & R_PROGRAM)
 404                         fprintf(file, "IGNORE !%s\n", rp->r_file);
 405                 else
 406                         fprintf(file, "IGNORE %s\n", noblanks(rp->r_file));
 407 
 408         return (0);
 409 }
 410 
 411 /*
 412  * routine:
 413  *      add_rule
 414  *
 415  * purpose:
 416  *      to add a new rule
 417  *
 418  * parameters:
 419  *      pointer to list base
 420  *      rule flags
 421  *      associated name/arguments
 422  *
 423  * returns:
 424  *      error flags
 425  *
 426  * notes:
 427  *      we always copy the argument string because most of them
 428  *      were read from a file and are just in a transient buffer
 429  */
 430 static errmask_t add_rule(struct base *bp, int flags, const char *args)
 431 {       struct rule *rp;
 432         struct rule **list;
 433 
 434         rp = malloc(sizeof (struct rule));
 435         if (rp == 0)
 436                 nomem("rule struture");
 437 
 438         /* initialize the new base                      */
 439         memset((void *) rp, 0, sizeof (struct rule));
 440         rp->r_flags = flags;
 441         rp->r_file = strdup(args);
 442 
 443         /* figure out which list to put it on           */
 444         if (flags&R_IGNORE)
 445                 list = &bp->b_excludes;
 446         else if (flags&R_RESTRICT)
 447                 list = &bp->b_restrictions;
 448         else
 449                 list = &bp->b_includes;
 450 
 451         while (*list)
 452                 list = &((*list)->r_next);
 453         *list = rp;
 454 
 455         if (flags & R_NEW)
 456                 rules_added++;
 457 
 458         if (opt_debug & DBG_RULE) {
 459                 fprintf(stderr, "RULE: base=%d, ", bp->b_ident);
 460                 fprintf(stderr, "flags=%s, ",
 461                         showflags(rflags, rp->r_flags));
 462                 fprintf(stderr, "arg=%s\n", rp->r_file);
 463         }
 464 
 465         return (0);
 466 }
 467 
 468 /*
 469  * routine:
 470  *      add_ignore, add_include
 471  *
 472  * purpose:
 473  *      wrappers for add_rule that permit outsiders (like main.c)
 474  *      not to know what is inside of a base, file, or list entry
 475  *
 476  * parameters:
 477  *      base under which rules should be added
 478  *      argument associated with rule
 479  *
 480  * returns:
 481  *      error flags
 482  *
 483  * notes:
 484  *      basically these routines figure out what the right
 485  *      flags are for a rule, and what list to put it on,
 486  *      and then call a common handler.
 487  */
 488 errmask_t
 489 add_ignore(struct base *bp, char *name)
 490 {       int flags = R_IGNORE | R_NEW;
 491 
 492         if (bp == 0)
 493                 bp = &omnibase;
 494 
 495         if (wildcards(name))
 496                 flags |= R_WILD;
 497 
 498         return (add_rule(bp, flags, name));
 499 }
 500 
 501 errmask_t
 502 add_include(struct base *bp, char *name)
 503 {       int flags = R_NEW;
 504 
 505         if (bp == 0)
 506                 bp = &omnibase;
 507 
 508         if (wildcards(name))
 509                 flags |= R_WILD;
 510 
 511         bp->b_flags |= F_LISTED;
 512 
 513         return (add_rule(bp, flags, name));
 514 }
 515 
 516 /*
 517  * routine:
 518  *      add_restr
 519  *
 520  * purpose:
 521  *      to add a restriction to a base
 522  *
 523  * parameters:
 524  *      address of base
 525  *      restriction string
 526  *
 527  * returns:
 528  *      error mask
 529  *
 530  * notes:
 531  *      a restriction is specified on the command line and
 532  *      tells us to limit our analysis/reconcilation to
 533  *      specified files and/or directories.  We deal with
 534  *      these by adding a restriction rule to any base that
 535  *      looks like it might fit the restriction.  We need to
 536  *      treat this as a rule because the restriction string
 537  *      may extend beyond the base directory and part-way into
 538  *      its tree ... meaning that individual file names under
 539  *      the base will have to be checked against the restriction.
 540  */
 541 errmask_t
 542 add_restr(char *restr)
 543 {       const char *s;
 544         errmask_t errs = 0;
 545         struct base *bp;
 546 
 547         for (bp = bases; bp; bp = bp->b_next) {
 548                 /*
 549                  * see if this restriction could apply to this base.
 550                  * It could match either the source or destination
 551                  * directory name for this base.  If it matches neither
 552                  * then the restriction does not apply to this base.
 553                  */
 554                 s = prefix(restr, bp->b_src_name);
 555                 if (s == 0)
 556                         s = prefix(restr, bp->b_dst_name);
 557                 if (s == 0)
 558                         continue;
 559 
 560                 /*
 561                  * if there is more restriction string after the
 562                  * base, we will need to note the remainder of the
 563                  * string so that we can match individual files
 564                  * against it.
 565                  */
 566                 if (*s == '/')
 567                         s++;
 568 
 569                 errs |= add_rule(bp, R_RESTRICT, s);
 570                 restr_added++;
 571         }
 572 
 573         return (errs);
 574 }
 575 
 576 /*
 577  * routine:
 578  *      check_restr
 579  *
 580  * purpose:
 581  *      to see if an argument falls within restrictions
 582  *
 583  * parameters:
 584  *      pointer to relevant base
 585  *      file name
 586  *
 587  * returns:
 588  *      TRUE    name is within restrictions
 589  *      FALSE   name is outside of restrictions
 590  *      MAYBE   name is on the path to a restriction
 591  *
 592  * notes:
 593  *      if no restrictions have been specified, we evaluate
 594  *      everything.  If any restrictions have been specified,
 595  *      we process only files that match one of the restrictions.
 596  *
 597  *      add_restr has ensured that if the restriction includes
 598  *      a portion that must be matched by individual files under
 599  *      the base, that the restriction rule will contain that
 600  *      portion of the restriction which must be matched against
 601  *      individual file names.
 602  */
 603 bool_t
 604 check_restr(struct base *bp, const char *name)
 605 {       struct rule *rp;
 606 
 607         /* if there are no restrictions, everything is OK       */
 608         if (restr_added == 0)
 609                 return (TRUE);
 610 
 611         /* now we have to run through the list                  */
 612         for (rp = bp->b_restrictions; rp; rp = rp->r_next) {
 613                 /* see if current path is under the restriction */
 614                 if (prefix(name, rp->r_file))
 615                         return (TRUE);
 616 
 617                 /* see if current path is on the way to restr   */
 618                 if (prefix(rp->r_file, name))
 619                         /*
 620                          * this is kinky, but walker really needs
 621                          * to know the difference between a directory
 622                          * that we are unreservedly scanning, and one
 623                          * that we are scanning only to find something
 624                          * beneath it.
 625                          */
 626                         return (MAYBE);
 627         }
 628 
 629         /*
 630          * there are restrictions in effect and this file doesn't seem
 631          * to meet any of them
 632          */
 633         if (opt_debug & DBG_RULE)
 634                 fprintf(stderr, "RULE: FAIL RESTRICTION base=%d, file=%s\n",
 635                         bp->b_ident, name);
 636 
 637         return (FALSE);
 638 }