Refactorings to the filter code, most notably:
authorMatt McCutchen <matt@mattmccutchen.net>
Sat, 23 May 2009 17:59:14 +0000 (10:59 -0700)
committerWayne Davison <wayned@samba.org>
Sat, 23 May 2009 18:20:40 +0000 (11:20 -0700)
- Improve function name: parse_rule -> parse_filter_str (to make the
  similarity with parse_filter_file clearer, and better indicate that
  it can parse multiple rules when FILTRULE_WORD_SPLIT is specified).

- In preparation for rule prefixes containing information beyond the
  rflags, change the code to pass around a full "template" filter_rule
  instead of just rflags.  Callers of parse_filter_{str,file} that want
  to specify only rflags can use rule_template(rflags) .

- Remove the MODIFIERS_* strings and instead hand-code the condition
  under which each modifier is valid.  This should make it easier to
  see that the conditions are correct.

- Tighten up default modifiers on merge rules:
  - Disallow "!" because it isn't useful.
  - If the merge rule specifies a side via "s" or "r", the rules in the
    file cannot also specify a side via "s", "r", "hide", etc.

[Patch was changed by Wayne a bit prior to application.]

batch.c
clientserver.c
compat.c
exclude.c
options.c
rsync.h
rsync.yo

diff --git a/batch.c b/batch.c
index 99b84ea..b63f0c5 100644 (file)
--- a/batch.c
+++ b/batch.c
@@ -196,7 +196,7 @@ static void write_filter_rules(int fd)
        write_sbuf(fd, " <<'#E#'\n");
        for (ent = filter_list.head; ent; ent = ent->next) {
                unsigned int plen;
-               char *p = get_rule_prefix(ent->rflags, "- ", 0, &plen);
+               char *p = get_rule_prefix(ent, "- ", 0, &plen);
                write_buf(fd, p, plen);
                write_sbuf(fd, ent->pattern);
                if (ent->rflags & FILTRULE_DIRECTORY)
index b16e0fc..88f341e 100644 (file)
@@ -620,24 +620,24 @@ static int rsync_module(int f_in, int f_out, int i, const char *addr, const char
                set_filter_dir(module_dir, module_dirlen);
 
        p = lp_filter(i);
-       parse_rule(&daemon_filter_list, p, FILTRULE_WORD_SPLIT,
+       parse_filter_str(&daemon_filter_list, p, rule_template(FILTRULE_WORD_SPLIT),
                   XFLG_ABS_IF_SLASH | XFLG_DIR2WILD3);
 
        p = lp_include_from(i);
-       parse_filter_file(&daemon_filter_list, p, FILTRULE_INCLUDE,
+       parse_filter_file(&daemon_filter_list, p, rule_template(FILTRULE_INCLUDE),
            XFLG_ABS_IF_SLASH | XFLG_DIR2WILD3 | XFLG_OLD_PREFIXES | XFLG_FATAL_ERRORS);
 
        p = lp_include(i);
-       parse_rule(&daemon_filter_list, p,
-                  FILTRULE_INCLUDE | FILTRULE_WORD_SPLIT,
+       parse_filter_str(&daemon_filter_list, p,
+                  rule_template(FILTRULE_INCLUDE | FILTRULE_WORD_SPLIT),
                   XFLG_ABS_IF_SLASH | XFLG_DIR2WILD3 | XFLG_OLD_PREFIXES);
 
        p = lp_exclude_from(i);
-       parse_filter_file(&daemon_filter_list, p, 0,
+       parse_filter_file(&daemon_filter_list, p, rule_template(0),
            XFLG_ABS_IF_SLASH | XFLG_DIR2WILD3 | XFLG_OLD_PREFIXES | XFLG_FATAL_ERRORS);
 
        p = lp_exclude(i);
-       parse_rule(&daemon_filter_list, p, FILTRULE_WORD_SPLIT,
+       parse_filter_str(&daemon_filter_list, p, rule_template(FILTRULE_WORD_SPLIT),
                   XFLG_ABS_IF_SLASH | XFLG_DIR2WILD3 | XFLG_OLD_PREFIXES);
 
        log_init(1);
index f1a1e70..10add12 100644 (file)
--- a/compat.c
+++ b/compat.c
@@ -294,7 +294,7 @@ void setup_protocol(int f_out,int f_in)
                int rflags = FILTRULE_NO_PREFIXES | FILTRULE_DIRECTORY;
                if (!am_sender || protocol_version >= 30)
                        rflags |= FILTRULE_PERISHABLE;
-               parse_rule(&filter_list, partial_dir, rflags, 0);
+               parse_filter_str(&filter_list, partial_dir, rule_template(rflags), 0);
        }
 
 
index 97ac173..0aa3769 100644 (file)
--- a/exclude.c
+++ b/exclude.c
@@ -47,10 +47,6 @@ 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 "/!Crsp"
-#define MODIFIERS_HIDE_PROTECT "/!p"
-
 #define SLASH_WILD3_SUFFIX "/***"
 
 /* The dirbuf is set by push_local_filters() to the current subdirectory
@@ -124,8 +120,6 @@ static void teardown_mergelist(filter_rule *ex)
 
 static void free_filter(filter_rule *ex)
 {
-       if (ex->rflags & FILTRULE_PERDIR_MERGE)
-               teardown_mergelist(ex);
        free(ex->pattern);
        free(ex);
 }
@@ -145,47 +139,46 @@ static void free_filters(filter_rule *head)
 
        while (rev_head) {
                filter_rule *prev = rev_head->next;
+               /* Tear down mergelists here, not in free_filter, so that we
+                * affect only real filter lists and not temporarily allocated
+                * filters. */
+               if (rev_head->rflags & FILTRULE_PERDIR_MERGE)
+                       teardown_mergelist(rev_head);
                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(filter_rule_list *listp, const char *pat,
-                    unsigned int pat_len, uint32 rflags, int xflags)
+ * is not null-terminated.  "rule" is either held or freed, so the
+ * caller should not free it. */
+static void add_rule(filter_rule_list *listp, const char *pat, unsigned int pat_len,
+                    filter_rule *rule, int xflags)
 {
-       filter_rule *rule;
        const char *cp;
        unsigned int pre_len, suf_len, slash_cnt = 0;
 
        if (DEBUG_GTE(FILTER, 2)) {
                rprintf(FINFO, "[%s] add_rule(%s%.*s%s)%s\n",
-                       who_am_i(), get_rule_prefix(rflags, pat, 0, NULL),
+                       who_am_i(), get_rule_prefix(rule, pat, 0, NULL),
                        (int)pat_len, pat,
-                       (rflags & FILTRULE_DIRECTORY) ? "/" : "",
+                       (rule->rflags & FILTRULE_DIRECTORY) ? "/" : "",
                        listp->debug_type);
        }
 
        /* These flags also indicate that we're reading a list that
         * needs to be filtered now, not post-filtered later. */
-       if (xflags & (XFLG_ANCHORED2ABS|XFLG_ABS_IF_SLASH)) {
-               uint32 mf = rflags & (FILTRULE_RECEIVER_SIDE|FILTRULE_SENDER_SIDE);
-               if (am_sender) {
-                       if (mf == FILTRULE_RECEIVER_SIDE)
-                               return;
-               } else {
-                       if (mf == FILTRULE_SENDER_SIDE)
-                               return;
-               }
+       if (xflags & (XFLG_ANCHORED2ABS|XFLG_ABS_IF_SLASH)
+               && (rule->rflags & FILTRULES_SIDES)
+                       == (am_sender ? FILTRULE_RECEIVER_SIDE : FILTRULE_SENDER_SIDE)) {
+               /* This filter applies only to the other side.  Drop it. */
+               free_filter(rule);
+               return;
        }
 
-       if (!(rule = new0(filter_rule)))
-               out_of_memory("add_rule");
-
        if (pat_len > 1 && pat[pat_len-1] == '/') {
                pat_len--;
-               rflags |= FILTRULE_DIRECTORY;
+               rule->rflags |= FILTRULE_DIRECTORY;
        }
 
        for (cp = pat; cp < pat + pat_len; cp++) {
@@ -193,10 +186,10 @@ static void add_rule(filter_rule_list *listp, const char *pat,
                        slash_cnt++;
        }
 
-       if (!(rflags & (FILTRULE_ABS_PATH | FILTRULE_MERGE_FILE))
+       if (!(rule->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;
+               rule->rflags |= FILTRULE_ABS_PATH;
                if (*pat == '/')
                        pre_len = dirbuf_len - module_dirlen - 1;
                else
@@ -206,8 +199,8 @@ static void add_rule(filter_rule_list *listp, const char *pat,
 
        /* The daemon wants dir-exclude rules to get an appended "/" + "***". */
        if (xflags & XFLG_DIR2WILD3
-        && BITS_SETnUNSET(rflags, FILTRULE_DIRECTORY, FILTRULE_INCLUDE)) {
-               rflags &= ~FILTRULE_DIRECTORY;
+        && BITS_SETnUNSET(rule->rflags, FILTRULE_DIRECTORY, FILTRULE_INCLUDE)) {
+               rule->rflags &= ~FILTRULE_DIRECTORY;
                suf_len = sizeof SLASH_WILD3_SUFFIX - 1;
        } else
                suf_len = 0;
@@ -230,22 +223,22 @@ static void add_rule(filter_rule_list *listp, const char *pat,
        }
 
        if (strpbrk(rule->pattern, "*[?")) {
-               rflags |= FILTRULE_WILD;
+               rule->rflags |= FILTRULE_WILD;
                if ((cp = strstr(rule->pattern, "**")) != NULL) {
-                       rflags |= FILTRULE_WILD2;
+                       rule->rflags |= FILTRULE_WILD2;
                        /* If the pattern starts with **, note that. */
                        if (cp == rule->pattern)
-                               rflags |= FILTRULE_WILD2_PREFIX;
+                               rule->rflags |= FILTRULE_WILD2_PREFIX;
                        /* If the pattern ends with ***, note that. */
                        if (pat_len >= 3
                         && rule->pattern[pat_len-3] == '*'
                         && rule->pattern[pat_len-2] == '*'
                         && rule->pattern[pat_len-1] == '*')
-                               rflags |= FILTRULE_WILD3_SUFFIX;
+                               rule->rflags |= FILTRULE_WILD3_SUFFIX;
                }
        }
 
-       if (rflags & FILTRULE_PERDIR_MERGE) {
+       if (rule->rflags & FILTRULE_PERDIR_MERGE) {
                filter_rule_list *lp;
                unsigned int len;
                int i;
@@ -294,8 +287,6 @@ static void add_rule(filter_rule_list *listp, const char *pat,
        } else
                rule->u.slash_cnt = slash_cnt;
 
-       rule->rflags = rflags;
-
        if (!listp->tail) {
                rule->next = listp->head;
                listp->head = listp->tail = rule;
@@ -453,7 +444,7 @@ static BOOL setup_merge_file(int mergelist_num, filter_rule *ex,
                *y = '\0';
                dirbuf_len = y - dirbuf;
                strlcpy(x, ex->pattern, MAXPATHLEN - (x - buf));
-               parse_filter_file(lp, buf, ex->rflags, XFLG_ANCHORED2ABS);
+               parse_filter_file(lp, buf, ex, 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
@@ -537,7 +528,7 @@ void *push_local_filters(const char *dir, unsigned int dirlen)
 
                if (strlcpy(dirbuf + dirbuf_len, ex->pattern,
                    MAXPATHLEN - dirbuf_len) < MAXPATHLEN - dirbuf_len) {
-                       parse_filter_file(lp, dirbuf, ex->rflags,
+                       parse_filter_file(lp, dirbuf, ex,
                                          XFLG_ANCHORED2ABS);
                } else {
                        io_error |= IOERR_GENERAL;
@@ -777,31 +768,42 @@ static const uchar *rule_strcmp(const uchar *str, const char *rule, int rule_len
        return NULL;
 }
 
-/* Get the next include/exclude arg from the string.  The token will not
- * 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 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 rflags, int xflags,
-                                 unsigned int *len_ptr, uint32 *rflags_ptr)
+#define FILTRULES_FROM_CONTAINER (FILTRULE_ABS_PATH | FILTRULE_INCLUDE \
+                               | FILTRULE_DIRECTORY | FILTRULE_NEGATE \
+                               | FILTRULE_PERISHABLE)
+
+/* Gets the next include/exclude rule from *rulestr_ptr and advances
+ * *rulestr_ptr to point beyond it.  Stores the pattern's start (within
+ * *rulestr_ptr) and length in *pat_ptr and *pat_len_ptr, and returns a newly
+ * allocated filter_rule containing the rest of the information.  Returns
+ * NULL if there are no more rules in the input.
+ *
+ * The template provides defaults for the new rule to inherit, and the
+ * template rflags and the xflags additionally affect parsing. */
+static filter_rule *parse_rule_tok(const char **rulestr_ptr,
+                                  const filter_rule *template, int xflags,
+                                  const char **pat_ptr, unsigned int *pat_len_ptr)
 {
-       const uchar *s = (const uchar *)p;
-       uint32 new_rflags;
+       const uchar *s = (const uchar *)*rulestr_ptr;
+       filter_rule *rule;
        unsigned int len;
 
-       if (rflags & FILTRULE_WORD_SPLIT) {
+       if (template->rflags & FILTRULE_WORD_SPLIT) {
                /* Skip over any initial whitespace. */
                while (isspace(*s))
                        s++;
                /* Update to point to real start of rule. */
-               p = (const char *)s;
+               *rulestr_ptr = (const char *)s;
        }
        if (!*s)
                return NULL;
 
-       new_rflags = rflags & FILTRULES_FROM_CONTAINER;
+       if (!(rule = new0(filter_rule)))
+               out_of_memory("parse_rule_tok");
+
+       /* Inherit from the template.  Don't inherit FILTRULES_SIDES; we check
+        * that later. */
+       rule->rflags = template->rflags & FILTRULES_FROM_CONTAINER;
 
        /* Figure out what kind of a filter rule "s" is pointing at.  Note
         * that if FILTRULE_NO_PREFIXES is set, the rule is either an include
@@ -809,20 +811,21 @@ static const char *parse_rule_tok(const char *p, uint32 rflags, int xflags,
         * flag (above).  XFLG_OLD_PREFIXES indicates a compatibility mode
         * for old include/exclude patterns where just "+ " and "- " are
         * allowed as optional prefixes.  */
-       if (rflags & FILTRULE_NO_PREFIXES) {
-               if (*s == '!' && rflags & FILTRULE_CVS_IGNORE)
-                       new_rflags |= FILTRULE_CLEAR_LIST; /* Tentative! */
+       if (template->rflags & FILTRULE_NO_PREFIXES) {
+               if (*s == '!' && template->rflags & FILTRULE_CVS_IGNORE)
+                       rule->rflags |= FILTRULE_CLEAR_LIST; /* Tentative! */
        } else if (xflags & XFLG_OLD_PREFIXES) {
                if (*s == '-' && s[1] == ' ') {
-                       new_rflags &= ~FILTRULE_INCLUDE;
+                       rule->rflags &= ~FILTRULE_INCLUDE;
                        s += 2;
                } else if (*s == '+' && s[1] == ' ') {
-                       new_rflags |= FILTRULE_INCLUDE;
+                       rule->rflags |= FILTRULE_INCLUDE;
                        s += 2;
                } else if (*s == '!')
-                       new_rflags |= FILTRULE_CLEAR_LIST; /* Tentative! */
+                       rule->rflags |= FILTRULE_CLEAR_LIST; /* Tentative! */
        } else {
-               char ch = 0, *mods = "";
+               char ch = 0;
+               BOOL prefix_specifies_side = False;
                switch (*s) {
                case 'c':
                        if ((s = RULE_STRCMP(s, "clear")) != NULL)
@@ -868,104 +871,126 @@ static const char *parse_rule_tok(const char *p, uint32 rflags, int xflags,
                }
                switch (ch) {
                case ':':
-                       new_rflags |= FILTRULE_PERDIR_MERGE
-                                   | FILTRULE_FINISH_SETUP;
+                       rule->rflags |= FILTRULE_PERDIR_MERGE
+                                      | FILTRULE_FINISH_SETUP;
                        /* FALL THROUGH */
                case '.':
-                       new_rflags |= FILTRULE_MERGE_FILE;
-                       mods = MODIFIERS_INCL_EXCL MODIFIERS_MERGE_FILE;
+                       rule->rflags |= FILTRULE_MERGE_FILE;
                        break;
                case '+':
-                       new_rflags |= FILTRULE_INCLUDE;
-                       /* FALL THROUGH */
+                       rule->rflags |= FILTRULE_INCLUDE;
+                       break;
                case '-':
-                       mods = MODIFIERS_INCL_EXCL;
                        break;
                case 'S':
-                       new_rflags |= FILTRULE_INCLUDE;
+                       rule->rflags |= FILTRULE_INCLUDE;
                        /* FALL THROUGH */
                case 'H':
-                       new_rflags |= FILTRULE_SENDER_SIDE;
-                       mods = MODIFIERS_HIDE_PROTECT;
+                       rule->rflags |= FILTRULE_SENDER_SIDE;
+                       prefix_specifies_side = True;
                        break;
                case 'R':
-                       new_rflags |= FILTRULE_INCLUDE;
+                       rule->rflags |= FILTRULE_INCLUDE;
                        /* FALL THROUGH */
                case 'P':
-                       new_rflags |= FILTRULE_RECEIVER_SIDE;
-                       mods = MODIFIERS_HIDE_PROTECT;
+                       rule->rflags |= FILTRULE_RECEIVER_SIDE;
+                       prefix_specifies_side = True;
                        break;
                case '!':
-                       new_rflags |= FILTRULE_CLEAR_LIST;
-                       mods = NULL;
+                       rule->rflags |= FILTRULE_CLEAR_LIST;
                        break;
                default:
-                       rprintf(FERROR, "Unknown filter rule: `%s'\n", p);
+                       rprintf(FERROR, "Unknown filter rule: `%s'\n", *rulestr_ptr);
                        exit_cleanup(RERR_SYNTAX);
                }
-               while (mods && *++s && *s != ' ' && *s != '_') {
-                       if (strchr(mods, *s) == NULL) {
-                               if (rflags & FILTRULE_WORD_SPLIT && isspace(*s)) {
-                                       s--;
-                                       break;
-                               }
+               while (ch != '!' && *++s && *s != ' ' && *s != '_') {
+                       if (template->rflags & FILTRULE_WORD_SPLIT && isspace(*s)) {
+                               s--;
+                               break;
+                       }
+                       switch (*s) {
+                       default:
                            invalid:
                                rprintf(FERROR,
-                                       "invalid modifier sequence at '%c' in filter rule: %s\n",
-                                       *s, p);
+                                       "invalid modifier '%c' at position %d in filter rule: %s\n",
+                                       *s, s - (const uchar *)*rulestr_ptr, *rulestr_ptr);
                                exit_cleanup(RERR_SYNTAX);
-                       }
-                       switch (*s) {
                        case '-':
-                               if (new_rflags & FILTRULE_NO_PREFIXES)
-                                   goto invalid;
-                               new_rflags |= FILTRULE_NO_PREFIXES;
+                               if (!BITS_SETnUNSET(rule->rflags, FILTRULE_MERGE_FILE, FILTRULE_NO_PREFIXES))
+                                       goto invalid;
+                               rule->rflags |= FILTRULE_NO_PREFIXES;
                                break;
                        case '+':
-                               if (new_rflags & FILTRULE_NO_PREFIXES)
-                                   goto invalid;
-                               new_rflags |= FILTRULE_NO_PREFIXES
-                                           | FILTRULE_INCLUDE;
+                               if (!BITS_SETnUNSET(rule->rflags, FILTRULE_MERGE_FILE, FILTRULE_NO_PREFIXES))
+                                       goto invalid;
+                               rule->rflags |= FILTRULE_NO_PREFIXES
+                                             | FILTRULE_INCLUDE;
                                break;
                        case '/':
-                               new_rflags |= FILTRULE_ABS_PATH;
+                               rule->rflags |= FILTRULE_ABS_PATH;
                                break;
                        case '!':
-                               new_rflags |= FILTRULE_NEGATE;
+                               /* Negation really goes with the pattern, so it
+                                * isn't useful as a merge-file default. */
+                               if (rule->rflags & FILTRULE_MERGE_FILE)
+                                       goto invalid;
+                               rule->rflags |= FILTRULE_NEGATE;
                                break;
                        case 'C':
-                               if (new_rflags & FILTRULE_NO_PREFIXES)
-                                   goto invalid;
-                               new_rflags |= FILTRULE_NO_PREFIXES
-                                           | FILTRULE_WORD_SPLIT
-                                           | FILTRULE_NO_INHERIT
-                                           | FILTRULE_CVS_IGNORE;
+                               if (rule->rflags & FILTRULE_NO_PREFIXES || prefix_specifies_side)
+                                       goto invalid;
+                               rule->rflags |= FILTRULE_NO_PREFIXES
+                                             | FILTRULE_WORD_SPLIT
+                                             | FILTRULE_NO_INHERIT
+                                             | FILTRULE_CVS_IGNORE;
                                break;
                        case 'e':
-                               new_rflags |= FILTRULE_EXCLUDE_SELF;
+                               if (!(rule->rflags & FILTRULE_MERGE_FILE))
+                                       goto invalid;
+                               rule->rflags |= FILTRULE_EXCLUDE_SELF;
                                break;
                        case 'n':
-                               new_rflags |= FILTRULE_NO_INHERIT;
+                               if (!(rule->rflags & FILTRULE_MERGE_FILE))
+                                       goto invalid;
+                               rule->rflags |= FILTRULE_NO_INHERIT;
                                break;
                        case 'p':
-                               new_rflags |= FILTRULE_PERISHABLE;
+                               rule->rflags |= FILTRULE_PERISHABLE;
                                break;
                        case 'r':
-                               new_rflags |= FILTRULE_RECEIVER_SIDE;
+                               if (prefix_specifies_side)
+                                       goto invalid;
+                               rule->rflags |= FILTRULE_RECEIVER_SIDE;
                                break;
                        case 's':
-                               new_rflags |= FILTRULE_SENDER_SIDE;
+                               if (prefix_specifies_side)
+                                       goto invalid;
+                               rule->rflags |= FILTRULE_SENDER_SIDE;
                                break;
                        case 'w':
-                               new_rflags |= FILTRULE_WORD_SPLIT;
+                               if (!(rule->rflags & FILTRULE_MERGE_FILE))
+                                       goto invalid;
+                               rule->rflags |= FILTRULE_WORD_SPLIT;
                                break;
                        }
                }
                if (*s)
                        s++;
        }
+       if (template->rflags & FILTRULES_SIDES) {
+               if (rule->rflags & FILTRULES_SIDES) {
+                       /* The filter and template both specify side(s).  This
+                        * is dodgy (and won't work correctly if the template is
+                        * a one-sided per-dir merge rule), so reject it. */
+                       rprintf(FERROR,
+                               "specified-side merge file contains specified-side filter: %s\n",
+                               *rulestr_ptr);
+                       exit_cleanup(RERR_SYNTAX);
+               }
+               rule->rflags |= template->rflags & FILTRULES_SIDES;
+       }
 
-       if (rflags & FILTRULE_WORD_SPLIT) {
+       if (template->rflags & FILTRULE_WORD_SPLIT) {
                const uchar *cp = s;
                /* Token ends at whitespace or the end of the string. */
                while (!isspace(*cp) && *cp != '\0')
@@ -974,17 +999,17 @@ static const char *parse_rule_tok(const char *p, uint32 rflags, int xflags,
        } else
                len = strlen((char*)s);
 
-       if (new_rflags & FILTRULE_CLEAR_LIST) {
-               if (!(rflags & FILTRULE_NO_PREFIXES)
+       if (rule->rflags & FILTRULE_CLEAR_LIST) {
+               if (!(rule->rflags & FILTRULE_NO_PREFIXES)
                 && !(xflags & XFLG_OLD_PREFIXES) && len) {
                        rprintf(FERROR,
-                               "'!' rule has trailing characters: %s\n", p);
+                               "'!' rule has trailing characters: %s\n", *rulestr_ptr);
                        exit_cleanup(RERR_SYNTAX);
                }
                if (len > 1)
-                       new_rflags &= ~FILTRULE_CLEAR_LIST;
-       } else if (!len && !(new_rflags & FILTRULE_CVS_IGNORE)) {
-               rprintf(FERROR, "unexpected end of filter rule: %s\n", p);
+                       rule->rflags &= ~FILTRULE_CLEAR_LIST;
+       } else if (!len && !(rule->rflags & FILTRULE_CVS_IGNORE)) {
+               rprintf(FERROR, "unexpected end of filter rule: %s\n", *rulestr_ptr);
                exit_cleanup(RERR_SYNTAX);
        }
 
@@ -992,14 +1017,15 @@ static const char *parse_rule_tok(const char *p, uint32 rflags, int xflags,
         * 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;
-       *rflags_ptr = new_rflags;
-       return (const char *)s;
+        && !(rule->rflags & FILTRULES_SIDES)
+        && (!(rule->rflags & FILTRULE_PERDIR_MERGE)
+         || rule->rflags & FILTRULE_NO_PREFIXES))
+               rule->rflags |= FILTRULE_SENDER_SIDE;
+
+       *pat_ptr = (const char *)s;
+       *pat_len_ptr = len;
+       *rulestr_ptr = *pat_ptr + len;
+       return rule;
 }
 
 static char default_cvsignore[] =
@@ -1021,39 +1047,50 @@ static void get_cvs_excludes(uint32 rflags)
                return;
        initialized = 1;
 
-       parse_rule(&cvs_filter_list, default_cvsignore,
-                  rflags | (protocol_version >= 30 ? FILTRULE_PERISHABLE : 0),
-                  0);
+       parse_filter_str(&cvs_filter_list, default_cvsignore,
+                        rule_template(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, rflags, 0);
+               parse_filter_file(&cvs_filter_list, fname, rule_template(rflags), 0);
 
-       parse_rule(&cvs_filter_list, getenv("CVSIGNORE"), rflags, 0);
+       parse_filter_str(&cvs_filter_list, getenv("CVSIGNORE"), rule_template(rflags), 0);
 }
 
-void parse_rule(filter_rule_list *listp, const char *pattern, uint32 rflags, int xflags)
+const filter_rule *rule_template(uint32 rflags)
 {
+       static filter_rule template; /* zero-initialized */
+       template.rflags = rflags;
+       return &template;
+}
+
+void parse_filter_str(filter_rule_list *listp, const char *rulestr,
+                    const filter_rule *template, int xflags)
+{
+       filter_rule *rule;
        const char *pat;
        unsigned int pat_len;
-       uint32 new_rflags;
 
-       if (!pattern)
+       if (!rulestr)
                return;
 
        while (1) {
+               uint32 new_rflags;
+
                /* Remember that the returned string is NOT '\0' terminated! */
-               if (!(pat = parse_rule_tok(pattern, rflags, xflags, &pat_len, &new_rflags)))
+               if (!(rule = parse_rule_tok(&rulestr, template, xflags, &pat, &pat_len)))
                        break;
 
-               pattern = pat + pat_len;
-
                if (pat_len >= MAXPATHLEN) {
                        rprintf(FERROR, "discarding over-long filter: %.*s\n",
                                (int)pat_len, pat);
+                   free_continue:
+                       free_filter(rule);
                        continue;
                }
 
+               new_rflags = rule->rflags;
                if (new_rflags & FILTRULE_CLEAR_LIST) {
                        if (DEBUG_GTE(FILTER, 2)) {
                                rprintf(FINFO,
@@ -1061,41 +1098,46 @@ void parse_rule(filter_rule_list *listp, const char *pattern, uint32 rflags, int
                                        who_am_i(), listp->debug_type);
                        }
                        clear_filter_list(listp);
-                       continue;
+                       goto free_continue;
                }
 
                if (new_rflags & FILTRULE_MERGE_FILE) {
-                       unsigned int len;
                        if (!pat_len) {
                                pat = ".cvsignore";
                                pat_len = 10;
                        }
-                       len = pat_len;
                        if (new_rflags & FILTRULE_EXCLUDE_SELF) {
-                               const char *name = pat + len;
-                               while (name > pat && name[-1] != '/') name--;
-                               add_rule(listp, name, len - (name - pat), 0, 0);
-                               new_rflags &= ~FILTRULE_EXCLUDE_SELF;
+                               const char *name;
+                               filter_rule *excl_self;
+
+                               if (!(excl_self = new0(filter_rule)))
+                                       out_of_memory("parse_filter_str");
+                               /* Find the beginning of the basename and add an exclude for it. */
+                               for (name = pat + pat_len; name > pat && name[-1] != '/'; name--) {}
+                               add_rule(listp, name, (pat + pat_len) - name, excl_self, 0);
+                               rule->rflags &= ~FILTRULE_EXCLUDE_SELF;
                        }
                        if (new_rflags & FILTRULE_PERDIR_MERGE) {
                                if (parent_dirscan) {
                                        const char *p;
-                                       if (!(p = parse_merge_name(pat, &len, module_dirlen)))
-                                               continue;
-                                       add_rule(listp, p, len, new_rflags, 0);
+                                       unsigned int len = pat_len;
+                                       if ((p = parse_merge_name(pat, &len, module_dirlen)))
+                                               add_rule(listp, p, len, rule, 0);
+                                       else
+                                               free_filter(rule);
                                        continue;
                                }
                        } else {
                                const char *p;
-                               if (!(p = parse_merge_name(pat, &len, 0)))
-                                       continue;
-                               parse_filter_file(listp, p, new_rflags,
-                                                 XFLG_FATAL_ERRORS);
+                               unsigned int len = pat_len;
+                               if ((p = parse_merge_name(pat, &len, 0)))
+                                       parse_filter_file(listp, p, rule, XFLG_FATAL_ERRORS);
+                               free_filter(rule);
                                continue;
                        }
                }
 
-               add_rule(listp, pat, pat_len, new_rflags, xflags);
+               add_rule(listp, pat, pat_len, rule, xflags);
 
                if (new_rflags & FILTRULE_CVS_IGNORE
                    && !(new_rflags & FILTRULE_MERGE_FILE))
@@ -1103,13 +1145,12 @@ void parse_rule(filter_rule_list *listp, const char *pattern, uint32 rflags, int
        }
 }
 
-void parse_filter_file(filter_rule_list *listp, const char *fname,
-                      uint32 rflags, int xflags)
+void parse_filter_file(filter_rule_list *listp, const char *fname, const filter_rule *template, int xflags)
 {
        FILE *fp;
        char line[BIGPATHBUFLEN];
        char *eob = line + sizeof line - 1;
-       int word_split = rflags & FILTRULE_WORD_SPLIT;
+       BOOL word_split = (template->rflags & FILTRULE_WORD_SPLIT) != 0;
 
        if (!fname || !*fname)
                return;
@@ -1129,7 +1170,7 @@ void parse_filter_file(filter_rule_list *listp, const char *fname,
 
        if (DEBUG_GTE(FILTER, 2)) {
                rprintf(FINFO, "[%s] parse_filter_file(%s,%x,%x)%s\n",
-                       who_am_i(), fname, rflags, xflags,
+                       who_am_i(), fname, template->rflags, xflags,
                        fp ? "" : " [not found]");
        }
 
@@ -1137,7 +1178,7 @@ void parse_filter_file(filter_rule_list *listp, const char *fname,
                if (xflags & XFLG_FATAL_ERRORS) {
                        rsyserr(FERROR, errno,
                                "failed to open %sclude file %s",
-                               rflags & FILTRULE_INCLUDE ? "in" : "ex",
+                               template->rflags & FILTRULE_INCLUDE ? "in" : "ex",
                                fname);
                        exit_cleanup(RERR_FILEIO);
                }
@@ -1172,7 +1213,7 @@ void parse_filter_file(filter_rule_list *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, rflags, xflags);
+                       parse_filter_str(listp, line, template, xflags);
                if (ch == EOF)
                        break;
        }
@@ -1182,18 +1223,18 @@ void parse_filter_file(filter_rule_list *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 rflags, const char *pat, int for_xfer,
+char *get_rule_prefix(filter_rule *rule, 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 (rflags & FILTRULE_PERDIR_MERGE) {
+       if (rule->rflags & FILTRULE_PERDIR_MERGE) {
                if (legal_len == 1)
                        return NULL;
                *op++ = ':';
-       } else if (rflags & FILTRULE_INCLUDE)
+       } else if (rule->rflags & FILTRULE_INCLUDE)
                *op++ = '+';
        else if (legal_len != 1
            || ((*pat == '-' || *pat == '+') && pat[1] == ' '))
@@ -1201,32 +1242,34 @@ char *get_rule_prefix(int rflags, const char *pat, int for_xfer,
        else
                legal_len = 0;
 
-       if (rflags & FILTRULE_NEGATE)
+       if (rule->rflags & FILTRULE_ABS_PATH)
+               *op++ = '/';
+       if (rule->rflags & FILTRULE_NEGATE)
                *op++ = '!';
-       if (rflags & FILTRULE_CVS_IGNORE)
+       if (rule->rflags & FILTRULE_CVS_IGNORE)
                *op++ = 'C';
        else {
-               if (rflags & FILTRULE_NO_INHERIT)
+               if (rule->rflags & FILTRULE_NO_INHERIT)
                        *op++ = 'n';
-               if (rflags & FILTRULE_WORD_SPLIT)
+               if (rule->rflags & FILTRULE_WORD_SPLIT)
                        *op++ = 'w';
-               if (rflags & FILTRULE_NO_PREFIXES) {
-                       if (rflags & FILTRULE_INCLUDE)
+               if (rule->rflags & FILTRULE_NO_PREFIXES) {
+                       if (rule->rflags & FILTRULE_INCLUDE)
                                *op++ = '+';
                        else
                                *op++ = '-';
                }
        }
-       if (rflags & FILTRULE_EXCLUDE_SELF)
+       if (rule->rflags & FILTRULE_EXCLUDE_SELF)
                *op++ = 'e';
-       if (rflags & FILTRULE_SENDER_SIDE
+       if (rule->rflags & FILTRULE_SENDER_SIDE
            && (!for_xfer || protocol_version >= 29))
                *op++ = 's';
-       if (rflags & FILTRULE_RECEIVER_SIDE
+       if (rule->rflags & FILTRULE_RECEIVER_SIDE
            && (!for_xfer || protocol_version >= 29
             || (delete_excluded && am_sender)))
                *op++ = 'r';
-       if (rflags & FILTRULE_PERISHABLE) {
+       if (rule->rflags & FILTRULE_PERISHABLE) {
                if (!for_xfer || protocol_version >= 30)
                        *op++ = 'p';
                else if (am_sender)
@@ -1282,7 +1325,7 @@ static void send_rules(int f_out, filter_rule_list *flp)
                        if (f == f_out)
                                continue;
                }
-               p = get_rule_prefix(ent->rflags, ent->pattern, 1, &plen);
+               p = get_rule_prefix(ent, ent->pattern, 1, &plen);
                if (!p) {
                        rprintf(FERROR,
                                "filter rules are too modern for remote rsync.\n");
@@ -1314,8 +1357,8 @@ void send_filter_list(int f_out)
                f_out = -1;
        if (cvs_exclude && am_sender) {
                if (protocol_version >= 29)
-                       parse_rule(&filter_list, ":C", 0, 0);
-               parse_rule(&filter_list, "-C", 0, 0);
+                       parse_filter_str(&filter_list, ":C", rule_template(0), 0);
+               parse_filter_str(&filter_list, "-C", rule_template(0), 0);
        }
 
        send_rules(f_out, &filter_list);
@@ -1325,9 +1368,9 @@ void send_filter_list(int f_out)
 
        if (cvs_exclude) {
                if (!am_sender || protocol_version < 29)
-                       parse_rule(&filter_list, ":C", 0, 0);
+                       parse_filter_str(&filter_list, ":C", rule_template(0), 0);
                if (!am_sender)
-                       parse_rule(&filter_list, "-C", 0, 0);
+                       parse_filter_str(&filter_list, "-C", rule_template(0), 0);
        }
 }
 
@@ -1346,15 +1389,15 @@ void recv_filter_list(int f_in)
                        if (len >= sizeof line)
                                overflow_exit("recv_rules");
                        read_sbuf(f_in, line, len);
-                       parse_rule(&filter_list, line, 0, xflags);
+                       parse_filter_str(&filter_list, line, rule_template(0), xflags);
                }
        }
 
        if (cvs_exclude) {
                if (local_server || am_sender || protocol_version < 29)
-                       parse_rule(&filter_list, ":C", 0, 0);
+                       parse_filter_str(&filter_list, ":C", rule_template(0), 0);
                if (local_server || am_sender)
-                       parse_rule(&filter_list, "-C", 0, 0);
+                       parse_filter_str(&filter_list, "-C", rule_template(0), 0);
        }
 
        if (local_server) /* filter out any rules that aren't for us. */
index bd6a495..53640ad 100644 (file)
--- a/options.c
+++ b/options.c
@@ -1399,17 +1399,18 @@ int parse_arguments(int *argc_p, const char ***argv_p)
                        break;
 
                case OPT_FILTER:
-                       parse_rule(&filter_list, poptGetOptArg(pc), 0, 0);
+                       parse_filter_str(&filter_list, poptGetOptArg(pc),
+                                       rule_template(0), 0);
                        break;
 
                case OPT_EXCLUDE:
-                       parse_rule(&filter_list, poptGetOptArg(pc),
-                                  0, XFLG_OLD_PREFIXES);
+                       parse_filter_str(&filter_list, poptGetOptArg(pc),
+                                       rule_template(0), XFLG_OLD_PREFIXES);
                        break;
 
                case OPT_INCLUDE:
-                       parse_rule(&filter_list, poptGetOptArg(pc),
-                                  FILTRULE_INCLUDE, XFLG_OLD_PREFIXES);
+                       parse_filter_str(&filter_list, poptGetOptArg(pc),
+                                       rule_template(FILTRULE_INCLUDE), XFLG_OLD_PREFIXES);
                        break;
 
                case OPT_EXCLUDE_FROM:
@@ -1432,7 +1433,7 @@ int parse_arguments(int *argc_p, const char ***argv_p)
                                        goto options_rejected;
                        }
                        parse_filter_file(&filter_list, arg,
-                               opt == OPT_INCLUDE_FROM ? FILTRULE_INCLUDE : 0,
+                               rule_template(opt == OPT_INCLUDE_FROM ? FILTRULE_INCLUDE : 0),
                                XFLG_FATAL_ERRORS | XFLG_OLD_PREFIXES);
                        break;
 
@@ -1489,10 +1490,10 @@ int parse_arguments(int *argc_p, const char ***argv_p)
                case 'F':
                        switch (++F_option_cnt) {
                        case 1:
-                               parse_rule(&filter_list,": /.rsync-filter",0,0);
+                               parse_filter_str(&filter_list,": /.rsync-filter",rule_template(0),0);
                                break;
                        case 2:
-                               parse_rule(&filter_list,"- .rsync-filter",0,0);
+                               parse_filter_str(&filter_list,"- .rsync-filter",rule_template(0),0);
                                break;
                        }
                        break;
@@ -1895,7 +1896,7 @@ int parse_arguments(int *argc_p, const char ***argv_p)
                list_only |= 1;
 
        if (xfer_dirs >= 4) {
-               parse_rule(&filter_list, "- /*/*", 0, 0);
+               parse_filter_str(&filter_list, "- /*/*", rule_template(0), 0);
                recurse = xfer_dirs = 1;
        } else if (recurse)
                xfer_dirs = 1;
@@ -2033,7 +2034,7 @@ int parse_arguments(int *argc_p, const char ***argv_p)
        } else if (make_backups && delete_mode && !delete_excluded && !am_server) {
                snprintf(backup_dir_buf, sizeof backup_dir_buf,
                        "P *%s", backup_suffix);
-               parse_rule(&filter_list, backup_dir_buf, 0, 0);
+               parse_filter_str(&filter_list, backup_dir_buf, rule_template(0), 0);
        }
 
        if (make_backups && !backup_dir) {
diff --git a/rsync.h b/rsync.h
index 0efccfa..4a15c24 100644 (file)
--- a/rsync.h
+++ b/rsync.h
@@ -819,10 +819,7 @@ struct map_struct {
 #define FILTRULE_CLEAR_LIST    (1<<18)/* this item is the "!" token */
 #define FILTRULE_PERISHABLE    (1<<19)/* perishable if parent dir goes away */
 
-#define FILTRULES_FROM_CONTAINER (FILTRULE_ABS_PATH | FILTRULE_INCLUDE \
-                               | FILTRULE_DIRECTORY | FILTRULE_SENDER_SIDE \
-                               | FILTRULE_NEGATE | FILTRULE_RECEIVER_SIDE \
-                               | FILTRULE_PERISHABLE)
+#define FILTRULES_SIDES (FILTRULE_SENDER_SIDE | FILTRULE_RECEIVER_SIDE)
 
 typedef struct filter_struct {
        struct filter_struct *next;
index 0e93d4c..335f76d 100644 (file)
--- a/rsync.yo
+++ b/rsync.yo
@@ -2671,10 +2671,14 @@ itemization(
   also disabled).
   it() You may also specify any of the modifiers for the "+" or "-" rules
   (above) in order to have the rules that are read in from the file
-  default to having that modifier set.  For instance, "merge,-/ .excl" would
+  default to having that modifier set (except for the bf(!) modifier, which
+  would not be useful).  For instance, "merge,-/ .excl" would
   treat the contents of .excl as absolute-path excludes,
   while "dir-merge,s .filt" and ":sC" would each make all their
-  per-directory rules apply only on the sending side.
+  per-directory rules apply only on the sending side.  If the merge rule
+  specifies sides to affect (via the bf(s) or bf(r) modifier or both),
+  then the rules in the file must not specify sides (via a modifier or
+  a rule prefix such as bf(hide)).
 )
 
 Per-directory rules are inherited in all subdirectories of the directory