xref: /illumos-gate/usr/src/cmd/filesync/base.c (revision dd4b99162fc201c2eb6176bbfa55c5b8217fab79)
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  *	base.c
27  *
28  * purpose:
29  *	routines to create, traverse, read and write the baseline database
30  *
31  * contents:
32  *	manipulation:
33  *		add_base, add_file_to_base, add_file_to_dir
34  *		(static) add_file_to_list
35  *	reading baseline:
36  *		read_baseline
37  *		(static) gettype
38  *	writing baseline:
39  *		write_baseline
40  *		(static) bw_header, bw_base, bw_file, showtype
41  */
42 
43 #include <stdlib.h>
44 #include <stdio.h>
45 #include <string.h>
46 #include <time.h>
47 
48 #include "filesync.h"
49 #include "database.h"
50 #include "messages.h"
51 
52 #define	BASE_MAJOR	1		/* base file format major rev	*/
53 #define	BASE_MINOR	2		/* base file format minor rev	*/
54 #define	BASE_TAG	"filesync-BaseLine"
55 
56 /*
57  * globals
58  */
59 struct base omnibase;			/* dummy to hold global rules	*/
60 struct base *bases;			/* pointer to the base list	*/
61 
62 /*
63  * locals
64  */
65 static int num_bases;			/* used to generate sequence #s	*/
66 static errmask_t bw_header(FILE *);	/* write out baseline header	*/
67 static errmask_t bw_base(FILE *, struct base *); /* write out one base	*/
68 static errmask_t bw_file(FILE *, struct file *, int);
69 static struct file *add_file_to_list(struct file **, const char *);
70 static char showtype(int);
71 static long gettype(int);
72 
73 /*
74  * routine:
75  *	add_base
76  *
77  * purpose:
78  *	to find a base pair in the chain, adding it if necessary
79  *
80  * parameters:
81  *	spec for source directory
82  *	spec for dest directory
83  *
84  * returns:
85  *	pointer to the base pair
86  *
87  */
88 struct base *
89 add_base(const char *src, const char *dst)
90 {
91 	struct base *bp, **bpp;
92 
93 	/* first see if we already have it */
94 	for (bpp = &bases; (bp = *bpp) != 0; bpp = &bp->b_next) {
95 		/* base must match on both src and dst	*/
96 		if (strcmp(src, bp->b_src_spec))
97 			continue;
98 		if (strcmp(dst, bp->b_dst_spec))
99 			continue;
100 
101 		if (opt_debug & DBG_BASE)
102 			fprintf(stderr, "BASE: FOUND base=%d, src=%s, dst=%s\n",
103 			    bp->b_ident, src, dst);
104 		return (bp);
105 	}
106 
107 	/* no joy, so we have to allocate one		*/
108 	bp = malloc(sizeof (struct base));
109 	if (bp == 0)
110 		nomem("base structure");
111 
112 	/* initialize the new base			*/
113 	memset((void *) bp, 0, sizeof (struct base));
114 	bp->b_ident = ++num_bases;
115 	bp->b_src_spec = strdup(src);
116 	bp->b_dst_spec = strdup(dst);
117 
118 	/* names are expanded at run-time, and this is run-time	*/
119 	if ((bp->b_src_name = expand(bp->b_src_spec)) == 0) {
120 		fprintf(stderr, gettext(ERR_badbase), bp->b_src_spec);
121 		exit(ERR_FILES);
122 	}
123 
124 	if ((bp->b_dst_name = expand(bp->b_dst_spec)) == 0) {
125 		fprintf(stderr, gettext(ERR_badbase), bp->b_dst_spec);
126 		exit(ERR_FILES);
127 	}
128 
129 	/* chain it in					*/
130 	*bpp = bp;
131 
132 	if (opt_debug & DBG_BASE)
133 		fprintf(stderr, "BASE: ADDED base=%d, src=%s, dst=%s\n",
134 		    bp->b_ident, src, dst);
135 
136 	return (bp);
137 }
138 
139 /*
140  * routine:
141  *	add_file_to_list
142  *
143  * purpose:
144  *	to find a file on a list, or if necessary add it to the list
145  *
146  *	this is an internal routine, used only by add_file_to_base
147  *	and add_file_to_dir.
148  *
149  * parameters:
150  *	pointer to the list head
151  *
152  * returns:
153  *	pointer to a file structure
154  *
155  * notes:
156  *
157  *	list is sorted to provide some search optimization
158  *
159  *	most files are in the baseline, and so come in in alphabetical
160  *	order.  If we keep a guess pointer to the last file we added/found,
161  *	there is a better than even chance that this one should be
162  *	added immediately onto the end of it ... and in so doing we
163  *	can save ourselves the trouble of searching the lists most
164  *	of the time.
165  *
166  *	this win would be even better if the FTW traversal was sorted,
167  *	but building the baseline is enough of a win to justify the
168  *	feature ... but even without this we run a 60%-70% hit rate.
169  */
170 static struct file *
171 add_file_to_list(struct file **pp, const char *name)
172 {
173 	struct file *fp, *new;
174 	int rslt;
175 
176 	static struct file **last_list;
177 	static struct file *last_file;
178 
179 	/*
180 	 * start with the guess pointer, we hope to find that
181 	 * this request will be satisfied by the next file in
182 	 * the list.  The two cases we are trying to optimize
183 	 * are:
184 	 *	appending to the list, with appends in alphabetical order
185 	 *	searches of the list, with searches in alphabetical order
186 	 */
187 	if (last_list == pp && (new = last_file) != 0) {
188 		/* we like to think we belong farther down-list	*/
189 		if (strcmp(name, new->f_name) > 0) {
190 			fp = new->f_next;
191 			/* if we're at the end, we just won	*/
192 			if (fp == 0) {
193 				pp = &new->f_next;
194 				goto makeit;
195 			}
196 
197 			/* or if the next one is what we want	*/
198 			if (strcmp(name, fp->f_name) == 0) {
199 				fp->f_flags &= ~F_NEW;
200 				new = fp;
201 				goto gotit;
202 			}
203 		}
204 	}
205 
206 	/*
207 	 * our guess pointer failed, so it is exhaustive search time
208 	 */
209 	last_list = pp;
210 
211 	for (fp = *pp; fp; pp = &fp->f_next, fp = *pp) {
212 		rslt = strcmp(name, fp->f_name);
213 
214 		/* see if we got a match	*/
215 		if (rslt == 0) {
216 			fp->f_flags &= ~F_NEW;
217 			new = fp;
218 			goto gotit;
219 		}
220 
221 		/* see if we should go no farther	*/
222 		if (rslt < 0)
223 			break;
224 	}
225 
226 makeit:
227 	/*
228 	 * we didn't find it:
229 	 *	pp points at where our pointer should go
230 	 *	fp points at the node after ours
231 	 */
232 	new = malloc(sizeof (*new));
233 	if (new == 0)
234 		nomem("file structure");
235 
236 	/* initialize the new node	*/
237 	memset(new, 0, sizeof (struct file));
238 	new->f_name = strdup(name);
239 	new->f_flags = F_NEW;
240 
241 	/* chain it into the list	*/
242 	new->f_next = fp;
243 	*pp = new;
244 
245 gotit:	/* remember this as our next guess pointer	*/
246 	last_file = new;
247 	return (new);
248 }
249 
250 /*
251  * routine:
252  *	add_file_to_base
253  *
254  * purpose:
255  *	to add a file-node to a baseline
256  *
257  * parameters:
258  *	pointer to base
259  *	name of file to be added
260  *
261  * returns:
262  *	pointer to file structure
263  */
264 struct file *
265 add_file_to_base(struct base *bp, const char *name)
266 {
267 	struct file *fp;
268 
269 	fp = add_file_to_list(&bp->b_files, name);
270 	fp->f_base = bp;
271 	fp->f_depth = 0;
272 
273 	if (opt_debug & DBG_LIST)
274 		fprintf(stderr, "LIST: base=%d, %s file=%s\n",
275 		    bp->b_ident, (fp->f_flags&F_NEW) ? "NEW" : "FOUND",
276 		    name);
277 
278 	return (fp);
279 }
280 
281 /*
282  * routine:
283  *	add_file_to_dir
284  *
285  * purpose:
286  *	to add a file-node to a directory
287  *
288  * parameters:
289  *	pointer to file entry for directory
290  *	name of file to be added
291  *
292  * returns:
293  *	pointer to file structure
294  */
295 struct file *
296 add_file_to_dir(struct file *dp, const char *name)
297 {
298 	struct file *fp;
299 
300 	fp = add_file_to_list(&dp->f_files, name);
301 	fp->f_base = dp->f_base;
302 	fp->f_depth = dp->f_depth + 1;
303 
304 	if (opt_debug & DBG_LIST)
305 		fprintf(stderr, "LIST: dir=%s, %s file=%s\n",
306 		    dp->f_name, (fp->f_flags&F_NEW) ? "NEW" : "FOUND",
307 		    name);
308 
309 	return (fp);
310 }
311 
312 /*
313  * routine:
314  *	read_baseline
315  *
316  * purpose:
317  *	to read in the baseline file
318  *
319  * parameters:
320  *	name of baseline file
321  *
322  * returns:
323  *	error mask
324  */
325 errmask_t
326 read_baseline(char *name)
327 {
328 	FILE *file;
329 	errmask_t errs = 0;
330 
331 	char *s;
332 	char *s1 = 0;
333 	char type;
334 	char *field = "???";
335 
336 	unsigned long l;
337 	unsigned long long ll;	/* intermediate for 64 bit file support	*/
338 	int level;
339 	int major, minor;
340 
341 	struct base *bp = 0;
342 	struct file *fp;
343 	struct fileinfo *ip;
344 	aclent_t *ap;
345 
346 	struct file *dirstack[ MAX_DEPTH ];
347 
348 	file = fopen(name, "r");
349 	if (file == NULL) {
350 		fprintf(stderr, gettext(ERR_open), gettext(TXT_base), name);
351 		return (ERR_FILES);
352 	}
353 	lex_linenum = 0;
354 
355 	if (opt_debug & DBG_FILES)
356 		fprintf(stderr, "FILE: READ BASELINE %s\n", name);
357 
358 	while (!feof(file)) {
359 		/* find the first token on the line	*/
360 		s = lex(file);
361 
362 		/* skip blank lines and comments	*/
363 		if (s == 0 || *s == 0 || *s == '#' || *s == '*')
364 			continue;
365 
366 		field = "keyword";
367 
368 		/* see if the first token is a known keyword	*/
369 		if (strcmp(s, "VERSION") == 0 || strcmp(s, BASE_TAG) == 0) {
370 			s = lex(0);
371 			field = gettext(TXT_noargs);
372 			if (s == 0)
373 				goto bad;
374 
375 			major = strtol(s, &s1, 10);
376 			field = gettext(TXT_badver);
377 			if (*s1 != '.')
378 				goto bad;
379 			minor = strtol(&s1[1], 0, 10);
380 
381 			if (major != BASE_MAJOR || minor > BASE_MINOR) {
382 				fprintf(stderr, gettext(ERR_badver),
383 				    major, minor, gettext(TXT_base), name);
384 				errs |= ERR_FILES;
385 			}
386 			s1 = 0;
387 			continue;
388 		}
389 
390 		if (strcmp(s, "BASE_SRC") == 0) {
391 			s = lex(0);
392 			field = "source directory";
393 			if (s == 0)
394 				goto bad;
395 			s1 = strdup(s);
396 			bp = 0;
397 			continue;
398 		}
399 
400 		if (strcmp(s, "BASE_DST") == 0) {
401 			s = lex(0);
402 			field = "destination directory";
403 			if (s == 0)
404 				goto bad;
405 
406 			/* make sure we have a source too */
407 			if (s1 == 0) {
408 				field = "no source directory";
409 				goto bad;
410 			}
411 
412 			bp = add_base(s1, s);
413 			free(s1);
414 			s1 = 0;
415 			continue;
416 		}
417 
418 		if (strcmp(s, "FILE") == 0) {
419 			/* make sure we have a base to add to */
420 			if (bp == 0) {
421 				field = "missing base";
422 				goto bad;
423 			}
424 
425 			s = lex(0);	/* level	*/
426 			field = "level";
427 			if (s == 0 || *s == 0)
428 				goto bad;
429 			l = strtoul(s, 0, 0);
430 			level = l;
431 
432 			s = lex(0);	/* type	*/
433 			field = "file type";
434 			if (s == 0 || *s == 0)
435 				goto bad;
436 			type = *s;
437 			if (gettype(type) < 0)
438 				goto bad;
439 
440 			s = lex(0);	/* name	*/
441 			field = "file name";
442 			if (s == 0 || *s == 0)
443 				goto bad;
444 
445 			/* allocate a file structure for this entry	*/
446 			if (level == 0)
447 				fp = add_file_to_base(bp, s);
448 			else
449 				fp = add_file_to_dir(dirstack[level-1], s);
450 
451 			fp->f_flags |= F_IN_BASELINE;
452 
453 			/* maintain the directory stack			*/
454 			if (level >= MAX_DEPTH) {
455 				fprintf(stderr, gettext(ERR_deep), s);
456 				exit(ERR_OTHER);
457 			}
458 
459 			dirstack[ level ] = fp;
460 
461 			/* get a pointer to the baseline file info structure */
462 			ip = &fp->f_info[ OPT_BASE ];
463 
464 			ip->f_type = gettype(type);	/* note file type */
465 
466 			s = lex(0);	/* modes	*/
467 			field = "file modes";
468 			if (s == 0 || *s == 0)
469 				goto bad;
470 			l = strtoul(s, 0, 0);
471 			ip->f_mode = l;
472 
473 			s = lex(0);	/* uid	*/
474 			field = "file UID";
475 			if (s == 0 || *s == 0)
476 				goto bad;
477 			l = strtoul(s, 0, 0);
478 			ip->f_uid = l;
479 
480 			s = lex(0);	/* gid	*/
481 			field = "file GID";
482 			if (s == 0 || *s == 0)
483 				goto bad;
484 			l = strtoul(s, 0, 0);
485 			ip->f_gid = l;
486 
487 			s = lex(0);	/* source inode	*/
488 			field = "source i#";
489 			if (s == 0 || *s == 0)
490 				goto bad;
491 			ll = strtoull(s, 0, 0);
492 			fp->f_s_inum = (ino_t)ll;
493 
494 			s = lex(0);	/* source major	*/
495 			field = "source major";
496 			if (s == 0 || *s == 0)
497 				goto bad;
498 			l = strtoul(s, 0, 0);
499 			fp->f_s_maj = l;
500 
501 			s = lex(0);	/* source minor	*/
502 			field = "source minor";
503 			if (s == 0 || *s == 0)
504 				goto bad;
505 			l = strtoul(s, 0, 0);
506 			fp->f_s_min = l;
507 
508 			s = lex(0);	/* source nlink	*/
509 			field = "source nlink";
510 			if (s == 0 || *s == 0)
511 				goto bad;
512 			l = strtoul(s, 0, 0);
513 			fp->f_s_nlink = l;
514 
515 			s = lex(0);	/* source mod	*/
516 			field = "source modtime";
517 			if (s == 0 || *s == 0)
518 				goto bad;
519 			l = strtoul(s, 0, 0);
520 			fp->f_s_modtime = l;
521 
522 			s = lex(0);	/* dest inode	*/
523 			field = "destination i#";
524 			if (s == 0 || *s == 0)
525 				goto bad;
526 			ll = strtoull(s, 0, 0);
527 			fp->f_d_inum = (ino_t)ll;
528 
529 			s = lex(0);	/* dest major	*/
530 			field = "destination major";
531 			if (s == 0 || *s == 0)
532 				goto bad;
533 			l = strtoul(s, 0, 0);
534 			fp->f_d_maj = l;
535 
536 			s = lex(0);	/* dest minor	*/
537 			field = "destination minor";
538 			if (s == 0 || *s == 0)
539 				goto bad;
540 			l = strtoul(s, 0, 0);
541 			fp->f_d_min = l;
542 
543 			s = lex(0);	/* dest nlink	*/
544 			field = "dest nlink";
545 			if (s == 0 || *s == 0)
546 				goto bad;
547 			l = strtoul(s, 0, 0);
548 			fp->f_d_nlink = l;
549 
550 			s = lex(0);	/* dest mod	*/
551 			field = "dest modtime";
552 			if (s == 0 || *s == 0)
553 				goto bad;
554 			l = strtoul(s, 0, 0);
555 			fp->f_d_modtime = l;
556 
557 			s = lex(0);	/* major or size */
558 
559 			if (type == 'C' || type == 'B') {
560 				field = "rdev major";
561 				if (s == 0 || *s == 0)
562 					goto bad;
563 				l = strtoul(s, 0, 0);
564 				ip->f_rd_maj = l;
565 
566 				s = lex(0);	/* minor */
567 				field = "rdev minor";
568 				if (s == 0 || *s == 0)
569 					goto bad;
570 				l = strtoul(s, 0, 0);
571 				ip->f_rd_min = l;
572 			} else {
573 				field = "file size";
574 				if (s == 0 || *s == 0)
575 					goto bad;
576 				ll = strtoul(s, 0, 0);
577 				ip->f_size = (off_t)ll;	/* size	*/
578 			}
579 
580 			/*
581 			 * all fields after this point were added to the
582 			 * 1.0 format and so should be considered optional
583 			 */
584 			s = lex(0);		/* acl length ? */
585 			field = "acl count";
586 			if (s && *s) {
587 				l = strtoul(s, 0, 0);
588 				ip->f_numacls = l;
589 				ip->f_acls = malloc(ip->f_numacls *
590 				    sizeof (aclent_t));
591 				if (ip->f_acls == 0)
592 					nomem("Access Control List");
593 			}
594 
595 			continue;
596 		}
597 
598 		if (strcmp(s, "ACL") == 0) {
599 			/* make sure there is a place to put the ACL	*/
600 			if (ip == 0 || ip->f_acls == 0) {
601 				field = "ACL w/o FILE/LIST";
602 				goto bad;
603 			}
604 
605 			/* acl entry number	*/
606 			s = lex(0);
607 			field = "acl index";
608 			if (s == 0)
609 				goto bad;
610 			l = strtoul(s, 0, 0);
611 			if (l >= ip->f_numacls)
612 				goto bad;
613 			else
614 				ap = &ip->f_acls[l];
615 
616 			/* acl entry type	*/
617 			s = lex(0);
618 			field = "acl type";
619 			if (s == 0)
620 				goto bad;
621 			l = strtoul(s, 0, 0);
622 			ap->a_type = l;
623 
624 			/* acl entry ID		*/
625 			s = lex(0);
626 			field = "acl id";
627 			if (s == 0)
628 				goto bad;
629 			l = strtoul(s, 0, 0);
630 			ap->a_id = l;
631 
632 			/* acl entry perms	*/
633 			s = lex(0);
634 			field = "acl perm";
635 			if (s == 0)
636 				goto bad;
637 			l = strtoul(s, 0, 0);
638 			ap->a_perm = l;
639 
640 			continue;
641 		}
642 
643 	bad:	/* log the error and continue processing to find others	*/
644 		fprintf(stderr, gettext(ERR_badinput), lex_linenum,
645 		    field, name);
646 		errs |= ERR_FILES;
647 	}
648 
649 	(void) fclose(file);
650 	return (errs);
651 }
652 
653 /*
654  * routine:
655  *	write_baseline
656  *
657  * purpose:
658  *	to rewrite the baseline file
659  *
660  * parameters:
661  *	name of the new baseline file
662  *
663  * returns:
664  *	error mask
665  */
666 errmask_t
667 write_baseline(char *name)
668 {
669 	FILE *newfile;
670 	errmask_t errs = 0;
671 	struct base *bp;
672 	char tmpname[ MAX_PATH ];
673 
674 	if (opt_debug & DBG_FILES)
675 		fprintf(stderr, "FILE: WRITE BASELINE %s\n", name);
676 
677 	/* if no-touch is specified, we don't update files	*/
678 	if (opt_notouch)
679 		return (0);
680 
681 	/* create a temporary output file			*/
682 	sprintf(tmpname, "%s-TMP", name);
683 
684 	/* create our output file	*/
685 	newfile = fopen(tmpname, "w+");
686 	if (newfile == NULL) {
687 		fprintf(stderr, gettext(ERR_creat), gettext(TXT_base),
688 		    tmpname);
689 		return (ERR_FILES);
690 	}
691 
692 	errs |= bw_header(newfile);
693 	for (bp = bases; bp; bp = bp->b_next)
694 		errs |= bw_base(newfile, bp);
695 
696 	if (ferror(newfile)) {
697 		fprintf(stderr, gettext(ERR_write), gettext(TXT_base),
698 		    tmpname);
699 		errs |= ERR_FILES;
700 	}
701 
702 	if (fclose(newfile)) {
703 		fprintf(stderr, gettext(ERR_fclose), gettext(TXT_base),
704 		    tmpname);
705 		errs |= ERR_FILES;
706 	}
707 
708 	/* now switch the new file for the old one	*/
709 	if (errs == 0)
710 		if (rename(tmpname, name) != 0) {
711 			fprintf(stderr, gettext(ERR_rename),
712 			    gettext(TXT_base), tmpname, name);
713 			errs |= ERR_FILES;
714 		}
715 
716 	return (errs);
717 }
718 
719 /*
720  * routine:
721  *	bw_header
722  *
723  * purpose:
724  *	to write out a baseline header
725  *
726  * parameters:
727  *	FILE* for the output file
728  *
729  * returns:
730  *	error mask
731  *
732  * notes:
733  */
734 static errmask_t
735 bw_header(FILE *file)
736 {
737 	time_t now;
738 	struct tm *local;
739 
740 	/* figure out what time it is	*/
741 	(void) time(&now);
742 	local = localtime(&now);
743 
744 	fprintf(file, "%s %d.%d\n", BASE_TAG, BASE_MAJOR, BASE_MINOR);
745 	fprintf(file, "#\n");
746 	fprintf(file, "# filesync baseline, last written by %s, %s",
747 	    cuserid(NULL), asctime(local));
748 	fprintf(file, "#\n");
749 
750 	return (0);
751 }
752 
753 /*
754  * routine:
755  *	bw_base
756  *
757  * purpose:
758  *	to write out the summary for one base-pair
759  *
760  * parameters:
761  *	FILE * for the output file
762  *
763  * returns:
764  *	error mask
765  *
766  * notes:
767  */
768 static errmask_t
769 bw_base(FILE *file, struct base *bp)
770 {
771 	struct file *fp;
772 	errmask_t errs = 0;
773 
774 	/* see if this base is to be dropped from baseline	*/
775 	if (bp->b_flags & F_REMOVE)
776 		return (0);
777 
778 	fprintf(file, "\n");
779 	fprintf(file, "BASE_SRC %s\n", noblanks(bp->b_src_spec));
780 	fprintf(file, "BASE_DST %s\n", noblanks(bp->b_dst_spec));
781 
782 	for (fp = bp->b_files; fp; fp = fp->f_next)
783 		errs |= bw_file(file, fp, 0);
784 
785 	return (errs);
786 }
787 
788 /*
789  * routine:
790  *	bw_file
791  *
792  * purpose:
793  *	to write a file description out to the baseline
794  *
795  * parameters:
796  *	output FILE
797  *	pointer to file description
798  *	recursion depth
799  *
800  * returns:
801  *	error mask
802  *
803  * notes:
804  *	some of the information we write out is kept separately
805  *	for source and destination files because the values should
806  *	be expected to be different for different systems/copies.
807  *
808  *	if a file has an unresolved conflict, we want to leave
809  *	the old values in place so that we continue to compare
810  *	files against the last time they agreed.
811  */
812 static errmask_t
813 bw_file(FILE *file, struct file *fp, int depth)
814 {
815 	struct file *cp;
816 	int i;
817 	errmask_t errs = 0;
818 	long long ll;		/* intermediate for 64 bit file support	*/
819 	struct fileinfo *ip = &fp->f_info[OPT_BASE];
820 
821 	/* if this file is to be removed from baseline, skip it	*/
822 	if (fp->f_flags & F_REMOVE)
823 		return (0);
824 
825 	/*
826 	 * if this node is in conflict, or if it has not been
827 	 * evaluated this time around, we should just leave the
828 	 * baseline file the way it was before.  If there is a
829 	 * conflict, let the baseline reflect the last agreement.
830 	 * If the node wasn't evaluated, let the baseline reflect
831 	 * our last knowledge.
832 	 */
833 	if (fp->f_flags & F_CONFLICT || (fp->f_flags&F_EVALUATE) == 0) {
834 		fp->f_info[OPT_SRC].f_ino	= fp->f_s_inum;
835 		fp->f_info[OPT_SRC].f_nlink	= fp->f_s_nlink;
836 		fp->f_info[OPT_SRC].f_d_maj	= fp->f_s_maj;
837 		fp->f_info[OPT_SRC].f_d_min	= fp->f_s_min;
838 		fp->f_info[OPT_SRC].f_modtime	= fp->f_s_modtime;
839 		fp->f_info[OPT_DST].f_ino	= fp->f_d_inum;
840 		fp->f_info[OPT_DST].f_nlink	= fp->f_d_nlink;
841 		fp->f_info[OPT_DST].f_d_maj	= fp->f_d_maj;
842 		fp->f_info[OPT_DST].f_d_min	= fp->f_d_min;
843 		fp->f_info[OPT_DST].f_modtime	= fp->f_d_modtime;
844 	}
845 
846 	/* write out the entry for this file		*/
847 	fprintf(file, "FILE %d %c %-20s 0%04o", depth, showtype(ip->f_type),
848 	    noblanks(fp->f_name), ip->f_mode);
849 	fprintf(file, " %6ld %6ld", ip->f_uid, ip->f_gid);
850 
851 	ll = fp->f_info[OPT_SRC].f_ino;
852 	fprintf(file, "\t%6lld %4ld %4ld %4d 0x%08lx",
853 	    ll,
854 	    fp->f_info[OPT_SRC].f_d_maj,
855 	    fp->f_info[OPT_SRC].f_d_min,
856 	    fp->f_info[OPT_SRC].f_nlink,
857 	    fp->f_info[OPT_SRC].f_modtime);
858 
859 	ll = fp->f_info[OPT_DST].f_ino;
860 	fprintf(file, "\t%6lld %4ld %4ld %4d 0x%08lx",
861 	    ll,
862 	    fp->f_info[OPT_DST].f_d_maj,
863 	    fp->f_info[OPT_DST].f_d_min,
864 	    fp->f_info[OPT_DST].f_nlink,
865 	    fp->f_info[OPT_DST].f_modtime);
866 
867 	/* last fields are file type specific	*/
868 	if (S_ISBLK(ip->f_type) || S_ISCHR(ip->f_type))
869 		fprintf(file, "\t%4ld %4ld", ip->f_rd_maj, ip->f_rd_min);
870 	else {
871 		ll = ip->f_size;
872 		fprintf(file, "\t%lld", ll);
873 	}
874 
875 	/* ACL count goes at the end because it was added	*/
876 	fprintf(file, "\t%d", ip->f_numacls);
877 
878 	fprintf(file, "\n");
879 
880 	/* if this file has ACLs, we have to write them out too	*/
881 	for (i = 0; i < ip->f_numacls; i++)
882 		fprintf(file, "ACL %d %d %ld %o\n", i, ip->f_acls[i].a_type,
883 		    ip->f_acls[i].a_id, ip->f_acls[i].a_perm);
884 
885 	/* then enumerate all of the children (if any)	*/
886 	for (cp = fp->f_files; cp; cp = cp->f_next)
887 		errs |= bw_file(file, cp, depth + 1);
888 
889 	return (errs);
890 }
891 
892 /*
893  * routines:
894  *	gettype/showtype
895  *
896  * purpose:
897  *	to convert between a file type (as found in a mode word)
898  *	and a single character representation
899  *
900  * parameters/return
901  *	mode word -> character
902  *	character -> mode word
903  */
904 static char types[16] __nonstring = "-PC?DNB?F?S?s???";
905 
906 static char showtype(int mode)
907 {
908 	return (types[ (mode & S_IFMT) >> 12 ]);
909 }
910 
911 static long gettype(int code)
912 {
913 	int i;
914 
915 	for (i = 0; i < 16; i++)
916 		if (types[i] == code)
917 			return (i << 12);
918 
919 	return (-1);
920 }
921