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