diff options
Diffstat (limited to 'usr/src/cmd/filesync/base.c')
-rw-r--r-- | usr/src/cmd/filesync/base.c | 912 |
1 files changed, 912 insertions, 0 deletions
diff --git a/usr/src/cmd/filesync/base.c b/usr/src/cmd/filesync/base.c new file mode 100644 index 0000000000..efcfeb6046 --- /dev/null +++ b/usr/src/cmd/filesync/base.c @@ -0,0 +1,912 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License, Version 1.0 only + * (the "License"). You may not use this file except in compliance + * with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ +/* + * Copyright (c) 1995 Sun Microsystems, Inc. All Rights Reserved + * + * module: + * base.c + * + * purpose: + * routines to create, traverse, read and write the baseline database + * + * contents: + * manipulation: + * add_base, add_file_to_base, add_file_to_dir + * (static) add_file_to_list + * reading baseline: + * read_baseline + * (static) gettype + * writing baseline: + * write_baseline + * (static) bw_header, bw_base, bw_file, showtype + */ +#ident "%W% %E% SMI" + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <time.h> + +#include "filesync.h" +#include "database.h" +#include "messages.h" + +#define BASE_MAJOR 1 /* base file format major rev */ +#define BASE_MINOR 2 /* base file format minor rev */ +#define BASE_TAG "filesync-BaseLine" + +/* + * globals + */ +struct base omnibase; /* dummy to hold global rules */ +struct base *bases; /* pointer to the base list */ + +/* + * locals + */ +static int num_bases; /* used to generate sequence #s */ +static errmask_t bw_header(FILE *); /* write out baseline header */ +static errmask_t bw_base(FILE *, struct base *); /* write out one base */ +static errmask_t bw_file(FILE *, struct file *, int); +static struct file *add_file_to_list(struct file **, const char *); +static char showtype(int); +static long gettype(int); + +/* + * routine: + * add_base + * + * purpose: + * to find a base pair in the chain, adding it if necessary + * + * parameters: + * spec for source directory + * spec for dest directory + * + * returns: + * pointer to the base pair + * + */ +struct base * +add_base(const char *src, const char *dst) +{ struct base *bp, **bpp; + + /* first see if we already have it */ + for (bpp = &bases; (bp = *bpp) != 0; bpp = &bp->b_next) { + /* base must match on both src and dst */ + if (strcmp(src, bp->b_src_spec)) + continue; + if (strcmp(dst, bp->b_dst_spec)) + continue; + + if (opt_debug & DBG_BASE) + fprintf(stderr, "BASE: FOUND base=%d, src=%s, dst=%s\n", + bp->b_ident, src, dst); + return (bp); + } + + /* no joy, so we have to allocate one */ + bp = malloc(sizeof (struct base)); + if (bp == 0) + nomem("base structure"); + + /* initialize the new base */ + memset((void *) bp, 0, sizeof (struct base)); + bp->b_ident = ++num_bases; + bp->b_src_spec = strdup(src); + bp->b_dst_spec = strdup(dst); + + /* names are expanded at run-time, and this is run-time */ + if ((bp->b_src_name = expand(bp->b_src_spec)) == 0) { + fprintf(stderr, gettext(ERR_badbase), bp->b_src_spec); + exit(ERR_FILES); + } + + if ((bp->b_dst_name = expand(bp->b_dst_spec)) == 0) { + fprintf(stderr, gettext(ERR_badbase), bp->b_dst_spec); + exit(ERR_FILES); + } + + /* chain it in */ + *bpp = bp; + + if (opt_debug & DBG_BASE) + fprintf(stderr, "BASE: ADDED base=%d, src=%s, dst=%s\n", + bp->b_ident, src, dst); + + return (bp); +} + +/* + * routine: + * add_file_to_list + * + * purpose: + * to find a file on a list, or if necessary add it to the list + * + * this is an internal routine, used only by add_file_to_base + * and add_file_to_dir. + * + * parameters: + * pointer to the list head + * + * returns: + * pointer to a file structure + * + * notes: + * + * list is sorted to provide some search optimization + * + * most files are in the baseline, and so come in in alphabetical + * order. If we keep a guess pointer to the last file we added/found, + * there is a better than even chance that this one should be + * added immediately onto the end of it ... and in so doing we + * can save ourselves the trouble of searching the lists most + * of the time. + * + * this win would be even better if the FTW traversal was sorted, + * but building the baseline is enough of a win to justify the + * feature ... but even without this we run a 60%-70% hit rate. + */ +static struct file * +add_file_to_list(struct file **pp, const char *name) +{ struct file *fp, *new; + int rslt; + + static struct file **last_list; + static struct file *last_file; + + /* + * start with the guess pointer, we hope to find that + * this request will be satisfied by the next file in + * the list. The two cases we are trying to optimize + * are: + * appending to the list, with appends in alphabetical order + * searches of the list, with searches in alphabetical order + */ + if (last_list == pp && (new = last_file) != 0) { + /* we like to think we belong farther down-list */ + if (strcmp(name, new->f_name) > 0) { + fp = new->f_next; + /* if we're at the end, we just won */ + if (fp == 0) { + pp = &new->f_next; + goto makeit; + } + + /* or if the next one is what we want */ + if (strcmp(name, fp->f_name) == 0) { + fp->f_flags &= ~F_NEW; + new = fp; + goto gotit; + } + } + } + + /* + * our guess pointer failed, so it is exhaustive search time + */ + last_list = pp; + + for (fp = *pp; fp; pp = &fp->f_next, fp = *pp) { + rslt = strcmp(name, fp->f_name); + + /* see if we got a match */ + if (rslt == 0) { + fp->f_flags &= ~F_NEW; + new = fp; + goto gotit; + } + + /* see if we should go no farther */ + if (rslt < 0) + break; + } + +makeit: + /* + * we didn't find it: + * pp points at where our pointer should go + * fp points at the node after ours + */ + new = (struct file *) malloc(sizeof (*new)); + if (new == 0) + nomem("file structure"); + + /* initialize the new node */ + memset((void *) new, 0, sizeof (struct file)); + new->f_name = strdup(name); + new->f_flags = F_NEW; + + /* chain it into the list */ + new->f_next = fp; + *pp = new; + +gotit: /* remember this as our next guess pointer */ + last_file = new; + return (new); +} + +/* + * routine: + * add_file_to_base + * + * purpose: + * to add a file-node to a baseline + * + * parameters: + * pointer to base + * name of file to be added + * + * returns: + * pointer to file structure + */ +struct file * +add_file_to_base(struct base *bp, const char *name) +{ struct file *fp; + + fp = add_file_to_list(&bp->b_files, name); + fp->f_base = bp; + fp->f_depth = 0; + + if (opt_debug & DBG_LIST) + fprintf(stderr, "LIST: base=%d, %s file=%s\n", + bp->b_ident, (fp->f_flags&F_NEW) ? "NEW" : "FOUND", + name); + + return (fp); +} + +/* + * routine: + * add_file_to_dir + * + * purpose: + * to add a file-node to a directory + * + * parameters: + * pointer to file entry for directory + * name of file to be added + * + * returns: + * pointer to file structure + */ +struct file * +add_file_to_dir(struct file *dp, const char *name) +{ struct file *fp; + + fp = add_file_to_list(&dp->f_files, name); + fp->f_base = dp->f_base; + fp->f_depth = dp->f_depth + 1; + + if (opt_debug & DBG_LIST) + fprintf(stderr, "LIST: dir=%s, %s file=%s\n", + dp->f_name, (fp->f_flags&F_NEW) ? "NEW" : "FOUND", + name); + + return (fp); +} + +/* + * routine: + * read_baseline + * + * purpose: + * to read in the baseline file + * + * parameters: + * name of baseline file + * + * returns: + * error mask + */ +errmask_t +read_baseline(char *name) +{ FILE *file; + errmask_t errs = 0; + + char *s; + char *s1 = 0; + char type; + char *field = "???"; + + unsigned long l; + unsigned long long ll; /* intermediate for 64 bit file support */ + int level; + int major, minor; + + struct base *bp = 0; + struct file *fp; + struct fileinfo *ip; + aclent_t *ap; + + struct file *dirstack[ MAX_DEPTH ]; + + file = fopen(name, "r"); + if (file == NULL) { + fprintf(stderr, gettext(ERR_open), gettext(TXT_base), + name); + return (ERR_FILES); + } + lex_linenum = 0; + + if (opt_debug & DBG_FILES) + fprintf(stderr, "FILE: READ BASELINE %s\n", name); + + while (!feof(file)) { + /* find the first token on the line */ + s = lex(file); + + /* skip blank lines and comments */ + if (s == 0 || *s == 0 || *s == '#' || *s == '*') + continue; + + field = "keyword"; + + /* see if the first token is a known keyword */ + if (strcmp(s, "VERSION") == 0 || strcmp(s, BASE_TAG) == 0) { + s = lex(0); + field = gettext(TXT_noargs); + if (s == 0) + goto bad; + + major = strtol(s, &s1, 10); + field = gettext(TXT_badver); + if (*s1 != '.') + goto bad; + minor = strtol(&s1[1], 0, 10); + + if (major != BASE_MAJOR || minor > BASE_MINOR) { + fprintf(stderr, gettext(ERR_badver), + major, minor, gettext(TXT_base), name); + errs |= ERR_FILES; + } + s1 = 0; + continue; + } + + if (strcmp(s, "BASE_SRC") == 0) { + s = lex(0); + field = "source directory"; + if (s == 0) + goto bad; + s1 = strdup(s); + bp = 0; + continue; + } + + if (strcmp(s, "BASE_DST") == 0) { + s = lex(0); + field = "destination directory"; + if (s == 0) + goto bad; + + /* make sure we have a source too */ + if (s1 == 0) { + field = "no source directory"; + goto bad; + } + + bp = add_base(s1, s); + free(s1); + s1 = 0; + continue; + } + + if (strcmp(s, "FILE") == 0) { + /* make sure we have a base to add to */ + if (bp == 0) { + field = "missing base"; + goto bad; + } + + s = lex(0); /* level */ + field = "level"; + if (s == 0 || *s == 0) + goto bad; + l = strtoul(s, 0, 0); + level = l; + + s = lex(0); /* type */ + field = "file type"; + if (s == 0 || *s == 0) + goto bad; + type = *s; + if (gettype(type) < 0) + goto bad; + + s = lex(0); /* name */ + field = "file name"; + if (s == 0 || *s == 0) + goto bad; + + /* allocate a file structure for this entry */ + if (level == 0) + fp = add_file_to_base(bp, s); + else + fp = add_file_to_dir(dirstack[level-1], s); + + fp->f_flags |= F_IN_BASELINE; + + /* maintain the directory stack */ + if (level >= MAX_DEPTH) { + fprintf(stderr, gettext(ERR_deep), s); + exit(ERR_OTHER); + } + + dirstack[ level ] = fp; + + /* get a pointer to the baseline file info structure */ + ip = &fp->f_info[ OPT_BASE ]; + + ip->f_type = gettype(type); /* note file type */ + + s = lex(0); /* modes */ + field = "file modes"; + if (s == 0 || *s == 0) + goto bad; + l = strtoul(s, 0, 0); + ip->f_mode = l; + + s = lex(0); /* uid */ + field = "file UID"; + if (s == 0 || *s == 0) + goto bad; + l = strtoul(s, 0, 0); + ip->f_uid = l; + + s = lex(0); /* gid */ + field = "file GID"; + if (s == 0 || *s == 0) + goto bad; + l = strtoul(s, 0, 0); + ip->f_gid = l; + + s = lex(0); /* source inode */ + field = "source i#"; + if (s == 0 || *s == 0) + goto bad; + ll = strtoull(s, 0, 0); + fp->f_s_inum = (ino_t) ll; + + s = lex(0); /* source major */ + field = "source major"; + if (s == 0 || *s == 0) + goto bad; + l = strtoul(s, 0, 0); + fp->f_s_maj = l; + + s = lex(0); /* source minor */ + field = "source minor"; + if (s == 0 || *s == 0) + goto bad; + l = strtoul(s, 0, 0); + fp->f_s_min = l; + + s = lex(0); /* source nlink */ + field = "source nlink"; + if (s == 0 || *s == 0) + goto bad; + l = strtoul(s, 0, 0); + fp->f_s_nlink = l; + + s = lex(0); /* source mod */ + field = "source modtime"; + if (s == 0 || *s == 0) + goto bad; + l = strtoul(s, 0, 0); + fp->f_s_modtime = l; + + s = lex(0); /* dest inode */ + field = "destination i#"; + if (s == 0 || *s == 0) + goto bad; + ll = strtoull(s, 0, 0); + fp->f_d_inum = (ino_t) ll; + + s = lex(0); /* dest major */ + field = "destination major"; + if (s == 0 || *s == 0) + goto bad; + l = strtoul(s, 0, 0); + fp->f_d_maj = l; + + s = lex(0); /* dest minor */ + field = "destination minor"; + if (s == 0 || *s == 0) + goto bad; + l = strtoul(s, 0, 0); + fp->f_d_min = l; + + s = lex(0); /* dest nlink */ + field = "dest nlink"; + if (s == 0 || *s == 0) + goto bad; + l = strtoul(s, 0, 0); + fp->f_d_nlink = l; + + s = lex(0); /* dest mod */ + field = "dest modtime"; + if (s == 0 || *s == 0) + goto bad; + l = strtoul(s, 0, 0); + fp->f_d_modtime = l; + + s = lex(0); /* major or size */ + + if (type == 'C' || type == 'B') { + field = "rdev major"; + if (s == 0 || *s == 0) + goto bad; + l = strtoul(s, 0, 0); + ip->f_rd_maj = l; + + s = lex(0); /* minor */ + field = "rdev minor"; + if (s == 0 || *s == 0) + goto bad; + l = strtoul(s, 0, 0); + ip->f_rd_min = l; + } else { + field = "file size"; + if (s == 0 || *s == 0) + goto bad; + ll = strtoul(s, 0, 0); + ip->f_size = (off_t) ll; /* size */ + } + + /* + * all fields after this point were added to the + * 1.0 format and so should be considered optional + */ + s = lex(0); /* acl length ? */ + field = "acl count"; + if (s && *s) { + l = strtoul(s, 0, 0); + ip->f_numacls = l; + ip->f_acls = (aclent_t *) malloc(ip->f_numacls * + sizeof (aclent_t)); + if (ip->f_acls == 0) + nomem("Access Control List"); + } + + continue; + } + + if (strcmp(s, "ACL") == 0) { + /* make sure there is a place to put the ACL */ + if (ip == 0 || ip->f_acls == 0) { + field = "ACL w/o FILE/LIST"; + goto bad; + } + + /* acl entry number */ + s = lex(0); + field = "acl index"; + if (s == 0) + goto bad; + l = strtoul(s, 0, 0); + if (l >= ip->f_numacls) + goto bad; + else + ap = &ip->f_acls[l]; + + /* acl entry type */ + s = lex(0); + field = "acl type"; + if (s == 0) + goto bad; + l = strtoul(s, 0, 0); + ap->a_type = l; + + /* acl entry ID */ + s = lex(0); + field = "acl id"; + if (s == 0) + goto bad; + l = strtoul(s, 0, 0); + ap->a_id = l; + + /* acl entry perms */ + s = lex(0); + field = "acl perm"; + if (s == 0) + goto bad; + l = strtoul(s, 0, 0); + ap->a_perm = l; + + continue; + } + + bad: /* log the error and continue processing to find others */ + fprintf(stderr, gettext(ERR_badinput), lex_linenum, + field, name); + errs |= ERR_FILES; + } + + (void) fclose(file); + return (errs); +} + +/* + * routine: + * write_baseline + * + * purpose: + * to rewrite the baseline file + * + * parameters: + * name of the new baseline file + * + * returns: + * error mask + */ +errmask_t +write_baseline(char *name) +{ FILE *newfile; + errmask_t errs = 0; + struct base *bp; + char tmpname[ MAX_PATH ]; + + if (opt_debug & DBG_FILES) + fprintf(stderr, "FILE: WRITE BASELINE %s\n", name); + + /* if no-touch is specified, we don't update files */ + if (opt_notouch) + return (0); + + /* create a temporary output file */ + sprintf(tmpname, "%s-TMP", name); + + /* create our output file */ + newfile = fopen(tmpname, "w+"); + if (newfile == NULL) { + fprintf(stderr, gettext(ERR_creat), gettext(TXT_base), + tmpname); + return (ERR_FILES); + } + + errs |= bw_header(newfile); + for (bp = bases; bp; bp = bp->b_next) + errs |= bw_base(newfile, bp); + + if (ferror(newfile)) { + fprintf(stderr, gettext(ERR_write), gettext(TXT_base), + tmpname); + errs |= ERR_FILES; + } + + if (fclose(newfile)) { + fprintf(stderr, gettext(ERR_fclose), gettext(TXT_base), + tmpname); + errs |= ERR_FILES; + } + + /* now switch the new file for the old one */ + if (errs == 0) + if (rename(tmpname, name) != 0) { + fprintf(stderr, gettext(ERR_rename), + gettext(TXT_base), tmpname, name); + errs |= ERR_FILES; + } + + return (errs); +} + +/* + * routine: + * bw_header + * + * purpose: + * to write out a baseline header + * + * parameters: + * FILE* for the output file + * + * returns: + * error mask + * + * notes: + */ +static errmask_t +bw_header(FILE *file) +{ time_t now; + struct tm *local; + + /* figure out what time it is */ + (void) time(&now); + local = localtime(&now); + + fprintf(file, "%s %d.%d\n", BASE_TAG, BASE_MAJOR, BASE_MINOR); + fprintf(file, "#\n"); + fprintf(file, "# filesync baseline, last written by %s, %s", + cuserid((char *) 0), asctime(local)); + fprintf(file, "#\n"); + + return (0); +} + +/* + * routine: + * bw_base + * + * purpose: + * to write out the summary for one base-pair + * + * parameters: + * FILE * for the output file + * + * returns: + * error mask + * + * notes: + */ +static errmask_t +bw_base(FILE *file, struct base *bp) +{ struct file *fp; + errmask_t errs = 0; + + /* see if this base is to be dropped from baseline */ + if (bp->b_flags & F_REMOVE) + return (0); + + fprintf(file, "\n"); + fprintf(file, "BASE_SRC %s\n", noblanks(bp->b_src_spec)); + fprintf(file, "BASE_DST %s\n", noblanks(bp->b_dst_spec)); + + for (fp = bp->b_files; fp; fp = fp->f_next) + errs |= bw_file(file, fp, 0); + + return (errs); +} + +/* + * routine: + * bw_file + * + * purpose: + * to write a file description out to the baseline + * + * parameters: + * output FILE + * pointer to file description + * recursion depth + * + * returns: + * error mask + * + * notes: + * some of the information we write out is kept separately + * for source and destination files because the values should + * be expected to be different for different systems/copies. + * + * if a file has an unresolved conflict, we want to leave + * the old values in place so that we continue to compare + * files against the last time they agreed. + */ +static errmask_t +bw_file(FILE *file, struct file *fp, int depth) +{ struct file *cp; + int i; + errmask_t errs = 0; + long long ll; /* intermediate for 64 bit file support */ + struct fileinfo *ip = &fp->f_info[OPT_BASE]; + + /* if this file is to be removed from baseline, skip it */ + if (fp->f_flags & F_REMOVE) + return (0); + + /* + * if this node is in conflict, or if it has not been + * evaluated this time around, we should just leave the + * baseline file the way it was before. If there is a + * conflict, let the baseline reflect the last agreement. + * If the node wasn't evaluated, let the baseline reflect + * our last knowledge. + */ + if (fp->f_flags & F_CONFLICT || (fp->f_flags&F_EVALUATE) == 0) { + fp->f_info[OPT_SRC].f_ino = fp->f_s_inum; + fp->f_info[OPT_SRC].f_nlink = fp->f_s_nlink; + fp->f_info[OPT_SRC].f_d_maj = fp->f_s_maj; + fp->f_info[OPT_SRC].f_d_min = fp->f_s_min; + fp->f_info[OPT_SRC].f_modtime = fp->f_s_modtime; + fp->f_info[OPT_DST].f_ino = fp->f_d_inum; + fp->f_info[OPT_DST].f_nlink = fp->f_d_nlink; + fp->f_info[OPT_DST].f_d_maj = fp->f_d_maj; + fp->f_info[OPT_DST].f_d_min = fp->f_d_min; + fp->f_info[OPT_DST].f_modtime = fp->f_d_modtime; + } + + /* write out the entry for this file */ + fprintf(file, "FILE %d %c %-20s 0%04o", depth, showtype(ip->f_type), + noblanks(fp->f_name), ip->f_mode); + fprintf(file, " %6ld %6ld", ip->f_uid, ip->f_gid); + + ll = fp->f_info[OPT_SRC].f_ino; + fprintf(file, "\t%6lld %4ld %4ld %4d 0x%08lx", + ll, + fp->f_info[OPT_SRC].f_d_maj, + fp->f_info[OPT_SRC].f_d_min, + fp->f_info[OPT_SRC].f_nlink, + fp->f_info[OPT_SRC].f_modtime); + + ll = fp->f_info[OPT_DST].f_ino; + fprintf(file, "\t%6lld %4ld %4ld %4d 0x%08lx", + ll, + fp->f_info[OPT_DST].f_d_maj, + fp->f_info[OPT_DST].f_d_min, + fp->f_info[OPT_DST].f_nlink, + fp->f_info[OPT_DST].f_modtime); + + /* last fields are file type specific */ + if (S_ISBLK(ip->f_type) || S_ISCHR(ip->f_type)) + fprintf(file, "\t%4ld %4ld", ip->f_rd_maj, ip->f_rd_min); + else { + ll = ip->f_size; + fprintf(file, "\t%lld", ll); + } + + /* ACL count goes at the end because it was added */ + fprintf(file, "\t%d", ip->f_numacls); + + fprintf(file, "\n"); + + /* if this file has ACLs, we have to write them out too */ + for (i = 0; i < ip->f_numacls; i++) + fprintf(file, "ACL %d %d %ld %o\n", i, ip->f_acls[i].a_type, + ip->f_acls[i].a_id, ip->f_acls[i].a_perm); + + /* then enumerate all of the children (if any) */ + for (cp = fp->f_files; cp; cp = cp->f_next) + errs |= bw_file(file, cp, depth + 1); + + return (errs); +} + +/* + * routines: + * gettype/showtype + * + * purpose: + * to convert between a file type (as found in a mode word) + * and a single character representation + * + * parameters/return + * mode word -> character + * character -> mode word + */ +static char types[16] = "-PC?DNB?F?S?s???"; + +static char showtype(int mode) +{ + return (types[ (mode & S_IFMT) >> 12 ]); +} + +static long gettype(int code) +{ int i; + + for (i = 0; i < 16; i++) + if (types[i] == code) + return (i << 12); + + return (-1); +} |