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