Tweaking the license text a bit more.
[rsync/rsync.git] / exclude.c
index a6d5b5c..1a0be70 100644 (file)
--- 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 <tridge@samba.org>
- * Copyright (C) 1996 by Paul Mackerras
- * Copyright (C) 2002 by Martin Pool
+ * Copyright (C) 1996-2001 Andrew Tridgell <tridge@samba.org>
+ * Copyright (C) 1996 Paul Mackerras
+ * Copyright (C) 2002 Martin Pool
+ * Copyright (C) 2003-2007 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,
  * 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 <mbp@samba.org> */
-
 #include "rsync.h"
 
 extern int verbose;
@@ -34,6 +30,8 @@ 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;
@@ -46,14 +44,15 @@ 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]" };
+struct filter_list_struct cvs_filter_list = { 0, 0, " [global CVS]" };
+struct filter_list_struct server_filter_list = { 0, 0, " [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 "/!C"
+#define MODIFIERS_INCL_EXCL "/!Crsp"
+#define MODIFIERS_HIDE_PROTECT "/!p"
 
 /* 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
@@ -132,14 +131,31 @@ static void add_rule(struct filter_list_struct *listp, const char *pat,
                        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 = 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 == '/'
-           && !(mflags & (MATCHFLG_ABS_PATH | MATCHFLG_MERGE_FILE))) {
+       if (!(mflags & (MATCHFLG_ABS_PATH | MATCHFLG_MERGE_FILE))
+        && ((xflags & (XFLG_ANCHORED2ABS|XFLG_ABS_IF_SLASH) && *pat == '/')
+         || (xflags & XFLG_ABS_IF_SLASH && strchr(pat, '/') != NULL))) {
                mflags |= MATCHFLG_ABS_PATH;
-               ex_len = dirbuf_len - module_dirlen - 1;
+               if (*pat == '/')
+                       ex_len = dirbuf_len - module_dirlen - 1;
+               else
+                       ex_len = 0;
        } else
                ex_len = 0;
        if (!(ret->pattern = new_array(char, ex_len + pat_len + 1)))
@@ -156,6 +172,12 @@ static void add_rule(struct filter_list_struct *listp, const char *pat,
                        /* If the pattern starts with **, note that. */
                        if (cp == ret->pattern)
                                mflags |= MATCHFLG_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] == '*')
+                               mflags |= MATCHFLG_WILD3_SUFFIX;
                }
        }
 
@@ -276,29 +298,28 @@ 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, NULL)) {
                        rprintf(FERROR, "merge-file name overflows: %s\n",
                                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, 1);
        }
-       
-       fn_len = strlen(fn);
-       if (fn == buf)
-               goto done;
 
-       if (dirbuf_len + fn_len >= MAXPATHLEN) {
-               rprintf(FERROR, "merge-file name overflows: %s\n", fn);
-               return NULL;
+       /* If the name isn't in buf yet, it's wasn't absolute. */
+       if (fn != buf) {
+               if (dirbuf_len + fn_len >= MAXPATHLEN) {
+                       rprintf(FERROR, "merge-file name overflows: %s\n", fn);
+                       return NULL;
+               }
+               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);
        }
-       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;
@@ -471,73 +492,95 @@ void pop_local_filters(void *mem)
        free(pop);
 }
 
