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
main(int argc,char ** argv)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
usage(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
confirm(char * message)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
nomem(char * reason)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
findfiles(void)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
cleanup(errmask_t errmask)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
check_access(char * name,int * newflag)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
whoami()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