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 2007 Sun Microsystems, Inc.  All rights reserved.
  23  * Use is subject to license terms.
  24  */
  25 
  26 /*
  27  * module:
  28  *      main.c
  29  *
  30  * purpose:
  31  *      argument handling and top level dispatch
  32  *
  33  * contents:
  34  *      main            argument handling and main loop
  35  *      usage           (static) print out usage message
  36  *      confirm         prompt the user for a confirmation and get it
  37  *      nomem           fatal error handler for malloc failures
  38  *      findfiles       (static) locate our baseline and rules files
  39  *      cleanup         (static) unlock baseline and delete temp file
  40  *      check_access    (static) do we have adequate access to a file/directory
  41  *      whoami          (static) get uid/gid/umask
  42  */
  43 
  44 #pragma ident   "%Z%%M% %I%     %E% SMI"
  45 
  46 #include <unistd.h>
  47 #include <stdlib.h>
  48 #include <fcntl.h>
  49 #include <stdio.h>
  50 #include <string.h>
  51 #include <ctype.h>
  52 #include <errno.h>
  53 #include <sys/stat.h>
  54 
  55 #include "filesync.h"
  56 #include "database.h"
  57 #include "messages.h"
  58 #include "debug.h"
  59 
  60 /*
  61  * local routines in this module:
  62  */
  63 static errmask_t findfiles();           /* find rule and baseline files */
  64 static void cleanup(int);               /* cleanup locks and temps      */
  65 static errmask_t check_access(char *, int *); /* check access to file   */
  66 static void whoami();                   /* gather information about me  */
  67 static void usage(void);                /* general usage                */
  68 
  69 
  70 /*
  71  * globals exported to the rest of the program
  72  */
  73 bool_t  opt_mtime;      /* preserve modification times on propagations  */
  74 bool_t  opt_notouch;    /* don't actually make any changes              */
  75 bool_t  opt_quiet;      /* disable reconciliation command output        */
  76 bool_t  opt_verbose;    /* enable analysis descriptions                 */
  77 side_t  opt_force;      /* designated winner for conflicts              */
  78 side_t  opt_oneway;     /* one way only propagation                     */
  79 side_t  opt_onesided;   /* permit one-sided evaluation                  */
  80 bool_t  opt_everything; /* everything must agree (modes/uid/gid)        */
  81 bool_t  opt_yes;        /* pre-confirm massive deletions are OK         */
  82 bool_t  opt_acls;       /* always scan for acls on all files            */
  83 bool_t  opt_errors;     /* simulate errors on specified files           */
  84 bool_t  opt_halt;       /* halt on propagation errors                   */
  85 dbgmask_t opt_debug;    /* debug mask                                   */
  86 
  87 uid_t   my_uid;         /* default UID for files I create               */
  88 gid_t   my_gid;         /* default GID for files I create               */
  89 
  90 static char *file_rules; /* name of rules file                          */
  91 static char *file_base; /* name of baseline file                        */
  92 
  93 static int new_baseline; /* are we creating a new baseline              */
  94 static int new_rules;   /* are we creating a new rules file             */
  95 static int my_umask;    /* default UMASK for files I create             */
  96 static int lockfd;      /* file descriptor for locking baseline         */
  97 
  98 static char *rlist[MAX_RLIST];
  99 static int num_restrs = 0;
 100 
 101 /*
 102  * routine:
 103  *      main
 104  *
 105  * purpose:
 106  *      argument processing and primary dispatch
 107  *
 108  * returns:
 109  *      error codes per filesync.1 (ERR_* in filesync.h)
 110  *
 111  * notes:
 112  *      read filesync.1 in order to understand the argument processing
 113  *
 114  *      most of the command line options just set some opt_ global
 115  *      variable that is later looked at by the code that actually
 116  *      implements the features.  Only file names are really processed
 117  *      in this routine.
 118  */
 119 int
 120 main(int argc, char **argv)
 121 {       int i;
 122         int c;
 123         errmask_t errs = ERR_OK;
 124         int do_prune = 0;
 125         char *srcname = 0;
 126         char *dstname = 0;
 127         struct base *bp;
 128 
 129         /* keep the error messages simple       */
 130         argv[0] = "filesync";
 131 
 132         /* gather together all of the options   */
 133         while ((c = getopt(argc, argv, "AaehmnqvyD:E:r:s:d:f:o:")) != EOF)
 134                 switch (c) {
 135                         case 'a':       /* always scan for acls */
 136                                 opt_acls = TRUE;
 137                                 break;
 138                         case 'e':       /* everything agrees    */
 139                                 opt_everything = TRUE;
 140                                 break;
 141                         case 'h':       /* halt on error        */
 142                                 opt_halt = TRUE;
 143                                 break;
 144                         case 'm':       /* preserve modtimes    */
 145                                 opt_mtime = TRUE;
 146                                 break;
 147                         case 'n':       /* notouch              */
 148                                 opt_notouch = TRUE;
 149                                 break;
 150                         case 'q':       /* quiet                */
 151                                 opt_quiet = TRUE;
 152                                 break;
 153                         case 'v':       /* verbose              */
 154                                 opt_verbose = TRUE;
 155                                 break;
 156                         case 'y':       /* yes                  */
 157                                 opt_yes = TRUE;
 158                                 break;
 159                         case 'D':       /* debug options        */
 160                                 if (!isdigit(optarg[0])) {
 161                                         dbg_usage();
 162                                         exit(ERR_INVAL);
 163                                 }
 164                                 opt_debug |= strtol(optarg, (char **)NULL, 0);
 165                                 break;
 166 
 167                         case 'E':       /* error simulation     */
 168                                 if (dbg_set_error(optarg)) {
 169                                         err_usage();
 170                                         exit(ERR_INVAL);
 171                                 }
 172                                 opt_errors = TRUE;
 173                                 break;
 174 
 175                         case 'f':       /* force conflict resolution    */
 176                                 switch (optarg[0]) {
 177                                         case 's':
 178                                                 opt_force = OPT_SRC;
 179                                                 break;
 180                                         case 'd':
 181                                                 opt_force = OPT_DST;
 182                                                 break;
 183                                         case 'o':
 184                                                 opt_force = OPT_OLD;
 185                                                 break;
 186                                         case 'n':
 187                                                 opt_force = OPT_NEW;
 188                                                 break;
 189                                         default:
 190                                                 fprintf(stderr,
 191                                                         gettext(ERR_badopt),
 192                                                         c, optarg);
 193                                                 errs |= ERR_INVAL;
 194                                                 break;
 195                                 }
 196                                 break;
 197 
 198                         case 'o':       /* one way propagation          */
 199                                 switch (optarg[0]) {
 200                                         case 's':
 201                                                 opt_oneway = OPT_SRC;
 202                                                 break;
 203                                         case 'd':
 204                                                 opt_oneway = OPT_DST;
 205                                                 break;
 206                                         default:
 207                                                 fprintf(stderr,
 208                                                         gettext(ERR_badopt),
 209                                                         c, optarg);
 210                                                 errs |= ERR_INVAL;
 211                                                 break;
 212                                 }
 213                                 break;
 214 
 215                         case 'r':       /* restricted reconciliation    */
 216                                 if (num_restrs < MAX_RLIST)
 217                                         rlist[ num_restrs++ ] = optarg;
 218                                 else {
 219                                         fprintf(stderr, gettext(ERR_tomany),
 220                                                 MAX_RLIST);
 221                                         errs |= ERR_INVAL;
 222                                 }
 223                                 break;
 224 
 225                         case 's':
 226                                 if ((srcname = qualify(optarg)) == 0)
 227                                         errs |= ERR_MISSING;
 228                                 break;
 229 
 230                         case 'd':
 231                                 if ((dstname = qualify(optarg)) == 0)
 232                                         errs |= ERR_MISSING;
 233                                 break;
 234 
 235                         default:
 236                         case '?':
 237                                 errs |= ERR_INVAL;
 238                                 break;
 239                 }
 240 
 241         if (opt_debug & DBG_MISC)
 242                 fprintf(stderr, "MISC: DBG=%s\n", showflags(dbgmap, opt_debug));
 243 
 244         /* if we have file names, we need a source and destination */
 245         if (optind < argc) {
 246                 if (srcname == 0) {
 247                         fprintf(stderr, gettext(ERR_nosrc));
 248                         errs |= ERR_INVAL;
 249                 }
 250                 if (dstname == 0) {
 251                         fprintf(stderr, gettext(ERR_nodst));
 252                         errs |= ERR_INVAL;
 253                 }
 254         }
 255 
 256         /* check for simple usage errors        */
 257         if (errs & ERR_INVAL) {
 258                 usage();
 259                 exit(errs);
 260         }
 261 
 262         /* locate our baseline and rules files  */
 263         if (c = findfiles())
 264                 exit(c);
 265 
 266         /* figure out file creation defaults    */
 267         whoami();
 268 
 269         /* read in our initial baseline         */
 270         if (!new_baseline && (c = read_baseline(file_base)))
 271                 errs |= c;
 272 
 273         /* read in the rules file if we need or have rules      */
 274         if (optind >= argc && new_rules) {
 275                 fprintf(stderr, ERR_nonames);
 276                 errs |= ERR_INVAL;
 277         } else if (!new_rules)
 278                 errs |= read_rules(file_rules);
 279 
 280         /* if anything has failed with our setup, go no further */
 281         if (errs) {
 282                 cleanup(errs);
 283                 exit(errs);
 284         }
 285 
 286         /*
 287          * figure out whether or not we are willing to do a one-sided
 288          * analysis (where we don't even look at the other side.  This
 289          * is an "I'm just curious what has changed" query, and we are
 290          * only willing to do it if:
 291          *      we aren't actually going to do anything
 292          *      we have a baseline we can compare against
 293          * otherwise, we are going to insist on being able to access
 294          * both the source and destination.
 295          */
 296         if (opt_notouch && !new_baseline)
 297                 opt_onesided = opt_oneway;
 298 
 299         /*
 300          * there are two interested usage scenarios:
 301          *      file names specified
 302          *              create new rules for the specified files
 303          *              evaulate and reconcile only the specified files
 304          *      no file names specified
 305          *              use already existing rules
 306          *              consider restricting them to specified subdirs/files
 307          */
 308         if (optind < argc) {
 309                 /* figure out what base pair we're working on   */
 310                 bp = add_base(srcname, dstname);
 311 
 312                 /* perverse default rules to avoid trouble      */
 313                 if (new_rules) {
 314                         errs |= add_ignore(0, SUFX_RULES);
 315                         errs |= add_ignore(0, SUFX_BASE);
 316                 }
 317 
 318                 /* create include rules for each file/dir arg   */
 319                 while (optind < argc)
 320                         errs |= add_include(bp, argv[ optind++ ]);
 321 
 322                 /*
 323                  * evaluate the specified base on each side,
 324                  * being careful to limit evaulation to new rules
 325                  */
 326                 errs |= evaluate(bp, OPT_SRC, TRUE);
 327                 errs |= evaluate(bp, OPT_DST, TRUE);
 328         } else {
 329                 /* note any possible evaluation restrictions    */
 330                 for (i = 0; i < num_restrs; i++)
 331                         errs |= add_restr(rlist[i]);
 332 
 333                 /*
 334                  * we can only prune the baseline file if we have done
 335                  * a complete (unrestricted) analysis.
 336                  */
 337                 if (i == 0)
 338                         do_prune = 1;
 339 
 340                 /* evaulate each base on each side              */
 341                 for (bp = bases; bp; bp = bp->b_next) {
 342                         errs |= evaluate(bp, OPT_SRC, FALSE);
 343                         errs |= evaluate(bp, OPT_DST, FALSE);
 344                 }
 345         }
 346 
 347         /* if anything serious happened, skip reconciliation    */
 348         if (errs & ERR_FATAL) {
 349                 cleanup(errs);
 350                 exit(errs);
 351         }
 352 
 353         /* analyze and deal with the differenecs                */
 354         errs |= analyze();
 355 
 356         /* see if there is any dead-wood in the baseline        */
 357         if (do_prune) {
 358                 c = prune();
 359 
 360                 if (c > 0 && opt_verbose)
 361                         fprintf(stdout, V_prunes, c);
 362         }
 363 
 364         /* print out a final summary                            */
 365         summary();
 366 
 367         /* update the rules and baseline files (if needed)      */
 368         (void) umask(my_umask);
 369         errs |= write_baseline(file_base);
 370         errs |= write_rules(file_rules);
 371 
 372         if (opt_debug & DBG_MISC)
 373                 fprintf(stderr, "MISC: EXIT=%s\n", showflags(errmap, errs));
 374 
 375         /* just returning ERR_RESOLVABLE upsets some people     */
 376         if (errs == ERR_RESOLVABLE && !opt_notouch)
 377                 errs = 0;
 378 
 379         /* all done     */
 380         cleanup(0);
 381         return (errs);
 382 }
 383 
 384 
 385 /*
 386  * routine:
 387  *      usage
 388  *
 389  * purpose:
 390  *      print out a usage message
 391  *
 392  * parameters:
 393  *      none
 394  *
 395  * returns:
 396  *      none
 397  *
 398  * note:
 399  *      the -D and -E switches are for development/test/support
 400  *      use only and do not show up in the general usage message.
 401  */
 402 static void
 403 usage(void)
 404 {
 405         fprintf(stderr, "%s\t%s %s\n", gettext(ERR_usage), "filesync",
 406                                         gettext(USE_simple));
 407         fprintf(stderr, "\t%s %s\n", "filesync", gettext(USE_all));
 408         fprintf(stderr, "\t-a .......... %s\n", gettext(USE_a));
 409         fprintf(stderr, "\t-e .......... %s\n", gettext(USE_e));
 410         fprintf(stderr, "\t-h .......... %s\n", gettext(USE_h));
 411         fprintf(stderr, "\t-m .......... %s\n", gettext(USE_m));
 412         fprintf(stderr, "\t-n .......... %s\n", gettext(USE_n));
 413         fprintf(stderr, "\t-q .......... %s\n", gettext(USE_q));
 414         fprintf(stderr, "\t-v .......... %s\n", gettext(USE_v));
 415         fprintf(stderr, "\t-y .......... %s\n", gettext(USE_y));
 416         fprintf(stderr, "\t-s dir ...... %s\n", gettext(USE_s));
 417         fprintf(stderr, "\t-d dir ...... %s\n", gettext(USE_d));
 418         fprintf(stderr, "\t-r dir ...... %s\n", gettext(USE_r));
 419         fprintf(stderr, "\t-f [sdon].... %s\n", gettext(USE_f));
 420         fprintf(stderr, "\t-o src/dst... %s\n", gettext(USE_o));
 421 }
 422 
 423 /*
 424  * routine:
 425  *      confirm
 426  *
 427  * purpose:
 428  *      to confirm that the user is willing to do something dangerous
 429  *
 430  * parameters:
 431  *      warning message to be printed
 432  *
 433  * returns:
 434  *      void
 435  *
 436  * notes:
 437  *      if this is a "notouch" or if the user has pre-confirmed,
 438  *      we should not obtain the confirmation and just return that
 439  *      the user has confirmed.
 440  */
 441 void
 442 confirm(char *message)
 443 {       FILE *ttyi, *ttyo;
 444         char ansbuf[ MAX_LINE ];
 445 
 446         /* if user pre-confirmed, we don't have to ask  */
 447         if (opt_yes || opt_notouch)
 448                 return;
 449 
 450         ttyo = fopen("/dev/tty", "w");
 451         ttyi = fopen("/dev/tty", "r");
 452         if (ttyi == NULL || ttyo == NULL)
 453                 exit(ERR_OTHER);
 454 
 455         /* explain the problem and prompt for confirmation      */
 456         fprintf(ttyo, message);
 457         fprintf(ttyo, gettext(WARN_proceed));
 458 
 459         /* if the user doesn't kill us, we can continue         */
 460         (void) fgets(ansbuf, sizeof (ansbuf), ttyi);
 461 
 462         /* close the files and return                           */
 463         (void) fclose(ttyi);
 464         (void) fclose(ttyo);
 465 }
 466 
 467 void
 468 nomem(char *reason)
 469 {
 470         fprintf(stderr, gettext(ERR_nomem), reason);
 471         exit(ERR_OTHER);
 472 }
 473 
 474 /*
 475  * routine:
 476  *      findfiles
 477  *
 478  * purpose:
 479  *      to locate our baseline and rules files
 480  *
 481  * parameters:
 482  *      none
 483  *
 484  * returns:
 485  *      error mask
 486  *      settings of file_base and file_rules
 487  *
 488  * side-effects:
 489  *      in order to keep multiple filesyncs from running in parallel
 490  *      we put an advisory lock on the baseline file.  If the baseline
 491  *      file does not exist we create one.  The unlocking (and deletion
 492  *      of extraneous baselines) is handled in cleanup.
 493  */
 494 static errmask_t
 495 findfiles(void)         /* find rule and baseline files */
 496 {       char *s, *where;
 497         char namebuf[MAX_PATH];
 498         int ret;
 499         errmask_t errs = 0;
 500 
 501         /* figure out where the files should be located */
 502         s = getenv("FILESYNC");
 503         where = (s && *s) ? expand(s) : expand(DFLT_PRFX);
 504 
 505         /* see if we got a viable name          */
 506         if (where == 0) {
 507                 fprintf(stderr, gettext(ERR_nofsync));
 508                 return (ERR_FILES);
 509         }
 510 
 511         /* try to form the name of the rules file */
 512         strcpy(namebuf, where);
 513         strcat(namebuf, SUFX_RULES);
 514         s = strdup(namebuf);
 515         errs = check_access(namebuf, &new_rules);
 516 
 517         /* if we cannot find a proper rules file, look in the old place */
 518         if (new_rules && errs == 0) {
 519                 strcpy(namebuf, where);
 520                 strcat(namebuf, SUFX_OLD);
 521                 file_rules = strdup(namebuf);
 522                 errs = check_access(namebuf, &new_rules);
 523 
 524                 /* if we couldn't find that either, go with new name    */
 525                 if (new_rules && errs == 0)
 526                         file_rules = s;
 527         } else
 528                 file_rules = s;
 529 
 530         /* try to form the name of the baseline file */
 531         strcpy(namebuf, where);
 532         strcat(namebuf, SUFX_BASE);
 533         file_base = strdup(namebuf);
 534         errs |= check_access(namebuf, &new_baseline);
 535 
 536         if (opt_debug & DBG_FILES) {
 537                 fprintf(stderr, "FILE: %s rules file: %s\n",
 538                         new_rules ? "new" : "existing", file_rules);
 539 
 540                 fprintf(stderr, "FILE: %s base file:  %s\n",
 541                         new_baseline ? "new" : "existing", file_base);
 542         }
 543 
 544         /*
 545          * in order to lock out other filesync programs we need some
 546          * file we can lock.  We do an advisory lock on the baseline
 547          * file.  If no baseline file exists, we create an empty one.
 548          */
 549         if (new_baseline)
 550                 lockfd = creat(file_base, 0666);
 551         else
 552                 lockfd = open(file_base, O_RDWR);
 553 
 554         if (lockfd < 0) {
 555                 fprintf(stderr, new_baseline ? ERR_creat : ERR_open,
 556                         TXT_base, file_base);
 557                 errs |= ERR_FILES;
 558         } else {
 559                 ret = lockf(lockfd, F_TLOCK, 0L);
 560                 if (ret < 0) {
 561                         fprintf(stderr, ERR_lock, TXT_base, file_base);
 562                         errs |= ERR_FILES;
 563                 } else if (opt_debug & DBG_FILES)
 564                         fprintf(stderr, "FILE: locking baseline file %s\n",
 565                                 file_base);
 566         }
 567 
 568         return (errs);
 569 }
 570 
 571 /*
 572  * routine:
 573  *      cleanup
 574  *
 575  * purpose:
 576  *      to clean up temporary files and locking prior to exit
 577  *
 578  * paremeters:
 579  *      error mask
 580  *
 581  * returns:
 582  *      void
 583  *
 584  * notes:
 585  *      if there are no errors, the baseline file is assumed to be good.
 586  *      Otherwise, if we created a temporary baseline file (just for
 587  *      locking) we will delete it.
 588  */
 589 static void
 590 cleanup(errmask_t errmask)
 591 {
 592         /* unlock the baseline file     */
 593         if (opt_debug & DBG_FILES)
 594                 fprintf(stderr, "FILE: unlock baseline file %s\n", file_base);
 595         (void) lockf(lockfd, F_ULOCK, 0);
 596 
 597         /* see if we need to delete a temporary copy    */
 598         if (errmask && new_baseline) {
 599                 if (opt_debug & DBG_FILES)
 600                         fprintf(stderr, "FILE: unlink temp baseline file %s\n",
 601                                 file_base);
 602                 (void) unlink(file_base);
 603         }
 604 }
 605 
 606 /*
 607  * routine:
 608  *      check_access
 609  *
 610  * purpose:
 611  *      to determine whether or not we can access an existing file
 612  *      or create a new one
 613  *
 614  * parameters:
 615  *      name of file (in a clobberable buffer)
 616  *      pointer to new file flag
 617  *
 618  * returns:
 619  *      error mask
 620  *      setting of the new file flag
 621  *
 622  * note:
 623  *      it is kind of a kluge that this routine clobbers the name,
 624  *      but it is only called from one place, it needs a modified
 625  *      copy of the name, and the one caller doesn't mind.
 626  */
 627 static errmask_t
 628 check_access(char *name, int *newflag)
 629 {       char *s;
 630 
 631         /* start out by asking for what we want         */
 632         if (access(name, R_OK|W_OK) == 0) {
 633                 *newflag = 0;
 634                 return (0);
 635         }
 636 
 637         /* if the problem is isn't non-existence, lose  */
 638         if (errno != ENOENT) {
 639                 *newflag = 0;
 640                 fprintf(stderr, gettext(ERR_rdwri), name);
 641                 return (ERR_FILES);
 642         }
 643 
 644         /*
 645          * the file doesn't exist, so there is still hope if we can
 646          * write in the directory that should contain the file
 647          */
 648         *newflag = 1;
 649 
 650         /* truncate the file name to its containing directory */
 651         for (s = name; s[1]; s++);
 652         while (s > name && *s != '/')
 653                 s--;
 654         if (s > name)
 655                 *s = 0;
 656         else if (*s == '/')
 657                 s[1] = 0;
 658         else
 659                 name = ".";
 660 
 661         /* then see if we have write access to the directory    */
 662         if (access(name, W_OK) == 0)
 663                 return (0);
 664 
 665         fprintf(stderr, gettext(ERR_dirwac), name);
 666         return (ERR_FILES);
 667 }
 668 
 669 /*
 670  * routine:
 671  *      whoami
 672  *
 673  * purpose:
 674  *      to figure out who I am and what the default modes/ownership
 675  *      is on files that I create.
 676  */
 677 static void
 678 whoami()
 679 {
 680         my_uid = geteuid();
 681         my_gid = getegid();
 682         my_umask = umask(0);
 683 
 684         if (opt_debug & DBG_MISC)
 685                 fprintf(stderr, "MISC: my_uid=%u, my_gid=%u, my_umask=%03o\n",
 686                         my_uid, my_gid, my_umask);
 687 }