+void change_local_filter_dir(const char *dname, int dlen, int dir_depth)
+{
+       static int min_depth = MAXPATHLEN, cur_depth = -1;
+       static void *filt_array[MAXPATHLEN/2+1];
+
+       if (!dname) {
+               while (cur_depth >= min_depth)
+                       pop_local_filters(filt_array[cur_depth--]);
+               min_depth = MAXPATHLEN;
+               cur_depth = -1;
+               return;
+       }
+
+       assert(dir_depth < MAXPATHLEN/2+1);
+
+       while (cur_depth >= dir_depth && cur_depth >= min_depth)
+               pop_local_filters(filt_array[cur_depth--]);
+       cur_depth = dir_depth;
+       if (cur_depth < min_depth)
+               min_depth = cur_depth;
+
+       filt_array[cur_depth] = push_local_filters(dname, dlen);
+}
+
 static int rule_matches(char *name, struct filter_struct *ex, int name_is_dir)
 {
-       char *p, full_name[MAXPATHLEN];
-       int match_start = 0;
+       int slash_handling, str_cnt = 0, anchored_match = 0;
        int ret_match = ex->match_flags & MATCHFLG_NEGATE ? 0 : 1;
-       char *pattern = ex->pattern;
+       char *p, *pattern = ex->pattern;
+       const char *strings[16]; /* more than enough */
 
+       if (*name == '/')
+               name++;
        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 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->match_flags & MATCHFLG_ABS_PATH && *name != '/'
            && 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->match_flags & MATCHFLG_WILD2_PREFIX && *name != '/') {
+               /* 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->match_flags & MATCHFLG_WILD3_SUFFIX)
+                       strings[str_cnt++] = "/";
+       } else if (ex->match_flags & MATCHFLG_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->match_flags & MATCHFLG_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->match_flags & MATCHFLG_WILD2_PREFIX)
+                                  && 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. */
+               slash_handling = -1;
+       } else {
+               /* The pattern matches only at the start of the path or name. */
+               slash_handling = 0;
+       }
+
+       if (ex->match_flags & MATCHFLG_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);
@@ -562,11 +605,13 @@ static void report_filter_result(char const *name,
         * 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);
+               static char *actions[2][2]
+                   = { {"show", "hid"}, {"risk", "protect"} };
+               const char *w = who_am_i();
+               rprintf(FINFO, "[%s] %sing %s %s because of pattern %s%s%s\n",
+                   w, actions[*w!='s'][!(ent->match_flags&MATCHFLG_INCLUDE)],
+                   name_is_dir ? "directory" : "file", name, ent->pattern,
+                   ent->match_flags & MATCHFLG_DIRECTORY ? "/" : "", type);
        }
 }
 
