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