X-Git-Url: https://mattmccutchen.net/rsync/rsync.git/blobdiff_plain/2217b30adfaff4e8579177bc5f8c0de268d3bf57..7b6c5c77942b8d11631c873b008e0b405d435bc0:/exclude.c diff --git a/exclude.c b/exclude.c index 87013ec4..08fa1108 100644 --- a/exclude.c +++ b/exclude.c @@ -1,12 +1,14 @@ -/* -*- c-file-style: "linux" -*- +/* + * The filter include/exclude routines. * - * Copyright (C) 1996-2001 by Andrew Tridgell - * Copyright (C) 1996 by Paul Mackerras - * Copyright (C) 2002 by Martin Pool + * Copyright (C) 1996-2001 Andrew Tridgell + * Copyright (C) 1996 Paul Mackerras + * Copyright (C) 2002 Martin Pool + * Copyright (C) 2003-2009 Wayne Davison * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or + * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, @@ -14,26 +16,19 @@ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * You should have received a copy of the GNU General Public License along + * with this program; if not, visit the http://fsf.org website. */ -/* a lot of this stuff was originally derived from GNU tar, although - it has now changed so much that it is hard to tell :) */ - -/* include/exclude cluestick added by Martin Pool */ - #include "rsync.h" -extern int verbose; extern int am_server; extern int am_sender; extern int eol_nulls; -extern int list_only; -extern int recurse; extern int io_error; extern int local_server; +extern int prune_empty_dirs; +extern int ignore_perishable; extern int delete_mode; extern int delete_excluded; extern int cvs_exclude; @@ -41,20 +36,22 @@ extern int sanitize_paths; extern int protocol_version; extern int module_id; -extern char curr_dir[]; +extern char curr_dir[MAXPATHLEN]; extern unsigned int curr_dir_len; extern unsigned int module_dirlen; -struct filter_list_struct filter_list = { 0, 0, "" }; -struct filter_list_struct cvs_filter_list = { 0, 0, " [cvsignore]" }; -struct filter_list_struct server_filter_list = { 0, 0, " [server]" }; +filter_rule_list filter_list = { .debug_type = "" }; +filter_rule_list cvs_filter_list = { .debug_type = " [global CVS]" }; +filter_rule_list daemon_filter_list = { .debug_type = " [daemon]" }; /* Need room enough for ":MODS " prefix plus some room to grow. */ #define MAX_RULE_PREFIX (16) #define MODIFIERS_MERGE_FILE "-+Cenw" -#define MODIFIERS_INCL_EXCL "/!Crs" -#define MODIFIERS_HIDE_PROTECT "/!" +#define MODIFIERS_INCL_EXCL "/!Crsp" +#define MODIFIERS_HIDE_PROTECT "/!p" + +#define SLASH_WILD3_SUFFIX "/***" /* The dirbuf is set by push_local_filters() to the current subdirectory * relative to curr_dir that is being processed. The path always has a @@ -70,7 +67,7 @@ static BOOL parent_dirscan = False; /* This array contains a list of all the currently active per-dir merge * files. This makes it easier to save the appropriate values when we * "push" down into each subdirectory. */ -static struct filter_struct **mergelist_parents; +static filter_rule **mergelist_parents; static int mergelist_cnt = 0; static int mergelist_size = 0; @@ -105,81 +102,151 @@ static int mergelist_size = 0; * values (so we can pop back to them later) and set the tail to NULL. */ -static void free_filter(struct filter_struct *ex) +static void teardown_mergelist(filter_rule *ex) { - if (ex->match_flags & MATCHFLG_PERDIR_MERGE) { - free(ex->u.mergelist->debug_type); - free(ex->u.mergelist); - mergelist_cnt--; + if (DEBUG_GTE(FILTER, 2)) { + rprintf(FINFO, "[%s] deactivating mergelist #%d%s\n", + who_am_i(), mergelist_cnt - 1, + ex->u.mergelist->debug_type); } + + /* We should deactivate mergelists in LIFO order. */ + assert(mergelist_cnt > 0); + assert(ex == mergelist_parents[mergelist_cnt - 1]); + + /* The parent_dirscan filters should have been freed. */ + assert(ex->u.mergelist->parent_dirscan_head == NULL); + + free(ex->u.mergelist->debug_type); + free(ex->u.mergelist); + mergelist_cnt--; +} + +static void free_filter(filter_rule *ex) +{ + if (ex->rflags & FILTRULE_PERDIR_MERGE) + teardown_mergelist(ex); free(ex->pattern); free(ex); } +static void free_filters(filter_rule *head) +{ + filter_rule *rev_head = NULL; + + /* Reverse the list so we deactivate mergelists in the proper LIFO + * order. */ + while (head) { + filter_rule *next = head->next; + head->next = rev_head; + rev_head = head; + head = next; + } + + while (rev_head) { + filter_rule *prev = rev_head->next; + free_filter(rev_head); + rev_head = prev; + } +} + /* Build a filter structure given a filter pattern. The value in "pat" * is not null-terminated. */ -static void add_rule(struct filter_list_struct *listp, const char *pat, - unsigned int pat_len, uint32 mflags, int xflags) +static void add_rule(filter_rule_list *listp, const char *pat, + unsigned int pat_len, uint32 rflags, int xflags) { - struct filter_struct *ret; + filter_rule *ret; const char *cp; - unsigned int ex_len; + unsigned int pre_len, suf_len, slash_cnt = 0; - if (verbose > 2) { + if (DEBUG_GTE(FILTER, 2)) { rprintf(FINFO, "[%s] add_rule(%s%.*s%s)%s\n", - who_am_i(), get_rule_prefix(mflags, pat, 0, NULL), + who_am_i(), get_rule_prefix(rflags, pat, 0, NULL), (int)pat_len, pat, - (mflags & MATCHFLG_DIRECTORY) ? "/" : "", + (rflags & FILTRULE_DIRECTORY) ? "/" : "", listp->debug_type); } - /* This flag also indicates that we're reading a list that + /* These flags also indicate that we're reading a list that * needs to be filtered now, not post-filtered later. */ - if (xflags & XFLG_ANCHORED2ABS) { - uint32 mf = mflags & (MATCHFLG_RECEIVER_SIDE|MATCHFLG_SENDER_SIDE); + if (xflags & (XFLG_ANCHORED2ABS|XFLG_ABS_IF_SLASH)) { + uint32 mf = rflags & (FILTRULE_RECEIVER_SIDE|FILTRULE_SENDER_SIDE); if (am_sender) { - if (mf == MATCHFLG_RECEIVER_SIDE) + if (mf == FILTRULE_RECEIVER_SIDE) return; } else { - if (mf == MATCHFLG_SENDER_SIDE) + if (mf == FILTRULE_SENDER_SIDE) return; } } - if (!(ret = new(struct filter_struct))) + if (!(ret = new0(filter_rule))) out_of_memory("add_rule"); - memset(ret, 0, sizeof ret[0]); - if (xflags & XFLG_ANCHORED2ABS && *pat == '/' - && !(mflags & (MATCHFLG_ABS_PATH | MATCHFLG_MERGE_FILE))) { - mflags |= MATCHFLG_ABS_PATH; - ex_len = dirbuf_len - module_dirlen - 1; + if (pat_len > 1 && pat[pat_len-1] == '/') { + pat_len--; + rflags |= FILTRULE_DIRECTORY; + } + + for (cp = pat; cp < pat + pat_len; cp++) { + if (*cp == '/') + slash_cnt++; + } + + if (!(rflags & (FILTRULE_ABS_PATH | FILTRULE_MERGE_FILE)) + && ((xflags & (XFLG_ANCHORED2ABS|XFLG_ABS_IF_SLASH) && *pat == '/') + || (xflags & XFLG_ABS_IF_SLASH && slash_cnt))) { + rflags |= FILTRULE_ABS_PATH; + if (*pat == '/') + pre_len = dirbuf_len - module_dirlen - 1; + else + pre_len = 0; } else - ex_len = 0; - if (!(ret->pattern = new_array(char, ex_len + pat_len + 1))) + pre_len = 0; + + /* The daemon wants dir-exclude rules to get an appended "/" + "***". */ + if (xflags & XFLG_DIR2WILD3 + && BITS_SETnUNSET(rflags, FILTRULE_DIRECTORY, FILTRULE_INCLUDE)) { + rflags &= ~FILTRULE_DIRECTORY; + suf_len = sizeof SLASH_WILD3_SUFFIX - 1; + } else + suf_len = 0; + + if (!(ret->pattern = new_array(char, pre_len + pat_len + suf_len + 1))) out_of_memory("add_rule"); - if (ex_len) - memcpy(ret->pattern, dirbuf + module_dirlen, ex_len); - strlcpy(ret->pattern + ex_len, pat, pat_len + 1); - pat_len += ex_len; + if (pre_len) { + memcpy(ret->pattern, dirbuf + module_dirlen, pre_len); + for (cp = ret->pattern; cp < ret->pattern + pre_len; cp++) { + if (*cp == '/') + slash_cnt++; + } + } + strlcpy(ret->pattern + pre_len, pat, pat_len + 1); + pat_len += pre_len; + if (suf_len) { + memcpy(ret->pattern + pat_len, SLASH_WILD3_SUFFIX, suf_len+1); + pat_len += suf_len; + slash_cnt++; + } if (strpbrk(ret->pattern, "*[?")) { - mflags |= MATCHFLG_WILD; + rflags |= FILTRULE_WILD; if ((cp = strstr(ret->pattern, "**")) != NULL) { - mflags |= MATCHFLG_WILD2; + rflags |= FILTRULE_WILD2; /* If the pattern starts with **, note that. */ if (cp == ret->pattern) - mflags |= MATCHFLG_WILD2_PREFIX; + rflags |= FILTRULE_WILD2_PREFIX; + /* If the pattern ends with ***, note that. */ + if (pat_len >= 3 + && ret->pattern[pat_len-3] == '*' + && ret->pattern[pat_len-2] == '*' + && ret->pattern[pat_len-1] == '*') + rflags |= FILTRULE_WILD3_SUFFIX; } } - if (pat_len > 1 && ret->pattern[pat_len-1] == '/') { - ret->pattern[pat_len-1] = 0; - mflags |= MATCHFLG_DIRECTORY; - } - - if (mflags & MATCHFLG_PERDIR_MERGE) { - struct filter_list_struct *lp; + if (rflags & FILTRULE_PERDIR_MERGE) { + filter_rule_list *lp; unsigned int len; int i; @@ -191,7 +258,7 @@ static void add_rule(struct filter_list_struct *listp, const char *pat, /* If the local merge file was already mentioned, don't * add it again. */ for (i = 0; i < mergelist_cnt; i++) { - struct filter_struct *ex = mergelist_parents[i]; + filter_rule *ex = mergelist_parents[i]; const char *s = strrchr(ex->pattern, '/'); if (s) s++; @@ -205,9 +272,9 @@ static void add_rule(struct filter_list_struct *listp, const char *pat, } } - if (!(lp = new_array(struct filter_list_struct, 1))) + if (!(lp = new_array(filter_rule_list, 1))) out_of_memory("add_rule"); - lp->head = lp->tail = NULL; + lp->head = lp->tail = lp->parent_dirscan_head = NULL; if (asprintf(&lp->debug_type, " [per-dir %s]", cp) < 0) out_of_memory("add_rule"); ret->u.mergelist = lp; @@ -215,18 +282,20 @@ static void add_rule(struct filter_list_struct *listp, const char *pat, if (mergelist_cnt == mergelist_size) { mergelist_size += 5; mergelist_parents = realloc_array(mergelist_parents, - struct filter_struct *, + filter_rule *, mergelist_size); if (!mergelist_parents) out_of_memory("add_rule"); } + if (DEBUG_GTE(FILTER, 2)) { + rprintf(FINFO, "[%s] activating mergelist #%d%s\n", + who_am_i(), mergelist_cnt, lp->debug_type); + } mergelist_parents[mergelist_cnt++] = ret; - } else { - for (cp = ret->pattern; (cp = strchr(cp, '/')) != NULL; cp++) - ret->u.slash_cnt++; - } + } else + ret->u.slash_cnt = slash_cnt; - ret->match_flags = mflags; + ret->rflags = rflags; if (!listp->tail) { ret->next = listp->head; @@ -238,17 +307,13 @@ static void add_rule(struct filter_list_struct *listp, const char *pat, } } -static void clear_filter_list(struct filter_list_struct *listp) +static void clear_filter_list(filter_rule_list *listp) { if (listp->tail) { - struct filter_struct *ent, *next; /* Truncate any inherited items from the local list. */ listp->tail->next = NULL; /* Now free everything that is left. */ - for (ent = listp->head; ent; ent = next) { - next = ent->next; - free_filter(ent); - } + free_filters(listp->head); } listp->head = listp->tail = NULL; @@ -290,30 +355,29 @@ static char *parse_merge_name(const char *merge_file, unsigned int *len_ptr, strlcpy(to, merge_file, *len_ptr + 1); merge_file = to; } - if (!sanitize_path(fn, merge_file, r, dirbuf_depth)) { + if (!sanitize_path(fn, merge_file, r, dirbuf_depth, SP_DEFAULT)) { rprintf(FERROR, "merge-file name overflows: %s\n", - safe_fname(merge_file)); + merge_file); return NULL; } + fn_len = strlen(fn); } else { strlcpy(fn, merge_file, len_ptr ? *len_ptr + 1 : MAXPATHLEN); - clean_fname(fn, 1); + fn_len = clean_fname(fn, CFN_COLLAPSE_DOT_DOT_DIRS); } - - fn_len = strlen(fn); - if (fn == buf) - goto done; - - if (dirbuf_len + fn_len >= MAXPATHLEN) { - rprintf(FERROR, "merge-file name overflows: %s\n", - safe_fname(fn)); - return NULL; + + /* If the name isn't in buf yet, it's wasn't absolute. */ + if (fn != buf) { + int d_len = dirbuf_len - prefix_skip; + if (d_len + fn_len >= MAXPATHLEN) { + rprintf(FERROR, "merge-file name overflows: %s\n", fn); + return NULL; + } + memcpy(buf, dirbuf + prefix_skip, d_len); + memcpy(buf + d_len, fn, fn_len + 1); + fn_len = clean_fname(buf, CFN_COLLAPSE_DOT_DOT_DIRS); } - memcpy(buf, dirbuf + prefix_skip, dirbuf_len - prefix_skip); - memcpy(buf + dirbuf_len - prefix_skip, fn, fn_len + 1); - fn_len = clean_fname(buf, 1); - done: if (len_ptr) *len_ptr = fn_len; return buf; @@ -333,7 +397,7 @@ void set_filter_dir(const char *dir, unsigned int dirlen) len = 0; memcpy(dirbuf + len, dir, dirlen); dirbuf[dirlen + len] = '\0'; - dirbuf_len = clean_fname(dirbuf, 1); + dirbuf_len = clean_fname(dirbuf, CFN_COLLAPSE_DOT_DOT_DIRS); if (dirbuf_len > 1 && dirbuf[dirbuf_len-1] == '.' && dirbuf[dirbuf_len-2] == '/') dirbuf_len -= 2; @@ -349,8 +413,8 @@ void set_filter_dir(const char *dir, unsigned int dirlen) * parent directory of the first transfer dir. If it does, we scan all the * dirs from that point through the parent dir of the transfer dir looking * for the per-dir merge-file in each one. */ -static BOOL setup_merge_file(struct filter_struct *ex, - struct filter_list_struct *lp) +static BOOL setup_merge_file(int mergelist_num, filter_rule *ex, + filter_rule_list *lp) { char buf[MAXPATHLEN]; char *x, *y, *pat = ex->pattern; @@ -359,6 +423,10 @@ static BOOL setup_merge_file(struct filter_struct *ex, if (!(x = parse_merge_name(pat, NULL, 0)) || *x != '/') return 0; + if (DEBUG_GTE(FILTER, 2)) { + rprintf(FINFO, "[%s] performing parent_dirscan for mergelist #%d%s\n", + who_am_i(), mergelist_num, lp->debug_type); + } y = strrchr(x, '/'); *y = '\0'; ex->pattern = strdup(y+1); @@ -369,7 +437,7 @@ static BOOL setup_merge_file(struct filter_struct *ex, else pathjoin(buf, MAXPATHLEN, dirbuf, x); - len = clean_fname(buf, 1); + len = clean_fname(buf, CFN_COLLAPSE_DOT_DOT_DIRS); if (len != 1 && len < MAXPATHLEN-1) { buf[len++] = '/'; buf[len] = '\0'; @@ -386,69 +454,95 @@ static BOOL setup_merge_file(struct filter_struct *ex, *y = '\0'; dirbuf_len = y - dirbuf; strlcpy(x, ex->pattern, MAXPATHLEN - (x - buf)); - parse_filter_file(lp, buf, ex->match_flags, XFLG_ANCHORED2ABS); - if (ex->match_flags & MATCHFLG_NO_INHERIT) + parse_filter_file(lp, buf, ex->rflags, XFLG_ANCHORED2ABS); + if (ex->rflags & FILTRULE_NO_INHERIT) { + /* Free the undesired rules to clean up any per-dir + * mergelists they defined. Otherwise pop_local_filters + * may crash trying to restore nonexistent state for + * those mergelists. */ + free_filters(lp->head); lp->head = NULL; + } lp->tail = NULL; strlcpy(y, save, MAXPATHLEN); while ((*x++ = *y++) != '/') {} } + /* Save current head for freeing when the mergelist becomes inactive. */ + lp->parent_dirscan_head = lp->head; parent_dirscan = False; + if (DEBUG_GTE(FILTER, 2)) { + rprintf(FINFO, "[%s] completed parent_dirscan for mergelist #%d%s\n", + who_am_i(), mergelist_num, lp->debug_type); + } free(pat); return 1; } +struct local_filter_state { + int mergelist_cnt; + filter_rule_list mergelists[1]; +}; + /* Each time rsync changes to a new directory it call this function to * handle all the per-dir merge-files. The "dir" value is the current path * relative to curr_dir (which might not be null-terminated). We copy it * into dirbuf so that we can easily append a file name on the end. */ void *push_local_filters(const char *dir, unsigned int dirlen) { - struct filter_list_struct *ap, *push; + struct local_filter_state *push; int i; set_filter_dir(dir, dirlen); + if (DEBUG_GTE(FILTER, 2)) { + rprintf(FINFO, "[%s] pushing local filters for %s\n", + who_am_i(), dirbuf); + } - if (!mergelist_cnt) + if (!mergelist_cnt) { + /* No old state to save and no new merge files to push. */ return NULL; + } - push = new_array(struct filter_list_struct, mergelist_cnt); + push = (struct local_filter_state *)new_array(char, + sizeof (struct local_filter_state) + + (mergelist_cnt-1) * sizeof (filter_rule_list)); if (!push) out_of_memory("push_local_filters"); - for (i = 0, ap = push; i < mergelist_cnt; i++) { - memcpy(ap++, mergelist_parents[i]->u.mergelist, - sizeof (struct filter_list_struct)); + push->mergelist_cnt = mergelist_cnt; + for (i = 0; i < mergelist_cnt; i++) { + memcpy(&push->mergelists[i], mergelist_parents[i]->u.mergelist, + sizeof (filter_rule_list)); } /* Note: parse_filter_file() might increase mergelist_cnt, so keep * this loop separate from the above loop. */ for (i = 0; i < mergelist_cnt; i++) { - struct filter_struct *ex = mergelist_parents[i]; - struct filter_list_struct *lp = ex->u.mergelist; + filter_rule *ex = mergelist_parents[i]; + filter_rule_list *lp = ex->u.mergelist; - if (verbose > 2) { - rprintf(FINFO, "[%s] pushing filter list%s\n", - who_am_i(), lp->debug_type); + if (DEBUG_GTE(FILTER, 2)) { + rprintf(FINFO, "[%s] pushing mergelist #%d%s\n", + who_am_i(), i, lp->debug_type); } lp->tail = NULL; /* Switch any local rules to inherited. */ - if (ex->match_flags & MATCHFLG_NO_INHERIT) + if (ex->rflags & FILTRULE_NO_INHERIT) lp->head = NULL; - if (ex->match_flags & MATCHFLG_FINISH_SETUP) { - ex->match_flags &= ~MATCHFLG_FINISH_SETUP; - if (setup_merge_file(ex, lp)) + if (ex->rflags & FILTRULE_FINISH_SETUP) { + ex->rflags &= ~FILTRULE_FINISH_SETUP; + if (setup_merge_file(i, ex, lp)) set_filter_dir(dir, dirlen); } if (strlcpy(dirbuf + dirbuf_len, ex->pattern, MAXPATHLEN - dirbuf_len) < MAXPATHLEN - dirbuf_len) { - parse_filter_file(lp, dirbuf, ex->match_flags, + parse_filter_file(lp, dirbuf, ex->rflags, XFLG_ANCHORED2ABS); } else { io_error |= IOERR_GENERAL; - rprintf(FINFO, + rprintf(FERROR, "cannot add local filter rules in long-named directory: %s\n", full_fname(dirbuf)); } @@ -460,99 +554,150 @@ void *push_local_filters(const char *dir, unsigned int dirlen) void pop_local_filters(void *mem) { - struct filter_list_struct *ap, *pop = (struct filter_list_struct*)mem; + struct local_filter_state *pop = (struct local_filter_state *)mem; int i; + int old_mergelist_cnt = pop ? pop->mergelist_cnt : 0; + + if (DEBUG_GTE(FILTER, 2)) + rprintf(FINFO, "[%s] popping local filters\n", who_am_i()); for (i = mergelist_cnt; i-- > 0; ) { - struct filter_struct *ex = mergelist_parents[i]; - struct filter_list_struct *lp = ex->u.mergelist; + filter_rule *ex = mergelist_parents[i]; + filter_rule_list *lp = ex->u.mergelist; - if (verbose > 2) { - rprintf(FINFO, "[%s] popping filter list%s\n", - who_am_i(), lp->debug_type); + if (DEBUG_GTE(FILTER, 2)) { + rprintf(FINFO, "[%s] popping mergelist #%d%s\n", + who_am_i(), i, lp->debug_type); } clear_filter_list(lp); + + if (i >= old_mergelist_cnt) { + /* This mergelist does not exist in the state to be + * restored. Free its parent_dirscan list to clean up + * any per-dir mergelists defined there so we don't + * crash trying to restore nonexistent state for them + * below. (Counterpart to setup_merge_file call in + * push_local_filters. Must be done here, not in + * free_filter, for LIFO order.) */ + if (DEBUG_GTE(FILTER, 2)) { + rprintf(FINFO, "[%s] freeing parent_dirscan filters of mergelist #%d%s\n", + who_am_i(), i, ex->u.mergelist->debug_type); + } + free_filters(lp->parent_dirscan_head); + lp->parent_dirscan_head = NULL; + } } - if (!pop) + /* If we cleaned things up properly, the only still-active mergelists + * should be those with a state to be restored. */ + assert(mergelist_cnt == old_mergelist_cnt); + + if (!pop) { + /* No state to restore. */ return; + } - for (i = 0, ap = pop; i < mergelist_cnt; i++) { - memcpy(mergelist_parents[i]->u.mergelist, ap++, - sizeof (struct filter_list_struct)); + for (i = 0; i < mergelist_cnt; i++) { + memcpy(mergelist_parents[i]->u.mergelist, &pop->mergelists[i], + sizeof (filter_rule_list)); } free(pop); } -static int rule_matches(char *name, struct filter_struct *ex, int name_is_dir) +void change_local_filter_dir(const char *dname, int dlen, int dir_depth) +{ + static int cur_depth = -1; + static void *filt_array[MAXPATHLEN/2+1]; + + if (!dname) { + for ( ; cur_depth >= 0; cur_depth--) { + if (filt_array[cur_depth]) { + pop_local_filters(filt_array[cur_depth]); + filt_array[cur_depth] = NULL; + } + } + return; + } + + assert(dir_depth < MAXPATHLEN/2+1); + + for ( ; cur_depth >= dir_depth; cur_depth--) { + if (filt_array[cur_depth]) { + pop_local_filters(filt_array[cur_depth]); + filt_array[cur_depth] = NULL; + } + } + + cur_depth = dir_depth; + filt_array[cur_depth] = push_local_filters(dname, dlen); +} + +static int rule_matches(const char *fname, filter_rule *ex, int name_is_dir) { - char *p, full_name[MAXPATHLEN]; - int match_start = 0; - int ret_match = ex->match_flags & MATCHFLG_NEGATE ? 0 : 1; - char *pattern = ex->pattern; + int slash_handling, str_cnt = 0, anchored_match = 0; + int ret_match = ex->rflags & FILTRULE_NEGATE ? 0 : 1; + char *p, *pattern = ex->pattern; + const char *strings[16]; /* more than enough */ + const char *name = fname + (*fname == '/'); if (!*name) return 0; - /* If the pattern does not have any slashes AND it does not have - * a "**" (which could match a slash), then we just match the - * name portion of the path. */ - if (!ex->u.slash_cnt && !(ex->match_flags & MATCHFLG_WILD2)) { + if (!ex->u.slash_cnt && !(ex->rflags & FILTRULE_WILD2)) { + /* If the pattern does not have any slashes AND it does + * not have a "**" (which could match a slash), then we + * just match the name portion of the path. */ if ((p = strrchr(name,'/')) != NULL) name = p+1; - } - else if (ex->match_flags & MATCHFLG_ABS_PATH && *name != '/' + } else if (ex->rflags & FILTRULE_ABS_PATH && *fname != '/' && curr_dir_len > module_dirlen + 1) { - pathjoin(full_name, sizeof full_name, - curr_dir + module_dirlen + 1, name); - name = full_name; + /* If we're matching against an absolute-path pattern, + * we need to prepend our full path info. */ + strings[str_cnt++] = curr_dir + module_dirlen + 1; + strings[str_cnt++] = "/"; + } else if (ex->rflags & FILTRULE_WILD2_PREFIX && *fname != '/') { + /* Allow "**"+"/" to match at the start of the string. */ + strings[str_cnt++] = "/"; } - - if (ex->match_flags & MATCHFLG_DIRECTORY && !name_is_dir) + strings[str_cnt++] = name; + if (name_is_dir) { + /* Allow a trailing "/"+"***" to match the directory. */ + if (ex->rflags & FILTRULE_WILD3_SUFFIX) + strings[str_cnt++] = "/"; + } else if (ex->rflags & FILTRULE_DIRECTORY) return !ret_match; + strings[str_cnt] = NULL; if (*pattern == '/') { - match_start = 1; + anchored_match = 1; pattern++; - if (*name == '/') - name++; } - if (ex->match_flags & MATCHFLG_WILD) { + if (!anchored_match && ex->u.slash_cnt + && !(ex->rflags & FILTRULE_WILD2)) { /* A non-anchored match with an infix slash and no "**" * needs to match the last slash_cnt+1 name elements. */ - if (!match_start && ex->u.slash_cnt - && !(ex->match_flags & MATCHFLG_WILD2)) { - int cnt = ex->u.slash_cnt + 1; - for (p = name + strlen(name) - 1; p >= name; p--) { - if (*p == '/' && !--cnt) - break; - } - name = p+1; - } - if (wildmatch(pattern, name)) + slash_handling = ex->u.slash_cnt + 1; + } else if (!anchored_match && !(ex->rflags & FILTRULE_WILD2_PREFIX) + && ex->rflags & FILTRULE_WILD2) { + /* A non-anchored match with an infix or trailing "**" (but not + * a prefixed "**") needs to try matching after every slash. */ + slash_handling = -1; + } else { + /* The pattern matches only at the start of the path or name. */ + slash_handling = 0; + } + + if (ex->rflags & FILTRULE_WILD) { + if (wildmatch_array(pattern, strings, slash_handling)) return ret_match; - if (ex->match_flags & MATCHFLG_WILD2_PREFIX) { - /* If the **-prefixed pattern has a '/' as the next - * character, then try to match the rest of the - * pattern at the root. */ - if (pattern[2] == '/' && wildmatch(pattern+3, name)) - return ret_match; - } - else if (!match_start && ex->match_flags & MATCHFLG_WILD2) { - /* A non-anchored match with an infix or trailing "**" - * (but not a prefixed "**") needs to try matching - * after every slash. */ - while ((name = strchr(name, '/')) != NULL) { - name++; - if (wildmatch(pattern, name)) - return ret_match; - } - } - } else if (match_start) { - if (strcmp(name,pattern) == 0) + } else if (str_cnt > 1) { + if (litmatch_array(pattern, strings, slash_handling)) + return ret_match; + } else if (anchored_match) { + if (strcmp(name, pattern) == 0) return ret_match; } else { int l1 = strlen(name); @@ -568,20 +713,22 @@ static int rule_matches(char *name, struct filter_struct *ex, int name_is_dir) } -static void report_filter_result(char const *name, - struct filter_struct const *ent, - int name_is_dir, const char *type) +static void report_filter_result(enum logcode code, char const *name, + filter_rule const *ent, + int name_is_dir, const char *type) { /* If a trailing slash is present to match only directories, * then it is stripped out by add_rule(). So as a special * case we add it back in here. */ - if (verbose >= 2) { - rprintf(FINFO, "[%s] %scluding %s %s because of pattern %s%s%s\n", - who_am_i(), - ent->match_flags & MATCHFLG_INCLUDE ? "in" : "ex", - name_is_dir ? "directory" : "file", name, ent->pattern, - ent->match_flags & MATCHFLG_DIRECTORY ? "/" : "", type); + if (DEBUG_GTE(FILTER, 1)) { + static char *actions[2][2] + = { {"show", "hid"}, {"risk", "protect"} }; + const char *w = who_am_i(); + rprintf(code, "[%s] %sing %s %s because of pattern %s%s%s\n", + w, actions[*w!='s'][!(ent->rflags & FILTRULE_INCLUDE)], + name_is_dir ? "directory" : "file", name, ent->pattern, + ent->rflags & FILTRULE_DIRECTORY ? "/" : "", type); } } @@ -590,29 +737,32 @@ static void report_filter_result(char const *name, * Return -1 if file "name" is defined to be excluded by the specified * exclude list, 1 if it is included, and 0 if it was not matched. */ -int check_filter(struct filter_list_struct *listp, char *name, int name_is_dir) +int check_filter(filter_rule_list *listp, enum logcode code, + const char *name, int name_is_dir) { - struct filter_struct *ent; + filter_rule *ent; for (ent = listp->head; ent; ent = ent->next) { - if (ent->match_flags & MATCHFLG_PERDIR_MERGE) { - int rc = check_filter(ent->u.mergelist, name, + if (ignore_perishable && ent->rflags & FILTRULE_PERISHABLE) + continue; + if (ent->rflags & FILTRULE_PERDIR_MERGE) { + int rc = check_filter(ent->u.mergelist, code, name, name_is_dir); if (rc) return rc; continue; } - if (ent->match_flags & MATCHFLG_CVS_IGNORE) { - int rc = check_filter(&cvs_filter_list, name, + if (ent->rflags & FILTRULE_CVS_IGNORE) { + int rc = check_filter(&cvs_filter_list, code, name, name_is_dir); if (rc) return rc; continue; } if (rule_matches(name, ent, name_is_dir)) { - report_filter_result(name, ent, name_is_dir, - listp->debug_type); - return ent->match_flags & MATCHFLG_INCLUDE ? 1 : -1; + report_filter_result(code, name, ent, name_is_dir, + listp->debug_type); + return ent->rflags & FILTRULE_INCLUDE ? 1 : -1; } } @@ -636,17 +786,17 @@ static const uchar *rule_strcmp(const uchar *str, const char *rule, int rule_len * be '\0' terminated, so use the returned length to limit the string. * Also, be sure to add this length to the returned pointer before passing * it back to ask for the next token. This routine parses the "!" (list- - * clearing) token and (depending on the mflags) the various prefixes. - * The *mflags_ptr value will be set on exit to the new MATCHFLG_* bits + * clearing) token and (depending on the rflags) the various prefixes. + * The *rflags_ptr value will be set on exit to the new FILTRULE_* bits * for the current token. */ -static const char *parse_rule_tok(const char *p, uint32 mflags, int xflags, - unsigned int *len_ptr, uint32 *mflags_ptr) +static const char *parse_rule_tok(const char *p, uint32 rflags, int xflags, + unsigned int *len_ptr, uint32 *rflags_ptr) { const uchar *s = (const uchar *)p; - uint32 new_mflags; + uint32 new_rflags; unsigned int len; - if (mflags & MATCHFLG_WORD_SPLIT) { + if (rflags & FILTRULE_WORD_SPLIT) { /* Skip over any initial whitespace. */ while (isspace(*s)) s++; @@ -656,27 +806,26 @@ static const char *parse_rule_tok(const char *p, uint32 mflags, int xflags, if (!*s) return NULL; - new_mflags = mflags & MATCHFLGS_FROM_CONTAINER; + new_rflags = rflags & FILTRULES_FROM_CONTAINER; /* Figure out what kind of a filter rule "s" is pointing at. Note - * that if MATCHFLG_NO_PREFIXES is set, the rule is either an include - * or an exclude based on the inheritance of the MATCHFLG_INCLUDE + * that if FILTRULE_NO_PREFIXES is set, the rule is either an include + * or an exclude based on the inheritance of the FILTRULE_INCLUDE * flag (above). XFLG_OLD_PREFIXES indicates a compatibility mode * for old include/exclude patterns where just "+ " and "- " are * allowed as optional prefixes. */ - if (mflags & MATCHFLG_NO_PREFIXES) { - if (*s == '!' && mflags & MATCHFLG_CVS_IGNORE) - new_mflags |= MATCHFLG_CLEAR_LIST; /* Tentative! */ + if (rflags & FILTRULE_NO_PREFIXES) { + if (*s == '!' && rflags & FILTRULE_CVS_IGNORE) + new_rflags |= FILTRULE_CLEAR_LIST; /* Tentative! */ } else if (xflags & XFLG_OLD_PREFIXES) { if (*s == '-' && s[1] == ' ') { - new_mflags &= ~MATCHFLG_INCLUDE; + new_rflags &= ~FILTRULE_INCLUDE; s += 2; } else if (*s == '+' && s[1] == ' ') { - new_mflags |= MATCHFLG_INCLUDE; + new_rflags |= FILTRULE_INCLUDE; s += 2; - } - if (*s == '!') - new_mflags |= MATCHFLG_CLEAR_LIST; /* Tentative! */ + } else if (*s == '!') + new_rflags |= FILTRULE_CLEAR_LIST; /* Tentative! */ } else { char ch = 0, *mods = ""; switch (*s) { @@ -716,7 +865,6 @@ static const char *parse_rule_tok(const char *p, uint32 mflags, int xflags, if ((s = RULE_STRCMP(s, "show")) != NULL) ch = 'S'; break; - default: ch = *s; if (s[1] == ',') @@ -725,44 +873,44 @@ static const char *parse_rule_tok(const char *p, uint32 mflags, int xflags, } switch (ch) { case ':': - new_mflags |= MATCHFLG_PERDIR_MERGE - | MATCHFLG_FINISH_SETUP; + new_rflags |= FILTRULE_PERDIR_MERGE + | FILTRULE_FINISH_SETUP; /* FALL THROUGH */ case '.': - new_mflags |= MATCHFLG_MERGE_FILE; + new_rflags |= FILTRULE_MERGE_FILE; mods = MODIFIERS_INCL_EXCL MODIFIERS_MERGE_FILE; break; case '+': - new_mflags |= MATCHFLG_INCLUDE; + new_rflags |= FILTRULE_INCLUDE; /* FALL THROUGH */ case '-': mods = MODIFIERS_INCL_EXCL; break; case 'S': - new_mflags |= MATCHFLG_INCLUDE; + new_rflags |= FILTRULE_INCLUDE; /* FALL THROUGH */ case 'H': - new_mflags |= MATCHFLG_SENDER_SIDE; + new_rflags |= FILTRULE_SENDER_SIDE; mods = MODIFIERS_HIDE_PROTECT; break; case 'R': - new_mflags |= MATCHFLG_INCLUDE; + new_rflags |= FILTRULE_INCLUDE; /* FALL THROUGH */ case 'P': - new_mflags |= MATCHFLG_RECEIVER_SIDE; + new_rflags |= FILTRULE_RECEIVER_SIDE; mods = MODIFIERS_HIDE_PROTECT; break; case '!': - new_mflags |= MATCHFLG_CLEAR_LIST; + new_rflags |= FILTRULE_CLEAR_LIST; mods = NULL; break; default: - rprintf(FERROR, "Unknown filter rule: %s\n", p); + rprintf(FERROR, "Unknown filter rule: `%s'\n", p); exit_cleanup(RERR_SYNTAX); } while (mods && *++s && *s != ' ' && *s != '_') { if (strchr(mods, *s) == NULL) { - if (mflags & MATCHFLG_WORD_SPLIT && isspace(*s)) { + if (rflags & FILTRULE_WORD_SPLIT && isspace(*s)) { s--; break; } @@ -774,44 +922,47 @@ static const char *parse_rule_tok(const char *p, uint32 mflags, int xflags, } switch (*s) { case '-': - if (new_mflags & MATCHFLG_NO_PREFIXES) + if (new_rflags & FILTRULE_NO_PREFIXES) goto invalid; - new_mflags |= MATCHFLG_NO_PREFIXES; + new_rflags |= FILTRULE_NO_PREFIXES; break; case '+': - if (new_mflags & MATCHFLG_NO_PREFIXES) + if (new_rflags & FILTRULE_NO_PREFIXES) goto invalid; - new_mflags |= MATCHFLG_NO_PREFIXES - | MATCHFLG_INCLUDE; + new_rflags |= FILTRULE_NO_PREFIXES + | FILTRULE_INCLUDE; break; case '/': - new_mflags |= MATCHFLG_ABS_PATH; + new_rflags |= FILTRULE_ABS_PATH; break; case '!': - new_mflags |= MATCHFLG_NEGATE; + new_rflags |= FILTRULE_NEGATE; break; case 'C': - if (new_mflags & MATCHFLG_NO_PREFIXES) + if (new_rflags & FILTRULE_NO_PREFIXES) goto invalid; - new_mflags |= MATCHFLG_NO_PREFIXES - | MATCHFLG_WORD_SPLIT - | MATCHFLG_NO_INHERIT - | MATCHFLG_CVS_IGNORE; + new_rflags |= FILTRULE_NO_PREFIXES + | FILTRULE_WORD_SPLIT + | FILTRULE_NO_INHERIT + | FILTRULE_CVS_IGNORE; break; case 'e': - new_mflags |= MATCHFLG_EXCLUDE_SELF; + new_rflags |= FILTRULE_EXCLUDE_SELF; break; case 'n': - new_mflags |= MATCHFLG_NO_INHERIT; + new_rflags |= FILTRULE_NO_INHERIT; + break; + case 'p': + new_rflags |= FILTRULE_PERISHABLE; break; case 'r': - new_mflags |= MATCHFLG_RECEIVER_SIDE; + new_rflags |= FILTRULE_RECEIVER_SIDE; break; case 's': - new_mflags |= MATCHFLG_SENDER_SIDE; + new_rflags |= FILTRULE_SENDER_SIDE; break; case 'w': - new_mflags |= MATCHFLG_WORD_SPLIT; + new_rflags |= FILTRULE_WORD_SPLIT; break; } } @@ -819,7 +970,7 @@ static const char *parse_rule_tok(const char *p, uint32 mflags, int xflags, s++; } - if (mflags & MATCHFLG_WORD_SPLIT) { + if (rflags & FILTRULE_WORD_SPLIT) { const uchar *cp = s; /* Token ends at whitespace or the end of the string. */ while (!isspace(*cp) && *cp != '\0') @@ -828,26 +979,36 @@ static const char *parse_rule_tok(const char *p, uint32 mflags, int xflags, } else len = strlen((char*)s); - if (new_mflags & MATCHFLG_CLEAR_LIST) { - if (!(xflags & XFLG_OLD_PREFIXES) && len) { + if (new_rflags & FILTRULE_CLEAR_LIST) { + if (!(rflags & FILTRULE_NO_PREFIXES) + && !(xflags & XFLG_OLD_PREFIXES) && len) { rprintf(FERROR, "'!' rule has trailing characters: %s\n", p); exit_cleanup(RERR_SYNTAX); } if (len > 1) - new_mflags &= ~MATCHFLG_CLEAR_LIST; - } else if (!len && !(new_mflags & MATCHFLG_CVS_IGNORE)) { + new_rflags &= ~FILTRULE_CLEAR_LIST; + } else if (!len && !(new_rflags & FILTRULE_CVS_IGNORE)) { rprintf(FERROR, "unexpected end of filter rule: %s\n", p); exit_cleanup(RERR_SYNTAX); } + /* --delete-excluded turns an un-modified include/exclude into a + * sender-side rule. We also affect per-dir merge files that take + * no prefixes as a simple optimization. */ + if (delete_excluded + && !(new_rflags & (FILTRULE_RECEIVER_SIDE|FILTRULE_SENDER_SIDE)) + && (!(new_rflags & FILTRULE_PERDIR_MERGE) + || new_rflags & FILTRULE_NO_PREFIXES)) + new_rflags |= FILTRULE_SENDER_SIDE; + *len_ptr = len; - *mflags_ptr = new_mflags; + *rflags_ptr = new_rflags; return (const char *)s; } -static char default_cvsignore[] = +static char default_cvsignore[] = /* These default ignored items come from the CVS manual. */ "RCS SCCS CVS CVS.adm RCSLOG cvslog.* tags TAGS" " .make.state .nse_depinfo *~ #* .#* ,* _$* *$" @@ -855,32 +1016,34 @@ static char default_cvsignore[] = " *.a *.olb *.o *.obj *.so *.exe" " *.Z *.elc *.ln core" /* The rest we added to suit ourself. */ - " .svn/"; + " .svn/ .git/ .bzr/"; -static void get_cvs_excludes(uint32 mflags) +static void get_cvs_excludes(uint32 rflags) { - char *p, fname[MAXPATHLEN]; static int initialized = 0; + char *p, fname[MAXPATHLEN]; if (initialized) return; initialized = 1; - parse_rule(&cvs_filter_list, default_cvsignore, mflags, 0); + parse_rule(&cvs_filter_list, default_cvsignore, + rflags | (protocol_version >= 30 ? FILTRULE_PERISHABLE : 0), + 0); p = module_id >= 0 && lp_use_chroot(module_id) ? "/" : getenv("HOME"); if (p && pathjoin(fname, MAXPATHLEN, p, ".cvsignore") < MAXPATHLEN) - parse_filter_file(&cvs_filter_list, fname, mflags, 0); + parse_filter_file(&cvs_filter_list, fname, rflags, 0); - parse_rule(&cvs_filter_list, getenv("CVSIGNORE"), mflags, 0); + parse_rule(&cvs_filter_list, getenv("CVSIGNORE"), rflags, 0); } -void parse_rule(struct filter_list_struct *listp, const char *pattern, - uint32 mflags, int xflags) +void parse_rule(filter_rule_list *listp, const char *pattern, + uint32 rflags, int xflags) { unsigned int pat_len; - uint32 new_mflags; + uint32 new_rflags; const char *cp, *p; if (!pattern) @@ -888,19 +1051,21 @@ void parse_rule(struct filter_list_struct *listp, const char *pattern, while (1) { /* Remember that the returned string is NOT '\0' terminated! */ - cp = parse_rule_tok(pattern, mflags, xflags, - &pat_len, &new_mflags); + cp = parse_rule_tok(pattern, rflags, xflags, + &pat_len, &new_rflags); if (!cp) break; + + pattern = cp + pat_len; + if (pat_len >= MAXPATHLEN) { - rprintf(FERROR, "discarding over-long filter: %s\n", - cp); + rprintf(FERROR, "discarding over-long filter: %.*s\n", + (int)pat_len, cp); continue; } - pattern = cp + pat_len; - if (new_mflags & MATCHFLG_CLEAR_LIST) { - if (verbose > 2) { + if (new_rflags & FILTRULE_CLEAR_LIST) { + if (DEBUG_GTE(FILTER, 2)) { rprintf(FINFO, "[%s] clearing filter list%s\n", who_am_i(), listp->debug_type); @@ -909,65 +1074,63 @@ void parse_rule(struct filter_list_struct *listp, const char *pattern, continue; } - if (new_mflags & MATCHFLG_MERGE_FILE) { + if (new_rflags & FILTRULE_MERGE_FILE) { unsigned int len; if (!pat_len) { cp = ".cvsignore"; pat_len = 10; } len = pat_len; - if (new_mflags & MATCHFLG_EXCLUDE_SELF) { - const char *name = strrchr(cp, '/'); - if (name) - len -= ++name - cp; - else - name = cp; + if (new_rflags & FILTRULE_EXCLUDE_SELF) { + const char *name = cp + len; + while (name > cp && name[-1] != '/') name--; + len -= name - cp; add_rule(listp, name, len, 0, 0); - new_mflags &= ~MATCHFLG_EXCLUDE_SELF; + new_rflags &= ~FILTRULE_EXCLUDE_SELF; len = pat_len; } - if (new_mflags & MATCHFLG_PERDIR_MERGE) { + if (new_rflags & FILTRULE_PERDIR_MERGE) { if (parent_dirscan) { if (!(p = parse_merge_name(cp, &len, module_dirlen))) continue; - add_rule(listp, p, len, new_mflags, 0); + add_rule(listp, p, len, new_rflags, 0); continue; } } else { if (!(p = parse_merge_name(cp, &len, 0))) continue; - parse_filter_file(listp, p, new_mflags, + parse_filter_file(listp, p, new_rflags, XFLG_FATAL_ERRORS); continue; } } - add_rule(listp, cp, pat_len, new_mflags, xflags); + add_rule(listp, cp, pat_len, new_rflags, xflags); - if (new_mflags & MATCHFLG_CVS_IGNORE - && !(new_mflags & MATCHFLG_MERGE_FILE)) - get_cvs_excludes(new_mflags); + if (new_rflags & FILTRULE_CVS_IGNORE + && !(new_rflags & FILTRULE_MERGE_FILE)) + get_cvs_excludes(new_rflags); } } -void parse_filter_file(struct filter_list_struct *listp, const char *fname, - uint32 mflags, int xflags) +void parse_filter_file(filter_rule_list *listp, const char *fname, + uint32 rflags, int xflags) { FILE *fp; - char line[MAXPATHLEN+MAX_RULE_PREFIX+1]; /* +1 for trailing slash. */ + char line[BIGPATHBUFLEN]; char *eob = line + sizeof line - 1; - int word_split = mflags & MATCHFLG_WORD_SPLIT; + int word_split = rflags & FILTRULE_WORD_SPLIT; if (!fname || !*fname) return; if (*fname != '-' || fname[1] || am_server) { - if (server_filter_list.head) { + if (daemon_filter_list.head) { strlcpy(line, fname, sizeof line); - clean_fname(line, 1); - if (check_filter(&server_filter_list, line, 0) < 0) + clean_fname(line, CFN_COLLAPSE_DOT_DOT_DIRS); + if (check_filter(&daemon_filter_list, FLOG, line, 0) < 0) fp = NULL; else fp = fopen(line, "rb"); @@ -976,9 +1139,9 @@ void parse_filter_file(struct filter_list_struct *listp, const char *fname, } else fp = stdin; - if (verbose > 2) { + if (DEBUG_GTE(FILTER, 2)) { rprintf(FINFO, "[%s] parse_filter_file(%s,%x,%x)%s\n", - who_am_i(), safe_fname(fname), mflags, xflags, + who_am_i(), fname, rflags, xflags, fp ? "" : " [not found]"); } @@ -986,8 +1149,8 @@ void parse_filter_file(struct filter_list_struct *listp, const char *fname, if (xflags & XFLG_FATAL_ERRORS) { rsyserr(FERROR, errno, "failed to open %sclude file %s", - mflags & MATCHFLG_INCLUDE ? "in" : "ex", - safe_fname(fname)); + rflags & FILTRULE_INCLUDE ? "in" : "ex", + fname); exit_cleanup(RERR_FILEIO); } return; @@ -999,8 +1162,10 @@ void parse_filter_file(struct filter_list_struct *listp, const char *fname, int ch, overflow = 0; while (1) { if ((ch = getc(fp)) == EOF) { - if (ferror(fp) && errno == EINTR) + if (ferror(fp) && errno == EINTR) { + clearerr(fp); continue; + } break; } if (word_split && isspace(ch)) @@ -1019,7 +1184,7 @@ void parse_filter_file(struct filter_list_struct *listp, const char *fname, *s = '\0'; /* Skip an empty token and (when line parsing) comments. */ if (*line && (word_split || (*line != ';' && *line != '#'))) - parse_rule(listp, line, mflags, xflags); + parse_rule(listp, line, rflags, xflags); if (ch == EOF) break; } @@ -1029,18 +1194,18 @@ void parse_filter_file(struct filter_list_struct *listp, const char *fname, /* If the "for_xfer" flag is set, the prefix is made compatible with the * current protocol_version (if possible) or a NULL is returned (if not * possible). */ -char *get_rule_prefix(int match_flags, const char *pat, int for_xfer, +char *get_rule_prefix(int rflags, const char *pat, int for_xfer, unsigned int *plen_ptr) { static char buf[MAX_RULE_PREFIX+1]; char *op = buf; int legal_len = for_xfer && protocol_version < 29 ? 1 : MAX_RULE_PREFIX-1; - if (match_flags & MATCHFLG_PERDIR_MERGE) { + if (rflags & FILTRULE_PERDIR_MERGE) { if (legal_len == 1) return NULL; *op++ = ':'; - } else if (match_flags & MATCHFLG_INCLUDE) + } else if (rflags & FILTRULE_INCLUDE) *op++ = '+'; else if (legal_len != 1 || ((*pat == '-' || *pat == '+') && pat[1] == ' ')) @@ -1048,29 +1213,37 @@ char *get_rule_prefix(int match_flags, const char *pat, int for_xfer, else legal_len = 0; - if (match_flags & MATCHFLG_CVS_IGNORE) + if (rflags & FILTRULE_NEGATE) + *op++ = '!'; + if (rflags & FILTRULE_CVS_IGNORE) *op++ = 'C'; else { - if (match_flags & MATCHFLG_NO_INHERIT) + if (rflags & FILTRULE_NO_INHERIT) *op++ = 'n'; - if (match_flags & MATCHFLG_WORD_SPLIT) + if (rflags & FILTRULE_WORD_SPLIT) *op++ = 'w'; - if (match_flags & MATCHFLG_NO_PREFIXES) { - if (match_flags & MATCHFLG_INCLUDE) + if (rflags & FILTRULE_NO_PREFIXES) { + if (rflags & FILTRULE_INCLUDE) *op++ = '+'; else *op++ = '-'; } } - if (match_flags & MATCHFLG_EXCLUDE_SELF) + if (rflags & FILTRULE_EXCLUDE_SELF) *op++ = 'e'; - if (match_flags & MATCHFLG_SENDER_SIDE + if (rflags & FILTRULE_SENDER_SIDE && (!for_xfer || protocol_version >= 29)) *op++ = 's'; - if (match_flags & MATCHFLG_RECEIVER_SIDE + if (rflags & FILTRULE_RECEIVER_SIDE && (!for_xfer || protocol_version >= 29 || (delete_excluded && am_sender))) *op++ = 'r'; + if (rflags & FILTRULE_PERISHABLE) { + if (!for_xfer || protocol_version >= 30) + *op++ = 'p'; + else if (am_sender) + return NULL; + } if (op - buf > legal_len) return NULL; if (legal_len) @@ -1081,20 +1254,29 @@ char *get_rule_prefix(int match_flags, const char *pat, int for_xfer, return buf; } -static void send_rules(int f_out, struct filter_list_struct *flp) +static void send_rules(int f_out, filter_rule_list *flp) { - struct filter_struct *ent, *prev = NULL; + filter_rule *ent, *prev = NULL; for (ent = flp->head; ent; ent = ent->next) { unsigned int len, plen, dlen; int elide = 0; char *p; - if (ent->match_flags & MATCHFLG_SENDER_SIDE) + /* Note we need to check delete_excluded here in addition to + * the code in parse_rule_tok() because some rules may have + * been added before we found the --delete-excluded option. + * We must also elide any CVS merge-file rules to avoid a + * backward compatibility problem, and we elide any no-prefix + * merge files as an optimization (since they can only have + * include/exclude rules). */ + if (ent->rflags & FILTRULE_SENDER_SIDE) elide = am_sender ? 1 : -1; - if (ent->match_flags & MATCHFLG_RECEIVER_SIDE) + if (ent->rflags & FILTRULE_RECEIVER_SIDE) elide = elide ? 0 : am_sender ? -1 : 1; - else if (delete_excluded && !elide) + else if (delete_excluded && !elide + && (!(ent->rflags & FILTRULE_PERDIR_MERGE) + || ent->rflags & FILTRULE_NO_PREFIXES)) elide = am_sender ? 1 : -1; if (elide < 0) { if (prev) @@ -1105,23 +1287,23 @@ static void send_rules(int f_out, struct filter_list_struct *flp) prev = ent; if (elide > 0) continue; - if (ent->match_flags & MATCHFLG_CVS_IGNORE - && !(ent->match_flags & MATCHFLG_MERGE_FILE)) { - int f = am_sender || protocol_version < 29 ? f_out : -1; + if (ent->rflags & FILTRULE_CVS_IGNORE + && !(ent->rflags & FILTRULE_MERGE_FILE)) { + int f = am_sender || protocol_version < 29 ? f_out : -2; send_rules(f, &cvs_filter_list); - if (f >= 0) + if (f == f_out) continue; } - p = get_rule_prefix(ent->match_flags, ent->pattern, 1, &plen); + p = get_rule_prefix(ent->rflags, ent->pattern, 1, &plen); if (!p) { rprintf(FERROR, "filter rules are too modern for remote rsync.\n"); - exit_cleanup(RERR_SYNTAX); + exit_cleanup(RERR_PROTOCOL); } if (f_out < 0) continue; len = strlen(ent->pattern); - dlen = ent->match_flags & MATCHFLG_DIRECTORY ? 1 : 0; + dlen = ent->rflags & FILTRULE_DIRECTORY ? 1 : 0; if (!(plen + len + dlen)) continue; write_int(f_out, plen + len + dlen); @@ -1137,8 +1319,8 @@ static void send_rules(int f_out, struct filter_list_struct *flp) /* This is only called by the client. */ void send_filter_list(int f_out) { - int receiver_wants_list = delete_mode - && (!delete_excluded || protocol_version >= 29); + int receiver_wants_list = prune_empty_dirs + || (delete_mode && (!delete_excluded || protocol_version >= 29)); if (local_server || (am_sender && !receiver_wants_list)) f_out = -1; @@ -1148,11 +1330,6 @@ void send_filter_list(int f_out) parse_rule(&filter_list, "-C", 0, 0); } - /* This is a complete hack - blame Rusty. FIXME! - * Remove this hack when older rsyncs (below 2.6.4) are gone. */ - if (list_only == 1 && !recurse) - parse_rule(&filter_list, "/*/*", MATCHFLG_NO_PREFIXES, 0); - send_rules(f_out, &filter_list); if (f_out >= 0) @@ -1169,16 +1346,17 @@ void send_filter_list(int f_out) /* This is only called by the server. */ void recv_filter_list(int f_in) { - char line[MAXPATHLEN+MAX_RULE_PREFIX+1]; /* +1 for trailing slash. */ + char line[BIGPATHBUFLEN]; int xflags = protocol_version >= 29 ? 0 : XFLG_OLD_PREFIXES; - int receiver_wants_list = delete_mode - && (!delete_excluded || protocol_version >= 29); + int receiver_wants_list = prune_empty_dirs + || (delete_mode + && (!delete_excluded || protocol_version >= 29)); unsigned int len; if (!local_server && (am_sender || receiver_wants_list)) { while ((len = read_int(f_in)) != 0) { if (len >= sizeof line) - overflow("recv_rules"); + overflow_exit("recv_rules"); read_sbuf(f_in, line, len); parse_rule(&filter_list, line, 0, xflags); }