@@ -580,6 +625,8 @@ int check_filter(struct filter_list_struct *listp, char *name, int name_is_dir)
        struct filter_struct *ent;
 
        for (ent = listp->head; ent; ent = ent->next) {
+               if (ignore_perishable && ent->match_flags & MATCHFLG_PERISHABLE)
+                       continue;
                if (ent->match_flags & MATCHFLG_PERDIR_MERGE) {
                        int rc = check_filter(ent->u.mergelist, name,
                                              name_is_dir);
@@ -604,6 +651,18 @@ 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.
@@ -638,7 +697,7 @@ static const char *parse_rule_tok(const char *p, uint32 mflags, int xflags,
         * for old include/exclude patterns where just "+ " and "- " are
         * allowed as optional prefixes.  */
        if (mflags & MATCHFLG_NO_PREFIXES) {
-               if (*s == '!')
+               if (*s == '!' && mflags & MATCHFLG_CVS_IGNORE)
                        new_mflags |= MATCHFLG_CLEAR_LIST; /* Tentative! */
        } else if (xflags & XFLG_OLD_PREFIXES) {
                if (*s == '-' && s[1] == ' ') {
@@ -647,12 +706,54 @@ static const char *parse_rule_tok(const char *p, uint32 mflags, int xflags,
                } else if (*s == '+' && s[1] == ' ') {
                        new_mflags |= MATCHFLG_INCLUDE;
                        s += 2;
-               }
-               if (*s == '!')
+               } else if (*s == '!')
                        new_mflags |= MATCHFLG_CLEAR_LIST; /* Tentative! */
        } else {
-               char *mods = "";
+               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 ':':
                        new_mflags |= MATCHFLG_PERDIR_MERGE
                                    | MATCHFLG_FINISH_SETUP;
@@ -667,12 +768,26 @@ static const char *parse_rule_tok(const char *p, uint32 mflags, int xflags,
                case '-':
                        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 '!':
                        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 != '_') {
@@ -719,6 +834,15 @@ static const char *parse_rule_tok(const char *p, uint32 mflags, int xflags,
                        case 'n':
                                new_mflags |= MATCHFLG_NO_INHERIT;
                                break;
+                       case 'p':
+                               new_mflags |= MATCHFLG_PERISHABLE;
+                               break;
+                       case 'r':
+                               new_mflags |= MATCHFLG_RECEIVER_SIDE;
+                               break;
+                       case 's':
+                               new_mflags |= MATCHFLG_SENDER_SIDE;
+                               break;
                        case 'w':
                                new_mflags |= MATCHFLG_WORD_SPLIT;
                                break;
@@ -738,7 +862,8 @@ static const char *parse_rule_tok(const char *p, uint32 mflags, int xflags,
                len = strlen((char*)s);
 
        if (new_mflags & MATCHFLG_CLEAR_LIST) {
-               if (!(xflags & XFLG_OLD_PREFIXES) && len) {
+               if (!(mflags & MATCHFLG_NO_PREFIXES)
+                && !(xflags & XFLG_OLD_PREFIXES) && len) {
                        rprintf(FERROR,
                                "'!' rule has trailing characters: %s\n", p);
                        exit_cleanup(RERR_SYNTAX);
@@ -750,12 +875,52 @@ static const char *parse_rule_tok(const char *p, uint32 mflags, int xflags,
                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_mflags & (MATCHFLG_RECEIVER_SIDE|MATCHFLG_SENDER_SIDE))
+        && (!(new_mflags & MATCHFLG_PERDIR_MERGE)
+         || new_mflags & MATCHFLG_NO_PREFIXES))
+               new_mflags |= MATCHFLG_SENDER_SIDE;
+
        *len_ptr = len;
        *mflags_ptr = new_mflags;
        return (const char *)s;
 }
 
 
+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/ .bzr/";
+
+static void get_cvs_excludes(uint32 mflags)
+{
+       static int initialized = 0;
+       char *p, fname[MAXPATHLEN];
+
+       if (initialized)
+               return;
+       initialized = 1;
+
+       parse_rule(&cvs_filter_list, default_cvsignore,
+                  mflags | (protocol_version >= 30 ? MATCHFLG_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_rule(&cvs_filter_list, getenv("CVSIGNORE"), mflags, 0);
+}
+
+
 void parse_rule(struct filter_list_struct *listp, const char *pattern,
                uint32 mflags, int xflags)
 {
@@ -772,12 +937,14 @@ void parse_rule(struct filter_list_struct *listp, const char *pattern,
                                    &pat_len, &new_mflags);
                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) {
@@ -797,11 +964,9 @@ void parse_rule(struct filter_list_struct *listp, const char *pattern,
                        }
                        len = pat_len;
                        if (new_mflags & MATCHFLG_EXCLUDE_SELF) {
-                               const char *name = strrchr(cp, '/');
-                               if (name)
-                                       len -= ++name - cp;
-                               else
-                                       name = cp;
+                               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;
                                len = pat_len;
@@ -827,7 +992,7 @@ void parse_rule(struct filter_list_struct *listp, const char *pattern,
 
                if (new_mflags & MATCHFLG_CVS_IGNORE
                    && !(new_mflags & MATCHFLG_MERGE_FILE))
-                       get_cvs_excludes();
+                       get_cvs_excludes(new_mflags);
        }
 }
 
@@ -836,7 +1001,7 @@ 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 line[BIGPATHBUFLEN];
        char *eob = line + sizeof line - 1;
        int word_split = mflags & MATCHFLG_WORD_SPLIT;
 
@@ -858,7 +1023,7 @@ void parse_filter_file(struct filter_list_struct *listp, const char *fname,
 
        if (verbose > 2) {
                rprintf(FINFO, "[%s] parse_filter_file(%s,%x,%x)%s\n",
-                       who_am_i(), safe_fname(fname), mflags, xflags,
+                       who_am_i(), fname, mflags, xflags,
                        fp ? "" : " [not found]");
        }
 
@@ -867,7 +1032,7 @@ void parse_filter_file(struct filter_list_struct *listp, const char *fname,
                        rsyserr(FERROR, errno,
                                "failed to open %sclude file %s",
                                mflags & MATCHFLG_INCLUDE ? "in" : "ex",
-                               safe_fname(fname));
+                               fname);
                        exit_cleanup(RERR_FILEIO);
                }
                return;
@@ -879,8 +1044,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))
@@ -906,15 +1073,15 @@ void parse_filter_file(struct filter_list_struct *listp, const char *fname,
        fclose(fp);
 }
 
-/* If the "sending" flag is > 0, the prefix is made compatible with the
+/* 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 sending,
+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 = sending && protocol_version < 29 ? 1 : MAX_RULE_PREFIX;
+       int legal_len = for_xfer && protocol_version < 29 ? 1 : MAX_RULE_PREFIX-1;
 
        if (match_flags & MATCHFLG_PERDIR_MERGE) {
                if (legal_len == 1)
@@ -928,8 +1095,8 @@ char *get_rule_prefix(int match_flags, const char *pat, int sending,
        else
                legal_len = 0;
 
-       if (match_flags & MATCHFLG_EXCLUDE_SELF)
-               *op++ = 'e';
+       if (match_flags & MATCHFLG_NEGATE)
+               *op++ = '!';
        if (match_flags & MATCHFLG_CVS_IGNORE)
                *op++ = 'C';
        else {
@@ -944,6 +1111,21 @@ char *get_rule_prefix(int match_flags, const char *pat, int sending,
                                *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 (match_flags & MATCHFLG_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)
@@ -951,32 +1133,57 @@ char *get_rule_prefix(int match_flags, const char *pat, int sending,
        *op = '\0';
        if (plen_ptr)
                *plen_ptr = op - buf;
-       if (op - buf > MAX_RULE_PREFIX)
-               overflow("get_rule_prefix");
        return buf;
 }
 
 static void send_rules(int f_out, struct filter_list_struct *flp)
 {
-       struct filter_struct *ent;
+       struct filter_struct *ent, *prev = NULL;
 
        for (ent = flp->head; ent; ent = ent->next) {
                unsigned int len, plen, dlen;
+               int elide = 0;
                char *p;
 
+               /* 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->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
+                && (!(ent->match_flags & MATCHFLG_PERDIR_MERGE)
+                 || ent->match_flags & MATCHFLG_NO_PREFIXES))
+                       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;
                if (ent->match_flags & MATCHFLG_CVS_IGNORE
                    && !(ent->match_flags & MATCHFLG_MERGE_FILE)) {
-                       if (am_sender || protocol_version < 29) {
-                               send_rules(f_out, &cvs_filter_list);
+                       int f = am_sender || protocol_version < 29 ? f_out : -2;
+                       send_rules(f, &cvs_filter_list);
+                       if (f == f_out)
                                continue;
-                       }
                }
                p = get_rule_prefix(ent->match_flags, 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;
                if (!(plen + len + dlen))
@@ -988,12 +1195,16 @@ static void send_rules(int f_out, struct filter_list_struct *flp)
                if (dlen)
                        write_byte(f_out, '/');
        }
+       flp->tail = prev;
 }
 
 /* This is only called by the client. */
 void send_filter_list(int f_out)
 {
-       if (local_server || (am_sender && (!delete_mode || delete_excluded)))
+       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;
        if (cvs_exclude && am_sender) {
                if (protocol_version >= 29)
@@ -1006,10 +1217,10 @@ void send_filter_list(int f_out)
        if (list_only == 1 && !recurse)
                parse_rule(&filter_list, "/*/*", MATCHFLG_NO_PREFIXES, 0);
 
-       if (f_out >= 0) {
-               send_rules(f_out, &filter_list);
+       send_rules(f_out, &filter_list);
+
+       if (f_out >= 0)
                write_int(f_out, 0);
-       }
 
        if (cvs_exclude) {
                if (!am_sender || protocol_version < 29)
@@ -1022,14 +1233,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 = prune_empty_dirs
+           || (delete_mode
+            && (!delete_excluded || protocol_version >= 29));
        unsigned int len;
 
-       if (!local_server && (am_sender || (delete_mode && !delete_excluded))) {
+       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);
                }
@@ -1041,34 +1255,7 @@ void recv_filter_list(int f_in)
                if (local_server || am_sender)
                        parse_rule(&filter_list, "-C", 0, 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/";
-
-void get_cvs_excludes(void)
-{
-       static unsigned cvs_mflags = MATCHFLG_WORD_SPLIT|MATCHFLG_NO_PREFIXES;
-       char *p, fname[MAXPATHLEN];
-       static int initialized = 0;
-
-       if (initialized)
-               return;
-       initialized = 1;
-
-       parse_rule(&cvs_filter_list, default_cvsignore, cvs_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, cvs_mflags, 0);
 
-       parse_rule(&cvs_filter_list, getenv("CVSIGNORE"), cvs_mflags, 0);
+       if (local_server) /* filter out any rules that aren't for us. */
+               send_rules(-1, &filter_list);
 }