Got rid of RERR_LOG_FAILURE.
[rsync.git] / exclude.c
index 9b96f533d26fe1023136fef9f651e70257f1015b..5536d8a94856cdb2c5b2b669168dce9a4c33488d 100644 (file)
--- a/exclude.c
+++ b/exclude.c
 
 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 delete_mode;
+extern int delete_excluded;
+extern int cvs_exclude;
 extern int sanitize_paths;
 extern int protocol_version;
 extern int module_id;
@@ -41,11 +46,16 @@ extern unsigned int curr_dir_len;
 extern unsigned int module_dirlen;
 
 struct filter_list_struct filter_list = { 0, 0, "" };
-struct filter_list_struct server_filter_list = { 0, 0, "server " };
+struct filter_list_struct cvs_filter_list = { 0, 0, " [cvsignore]" };
+struct filter_list_struct server_filter_list = { 0, 0, " [server]" };
 
 /* 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 "/!"
+
 /* 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
  * trailing slash appended, and the variable dirbuf_len contains the length
@@ -108,24 +118,36 @@ static void free_filter(struct filter_struct *ex)
 
 /* Build a filter structure given a filter pattern.  The value in "pat"
  * is not null-terminated. */
-static void filter_rule(struct filter_list_struct *listp, const char *pat,
-                       unsigned int pat_len, unsigned int mflags, int xflags)
+static void add_rule(struct filter_list_struct *listp, const char *pat,
+                    unsigned int pat_len, uint32 mflags, int xflags)
 {
        struct filter_struct *ret;
        const char *cp;
        unsigned int ex_len;
 
        if (verbose > 2) {
-               rprintf(FINFO, "[%s] filter_rule(%.*s, %s%s)\n",
-                       who_am_i(), (int)pat_len, pat,
-                       mflags & MATCHFLG_PERDIR_MERGE ? "per-dir-merge"
-                       : mflags & MATCHFLG_INCLUDE ? "include" : "exclude",
+               rprintf(FINFO, "[%s] add_rule(%s%.*s%s)%s\n",
+                       who_am_i(), get_rule_prefix(mflags, pat, 0, NULL),
+                       (int)pat_len, pat,
+                       (mflags & MATCHFLG_DIRECTORY) ? "/" : "",
                        listp->debug_type);
        }
 
-       ret = new(struct filter_struct);
-       if (!ret)
-               out_of_memory("filter_rule");
+       /* This flag also indicates 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 (am_sender) {
+                       if (mf == MATCHFLG_RECEIVER_SIDE)
+                               return;
+               } else {
+                       if (mf == MATCHFLG_SENDER_SIDE)
+                               return;
+               }
+       }
+
+       if (!(ret = new(struct filter_struct)))
+               out_of_memory("add_rule");
        memset(ret, 0, sizeof ret[0]);
 
        if (xflags & XFLG_ANCHORED2ABS && *pat == '/'
@@ -134,9 +156,8 @@ static void filter_rule(struct filter_list_struct *listp, const char *pat,
                ex_len = dirbuf_len - module_dirlen - 1;
        } else
                ex_len = 0;
-       ret->pattern = new_array(char, ex_len + pat_len + 1);
-       if (!ret->pattern)
-               out_of_memory("filter_rule");
+       if (!(ret->pattern = new_array(char, ex_len + pat_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);
@@ -185,10 +206,10 @@ static void filter_rule(struct filter_list_struct *listp, const char *pat,
                }
 
                if (!(lp = new_array(struct filter_list_struct, 1)))
-                       out_of_memory("filter_rule");
+                       out_of_memory("add_rule");
                lp->head = lp->tail = NULL;
-               if (asprintf(&lp->debug_type, " (per-dir %s)", cp) < 0)
-                       out_of_memory("filter_rule");
+               if (asprintf(&lp->debug_type, " [per-dir %s]", cp) < 0)
+                       out_of_memory("add_rule");
                ret->u.mergelist = lp;
 
                if (mergelist_cnt == mergelist_size) {
@@ -197,7 +218,7 @@ static void filter_rule(struct filter_list_struct *listp, const char *pat,
                                                struct filter_struct *,
                                                mergelist_size);
                        if (!mergelist_parents)
-                               out_of_memory("filter_rule");
+                               out_of_memory("add_rule");
                }
                mergelist_parents[mergelist_cnt++] = ret;
        } else {
@@ -271,7 +292,7 @@ static char *parse_merge_name(const char *merge_file, unsigned int *len_ptr,
                }
                if (!sanitize_path(fn, merge_file, r, dirbuf_depth)) {
                        rprintf(FERROR, "merge-file name overflows: %s\n",
-                               merge_file);
+                               safe_fname(merge_file));
                        return NULL;
                }
        } else {
@@ -284,7 +305,8 @@ static char *parse_merge_name(const char *merge_file, unsigned int *len_ptr,
                goto done;
 
        if (dirbuf_len + fn_len >= MAXPATHLEN) {
-               rprintf(FERROR, "merge-file name overflows: %s\n", fn);
+               rprintf(FERROR, "merge-file name overflows: %s\n",
+                       safe_fname(fn));
                return NULL;
        }
        memcpy(buf, dirbuf + prefix_skip, dirbuf_len - prefix_skip);
@@ -328,7 +350,7 @@ void set_filter_dir(const char *dir, unsigned int dirlen)
  * 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, int flags)
+                            struct filter_list_struct *lp)
 {
        char buf[MAXPATHLEN];
        char *x, *y, *pat = ex->pattern;
@@ -364,7 +386,7 @@ static BOOL setup_merge_file(struct filter_struct *ex,
                *y = '\0';
                dirbuf_len = y - dirbuf;
                strlcpy(x, ex->pattern, MAXPATHLEN - (x - buf));
-               add_filter_file(lp, buf, flags | XFLG_ANCHORED2ABS);
+               parse_filter_file(lp, buf, ex->match_flags, XFLG_ANCHORED2ABS);
                if (ex->match_flags & MATCHFLG_NO_INHERIT)
                        lp->head = NULL;
                lp->tail = NULL;
@@ -399,12 +421,11 @@ void *push_local_filters(const char *dir, unsigned int dirlen)
                       sizeof (struct filter_list_struct));
        }
 
-       /* Note: add_filter_file() might increase mergelist_cnt, so keep
+       /* 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;
-               int flags = 0;
 
                if (verbose > 2) {
                        rprintf(FINFO, "[%s] pushing filter list%s\n",
@@ -414,25 +435,18 @@ void *push_local_filters(const char *dir, unsigned int dirlen)
                lp->tail = NULL; /* Switch any local rules to inherited. */
                if (ex->match_flags & MATCHFLG_NO_INHERIT)
                        lp->head = NULL;
-               if (ex->match_flags & MATCHFLG_WORD_SPLIT)
-                       flags |= XFLG_WORD_SPLIT;
-               if (ex->match_flags & MATCHFLG_NO_PREFIXES)
-                       flags |= XFLG_NO_PREFIXES;
-               if (ex->match_flags & MATCHFLG_INCLUDE)
-                       flags |= XFLG_DEF_INCLUDE;
-               else if (ex->match_flags & MATCHFLG_NO_PREFIXES)
-                       flags |= XFLG_DEF_EXCLUDE;
 
                if (ex->match_flags & MATCHFLG_FINISH_SETUP) {
                        ex->match_flags &= ~MATCHFLG_FINISH_SETUP;
-                       if (setup_merge_file(ex, lp, flags))
+                       if (setup_merge_file(ex, lp))
                                set_filter_dir(dir, dirlen);
                }
 
                if (strlcpy(dirbuf + dirbuf_len, ex->pattern,
-                   MAXPATHLEN - dirbuf_len) < MAXPATHLEN - dirbuf_len)
-                       add_filter_file(lp, dirbuf, flags | XFLG_ANCHORED2ABS);
-               else {
+                   MAXPATHLEN - dirbuf_len) < MAXPATHLEN - dirbuf_len) {
+                       parse_filter_file(lp, dirbuf, ex->match_flags,
+                                         XFLG_ANCHORED2ABS);
+               } else {
                        io_error |= IOERR_GENERAL;
                        rprintf(FINFO,
                            "cannot add local filter rules in long-named directory: %s\n",
@@ -476,6 +490,7 @@ static int rule_matches(char *name, struct filter_struct *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;
 
        if (!*name)
@@ -496,7 +511,7 @@ static int rule_matches(char *name, struct filter_struct *ex, int name_is_dir)
        }
 
        if (ex->match_flags & MATCHFLG_DIRECTORY && !name_is_dir)
-               return 0;
+               return !ret_match;
 
        if (*pattern == '/') {
                match_start = 1;
@@ -518,13 +533,13 @@ static int rule_matches(char *name, struct filter_struct *ex, int name_is_dir)
                        name = p+1;
                }
                if (wildmatch(pattern, name))
-                       return 1;
+                       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 1;
+                               return ret_match;
                }
                else if (!match_start && ex->match_flags & MATCHFLG_WILD2) {
                        /* A non-anchored match with an infix or trailing "**"
@@ -533,23 +548,23 @@ static int rule_matches(char *name, struct filter_struct *ex, int name_is_dir)
                        while ((name = strchr(name, '/')) != NULL) {
                                name++;
                                if (wildmatch(pattern, name))
-                                       return 1;
+                                       return ret_match;
                        }
                }
        } else if (match_start) {
                if (strcmp(name,pattern) == 0)
-                       return 1;
+                       return ret_match;
        } else {
                int l1 = strlen(name);
                int l2 = strlen(pattern);
                if (l2 <= l1 &&
                    strcmp(name+(l1-l2),pattern) == 0 &&
                    (l1==l2 || name[l1-(l2+1)] == '/')) {
-                       return 1;
+                       return ret_match;
                }
        }
 
-       return 0;
+       return !ret_match;
 }
 
 
@@ -558,7 +573,7 @@ static void report_filter_result(char const *name,
                                  int name_is_dir, const char *type)
 {
        /* If a trailing slash is present to match only directories,
-        * then it is stripped out by filter_rule.  So as a special
+        * then it is stripped out by add_rule().  So as a special
         * case we add it back in here. */
 
        if (verbose >= 2) {
@@ -587,6 +602,13 @@ int check_filter(struct filter_list_struct *listp, char *name, int name_is_dir)
                                return rc;
                        continue;
                }
+               if (ent->match_flags & MATCHFLG_CVS_IGNORE) {
+                       int rc = check_filter(&cvs_filter_list, 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);
@@ -597,23 +619,34 @@ int check_filter(struct filter_list_struct *listp, char *name, int name_is_dir)
        return 0;
 }
 
+#define RULE_STRCMP(s,r) rule_strcmp((s), (r), sizeof (r) - 1)
+
+static const uchar *rule_strcmp(const uchar *str, const char *rule, int rule_len)
+{
+       if (strncmp((char*)str, rule, rule_len) != 0)
+               return NULL;
+       if (isspace(str[rule_len]) || str[rule_len] == '_' || !str[rule_len])
+               return str + rule_len - 1;
+       if (str[rule_len] == ',')
+               return str + 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 (if xflags does NOT contain XFLG_NO_PREFIXES) the
- * +/- prefixes for overriding the include/exclude mode.  The *flag_ptr
- * value will also be set to the MATCHFLG_* bits for the current token.
- */
-static const char *get_filter_tok(const char *p, int xflags,
-                               unsigned int *len_ptr, unsigned int *flag_ptr)
+ * clearing) token and (depending on the mflags) the various prefixes.
+ * The *mflags_ptr value will be set on exit to the new MATCHFLG_* 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)
 {
-       const unsigned char *s = (const unsigned char *)p;
-       unsigned int len, mflags = 0;
-       int empty_pat_is_OK = 0;
+       const uchar *s = (const uchar *)p;
+       uint32 new_mflags;
+       unsigned int len;
 
-       if (xflags & XFLG_WORD_SPLIT) {
+       if (mflags & MATCHFLG_WORD_SPLIT) {
                /* Skip over any initial whitespace. */
                while (isspace(*s))
                        s++;
@@ -623,90 +656,170 @@ static const char *get_filter_tok(const char *p, int xflags,
        if (!*s)
                return NULL;
 
-       /* Figure out what kind of a filter rule "s" is pointing at. */
-       if (!(xflags & (XFLG_DEF_INCLUDE | XFLG_DEF_EXCLUDE))) {
-               char *mods = "";
+       new_mflags = mflags & MATCHFLGS_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
+        * 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! */
+       } else if (xflags & XFLG_OLD_PREFIXES) {
+               if (*s == '-' && s[1] == ' ') {
+                       new_mflags &= ~MATCHFLG_INCLUDE;
+                       s += 2;
+               } else if (*s == '+' && s[1] == ' ') {
+                       new_mflags |= MATCHFLG_INCLUDE;
+                       s += 2;
+               } else if (*s == '!')
+                       new_mflags |= MATCHFLG_CLEAR_LIST; /* Tentative! */
+       } else {
+               char ch = 0, *mods = "";
                switch (*s) {
+               case 'c':
+                       if ((s = RULE_STRCMP(s, "clear")) != NULL)
+                               ch = '!';
+                       break;
+               case 'd':
+                       if ((s = RULE_STRCMP(s, "dir-merge")) != NULL)
+                               ch = ':';
+                       break;
+               case 'e':
+                       if ((s = RULE_STRCMP(s, "exclude")) != NULL)
+                               ch = '-';
+                       break;
+               case 'h':
+                       if ((s = RULE_STRCMP(s, "hide")) != NULL)
+                               ch = 'H';
+                       break;
+               case 'i':
+                       if ((s = RULE_STRCMP(s, "include")) != NULL)
+                               ch = '+';
+                       break;
+               case 'm':
+                       if ((s = RULE_STRCMP(s, "merge")) != NULL)
+                               ch = '.';
+                       break;
+               case 'p':
+                       if ((s = RULE_STRCMP(s, "protect")) != NULL)
+                               ch = 'P';
+                       break;
+               case 'r':
+                       if ((s = RULE_STRCMP(s, "risk")) != NULL)
+                               ch = 'R';
+                       break;
+               case 's':
+                       if ((s = RULE_STRCMP(s, "show")) != NULL)
+                               ch = 'S';
+                       break;
+
+               default:
+                       ch = *s;
+                       if (s[1] == ',')
+                               s++;
+                       break;
+               }
+               switch (ch) {
                case ':':
-                       mflags |= MATCHFLG_PERDIR_MERGE
-                               | MATCHFLG_FINISH_SETUP;
+                       new_mflags |= MATCHFLG_PERDIR_MERGE
+                                   | MATCHFLG_FINISH_SETUP;
                        /* FALL THROUGH */
                case '.':
-                       mflags |= MATCHFLG_MERGE_FILE;
-                       mods = "-+Cens";
+                       new_mflags |= MATCHFLG_MERGE_FILE;
+                       mods = MODIFIERS_INCL_EXCL MODIFIERS_MERGE_FILE;
                        break;
                case '+':
-                       mflags |= MATCHFLG_INCLUDE;
+                       new_mflags |= MATCHFLG_INCLUDE;
                        /* FALL THROUGH */
                case '-':
-                       mods = "/";
+                       mods = MODIFIERS_INCL_EXCL;
+                       break;
+               case 'S':
+                       new_mflags |= MATCHFLG_INCLUDE;
+                       /* FALL THROUGH */
+               case 'H':
+                       new_mflags |= MATCHFLG_SENDER_SIDE;
+                       mods = MODIFIERS_HIDE_PROTECT;
+                       break;
+               case 'R':
+                       new_mflags |= MATCHFLG_INCLUDE;
+                       /* FALL THROUGH */
+               case 'P':
+                       new_mflags |= MATCHFLG_RECEIVER_SIDE;
+                       mods = MODIFIERS_HIDE_PROTECT;
                        break;
                case '!':
-                       mflags |= MATCHFLG_CLEAR_LIST;
+                       new_mflags |= MATCHFLG_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 (xflags & XFLG_WORD_SPLIT && isspace(*s)) {
+                               if (mflags & MATCHFLG_WORD_SPLIT && isspace(*s)) {
                                        s--;
                                        break;
                                }
+                           invalid:
                                rprintf(FERROR,
-                                       "unknown modifier '%c' in filter rule: %s\n",
+                                       "invalid modifier sequence at '%c' in filter rule: %s\n",
                                        *s, p);
                                exit_cleanup(RERR_SYNTAX);
                        }
                        switch (*s) {
                        case '-':
-                               mflags |= MATCHFLG_NO_PREFIXES;
+                               if (new_mflags & MATCHFLG_NO_PREFIXES)
+                                   goto invalid;
+                               new_mflags |= MATCHFLG_NO_PREFIXES;
                                break;
                        case '+':
-                               mflags |= MATCHFLG_NO_PREFIXES
-                                       | MATCHFLG_INCLUDE;
+                               if (new_mflags & MATCHFLG_NO_PREFIXES)
+                                   goto invalid;
+                               new_mflags |= MATCHFLG_NO_PREFIXES
+                                           | MATCHFLG_INCLUDE;
                                break;
                        case '/':
-                               mflags |= MATCHFLG_ABS_PATH;
+                               new_mflags |= MATCHFLG_ABS_PATH;
+                               break;
+                       case '!':
+                               new_mflags |= MATCHFLG_NEGATE;
                                break;
                        case 'C':
-                               empty_pat_is_OK = 1;
-                               mflags |= MATCHFLG_NO_PREFIXES
-                                       | MATCHFLG_WORD_SPLIT
-                                       | MATCHFLG_NO_INHERIT;
+                               if (new_mflags & MATCHFLG_NO_PREFIXES)
+                                   goto invalid;
+                               new_mflags |= MATCHFLG_NO_PREFIXES
+                                           | MATCHFLG_WORD_SPLIT
+                                           | MATCHFLG_NO_INHERIT
+                                           | MATCHFLG_CVS_IGNORE;
                                break;
                        case 'e':
-                               mflags |= MATCHFLG_EXCLUDE_SELF;
+                               new_mflags |= MATCHFLG_EXCLUDE_SELF;
                                break;
                        case 'n':
-                               mflags |= MATCHFLG_NO_INHERIT;
+                               new_mflags |= MATCHFLG_NO_INHERIT;
+                               break;
+                       case 'r':
+                               new_mflags |= MATCHFLG_RECEIVER_SIDE;
+                               break;
+                       case 's':
+                               new_mflags |= MATCHFLG_SENDER_SIDE;
                                break;
                        case 'w':
-                               mflags |= MATCHFLG_WORD_SPLIT;
+                               new_mflags |= MATCHFLG_WORD_SPLIT;
                                break;
                        }
                }
                if (*s)
                        s++;
-       } else if (!(xflags & XFLG_NO_PREFIXES)
-           && (*s == '-' || *s == '+') && s[1] == ' ') {
-               if (*s == '+')
-                       mflags |= MATCHFLG_INCLUDE;
-               s += 2;
-       } else {
-               if (xflags & XFLG_DEF_INCLUDE)
-                       mflags |= MATCHFLG_INCLUDE;
-               if (*s == '!')
-                       mflags |= MATCHFLG_CLEAR_LIST; /* Tentative! */
        }
 
-       if (xflags & XFLG_DIRECTORY)
-               mflags |= MATCHFLG_DIRECTORY;
-
-       if (xflags & XFLG_WORD_SPLIT) {
-               const unsigned char *cp = s;
+       if (mflags & MATCHFLG_WORD_SPLIT) {
+               const uchar *cp = s;
                /* Token ends at whitespace or the end of the string. */
                while (!isspace(*cp) && *cp != '\0')
                        cp++;
@@ -714,29 +827,59 @@ static const char *get_filter_tok(const char *p, int xflags,
        } else
                len = strlen((char*)s);
 
-       if (mflags & MATCHFLG_CLEAR_LIST) {
-               if (!(xflags & (XFLG_DEF_INCLUDE | XFLG_DEF_EXCLUDE)) && len) {
+       if (new_mflags & MATCHFLG_CLEAR_LIST) {
+               if (!(xflags & XFLG_OLD_PREFIXES) && len) {
                        rprintf(FERROR,
                                "'!' rule has trailing characters: %s\n", p);
                        exit_cleanup(RERR_SYNTAX);
                }
                if (len > 1)
-                       mflags &= ~MATCHFLG_CLEAR_LIST;
-       } else if (!len && !empty_pat_is_OK) {
+                       new_mflags &= ~MATCHFLG_CLEAR_LIST;
+       } else if (!len && !(new_mflags & MATCHFLG_CVS_IGNORE)) {
                rprintf(FERROR, "unexpected end of filter rule: %s\n", p);
                exit_cleanup(RERR_SYNTAX);
        }
 
        *len_ptr = len;
-       *flag_ptr = mflags;
+       *mflags_ptr = new_mflags;
        return (const char *)s;
 }
 
 
-void add_filter(struct filter_list_struct *listp, const char *pattern,
-               int xflags)
+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 *~ #* .#* ,* _$* *$"
+       " *.old *.bak *.BAK *.orig *.rej .del-*"
+       " *.a *.olb *.o *.obj *.so *.exe"
+       " *.Z *.elc *.ln core"
+       /* The rest we added to suit ourself. */
+       " .svn/";
+
+static void get_cvs_excludes(uint32 mflags)
+{
+       char *p, fname[MAXPATHLEN];
+       static int initialized = 0;
+
+       if (initialized)
+               return;
+       initialized = 1;
+
+       parse_rule(&cvs_filter_list, default_cvsignore, mflags, 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_rule(&cvs_filter_list, getenv("CVSIGNORE"), mflags, 0);
+}
+
+
+void parse_rule(struct filter_list_struct *listp, const char *pattern,
+               uint32 mflags, int xflags)
 {
-       unsigned int pat_len, mflags;
+       unsigned int pat_len;
+       uint32 new_mflags;
        const char *cp, *p;
 
        if (!pattern)
@@ -744,7 +887,8 @@ void add_filter(struct filter_list_struct *listp, const char *pattern,
 
        while (1) {
                /* Remember that the returned string is NOT '\0' terminated! */
-               cp = get_filter_tok(pattern, xflags, &pat_len, &mflags);
+               cp = parse_rule_tok(pattern, mflags, xflags,
+                                   &pat_len, &new_mflags);
                if (!cp)
                        break;
                if (pat_len >= MAXPATHLEN) {
@@ -754,7 +898,7 @@ void add_filter(struct filter_list_struct *listp, const char *pattern,
                }
                pattern = cp + pat_len;
 
-               if (mflags & MATCHFLG_CLEAR_LIST) {
+               if (new_mflags & MATCHFLG_CLEAR_LIST) {
                        if (verbose > 2) {
                                rprintf(FINFO,
                                        "[%s] clearing filter list%s\n",
@@ -764,55 +908,56 @@ void add_filter(struct filter_list_struct *listp, const char *pattern,
                        continue;
                }
 
-               if (!pat_len) {
-                       cp = ".cvsignore";
-                       pat_len = 10;
-               }
-
-               if (mflags & MATCHFLG_MERGE_FILE) {
-                       unsigned int len = pat_len;
-                       if (mflags & MATCHFLG_EXCLUDE_SELF) {
+               if (new_mflags & MATCHFLG_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;
-                               filter_rule(listp, name, len, 0, 0);
-                               mflags &= ~MATCHFLG_EXCLUDE_SELF;
+                               add_rule(listp, name, len, 0, 0);
+                               new_mflags &= ~MATCHFLG_EXCLUDE_SELF;
                                len = pat_len;
                        }
-                       if (mflags & MATCHFLG_PERDIR_MERGE) {
+                       if (new_mflags & MATCHFLG_PERDIR_MERGE) {
                                if (parent_dirscan) {
-                                       if (!(p = parse_merge_name(cp, &len, module_dirlen)))
+                                       if (!(p = parse_merge_name(cp, &len,
+                                                               module_dirlen)))
                                                continue;
-                                       filter_rule(listp, p, len, mflags, 0);
+                                       add_rule(listp, p, len, new_mflags, 0);
                                        continue;
                                }
                        } else {
-                               int flgs = XFLG_FATAL_ERRORS;
                                if (!(p = parse_merge_name(cp, &len, 0)))
                                        continue;
-                               if (mflags & MATCHFLG_INCLUDE)
-                                       flgs |= XFLG_DEF_INCLUDE;
-                               else if (mflags & MATCHFLG_NO_PREFIXES)
-                                       flgs |= XFLG_DEF_EXCLUDE;
-                               add_filter_file(listp, p, flgs);
+                               parse_filter_file(listp, p, new_mflags,
+                                                 XFLG_FATAL_ERRORS);
                                continue;
                        }
                }
 
-               filter_rule(listp, cp, pat_len, mflags, xflags);
+               add_rule(listp, cp, pat_len, new_mflags, xflags);
+
+               if (new_mflags & MATCHFLG_CVS_IGNORE
+                   && !(new_mflags & MATCHFLG_MERGE_FILE))
+                       get_cvs_excludes(new_mflags);
        }
 }
 
 
-void add_filter_file(struct filter_list_struct *listp, const char *fname,
-                    int xflags)
+void parse_filter_file(struct filter_list_struct *listp, const char *fname,
+                      uint32 mflags, int xflags)
 {
        FILE *fp;
        char line[MAXPATHLEN+MAX_RULE_PREFIX+1]; /* +1 for trailing slash. */
        char *eob = line + sizeof line - 1;
-       int word_split = xflags & XFLG_WORD_SPLIT;
+       int word_split = mflags & MATCHFLG_WORD_SPLIT;
 
        if (!fname || !*fname)
                return;
@@ -831,8 +976,8 @@ void add_filter_file(struct filter_list_struct *listp, const char *fname,
                fp = stdin;
 
        if (verbose > 2) {
-               rprintf(FINFO, "[%s] add_filter_file(%s,%d)%s\n",
-                       who_am_i(), safe_fname(fname), xflags,
+               rprintf(FINFO, "[%s] parse_filter_file(%s,%x,%x)%s\n",
+                       who_am_i(), safe_fname(fname), mflags, xflags,
                        fp ? "" : " [not found]");
        }
 
@@ -840,7 +985,7 @@ void add_filter_file(struct filter_list_struct *listp, const char *fname,
                if (xflags & XFLG_FATAL_ERRORS) {
                        rsyserr(FERROR, errno,
                                "failed to open %sclude file %s",
-                               xflags & XFLG_DEF_INCLUDE ? "in" : "ex",
+                               mflags & MATCHFLG_INCLUDE ? "in" : "ex",
                                safe_fname(fname));
                        exit_cleanup(RERR_FILEIO);
                }
@@ -873,126 +1018,178 @@ void add_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 != '#')))
-                       add_filter(listp, line, xflags);
+                       parse_rule(listp, line, mflags, xflags);
                if (ch == EOF)
                        break;
        }
        fclose(fp);
 }
 
-char *get_rule_prefix(int match_flags, const char *pat, unsigned int *plen_ptr)
+/* 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,
+                     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 (legal_len == 1)
+                       return NULL;
                *op++ = ':';
-               if (match_flags & MATCHFLG_WORD_SPLIT)
-                       *op++ = 's';
+       } else if (match_flags & MATCHFLG_INCLUDE)
+               *op++ = '+';
+       else if (legal_len != 1
+           || ((*pat == '-' || *pat == '+') && pat[1] == ' '))
+               *op++ = '-';
+       else
+               legal_len = 0;
+
+       if (match_flags & MATCHFLG_CVS_IGNORE)
+               *op++ = 'C';
+       else {
                if (match_flags & MATCHFLG_NO_INHERIT)
                        *op++ = 'n';
-               if (match_flags & MATCHFLG_EXCLUDE_SELF)
-                       *op++ = 'e';
+               if (match_flags & MATCHFLG_WORD_SPLIT)
+                       *op++ = 'w';
                if (match_flags & MATCHFLG_NO_PREFIXES) {
                        if (match_flags & MATCHFLG_INCLUDE)
                                *op++ = '+';
                        else
                                *op++ = '-';
                }
-               *op++ = ' ';
-       } else if (match_flags & MATCHFLG_INCLUDE) {
-               *op++ = '+';
-               *op++ = ' ';
-       } else if (protocol_version >= 29
-           || ((*pat == '-' || *pat == '+') && pat[1] == ' ')) {
-               *op++ = '-';
-               *op++ = ' ';
        }
+       if (match_flags & MATCHFLG_EXCLUDE_SELF)
+               *op++ = 'e';
+       if (match_flags & MATCHFLG_SENDER_SIDE
+           && (!for_xfer || protocol_version >= 29))
+               *op++ = 's';
+       if (match_flags & MATCHFLG_RECEIVER_SIDE
+           && (!for_xfer || protocol_version >= 29
+            || (delete_excluded && am_sender)))
+               *op++ = 'r';
+       if (op - buf > legal_len)
+               return NULL;
+       if (legal_len)
+               *op++ = ' ';
        *op = '\0';
        if (plen_ptr)
                *plen_ptr = op - buf;
-       if (op - buf > MAX_RULE_PREFIX)
-               overflow("get_rule_prefix");
        return buf;
 }
 
-void send_filter_list(int f)
+static void send_rules(int f_out, struct filter_list_struct *flp)
 {
-       struct filter_struct *ent;
+       struct filter_struct *ent, *prev = NULL;
 
-       /* 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)
-               add_filter(&filter_list, "/*/*", XFLG_DEF_EXCLUDE);
-
-       for (ent = filter_list.head; ent; ent = ent->next) {
+       for (ent = flp->head; ent; ent = ent->next) {
                unsigned int len, plen, dlen;
+               int elide = 0;
                char *p;
 
-               len = strlen(ent->pattern);
-               if (len == 0 || len >= MAXPATHLEN)
+               if (ent->match_flags & MATCHFLG_SENDER_SIDE)
+                       elide = am_sender ? 1 : -1;
+               if (ent->match_flags & MATCHFLG_RECEIVER_SIDE)
+                       elide = elide ? 0 : am_sender ? -1 : 1;
+               else if (delete_excluded && !elide)
+                       elide = am_sender ? 1 : -1;
+               if (elide < 0) {
+                       if (prev)
+                               prev->next = ent->next;
+                       else
+                               flp->head = ent->next;
+               } else
+                       prev = ent;
+               if (elide > 0)
                        continue;
-               p = get_rule_prefix(ent->match_flags, ent->pattern, &plen);
-               if (protocol_version < 29 && *p == ':') {
-                       if (strcmp(p, ":sn- ") == 0
-                           && strcmp(ent->pattern, ".cvsignore") == 0)
+               if (ent->match_flags & MATCHFLG_CVS_IGNORE
+                   && !(ent->match_flags & MATCHFLG_MERGE_FILE)) {
+                       int f = am_sender || protocol_version < 29 ? f_out : -1;
+                       send_rules(f, &cvs_filter_list);
+                       if (f >= 0)
                                continue;
+               }
+               p = get_rule_prefix(ent->match_flags, ent->pattern, 1, &plen);
+               if (!p) {
                        rprintf(FERROR,
-                               "remote rsync is too old to understand per-directory merge files.\n");
+                               "filter rules are too modern for remote rsync.\n");
                        exit_cleanup(RERR_SYNTAX);
                }
+               if (f_out < 0)
+                       continue;
+               len = strlen(ent->pattern);
                dlen = ent->match_flags & MATCHFLG_DIRECTORY ? 1 : 0;
-               write_int(f, plen + len + dlen);
+               if (!(plen + len + dlen))
+                       continue;
+               write_int(f_out, plen + len + dlen);
                if (plen)
-                       write_buf(f, p, plen);
-               write_buf(f, ent->pattern, len);
+                       write_buf(f_out, p, plen);
+               write_buf(f_out, ent->pattern, len);
                if (dlen)
-                       write_byte(f, '/');
+                       write_byte(f_out, '/');
        }
-
-       write_int(f, 0);
+       flp->tail = prev;
 }
 
-
-void recv_filter_list(int f)
+/* This is only called by the client. */
+void send_filter_list(int f_out)
 {
-       char line[MAXPATHLEN+MAX_RULE_PREFIX+1]; /* +1 for trailing slash. */
-       unsigned int xflags = protocol_version >= 29 ? 0 : XFLG_DEF_EXCLUDE;
-       unsigned int l;
-
-       while ((l = read_int(f)) != 0) {
-               if (l >= sizeof line)
-                       overflow("recv_filter_list");
-               read_sbuf(f, line, l);
-               add_filter(&filter_list, line, xflags);
+       int receiver_wants_list = delete_mode
+               && (!delete_excluded || protocol_version >= 29);
+
+       if (local_server || (am_sender && !receiver_wants_list))
+               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);
        }
-}
 
+       /* 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);
 
-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 *~ #* .#* ,* _$* *$"
-       " *.old *.bak *.BAK *.orig *.rej .del-*"
-       " *.a *.olb *.o *.obj *.so *.exe"
-       " *.Z *.elc *.ln core"
-       /* The rest we added to suit ourself. */
-       " .svn/";
+       send_rules(f_out, &filter_list);
+
+       if (f_out >= 0)
+               write_int(f_out, 0);
 
-void add_cvs_excludes(void)
+       if (cvs_exclude) {
+               if (!am_sender || protocol_version < 29)
+                       parse_rule(&filter_list, ":C", 0, 0);
+               if (!am_sender)
+                       parse_rule(&filter_list, "-C", 0, 0);
+       }
+}
+
+/* This is only called by the server. */
+void recv_filter_list(int f_in)
 {
-       static unsigned int cvs_flags = XFLG_WORD_SPLIT | XFLG_NO_PREFIXES
-                                     | XFLG_DEF_EXCLUDE;
-       char fname[MAXPATHLEN];
-       char *p = module_id >= 0 && lp_use_chroot(module_id)
-               ? "/" : getenv("HOME");
+       char line[MAXPATHLEN+MAX_RULE_PREFIX+1]; /* +1 for trailing slash. */
+       int xflags = protocol_version >= 29 ? 0 : XFLG_OLD_PREFIXES;
+       int receiver_wants_list = delete_mode
+               && (!delete_excluded || protocol_version >= 29);
+       unsigned int len;
 
-       add_filter(&filter_list, ":C", 0);
-       add_filter(&filter_list, default_cvsignore, cvs_flags);
+       if (!local_server && (am_sender || receiver_wants_list)) {
+               while ((len = read_int(f_in)) != 0) {
+                       if (len >= sizeof line)
+                               overflow("recv_rules");
+                       read_sbuf(f_in, line, len);
+                       parse_rule(&filter_list, line, 0, xflags);
+               }
+       }
 
-       if (p && pathjoin(fname, MAXPATHLEN, p, ".cvsignore") < MAXPATHLEN) {
-               add_filter_file(&filter_list, fname, cvs_flags);
+       if (cvs_exclude) {
+               if (local_server || am_sender || protocol_version < 29)
+                       parse_rule(&filter_list, ":C", 0, 0);
+               if (local_server || am_sender)
+                       parse_rule(&filter_list, "-C", 0, 0);
        }
 
-       add_filter(&filter_list, getenv("CVSIGNORE"), cvs_flags);
+       if (local_server) /* filter out any rules that aren't for us. */
+               send_rules(-1, &filter_list);
 }