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