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-existance, 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 } 688