xref: /illumos-gate/usr/src/cmd/filesync/rules.c (revision 7a6d80f1660abd4755c68cbd094d4a914681d26e)
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 (c) 1995 Sun Microsystems, Inc.  All Rights Reserved
24  *
25  * module:
26  *	rules.c
27  *
28  * purpose:
29  *	to read and write the rules file and manage rules lists
30  *
31  * contents:
32  *	reading rules file
33  *		read_rules
34  *		(static) read_command
35  *	writing rules file
36  *		write_rules
37  *		(static) rw_header, rw_base
38  *	adding rules
39  *		add_ignore, add_include
40  *		(static) add_rule
41  *	adding/checking restrictions
42  *		add_restr, check_restr
43  */
44 
45 #include <stdio.h>
46 #include <stdlib.h>
47 #include <string.h>
48 #include <time.h>
49 #include <ctype.h>
50 
51 #include "filesync.h"
52 #include "database.h"
53 #include "messages.h"
54 #include "debug.h"
55 
56 /*
57  * routines:
58  */
59 static errmask_t rw_base(FILE *file, struct base *bp);
60 static errmask_t rw_header(FILE *file);
61 static errmask_t add_rule(struct base *, int, const char *);
62 static char *read_cmd(char *);
63 
64 /*
65  * globals
66  */
67 static int rules_added;
68 static int restr_added;
69 
70 /*
71  * locals
72  */
73 #define	RULE_MAJOR	1		/* rules file format major rev	*/
74 #define	RULE_MINOR	1		/* rules file format minor rev	*/
75 #define	RULE_TAG	"PACKINGRULES"	/* magic string for rules files	*/
76 
77 /*
78  * routine:
79  *	read_rules
80  *
81  * purpose:
82  *	to read in the rules file
83  *
84  * parameters:
85  *	name of rules file
86  *
87  * returns:
88  *	error mask
89  *
90  * notes:
91  *	later when I implement a proper (comment preserving) update
92  *	function I'm going to wish I had figured out how to build the
93  *	input functions for this function in a way that would make
94  *	the more usable for that too.
95  */
96 errmask_t
97 read_rules(char *name)
98 {	FILE *file;
99 	errmask_t errs = 0;
100 	int flags;
101 	int major, minor;
102 	char *s, *s1, *s2;
103 	struct base *bp;
104 	char *errstr = "???";
105 
106 	file = fopen(name, "r");
107 	if (file == NULL) {
108 		fprintf(stderr, gettext(ERR_open), gettext(TXT_rules),
109 			name);
110 		return (ERR_FILES);
111 	}
112 
113 	lex_linenum = 0;
114 
115 	if (opt_debug & DBG_FILES)
116 		fprintf(stderr, "FILE: READ RULES %s\n", name);
117 
118 	bp = &omnibase;		/* default base before any others	*/
119 
120 	while (!feof(file)) {
121 		/* find the first token on the line	*/
122 		s = lex(file);
123 
124 		/* skip blank lines and comments	*/
125 		if (s == 0 || *s == 0 || *s == '#' || *s == '*')
126 			continue;
127 
128 		/* see if the first token is a known keyword	*/
129 		if (strcmp(s, "BASE") == 0) {
130 
131 			/* get the source & destination tokens	*/
132 			errstr = gettext(TXT_srcdst);
133 			s1 = lex(0);
134 			if (s1 == 0)
135 				goto bad;
136 			s1 = strdup(s1);
137 
138 			s2 = lex(0);
139 			if (s2 == 0)
140 				goto bad;
141 			s2 = strdup(s2);
142 
143 			/* creat the new base pair		*/
144 			bp = add_base(s1, s2);
145 			bp->b_flags |= F_LISTED;
146 
147 			free(s1);
148 			free(s2);
149 			continue;
150 		}
151 
152 		if (strcmp(s, "LIST") == 0) {
153 
154 			/* make sure we are associated with a real base */
155 			if (bp == &omnibase) {
156 				errstr = gettext(TXT_nobase);
157 				goto bad;
158 			}
159 
160 			/* skip to the next token */
161 			s = lex(0);
162 			errstr = gettext(TXT_noargs);
163 			if (s == 0)
164 				goto bad;
165 
166 			/* see if it is a program or a name */
167 			if (*s == '!') {
168 				errs |= add_rule(bp, R_PROGRAM,
169 						read_cmd(&s[1]));
170 			} else {
171 				do {
172 					flags = wildcards(s) ? R_WILD : 0;
173 					errs |= add_rule(bp, flags, s);
174 					s = lex(0);
175 				} while (s != 0);
176 			}
177 			continue;
178 		}
179 
180 		if (strcmp(s, "IGNORE") == 0) {
181 
182 			/* skip to the next token */
183 			s = lex(0);
184 			errstr = gettext(TXT_noargs);
185 			if (s == 0)
186 				goto bad;
187 
188 			flags = R_IGNORE;
189 
190 			/* see if it is a program or a name */
191 			if (*s == '!') {
192 				errs |= add_rule(bp, R_PROGRAM|flags,
193 						read_cmd(&s[1]));
194 			} else {
195 				do {
196 					if (wildcards(s))
197 						flags |= R_WILD;
198 					errs |= add_rule(bp, flags, s);
199 					s = lex(0);
200 				} while (s != 0);
201 			}
202 			continue;
203 		}
204 
205 		if (strcmp(s, "VERSION") == 0 || strcmp(s, RULE_TAG) == 0) {
206 			s = lex(0);
207 			errstr = gettext(TXT_noargs);
208 			if (s == 0)
209 				goto bad;
210 
211 			major = strtol(s, &s1, 10);
212 			errstr = gettext(TXT_badver);
213 			if (*s1 != '.')
214 				goto bad;
215 			minor = strtol(&s1[1], 0, 10);
216 
217 			if (major != RULE_MAJOR || minor > RULE_MINOR) {
218 				fprintf(stderr, gettext(ERR_badver),
219 					major, minor, gettext(TXT_rules), name);
220 				errs |= ERR_FILES;
221 			}
222 			continue;
223 		}
224 
225 	bad:	/* log the error and continue processing to find others	*/
226 		fprintf(stderr, gettext(ERR_badinput),
227 			lex_linenum, errstr, name);
228 		errs |= ERR_FILES;
229 	}
230 
231 
232 	(void) fclose(file);
233 	return (errs);
234 }
235 
236 /*
237  * routine:
238  *	read_cmd
239  *
240  * purpose:
241  *	to lex a runnable command (! lines) into a buffer
242  *
243  * parameters:
244  *	first token
245  *
246  * returns:
247  *	pointer to a command line in a static buffer
248  *	(it is assumed the caller will copy it promptly)
249  *
250  * notes:
251  *	this is necessary because lex has already choped off
252  *	the first token for us
253  */
254 static char *read_cmd(char * s)
255 {
256 	static char cmdbuf[ MAX_LINE ];
257 
258 	cmdbuf[0] = 0;
259 
260 	do {
261 		if (*s) {
262 			strcat(cmdbuf, s);
263 			strcat(cmdbuf, " ");
264 		}
265 	} while ((s = lex(0)) != 0);
266 
267 	return (cmdbuf);
268 }
269 
270 /*
271  * routine:
272  *	write_rules
273  *
274  * purpose:
275  *	to rewrite the rules file, appending the new rules
276  *
277  * parameters:
278  *	name of output file
279  *
280  * returns:
281  *	error mask
282  *
283  */
284 errmask_t
285 write_rules(char *name)
286 {	FILE *newfile;
287 	errmask_t errs = 0;
288 	struct base *bp;
289 	char tmpname[ MAX_PATH ];
290 
291 	/* if no-touch is specified, we don't update files	*/
292 	if (opt_notouch || rules_added == 0)
293 		return (0);
294 
295 	/* create a temporary output file			*/
296 	sprintf(tmpname, "%s-TMP", name);
297 
298 	/* create our output file	*/
299 	newfile = fopen(tmpname, "w+");
300 	if (newfile == NULL) {
301 		fprintf(stderr, gettext(ERR_creat), gettext(TXT_rules),
302 			name);
303 		return (ERR_FILES);
304 	}
305 
306 	if (opt_debug & DBG_FILES)
307 		fprintf(stderr, "FILE: UPDATE RULES %s\n", name);
308 
309 	errs |= rw_header(newfile);
310 	errs |= rw_base(newfile, &omnibase);
311 	for (bp = bases; bp; bp = bp->b_next)
312 		errs |= rw_base(newfile, bp);
313 
314 	if (ferror(newfile)) {
315 		fprintf(stderr, gettext(ERR_write), gettext(TXT_rules),
316 			tmpname);
317 		errs |= ERR_FILES;
318 	}
319 
320 	if (fclose(newfile)) {
321 		fprintf(stderr, gettext(ERR_fclose), gettext(TXT_rules),
322 			tmpname);
323 		errs |= ERR_FILES;
324 	}
325 
326 	/* now switch the new file for the old one	*/
327 	if (errs == 0)
328 		if (rename(tmpname, name) != 0) {
329 			fprintf(stderr, gettext(ERR_rename),
330 				gettext(TXT_rules), tmpname, name);
331 			errs |= ERR_FILES;
332 		}
333 
334 	return (errs);
335 }
336 
337 /*
338  * routine:
339  *	rw_header
340  *
341  * purpose:
342  *	to write out a rules header
343  *
344  * parameters:
345  *	FILE* for the output file
346  *
347  * returns:
348  *	error mask
349  *
350  * notes:
351  */
352 static errmask_t rw_header(FILE *file)
353 {
354 	time_t now;
355 	struct tm *local;
356 
357 	/* figure out what time it is	*/
358 	(void) time(&now);
359 	local = localtime(&now);
360 
361 	fprintf(file, "%s %d.%d\n", RULE_TAG, RULE_MAJOR, RULE_MINOR);
362 	fprintf(file, "#\n");
363 	fprintf(file, "# filesync rules, last written by %s, %s",
364 		cuserid((char *) 0), asctime(local));
365 	fprintf(file, "#\n");
366 
367 	return (0);
368 }
369 
370 /*
371  * routine:
372  *	rw_base
373  *
374  * purpose:
375  *	to write out the summary for one base-pair
376  *
377  * parameters:
378  *	FILE * for the output file
379  *
380  * returns:
381  *	error mask
382  *
383  * notes:
384  */
385 static errmask_t rw_base(FILE *file, struct base *bp)
386 {	struct rule *rp;
387 
388 	fprintf(file, "\n");
389 
390 	/* global rules don't appear within a base */
391 	if (bp->b_ident)
392 		fprintf(file, "BASE %s %s\n", noblanks(bp->b_src_spec),
393 				noblanks(bp->b_dst_spec));
394 
395 	for (rp = bp->b_includes; rp; rp = rp->r_next)
396 		if (rp->r_flags & R_PROGRAM)
397 			fprintf(file, "LIST !%s\n", rp->r_file);
398 		else
399 			fprintf(file, "LIST %s\n", noblanks(rp->r_file));
400 
401 	for (rp = bp->b_excludes; rp; rp = rp->r_next)
402 		if (rp->r_flags & R_PROGRAM)
403 			fprintf(file, "IGNORE !%s\n", rp->r_file);
404 		else
405 			fprintf(file, "IGNORE %s\n", noblanks(rp->r_file));
406 
407 	return (0);
408 }
409 
410 /*
411  * routine:
412  *	add_rule
413  *
414  * purpose:
415  *	to add a new rule
416  *
417  * parameters:
418  *	pointer to list base
419  *	rule flags
420  *	associated name/arguments
421  *
422  * returns:
423  *	error flags
424  *
425  * notes:
426  *	we always copy the argument string because most of them
427  *	were read from a file and are just in a transient buffer
428  */
429 static errmask_t add_rule(struct base *bp, int flags, const char *args)
430 {	struct rule *rp;
431 	struct rule **list;
432 
433 	rp = malloc(sizeof (struct rule));
434 	if (rp == 0)
435 		nomem("rule struture");
436 
437 	/* initialize the new base			*/
438 	memset((void *) rp, 0, sizeof (struct rule));
439 	rp->r_flags = flags;
440 	rp->r_file = strdup(args);
441 
442 	/* figure out which list to put it on		*/
443 	if (flags&R_IGNORE)
444 		list = &bp->b_excludes;
445 	else if (flags&R_RESTRICT)
446 		list = &bp->b_restrictions;
447 	else
448 		list = &bp->b_includes;
449 
450 	while (*list)
451 		list = &((*list)->r_next);
452 	*list = rp;
453 
454 	if (flags & R_NEW)
455 		rules_added++;
456 
457 	if (opt_debug & DBG_RULE) {
458 		fprintf(stderr, "RULE: base=%d, ", bp->b_ident);
459 		fprintf(stderr, "flags=%s, ",
460 			showflags(rflags, rp->r_flags));
461 		fprintf(stderr, "arg=%s\n", rp->r_file);
462 	}
463 
464 	return (0);
465 }
466 
467 /*
468  * routine:
469  *	add_ignore, add_include
470  *
471  * purpose:
472  *	wrappers for add_rule that permit outsiders (like main.c)
473  *	not to know what is inside of a base, file, or list entry
474  *
475  * parameters:
476  *	base under which rules should be added
477  *	argument associated with rule
478  *
479  * returns:
480  *	error flags
481  *
482  * notes:
483  *	basically these routines figure out what the right
484  *	flags are for a rule, and what list to put it on,
485  *	and then call a common handler.
486  */
487 errmask_t
488 add_ignore(struct base *bp, char *name)
489 {	int flags = R_IGNORE | R_NEW;
490 
491 	if (bp == 0)
492 		bp = &omnibase;
493 
494 	if (wildcards(name))
495 		flags |= R_WILD;
496 
497 	return (add_rule(bp, flags, name));
498 }
499 
500 errmask_t
501 add_include(struct base *bp, char *name)
502 {	int flags = R_NEW;
503 
504 	if (bp == 0)
505 		bp = &omnibase;
506 
507 	if (wildcards(name))
508 		flags |= R_WILD;
509 
510 	bp->b_flags |= F_LISTED;
511 
512 	return (add_rule(bp, flags, name));
513 }
514 
515 /*
516  * routine:
517  *	add_restr
518  *
519  * purpose:
520  *	to add a restriction to a base
521  *
522  * parameters:
523  *	address of base
524  *	restriction string
525  *
526  * returns:
527  * 	error mask
528  *
529  * notes:
530  *	a restriction is specified on the command line and
531  *	tells us to limit our analysis/reconcilation to
532  *	specified files and/or directories.  We deal with
533  *	these by adding a restriction rule to any base that
534  *	looks like it might fit the restriction.  We need to
535  *	treat this as a rule because the restriction string
536  *	may extend beyond the base directory and part-way into
537  *	its tree ... meaning that individual file names under
538  *	the base will have to be checked against the restriction.
539  */
540 errmask_t
541 add_restr(char *restr)
542 {	const char *s;
543 	errmask_t errs = 0;
544 	struct base *bp;
545 
546 	for (bp = bases; bp; bp = bp->b_next) {
547 		/*
548 		 * see if this restriction could apply to this base.
549 		 * It could match either the source or destination
550 		 * directory name for this base.  If it matches neither
551 		 * then the restriction does not apply to this base.
552 		 */
553 		s = prefix(restr, bp->b_src_name);
554 		if (s == 0)
555 			s = prefix(restr, bp->b_dst_name);
556 		if (s == 0)
557 			continue;
558 
559 		/*
560 		 * if there is more restriction string after the
561 		 * base, we will need to note the remainder of the
562 		 * string so that we can match individual files
563 		 * against it.
564 		 */
565 		if (*s == '/')
566 			s++;
567 
568 		errs |= add_rule(bp, R_RESTRICT, s);
569 		restr_added++;
570 	}
571 
572 	return (errs);
573 }
574 
575 /*
576  * routine:
577  *	check_restr
578  *
579  * purpose:
580  *	to see if an argument falls within restrictions
581  *
582  * parameters:
583  *	pointer to relevant base
584  *	file name
585  *
586  * returns:
587  *	TRUE	name is within restrictions
588  *	FALSE	name is outside of restrictions
589  *	MAYBE	name is on the path to a restriction
590  *
591  * notes:
592  *	if no restrictions have been specified, we evaluate
593  *	everything.  If any restrictions have been specified,
594  *	we process only files that match one of the restrictions.
595  *
596  *	add_restr has ensured that if the restriction includes
597  *	a portion that must be matched by individual files under
598  *	the base, that the restriction rule will contain that
599  *	portion of the restriction which must be matched against
600  *	individual file names.
601  */
602 bool_t
603 check_restr(struct base *bp, const char *name)
604 {	struct rule *rp;
605 
606 	/* if there are no restrictions, everything is OK	*/
607 	if (restr_added == 0)
608 		return (TRUE);
609 
610 	/* now we have to run through the list			*/
611 	for (rp = bp->b_restrictions; rp; rp = rp->r_next) {
612 		/* see if current path is under the restriction	*/
613 		if (prefix(name, rp->r_file))
614 			return (TRUE);
615 
616 		/* see if current path is on the way to restr	*/
617 		if (prefix(rp->r_file, name))
618 			/*
619 			 * this is kinky, but walker really needs
620 			 * to know the difference between a directory
621 			 * that we are unreservedly scanning, and one
622 			 * that we are scanning only to find something
623 			 * beneath it.
624 			 */
625 			return (MAYBE);
626 	}
627 
628 	/*
629 	 * there are restrictions in effect and this file doesn't seem
630 	 * to meet any of them
631 	 */
632 	if (opt_debug & DBG_RULE)
633 		fprintf(stderr, "RULE: FAIL RESTRICTION base=%d, file=%s\n",
634 			bp->b_ident, name);
635 
636 	return (FALSE);
637 }
638