Diff for /embedaddon/rsync/exclude.c between versions 1.1.1.1 and 1.1.1.3

version 1.1.1.1, 2012/02/17 15:09:30 version 1.1.1.3, 2016/11/01 09:54:32
Line 4 Line 4
  * Copyright (C) 1996-2001 Andrew Tridgell <tridge@samba.org>   * Copyright (C) 1996-2001 Andrew Tridgell <tridge@samba.org>
  * Copyright (C) 1996 Paul Mackerras   * Copyright (C) 1996 Paul Mackerras
  * Copyright (C) 2002 Martin Pool   * Copyright (C) 2002 Martin Pool
 * Copyright (C) 2003-2009 Wayne Davison * Copyright (C) 2003-2015 Wayne Davison
  *   *
  * This program is free software; you can redistribute it and/or modify   * 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   * it under the terms of the GNU General Public License as published by
Line 22 Line 22
   
 #include "rsync.h"  #include "rsync.h"
   
 extern int verbose;  
 extern int am_server;  extern int am_server;
 extern int am_sender;  extern int am_sender;
 extern int eol_nulls;  extern int eol_nulls;
Line 41  extern char curr_dir[MAXPATHLEN]; Line 40  extern char curr_dir[MAXPATHLEN];
 extern unsigned int curr_dir_len;  extern unsigned int curr_dir_len;
 extern unsigned int module_dirlen;  extern unsigned int module_dirlen;
   
struct filter_list_struct filter_list = { 0, 0, "" };filter_rule_list filter_list = { .debug_type = "" };
struct filter_list_struct cvs_filter_list = { 0, 0, " [global CVS]" };filter_rule_list cvs_filter_list = { .debug_type = " [global CVS]" };
struct filter_list_struct daemon_filter_list = { 0, 0, " [daemon]" };filter_rule_list daemon_filter_list = { .debug_type = " [daemon]" };
   
 /* Need room enough for ":MODS " prefix plus some room to grow. */  /* Need room enough for ":MODS " prefix plus some room to grow. */
 #define MAX_RULE_PREFIX (16)  #define MAX_RULE_PREFIX (16)
   
 #define MODIFIERS_MERGE_FILE "-+Cenw"  
 #define MODIFIERS_INCL_EXCL "/!Crsp"  
 #define MODIFIERS_HIDE_PROTECT "/!p"  
   
 #define SLASH_WILD3_SUFFIX "/***"  #define SLASH_WILD3_SUFFIX "/***"
   
 /* The dirbuf is set by push_local_filters() to the current subdirectory  /* The dirbuf is set by push_local_filters() to the current subdirectory
Line 68  static BOOL parent_dirscan = False; Line 63  static BOOL parent_dirscan = False;
 /* This array contains a list of all the currently active per-dir merge  /* This array contains a list of all the currently active per-dir merge
  * files.  This makes it easier to save the appropriate values when we   * files.  This makes it easier to save the appropriate values when we
  * "push" down into each subdirectory. */   * "push" down into each subdirectory. */
static struct filter_struct **mergelist_parents;static filter_rule **mergelist_parents;
 static int mergelist_cnt = 0;  static int mergelist_cnt = 0;
 static int mergelist_size = 0;  static int mergelist_size = 0;
   
Line 103  static int mergelist_size = 0; Line 98  static int mergelist_size = 0;
  * values (so we can pop back to them later) and set the tail to NULL.   * values (so we can pop back to them later) and set the tail to NULL.
  */   */
   
static void free_filter(struct filter_struct *ex)static void teardown_mergelist(filter_rule *ex)
 {  {
        if (ex->match_flags & MATCHFLG_PERDIR_MERGE) {        int j;
                free(ex->u.mergelist->debug_type);
                free(ex->u.mergelist);        if (!ex->u.mergelist)
                mergelist_cnt--;                return;
 
         if (DEBUG_GTE(FILTER, 2)) {
                 rprintf(FINFO, "[%s] deactivating mergelist #%d%s\n",
                         who_am_i(), mergelist_cnt - 1,
                         ex->u.mergelist->debug_type);
         }          }
   
           free(ex->u.mergelist->debug_type);
           free(ex->u.mergelist);
   
           for (j = 0; j < mergelist_cnt; j++) {
                   if (mergelist_parents[j] == ex) {
                           mergelist_parents[j] = NULL;
                           break;
                   }
           }
           while (mergelist_cnt && mergelist_parents[mergelist_cnt-1] == NULL)
                   mergelist_cnt--;
   }
   
   static void free_filter(filter_rule *ex)
   {
           if (ex->rflags & FILTRULE_PERDIR_MERGE)
                   teardown_mergelist(ex);
         free(ex->pattern);          free(ex->pattern);
         free(ex);          free(ex);
 }  }
   
   static void free_filters(filter_rule *ent)
   {
           while (ent) {
                   filter_rule *next = ent->next;
                   free_filter(ent);
                   ent = next;
           }
   }
   
 /* Build a filter structure given a filter pattern.  The value in "pat"  /* Build a filter structure given a filter pattern.  The value in "pat"
 * is not null-terminated. */ * is not null-terminated.  "rule" is either held or freed, so the
static void add_rule(struct filter_list_struct *listp, const char *pat, * caller should not free it. */
                     unsigned int pat_len, uint32 mflags, int xflags)static void add_rule(filter_rule_list *listp, const char *pat, unsigned int pat_len,
                      filter_rule *rule, int xflags)
 {  {
         struct filter_struct *ret;  
         const char *cp;          const char *cp;
         unsigned int pre_len, suf_len, slash_cnt = 0;          unsigned int pre_len, suf_len, slash_cnt = 0;
   
        if (verbose > 2) {        if (DEBUG_GTE(FILTER, 2)) {
                 rprintf(FINFO, "[%s] add_rule(%s%.*s%s)%s\n",                  rprintf(FINFO, "[%s] add_rule(%s%.*s%s)%s\n",
                        who_am_i(), get_rule_prefix(mflags, pat, 0, NULL),                        who_am_i(), get_rule_prefix(rule, pat, 0, NULL),
                         (int)pat_len, pat,                          (int)pat_len, pat,
                        (mflags & MATCHFLG_DIRECTORY) ? "/" : "",                        (rule->rflags & FILTRULE_DIRECTORY) ? "/" : "",
                         listp->debug_type);                          listp->debug_type);
         }          }
   
         /* These flags also indicate that we're reading a list that          /* These flags also indicate that we're reading a list that
          * needs to be filtered now, not post-filtered later. */           * needs to be filtered now, not post-filtered later. */
        if (xflags & (XFLG_ANCHORED2ABS|XFLG_ABS_IF_SLASH)) {        if (xflags & (XFLG_ANCHORED2ABS|XFLG_ABS_IF_SLASH)
                uint32 mf = mflags & (MATCHFLG_RECEIVER_SIDE|MATCHFLG_SENDER_SIDE);                && (rule->rflags & FILTRULES_SIDES)
                if (am_sender) {                        == (am_sender ? FILTRULE_RECEIVER_SIDE : FILTRULE_SENDER_SIDE)) {
                        if (mf == MATCHFLG_RECEIVER_SIDE)                /* This filter applies only to the other side.  Drop it. */
                                return;                free_filter(rule);
                } else {                return;
                        if (mf == MATCHFLG_SENDER_SIDE) 
                                return; 
                } 
         }          }
   
         if (!(ret = new0(struct filter_struct)))  
                 out_of_memory("add_rule");  
   
         if (pat_len > 1 && pat[pat_len-1] == '/') {          if (pat_len > 1 && pat[pat_len-1] == '/') {
                 pat_len--;                  pat_len--;
                mflags |= MATCHFLG_DIRECTORY;                rule->rflags |= FILTRULE_DIRECTORY;
         }          }
   
         for (cp = pat; cp < pat + pat_len; cp++) {          for (cp = pat; cp < pat + pat_len; cp++) {
Line 157  static void add_rule(struct filter_list_struct *listp, Line 178  static void add_rule(struct filter_list_struct *listp,
                         slash_cnt++;                          slash_cnt++;
         }          }
   
        if (!(mflags & (MATCHFLG_ABS_PATH | MATCHFLG_MERGE_FILE))        if (!(rule->rflags & (FILTRULE_ABS_PATH | FILTRULE_MERGE_FILE))
          && ((xflags & (XFLG_ANCHORED2ABS|XFLG_ABS_IF_SLASH) && *pat == '/')           && ((xflags & (XFLG_ANCHORED2ABS|XFLG_ABS_IF_SLASH) && *pat == '/')
           || (xflags & XFLG_ABS_IF_SLASH && slash_cnt))) {            || (xflags & XFLG_ABS_IF_SLASH && slash_cnt))) {
                mflags |= MATCHFLG_ABS_PATH;                rule->rflags |= FILTRULE_ABS_PATH;
                 if (*pat == '/')                  if (*pat == '/')
                         pre_len = dirbuf_len - module_dirlen - 1;                          pre_len = dirbuf_len - module_dirlen - 1;
                 else                  else
Line 170  static void add_rule(struct filter_list_struct *listp, Line 191  static void add_rule(struct filter_list_struct *listp,
   
         /* The daemon wants dir-exclude rules to get an appended "/" + "***". */          /* The daemon wants dir-exclude rules to get an appended "/" + "***". */
         if (xflags & XFLG_DIR2WILD3          if (xflags & XFLG_DIR2WILD3
         && BITS_SETnUNSET(mflags, MATCHFLG_DIRECTORY, MATCHFLG_INCLUDE)) {         && BITS_SETnUNSET(rule->rflags, FILTRULE_DIRECTORY, FILTRULE_INCLUDE)) {
                mflags &= ~MATCHFLG_DIRECTORY;                rule->rflags &= ~FILTRULE_DIRECTORY;
                 suf_len = sizeof SLASH_WILD3_SUFFIX - 1;                  suf_len = sizeof SLASH_WILD3_SUFFIX - 1;
         } else          } else
                 suf_len = 0;                  suf_len = 0;
   
        if (!(ret->pattern = new_array(char, pre_len + pat_len + suf_len + 1)))        if (!(rule->pattern = new_array(char, pre_len + pat_len + suf_len + 1)))
                 out_of_memory("add_rule");                  out_of_memory("add_rule");
         if (pre_len) {          if (pre_len) {
                memcpy(ret->pattern, dirbuf + module_dirlen, pre_len);                memcpy(rule->pattern, dirbuf + module_dirlen, pre_len);
                for (cp = ret->pattern; cp < ret->pattern + pre_len; cp++) {                for (cp = rule->pattern; cp < rule->pattern + pre_len; cp++) {
                         if (*cp == '/')                          if (*cp == '/')
                                 slash_cnt++;                                  slash_cnt++;
                 }                  }
         }          }
        strlcpy(ret->pattern + pre_len, pat, pat_len + 1);        strlcpy(rule->pattern + pre_len, pat, pat_len + 1);
         pat_len += pre_len;          pat_len += pre_len;
         if (suf_len) {          if (suf_len) {
                memcpy(ret->pattern + pat_len, SLASH_WILD3_SUFFIX, suf_len+1);                memcpy(rule->pattern + pat_len, SLASH_WILD3_SUFFIX, suf_len+1);
                 pat_len += suf_len;                  pat_len += suf_len;
                 slash_cnt++;                  slash_cnt++;
         }          }
   
        if (strpbrk(ret->pattern, "*[?")) {        if (strpbrk(rule->pattern, "*[?")) {
                mflags |= MATCHFLG_WILD;                rule->rflags |= FILTRULE_WILD;
                if ((cp = strstr(ret->pattern, "**")) != NULL) {                if ((cp = strstr(rule->pattern, "**")) != NULL) {
                        mflags |= MATCHFLG_WILD2;                        rule->rflags |= FILTRULE_WILD2;
                         /* If the pattern starts with **, note that. */                          /* If the pattern starts with **, note that. */
                        if (cp == ret->pattern)                        if (cp == rule->pattern)
                                mflags |= MATCHFLG_WILD2_PREFIX;                                rule->rflags |= FILTRULE_WILD2_PREFIX;
                         /* If the pattern ends with ***, note that. */                          /* If the pattern ends with ***, note that. */
                         if (pat_len >= 3                          if (pat_len >= 3
                         && ret->pattern[pat_len-3] == '*'                         && rule->pattern[pat_len-3] == '*'
                         && ret->pattern[pat_len-2] == '*'                         && rule->pattern[pat_len-2] == '*'
                         && ret->pattern[pat_len-1] == '*')                         && rule->pattern[pat_len-1] == '*')
                                mflags |= MATCHFLG_WILD3_SUFFIX;                                rule->rflags |= FILTRULE_WILD3_SUFFIX;
                 }                  }
         }          }
   
        if (mflags & MATCHFLG_PERDIR_MERGE) {        if (rule->rflags & FILTRULE_PERDIR_MERGE) {
                struct filter_list_struct *lp;                filter_rule_list *lp;
                 unsigned int len;                  unsigned int len;
                 int i;                  int i;
   
                if ((cp = strrchr(ret->pattern, '/')) != NULL)                if ((cp = strrchr(rule->pattern, '/')) != NULL)
                         cp++;                          cp++;
                 else                  else
                        cp = ret->pattern;                        cp = rule->pattern;
   
                 /* If the local merge file was already mentioned, don't                  /* If the local merge file was already mentioned, don't
                  * add it again. */                   * add it again. */
                 for (i = 0; i < mergelist_cnt; i++) {                  for (i = 0; i < mergelist_cnt; i++) {
                        struct filter_struct *ex = mergelist_parents[i];                        filter_rule *ex = mergelist_parents[i];
                        const char *s = strrchr(ex->pattern, '/');                        const char *s;
                         if (!ex)
                                 continue;
                         s = strrchr(ex->pattern, '/');
                         if (s)                          if (s)
                                 s++;                                  s++;
                         else                          else
                                 s = ex->pattern;                                  s = ex->pattern;
                         len = strlen(s);                          len = strlen(s);
                        if (len == pat_len - (cp - ret->pattern)                        if (len == pat_len - (cp - rule->pattern) && memcmp(s, cp, len) == 0) {
                            && memcmp(s, cp, len) == 0) {                                free_filter(rule);
                                free_filter(ret); 
                                 return;                                  return;
                         }                          }
                 }                  }
   
                if (!(lp = new_array(struct filter_list_struct, 1)))                if (!(lp = new_array0(filter_rule_list, 1)))
                         out_of_memory("add_rule");                          out_of_memory("add_rule");
                 lp->head = lp->tail = NULL;  
                 if (asprintf(&lp->debug_type, " [per-dir %s]", cp) < 0)                  if (asprintf(&lp->debug_type, " [per-dir %s]", cp) < 0)
                         out_of_memory("add_rule");                          out_of_memory("add_rule");
                ret->u.mergelist = lp;                rule->u.mergelist = lp;
   
                 if (mergelist_cnt == mergelist_size) {                  if (mergelist_cnt == mergelist_size) {
                         mergelist_size += 5;                          mergelist_size += 5;
                         mergelist_parents = realloc_array(mergelist_parents,                          mergelist_parents = realloc_array(mergelist_parents,
                                                struct filter_struct *,                                                filter_rule *,
                                                 mergelist_size);                                                  mergelist_size);
                         if (!mergelist_parents)                          if (!mergelist_parents)
                                 out_of_memory("add_rule");                                  out_of_memory("add_rule");
                 }                  }
                mergelist_parents[mergelist_cnt++] = ret;                if (DEBUG_GTE(FILTER, 2)) {
                         rprintf(FINFO, "[%s] activating mergelist #%d%s\n",
                                 who_am_i(), mergelist_cnt, lp->debug_type);
                 }
                 mergelist_parents[mergelist_cnt++] = rule;
         } else          } else
                ret->u.slash_cnt = slash_cnt;                rule->u.slash_cnt = slash_cnt;
   
         ret->match_flags = mflags;  
   
         if (!listp->tail) {          if (!listp->tail) {
                ret->next = listp->head;                rule->next = listp->head;
                listp->head = listp->tail = ret;                listp->head = listp->tail = rule;
         } else {          } else {
                ret->next = listp->tail->next;                rule->next = listp->tail->next;
                listp->tail->next = ret;                listp->tail->next = rule;
                listp->tail = ret;                listp->tail = rule;
         }          }
 }  }
   
static void clear_filter_list(struct filter_list_struct *listp)/* This frees any non-inherited items, leaving just inherited items on the list. */
 static void pop_filter_list(filter_rule_list *listp)
 {  {
        if (listp->tail) {        filter_rule *inherited;
                struct filter_struct *ent, *next; 
                /* Truncate any inherited items from the local list. */ 
                listp->tail->next = NULL; 
                /* Now free everything that is left. */ 
                for (ent = listp->head; ent; ent = next) { 
                        next = ent->next; 
                        free_filter(ent); 
                } 
        } 
   
        listp->head = listp->tail = NULL;        if (!listp->tail)
                 return;
 
         inherited = listp->tail->next;
 
         /* Truncate any inherited items from the local list. */
         listp->tail->next = NULL;
         /* Now free everything that is left. */
         free_filters(listp->head);
 
         listp->head = inherited;
         listp->tail = NULL;
 }  }
   
 /* This returns an expanded (absolute) filename for the merge-file name if  /* This returns an expanded (absolute) filename for the merge-file name if
Line 330  static char *parse_merge_name(const char *merge_file,  Line 357  static char *parse_merge_name(const char *merge_file, 
                 fn_len = clean_fname(fn, CFN_COLLAPSE_DOT_DOT_DIRS);                  fn_len = clean_fname(fn, CFN_COLLAPSE_DOT_DOT_DIRS);
         }          }
   
        /* If the name isn't in buf yet, it's wasn't absolute. */        /* If the name isn't in buf yet, it wasn't absolute. */
         if (fn != buf) {          if (fn != buf) {
                 int d_len = dirbuf_len - prefix_skip;                  int d_len = dirbuf_len - prefix_skip;
                 if (d_len + fn_len >= MAXPATHLEN) {                  if (d_len + fn_len >= MAXPATHLEN) {
Line 377  void set_filter_dir(const char *dir, unsigned int dirl Line 404  void set_filter_dir(const char *dir, unsigned int dirl
  * parent directory of the first transfer dir.  If it does, we scan all the   * parent directory of the first transfer dir.  If it does, we scan all the
  * dirs from that point through the parent dir of the transfer dir looking   * dirs from that point through the parent dir of the transfer dir looking
  * for the per-dir merge-file in each one. */   * for the per-dir merge-file in each one. */
static BOOL setup_merge_file(struct filter_struct *ex,static BOOL setup_merge_file(int mergelist_num, filter_rule *ex,
                             struct filter_list_struct *lp)                             filter_rule_list *lp)
 {  {
         char buf[MAXPATHLEN];          char buf[MAXPATHLEN];
         char *x, *y, *pat = ex->pattern;          char *x, *y, *pat = ex->pattern;
Line 387  static BOOL setup_merge_file(struct filter_struct *ex, Line 414  static BOOL setup_merge_file(struct filter_struct *ex,
         if (!(x = parse_merge_name(pat, NULL, 0)) || *x != '/')          if (!(x = parse_merge_name(pat, NULL, 0)) || *x != '/')
                 return 0;                  return 0;
   
           if (DEBUG_GTE(FILTER, 2)) {
                   rprintf(FINFO, "[%s] performing parent_dirscan for mergelist #%d%s\n",
                           who_am_i(), mergelist_num, lp->debug_type);
           }
         y = strrchr(x, '/');          y = strrchr(x, '/');
         *y = '\0';          *y = '\0';
         ex->pattern = strdup(y+1);          ex->pattern = strdup(y+1);
Line 414  static BOOL setup_merge_file(struct filter_struct *ex, Line 445  static BOOL setup_merge_file(struct filter_struct *ex,
                 *y = '\0';                  *y = '\0';
                 dirbuf_len = y - dirbuf;                  dirbuf_len = y - dirbuf;
                 strlcpy(x, ex->pattern, MAXPATHLEN - (x - buf));                  strlcpy(x, ex->pattern, MAXPATHLEN - (x - buf));
                parse_filter_file(lp, buf, ex->match_flags, XFLG_ANCHORED2ABS);                parse_filter_file(lp, buf, ex, XFLG_ANCHORED2ABS);
                if (ex->match_flags & MATCHFLG_NO_INHERIT)                if (ex->rflags & FILTRULE_NO_INHERIT) {
                         /* Free the undesired rules to clean up any per-dir
                          * mergelists they defined.  Otherwise pop_local_filters
                          * may crash trying to restore nonexistent state for
                          * those mergelists. */
                         free_filters(lp->head);
                         lp->head = NULL;                          lp->head = NULL;
                   }
                 lp->tail = NULL;                  lp->tail = NULL;
                 strlcpy(y, save, MAXPATHLEN);                  strlcpy(y, save, MAXPATHLEN);
                 while ((*x++ = *y++) != '/') {}                  while ((*x++ = *y++) != '/') {}
         }          }
         parent_dirscan = False;          parent_dirscan = False;
           if (DEBUG_GTE(FILTER, 2)) {
                   rprintf(FINFO, "[%s] completed parent_dirscan for mergelist #%d%s\n",
                           who_am_i(), mergelist_num, lp->debug_type);
           }
         free(pat);          free(pat);
         return 1;          return 1;
 }  }
   
   struct local_filter_state {
           int mergelist_cnt;
           filter_rule_list mergelists[1];
   };
   
 /* Each time rsync changes to a new directory it call this function to  /* Each time rsync changes to a new directory it call this function to
  * handle all the per-dir merge-files.  The "dir" value is the current path   * handle all the per-dir merge-files.  The "dir" value is the current path
  * relative to curr_dir (which might not be null-terminated).  We copy it   * relative to curr_dir (which might not be null-terminated).  We copy it
  * into dirbuf so that we can easily append a file name on the end. */   * into dirbuf so that we can easily append a file name on the end. */
 void *push_local_filters(const char *dir, unsigned int dirlen)  void *push_local_filters(const char *dir, unsigned int dirlen)
 {  {
        struct filter_list_struct *ap, *push;        struct local_filter_state *push;
         int i;          int i;
   
         set_filter_dir(dir, dirlen);          set_filter_dir(dir, dirlen);
           if (DEBUG_GTE(FILTER, 2)) {
                   rprintf(FINFO, "[%s] pushing local filters for %s\n",
                           who_am_i(), dirbuf);
           }
   
        if (!mergelist_cnt)        if (!mergelist_cnt) {
                 /* No old state to save and no new merge files to push. */
                 return NULL;                  return NULL;
           }
   
        push = new_array(struct filter_list_struct, mergelist_cnt);        push = (struct local_filter_state *)new_array(char,
                           sizeof (struct local_filter_state)
                         + (mergelist_cnt-1) * sizeof (filter_rule_list));
         if (!push)          if (!push)
                 out_of_memory("push_local_filters");                  out_of_memory("push_local_filters");
   
        for (i = 0, ap = push; i < mergelist_cnt; i++) {        push->mergelist_cnt = mergelist_cnt;
                memcpy(ap++, mergelist_parents[i]->u.mergelist,        for (i = 0; i < mergelist_cnt; i++) {
                       sizeof (struct filter_list_struct));                filter_rule *ex = mergelist_parents[i];
                 if (!ex)
                         continue;
                 memcpy(&push->mergelists[i], ex->u.mergelist, sizeof (filter_rule_list));
         }          }
   
         /* Note: parse_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. */           * this loop separate from the above loop. */
         for (i = 0; i < mergelist_cnt; i++) {          for (i = 0; i < mergelist_cnt; i++) {
                struct filter_struct *ex = mergelist_parents[i];                filter_rule *ex = mergelist_parents[i];
                struct filter_list_struct *lp = ex->u.mergelist;                filter_rule_list *lp;
                 if (!ex)
                         continue;
                 lp = ex->u.mergelist;
   
                if (verbose > 2) {                if (DEBUG_GTE(FILTER, 2)) {
                        rprintf(FINFO, "[%s] pushing filter list%s\n",                        rprintf(FINFO, "[%s] pushing mergelist #%d%s\n",
                                who_am_i(), lp->debug_type);                                who_am_i(), i, lp->debug_type);
                 }                  }
   
                 lp->tail = NULL; /* Switch any local rules to inherited. */                  lp->tail = NULL; /* Switch any local rules to inherited. */
                if (ex->match_flags & MATCHFLG_NO_INHERIT)                if (ex->rflags & FILTRULE_NO_INHERIT)
                         lp->head = NULL;                          lp->head = NULL;
   
                if (ex->match_flags & MATCHFLG_FINISH_SETUP) {                if (ex->rflags & FILTRULE_FINISH_SETUP) {
                        ex->match_flags &= ~MATCHFLG_FINISH_SETUP;                        ex->rflags &= ~FILTRULE_FINISH_SETUP;
                        if (setup_merge_file(ex, lp))                        if (setup_merge_file(i, ex, lp))
                                 set_filter_dir(dir, dirlen);                                  set_filter_dir(dir, dirlen);
                 }                  }
   
                 if (strlcpy(dirbuf + dirbuf_len, ex->pattern,                  if (strlcpy(dirbuf + dirbuf_len, ex->pattern,
                     MAXPATHLEN - dirbuf_len) < MAXPATHLEN - dirbuf_len) {                      MAXPATHLEN - dirbuf_len) < MAXPATHLEN - dirbuf_len) {
                        parse_filter_file(lp, dirbuf, ex->match_flags,                        parse_filter_file(lp, dirbuf, ex,
                                           XFLG_ANCHORED2ABS);                                            XFLG_ANCHORED2ABS);
                 } else {                  } else {
                         io_error |= IOERR_GENERAL;                          io_error |= IOERR_GENERAL;
Line 488  void *push_local_filters(const char *dir, unsigned int Line 548  void *push_local_filters(const char *dir, unsigned int
   
 void pop_local_filters(void *mem)  void pop_local_filters(void *mem)
 {  {
        struct filter_list_struct *ap, *pop = (struct filter_list_struct*)mem;        struct local_filter_state *pop = (struct local_filter_state *)mem;
         int i;          int i;
           int old_mergelist_cnt = pop ? pop->mergelist_cnt : 0;
   
           if (DEBUG_GTE(FILTER, 2))
                   rprintf(FINFO, "[%s] popping local filters\n", who_am_i());
   
         for (i = mergelist_cnt; i-- > 0; ) {          for (i = mergelist_cnt; i-- > 0; ) {
                struct filter_struct *ex = mergelist_parents[i];                filter_rule *ex = mergelist_parents[i];
                struct filter_list_struct *lp = ex->u.mergelist;                filter_rule_list *lp;
                 if (!ex)
                         continue;
                 lp = ex->u.mergelist;
   
                if (verbose > 2) {                if (DEBUG_GTE(FILTER, 2)) {
                        rprintf(FINFO, "[%s] popping filter list%s\n",                        rprintf(FINFO, "[%s] popping mergelist #%d%s\n",
                                who_am_i(), lp->debug_type);                                who_am_i(), i, lp->debug_type);
                 }                  }
   
                clear_filter_list(lp);                pop_filter_list(lp);
                 if (i >= old_mergelist_cnt && lp->head) {
                         /* This mergelist does not exist in the state to be restored, but it
                          * still has inherited rules.  This can sometimes happen if a per-dir
                          * merge file calls setup_merge_file() in push_local_filters() and that
                          * leaves some inherited rules that aren't in the pushed list state. */
                         if (DEBUG_GTE(FILTER, 2)) {
                                 rprintf(FINFO, "[%s] freeing parent_dirscan filters of mergelist #%d%s\n",
                                         who_am_i(), i, ex->u.mergelist->debug_type);
                         }
                         pop_filter_list(lp);
                 }
         }          }
   
         if (!pop)          if (!pop)
                return;                return; /* No state to restore. */
   
        for (i = 0, ap = pop; i < mergelist_cnt; i++) {        for (i = 0; i < old_mergelist_cnt; i++) {
                memcpy(mergelist_parents[i]->u.mergelist, ap++,                filter_rule *ex = mergelist_parents[i];
                       sizeof (struct filter_list_struct));                if (!ex)
                         continue;
                 memcpy(ex->u.mergelist, &pop->mergelists[i], sizeof (filter_rule_list));
         }          }
   
         free(pop);          free(pop);
Line 542  void change_local_filter_dir(const char *dname, int dl Line 622  void change_local_filter_dir(const char *dname, int dl
         filt_array[cur_depth] = push_local_filters(dname, dlen);          filt_array[cur_depth] = push_local_filters(dname, dlen);
 }  }
   
static int rule_matches(const char *fname, struct filter_struct *ex, int name_is_dir)static int rule_matches(const char *fname, filter_rule *ex, int name_is_dir)
 {  {
         int slash_handling, str_cnt = 0, anchored_match = 0;          int slash_handling, str_cnt = 0, anchored_match = 0;
        int ret_match = ex->match_flags & MATCHFLG_NEGATE ? 0 : 1;        int ret_match = ex->rflags & FILTRULE_NEGATE ? 0 : 1;
         char *p, *pattern = ex->pattern;          char *p, *pattern = ex->pattern;
         const char *strings[16]; /* more than enough */          const char *strings[16]; /* more than enough */
         const char *name = fname + (*fname == '/');          const char *name = fname + (*fname == '/');
Line 553  static int rule_matches(const char *fname, struct filt Line 633  static int rule_matches(const char *fname, struct filt
         if (!*name)          if (!*name)
                 return 0;                  return 0;
   
        if (!ex->u.slash_cnt && !(ex->match_flags & MATCHFLG_WILD2)) {        if (!ex->u.slash_cnt && !(ex->rflags & FILTRULE_WILD2)) {
                 /* If the pattern does not have any slashes AND it does                  /* If the pattern does not have any slashes AND it does
                  * not have a "**" (which could match a slash), then we                   * not have a "**" (which could match a slash), then we
                  * just match the name portion of the path. */                   * just match the name portion of the path. */
                 if ((p = strrchr(name,'/')) != NULL)                  if ((p = strrchr(name,'/')) != NULL)
                         name = p+1;                          name = p+1;
        } else if (ex->match_flags & MATCHFLG_ABS_PATH && *fname != '/'        } else if (ex->rflags & FILTRULE_ABS_PATH && *fname != '/'
             && curr_dir_len > module_dirlen + 1) {              && curr_dir_len > module_dirlen + 1) {
                 /* If we're matching against an absolute-path pattern,                  /* If we're matching against an absolute-path pattern,
                  * we need to prepend our full path info. */                   * we need to prepend our full path info. */
                 strings[str_cnt++] = curr_dir + module_dirlen + 1;                  strings[str_cnt++] = curr_dir + module_dirlen + 1;
                 strings[str_cnt++] = "/";                  strings[str_cnt++] = "/";
        } else if (ex->match_flags & MATCHFLG_WILD2_PREFIX && *fname != '/') {        } else if (ex->rflags & FILTRULE_WILD2_PREFIX && *fname != '/') {
                 /* Allow "**"+"/" to match at the start of the string. */                  /* Allow "**"+"/" to match at the start of the string. */
                 strings[str_cnt++] = "/";                  strings[str_cnt++] = "/";
         }          }
         strings[str_cnt++] = name;          strings[str_cnt++] = name;
         if (name_is_dir) {          if (name_is_dir) {
                 /* Allow a trailing "/"+"***" to match the directory. */                  /* Allow a trailing "/"+"***" to match the directory. */
                if (ex->match_flags & MATCHFLG_WILD3_SUFFIX)                if (ex->rflags & FILTRULE_WILD3_SUFFIX)
                         strings[str_cnt++] = "/";                          strings[str_cnt++] = "/";
        } else if (ex->match_flags & MATCHFLG_DIRECTORY)        } else if (ex->rflags & FILTRULE_DIRECTORY)
                 return !ret_match;                  return !ret_match;
         strings[str_cnt] = NULL;          strings[str_cnt] = NULL;
   
Line 584  static int rule_matches(const char *fname, struct filt Line 664  static int rule_matches(const char *fname, struct filt
         }          }
   
         if (!anchored_match && ex->u.slash_cnt          if (!anchored_match && ex->u.slash_cnt
            && !(ex->match_flags & MATCHFLG_WILD2)) {            && !(ex->rflags & FILTRULE_WILD2)) {
                 /* A non-anchored match with an infix slash and no "**"                  /* A non-anchored match with an infix slash and no "**"
                  * needs to match the last slash_cnt+1 name elements. */                   * needs to match the last slash_cnt+1 name elements. */
                 slash_handling = ex->u.slash_cnt + 1;                  slash_handling = ex->u.slash_cnt + 1;
        } else if (!anchored_match && !(ex->match_flags & MATCHFLG_WILD2_PREFIX)        } else if (!anchored_match && !(ex->rflags & FILTRULE_WILD2_PREFIX)
                                   && ex->match_flags & MATCHFLG_WILD2) {                                   && ex->rflags & FILTRULE_WILD2) {
                 /* A non-anchored match with an infix or trailing "**" (but not                  /* A non-anchored match with an infix or trailing "**" (but not
                  * a prefixed "**") needs to try matching after every slash. */                   * a prefixed "**") needs to try matching after every slash. */
                 slash_handling = -1;                  slash_handling = -1;
Line 598  static int rule_matches(const char *fname, struct filt Line 678  static int rule_matches(const char *fname, struct filt
                 slash_handling = 0;                  slash_handling = 0;
         }          }
   
        if (ex->match_flags & MATCHFLG_WILD) {        if (ex->rflags & FILTRULE_WILD) {
                 if (wildmatch_array(pattern, strings, slash_handling))                  if (wildmatch_array(pattern, strings, slash_handling))
                         return ret_match;                          return ret_match;
         } else if (str_cnt > 1) {          } else if (str_cnt > 1) {
Line 620  static int rule_matches(const char *fname, struct filt Line 700  static int rule_matches(const char *fname, struct filt
         return !ret_match;          return !ret_match;
 }  }
   
   
 static void report_filter_result(enum logcode code, char const *name,  static void report_filter_result(enum logcode code, char const *name,
                                 struct filter_struct const *ent,                                 filter_rule const *ent,
                                 int name_is_dir, const char *type)                                 int name_is_dir, const char *type)
 {  {
         /* If a trailing slash is present to match only directories,          /* If a trailing slash is present to match only directories,
          * then it is stripped out by add_rule().  So as a special           * then it is stripped out by add_rule().  So as a special
          * case we add it back in here. */           * case we add it back in here. */
   
        if (verbose >= 2) {        if (DEBUG_GTE(FILTER, 1)) {
                 static char *actions[2][2]                  static char *actions[2][2]
                     = { {"show", "hid"}, {"risk", "protect"} };                      = { {"show", "hid"}, {"risk", "protect"} };
                 const char *w = who_am_i();                  const char *w = who_am_i();
                 rprintf(code, "[%s] %sing %s %s because of pattern %s%s%s\n",                  rprintf(code, "[%s] %sing %s %s because of pattern %s%s%s\n",
                    w, actions[*w!='s'][!(ent->match_flags&MATCHFLG_INCLUDE)],                    w, actions[*w!='s'][!(ent->rflags & FILTRULE_INCLUDE)],
                     name_is_dir ? "directory" : "file", name, ent->pattern,                      name_is_dir ? "directory" : "file", name, ent->pattern,
                    ent->match_flags & MATCHFLG_DIRECTORY ? "/" : "", type);                    ent->rflags & FILTRULE_DIRECTORY ? "/" : "", type);
         }          }
 }  }
   
/* Return -1 if file "name" is defined to be excluded by the specified
/* * exclude list, 1 if it is included, and 0 if it was not matched. */
 * Return -1 if file "name" is defined to be excluded by the specifiedint check_filter(filter_rule_list *listp, enum logcode code,
 * exclude list, 1 if it is included, and 0 if it was not matched. 
 */ 
int check_filter(struct filter_list_struct *listp, enum logcode code, 
                  const char *name, int name_is_dir)                   const char *name, int name_is_dir)
 {  {
        struct filter_struct *ent;        filter_rule *ent;
   
         for (ent = listp->head; ent; ent = ent->next) {          for (ent = listp->head; ent; ent = ent->next) {
                if (ignore_perishable && ent->match_flags & MATCHFLG_PERISHABLE)                if (ignore_perishable && ent->rflags & FILTRULE_PERISHABLE)
                         continue;                          continue;
                if (ent->match_flags & MATCHFLG_PERDIR_MERGE) {                if (ent->rflags & FILTRULE_PERDIR_MERGE) {
                         int rc = check_filter(ent->u.mergelist, code, name,                          int rc = check_filter(ent->u.mergelist, code, name,
                                               name_is_dir);                                                name_is_dir);
                         if (rc)                          if (rc)
                                 return rc;                                  return rc;
                         continue;                          continue;
                 }                  }
                if (ent->match_flags & MATCHFLG_CVS_IGNORE) {                if (ent->rflags & FILTRULE_CVS_IGNORE) {
                         int rc = check_filter(&cvs_filter_list, code, name,                          int rc = check_filter(&cvs_filter_list, code, name,
                                               name_is_dir);                                                name_is_dir);
                         if (rc)                          if (rc)
Line 670  int check_filter(struct filter_list_struct *listp, enu Line 746  int check_filter(struct filter_list_struct *listp, enu
                 if (rule_matches(name, ent, name_is_dir)) {                  if (rule_matches(name, ent, name_is_dir)) {
                         report_filter_result(code, name, ent, name_is_dir,                          report_filter_result(code, name, ent, name_is_dir,
                                              listp->debug_type);                                               listp->debug_type);
                        return ent->match_flags & MATCHFLG_INCLUDE ? 1 : -1;                        return ent->rflags & FILTRULE_INCLUDE ? 1 : -1;
                 }                  }
         }          }
   
Line 690  static const uchar *rule_strcmp(const uchar *str, cons Line 766  static const uchar *rule_strcmp(const uchar *str, cons
         return NULL;          return NULL;
 }  }
   
/* Get the next include/exclude arg from the string.  The token will not#define FILTRULES_FROM_CONTAINER (FILTRULE_ABS_PATH | FILTRULE_INCLUDE \
 * be '\0' terminated, so use the returned length to limit the string.                                | FILTRULE_DIRECTORY | FILTRULE_NEGATE \
 * Also, be sure to add this length to the returned pointer before passing                                | FILTRULE_PERISHABLE)
 * it back to ask for the next token.  This routine parses the "!" (list-
 * clearing) token and (depending on the mflags) the various prefixes./* Gets the next include/exclude rule from *rulestr_ptr and advances
 * The *mflags_ptr value will be set on exit to the new MATCHFLG_* bits * *rulestr_ptr to point beyond it.  Stores the pattern's start (within
 * for the current token. */ * *rulestr_ptr) and length in *pat_ptr and *pat_len_ptr, and returns a newly
static const char *parse_rule_tok(const char *p, uint32 mflags, int xflags, * allocated filter_rule containing the rest of the information.  Returns
                                  unsigned int *len_ptr, uint32 *mflags_ptr) * 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;        const uchar *s = (const uchar *)*rulestr_ptr;
        uint32 new_mflags;        filter_rule *rule;
         unsigned int len;          unsigned int len;
   
        if (mflags & MATCHFLG_WORD_SPLIT) {        if (template->rflags & FILTRULE_WORD_SPLIT) {
                 /* Skip over any initial whitespace. */                  /* Skip over any initial whitespace. */
                 while (isspace(*s))                  while (isspace(*s))
                         s++;                          s++;
                 /* Update to point to real start of rule. */                  /* Update to point to real start of rule. */
                p = (const char *)s;                *rulestr_ptr = (const char *)s;
         }          }
         if (!*s)          if (!*s)
                 return NULL;                  return NULL;
   
        new_mflags = mflags & MATCHFLGS_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          /* 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         * that if FILTRULE_NO_PREFIXES is set, the rule is either an include
         * or an exclude based on the inheritance of the MATCHFLG_INCLUDE         * or an exclude based on the inheritance of the FILTRULE_INCLUDE
          * flag (above).  XFLG_OLD_PREFIXES indicates a compatibility mode           * flag (above).  XFLG_OLD_PREFIXES indicates a compatibility mode
          * for old include/exclude patterns where just "+ " and "- " are           * for old include/exclude patterns where just "+ " and "- " are
          * allowed as optional prefixes.  */           * allowed as optional prefixes.  */
        if (mflags & MATCHFLG_NO_PREFIXES) {        if (template->rflags & FILTRULE_NO_PREFIXES) {
                if (*s == '!' && mflags & MATCHFLG_CVS_IGNORE)                if (*s == '!' && template->rflags & FILTRULE_CVS_IGNORE)
                        new_mflags |= MATCHFLG_CLEAR_LIST; /* Tentative! */                        rule->rflags |= FILTRULE_CLEAR_LIST; /* Tentative! */
         } else if (xflags & XFLG_OLD_PREFIXES) {          } else if (xflags & XFLG_OLD_PREFIXES) {
                 if (*s == '-' && s[1] == ' ') {                  if (*s == '-' && s[1] == ' ') {
                        new_mflags &= ~MATCHFLG_INCLUDE;                        rule->rflags &= ~FILTRULE_INCLUDE;
                         s += 2;                          s += 2;
                 } else if (*s == '+' && s[1] == ' ') {                  } else if (*s == '+' && s[1] == ' ') {
                        new_mflags |= MATCHFLG_INCLUDE;                        rule->rflags |= FILTRULE_INCLUDE;
                         s += 2;                          s += 2;
                 } else if (*s == '!')                  } else if (*s == '!')
                        new_mflags |= MATCHFLG_CLEAR_LIST; /* Tentative! */                        rule->rflags |= FILTRULE_CLEAR_LIST; /* Tentative! */
         } else {          } else {
                char ch = 0, *mods = "";                char ch = 0;
                 BOOL prefix_specifies_side = False;
                 switch (*s) {                  switch (*s) {
                 case 'c':                  case 'c':
                         if ((s = RULE_STRCMP(s, "clear")) != NULL)                          if ((s = RULE_STRCMP(s, "clear")) != NULL)
Line 781  static const char *parse_rule_tok(const char *p, uint3 Line 869  static const char *parse_rule_tok(const char *p, uint3
                 }                  }
                 switch (ch) {                  switch (ch) {
                 case ':':                  case ':':
                        new_mflags |= MATCHFLG_PERDIR_MERGE                        rule->rflags |= FILTRULE_PERDIR_MERGE
                                    | MATCHFLG_FINISH_SETUP;                                      | FILTRULE_FINISH_SETUP;
                         /* FALL THROUGH */                          /* FALL THROUGH */
                 case '.':                  case '.':
                        new_mflags |= MATCHFLG_MERGE_FILE;                        rule->rflags |= FILTRULE_MERGE_FILE;
                        mods = MODIFIERS_INCL_EXCL MODIFIERS_MERGE_FILE; 
                         break;                          break;
                 case '+':                  case '+':
                        new_mflags |= MATCHFLG_INCLUDE;                        rule->rflags |= FILTRULE_INCLUDE;
                        /* FALL THROUGH */                        break;
                 case '-':                  case '-':
                         mods = MODIFIERS_INCL_EXCL;  
                         break;                          break;
                 case 'S':                  case 'S':
                        new_mflags |= MATCHFLG_INCLUDE;                        rule->rflags |= FILTRULE_INCLUDE;
                         /* FALL THROUGH */                          /* FALL THROUGH */
                 case 'H':                  case 'H':
                        new_mflags |= MATCHFLG_SENDER_SIDE;                        rule->rflags |= FILTRULE_SENDER_SIDE;
                        mods = MODIFIERS_HIDE_PROTECT;                        prefix_specifies_side = True;
                         break;                          break;
                 case 'R':                  case 'R':
                        new_mflags |= MATCHFLG_INCLUDE;                        rule->rflags |= FILTRULE_INCLUDE;
                         /* FALL THROUGH */                          /* FALL THROUGH */
                 case 'P':                  case 'P':
                        new_mflags |= MATCHFLG_RECEIVER_SIDE;                        rule->rflags |= FILTRULE_RECEIVER_SIDE;
                        mods = MODIFIERS_HIDE_PROTECT;                        prefix_specifies_side = True;
                         break;                          break;
                 case '!':                  case '!':
                        new_mflags |= MATCHFLG_CLEAR_LIST;                        rule->rflags |= FILTRULE_CLEAR_LIST;
                        mods = NULL; 
                         break;                          break;
                 default:                  default:
                        rprintf(FERROR, "Unknown filter rule: `%s'\n", p);                        rprintf(FERROR, "Unknown filter rule: `%s'\n", *rulestr_ptr);
                         exit_cleanup(RERR_SYNTAX);                          exit_cleanup(RERR_SYNTAX);
                 }                  }
                while (mods && *++s && *s != ' ' && *s != '_') {                while (ch != '!' && *++s && *s != ' ' && *s != '_') {
                        if (strchr(mods, *s) == NULL) {                        if (template->rflags & FILTRULE_WORD_SPLIT && isspace(*s)) {
                                if (mflags & MATCHFLG_WORD_SPLIT && isspace(*s)) {                                s--;
                                        s--;                                break;
                                        break;                        }
                                }                        switch (*s) {
                         default:
                             invalid:                              invalid:
                                 rprintf(FERROR,                                  rprintf(FERROR,
                                        "invalid modifier sequence at '%c' in filter rule: %s\n",                                        "invalid modifier '%c' at position %d in filter rule: %s\n",
                                        *s, p);                                        *s, (int)(s - (const uchar *)*rulestr_ptr), *rulestr_ptr);
                                 exit_cleanup(RERR_SYNTAX);                                  exit_cleanup(RERR_SYNTAX);
                         }  
                         switch (*s) {  
                         case '-':                          case '-':
                                if (new_mflags & MATCHFLG_NO_PREFIXES)                                if (!BITS_SETnUNSET(rule->rflags, FILTRULE_MERGE_FILE, FILTRULE_NO_PREFIXES))
                                    goto invalid;                                        goto invalid;
                                new_mflags |= MATCHFLG_NO_PREFIXES;                                rule->rflags |= FILTRULE_NO_PREFIXES;
                                 break;                                  break;
                         case '+':                          case '+':
                                if (new_mflags & MATCHFLG_NO_PREFIXES)                                if (!BITS_SETnUNSET(rule->rflags, FILTRULE_MERGE_FILE, FILTRULE_NO_PREFIXES))
                                    goto invalid;                                        goto invalid;
                                new_mflags |= MATCHFLG_NO_PREFIXES                                rule->rflags |= FILTRULE_NO_PREFIXES
                                            | MATCHFLG_INCLUDE;                                              | FILTRULE_INCLUDE;
                                 break;                                  break;
                         case '/':                          case '/':
                                new_mflags |= MATCHFLG_ABS_PATH;                                rule->rflags |= FILTRULE_ABS_PATH;
                                 break;                                  break;
                         case '!':                          case '!':
                                new_mflags |= MATCHFLG_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;                                  break;
                         case 'C':                          case 'C':
                                if (new_mflags & MATCHFLG_NO_PREFIXES)                                if (rule->rflags & FILTRULE_NO_PREFIXES || prefix_specifies_side)
                                    goto invalid;                                        goto invalid;
                                new_mflags |= MATCHFLG_NO_PREFIXES                                rule->rflags |= FILTRULE_NO_PREFIXES
                                            | MATCHFLG_WORD_SPLIT                                              | FILTRULE_WORD_SPLIT
                                            | MATCHFLG_NO_INHERIT                                              | FILTRULE_NO_INHERIT
                                            | MATCHFLG_CVS_IGNORE;                                              | FILTRULE_CVS_IGNORE;
                                 break;                                  break;
                         case 'e':                          case 'e':
                                new_mflags |= MATCHFLG_EXCLUDE_SELF;                                if (!(rule->rflags & FILTRULE_MERGE_FILE))
                                         goto invalid;
                                 rule->rflags |= FILTRULE_EXCLUDE_SELF;
                                 break;                                  break;
                         case 'n':                          case 'n':
                                new_mflags |= MATCHFLG_NO_INHERIT;                                if (!(rule->rflags & FILTRULE_MERGE_FILE))
                                         goto invalid;
                                 rule->rflags |= FILTRULE_NO_INHERIT;
                                 break;                                  break;
                         case 'p':                          case 'p':
                                new_mflags |= MATCHFLG_PERISHABLE;                                rule->rflags |= FILTRULE_PERISHABLE;
                                 break;                                  break;
                         case 'r':                          case 'r':
                                new_mflags |= MATCHFLG_RECEIVER_SIDE;                                if (prefix_specifies_side)
                                         goto invalid;
                                 rule->rflags |= FILTRULE_RECEIVER_SIDE;
                                 break;                                  break;
                         case 's':                          case 's':
                                new_mflags |= MATCHFLG_SENDER_SIDE;                                if (prefix_specifies_side)
                                         goto invalid;
                                 rule->rflags |= FILTRULE_SENDER_SIDE;
                                 break;                                  break;
                         case 'w':                          case 'w':
                                new_mflags |= MATCHFLG_WORD_SPLIT;                                if (!(rule->rflags & FILTRULE_MERGE_FILE))
                                         goto invalid;
                                 rule->rflags |= FILTRULE_WORD_SPLIT;
                                 break;                                  break;
                         }                          }
                 }                  }
                 if (*s)                  if (*s)
                         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 (mflags & MATCHFLG_WORD_SPLIT) {        if (template->rflags & FILTRULE_WORD_SPLIT) {
                 const uchar *cp = s;                  const uchar *cp = s;
                 /* Token ends at whitespace or the end of the string. */                  /* Token ends at whitespace or the end of the string. */
                 while (!isspace(*cp) && *cp != '\0')                  while (!isspace(*cp) && *cp != '\0')
Line 887  static const char *parse_rule_tok(const char *p, uint3 Line 997  static const char *parse_rule_tok(const char *p, uint3
         } else          } else
                 len = strlen((char*)s);                  len = strlen((char*)s);
   
        if (new_mflags & MATCHFLG_CLEAR_LIST) {        if (rule->rflags & FILTRULE_CLEAR_LIST) {
                if (!(mflags & MATCHFLG_NO_PREFIXES)                if (!(rule->rflags & FILTRULE_NO_PREFIXES)
                  && !(xflags & XFLG_OLD_PREFIXES) && len) {                   && !(xflags & XFLG_OLD_PREFIXES) && len) {
                         rprintf(FERROR,                          rprintf(FERROR,
                                "'!' rule has trailing characters: %s\n", p);                                "'!' rule has trailing characters: %s\n", *rulestr_ptr);
                         exit_cleanup(RERR_SYNTAX);                          exit_cleanup(RERR_SYNTAX);
                 }                  }
                 if (len > 1)                  if (len > 1)
                        new_mflags &= ~MATCHFLG_CLEAR_LIST;                        rule->rflags &= ~FILTRULE_CLEAR_LIST;
        } else if (!len && !(new_mflags & MATCHFLG_CVS_IGNORE)) {        } else if (!len && !(rule->rflags & FILTRULE_CVS_IGNORE)) {
                rprintf(FERROR, "unexpected end of filter rule: %s\n", p);                rprintf(FERROR, "unexpected end of filter rule: %s\n", *rulestr_ptr);
                 exit_cleanup(RERR_SYNTAX);                  exit_cleanup(RERR_SYNTAX);
         }          }
   
        /* --delete-excluded turns an un-modified include/exclude into a sender-side rule. */        /* --delete-excluded turns an un-modified include/exclude into a sender-side rule.  */
         if (delete_excluded          if (delete_excluded
         && !(new_mflags & (MATCHFLG_RECEIVER_SIDE|MATCHFLG_SENDER_SIDE|MATCHFLG_MERGE_FILE|MATCHFLG_PERDIR_MERGE)))         && !(rule->rflags & (FILTRULES_SIDES|FILTRULE_MERGE_FILE|FILTRULE_PERDIR_MERGE)))
                new_mflags |= MATCHFLG_SENDER_SIDE;                rule->rflags |= FILTRULE_SENDER_SIDE;
   
        *len_ptr = len;        *pat_ptr = (const char *)s;
        *mflags_ptr = new_mflags;        *pat_len_ptr = len;
        return (const char *)s;        *rulestr_ptr = *pat_ptr + len;
         return rule;
 }  }
   
   
 static char default_cvsignore[] =  static char default_cvsignore[] =
         /* These default ignored items come from the CVS manual. */          /* These default ignored items come from the CVS manual. */
         "RCS SCCS CVS CVS.adm RCSLOG cvslog.* tags TAGS"          "RCS SCCS CVS CVS.adm RCSLOG cvslog.* tags TAGS"
Line 922  static char default_cvsignore[] = Line 1032  static char default_cvsignore[] =
         /* The rest we added to suit ourself. */          /* The rest we added to suit ourself. */
         " .svn/ .git/ .hg/ .bzr/";          " .svn/ .git/ .hg/ .bzr/";
   
static void get_cvs_excludes(uint32 mflags)static void get_cvs_excludes(uint32 rflags)
 {  {
         static int initialized = 0;          static int initialized = 0;
         char *p, fname[MAXPATHLEN];          char *p, fname[MAXPATHLEN];
Line 931  static void get_cvs_excludes(uint32 mflags) Line 1041  static void get_cvs_excludes(uint32 mflags)
                 return;                  return;
         initialized = 1;          initialized = 1;
   
        parse_rule(&cvs_filter_list, default_cvsignore,        parse_filter_str(&cvs_filter_list, default_cvsignore,
                   mflags | (protocol_version >= 30 ? MATCHFLG_PERISHABLE : 0),                         rule_template(rflags | (protocol_version >= 30 ? FILTRULE_PERISHABLE : 0)),
                   0);                         0);
   
         p = module_id >= 0 && lp_use_chroot(module_id) ? "/" : getenv("HOME");          p = module_id >= 0 && lp_use_chroot(module_id) ? "/" : getenv("HOME");
         if (p && pathjoin(fname, MAXPATHLEN, p, ".cvsignore") < MAXPATHLEN)          if (p && pathjoin(fname, MAXPATHLEN, p, ".cvsignore") < MAXPATHLEN)
                parse_filter_file(&cvs_filter_list, fname, mflags, 0);                parse_filter_file(&cvs_filter_list, fname, rule_template(rflags), 0);
   
        parse_rule(&cvs_filter_list, getenv("CVSIGNORE"), mflags, 0);        parse_filter_str(&cvs_filter_list, getenv("CVSIGNORE"), rule_template(rflags), 0);
 }  }
   
   const filter_rule *rule_template(uint32 rflags)
   {
           static filter_rule template; /* zero-initialized */
           template.rflags = rflags;
           return &template;
   }
   
void parse_rule(struct filter_list_struct *listp, const char *pattern,void parse_filter_str(filter_rule_list *listp, const char *rulestr,
                uint32 mflags, int xflags)                     const filter_rule *template, int xflags)
 {  {
           filter_rule *rule;
           const char *pat;
         unsigned int pat_len;          unsigned int pat_len;
         uint32 new_mflags;  
         const char *cp, *p;  
   
        if (!pattern)        if (!rulestr)
                 return;                  return;
   
         while (1) {          while (1) {
                   uint32 new_rflags;
   
                 /* Remember that the returned string is NOT '\0' terminated! */                  /* Remember that the returned string is NOT '\0' terminated! */
                cp = parse_rule_tok(pattern, mflags, xflags,                if (!(rule = parse_rule_tok(&rulestr, template, xflags, &pat, &pat_len)))
                                    &pat_len, &new_mflags); 
                if (!cp) 
                         break;                          break;
   
                 pattern = cp + pat_len;  
   
                 if (pat_len >= MAXPATHLEN) {                  if (pat_len >= MAXPATHLEN) {
                         rprintf(FERROR, "discarding over-long filter: %.*s\n",                          rprintf(FERROR, "discarding over-long filter: %.*s\n",
                                (int)pat_len, cp);                                (int)pat_len, pat);
                     free_continue:
                         free_filter(rule);
                         continue;                          continue;
                 }                  }
   
                if (new_mflags & MATCHFLG_CLEAR_LIST) {                new_rflags = rule->rflags;
                        if (verbose > 2) {                if (new_rflags & FILTRULE_CLEAR_LIST) {
                         if (DEBUG_GTE(FILTER, 2)) {
                                 rprintf(FINFO,                                  rprintf(FINFO,
                                         "[%s] clearing filter list%s\n",                                          "[%s] clearing filter list%s\n",
                                         who_am_i(), listp->debug_type);                                          who_am_i(), listp->debug_type);
                         }                          }
                        clear_filter_list(listp);                        pop_filter_list(listp);
                        continue;                        listp->head = NULL;
                         goto free_continue;
                 }                  }
   
                if (new_mflags & MATCHFLG_MERGE_FILE) {                if (new_rflags & FILTRULE_MERGE_FILE) {
                        unsigned int len; 
                         if (!pat_len) {                          if (!pat_len) {
                                cp = ".cvsignore";                                pat = ".cvsignore";
                                 pat_len = 10;                                  pat_len = 10;
                         }                          }
                        len = pat_len;                        if (new_rflags & FILTRULE_EXCLUDE_SELF) {
                        if (new_mflags & MATCHFLG_EXCLUDE_SELF) {                                const char *name;
                                const char *name = cp + len;                                filter_rule *excl_self;
                                while (name > cp && name[-1] != '/') name--;
                                len -= name - cp;                                if (!(excl_self = new0(filter_rule)))
                                add_rule(listp, name, len, 0, 0);                                        out_of_memory("parse_filter_str");
                                new_mflags &= ~MATCHFLG_EXCLUDE_SELF;                                /* Find the beginning of the basename and add an exclude for it. */
                                len = pat_len;                                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_mflags & MATCHFLG_PERDIR_MERGE) {                        if (new_rflags & FILTRULE_PERDIR_MERGE) {
                                 if (parent_dirscan) {                                  if (parent_dirscan) {
                                        if (!(p = parse_merge_name(cp, &len,                                        const char *p;
                                                                module_dirlen)))                                        unsigned int len = pat_len;
                                                continue;                                        if ((p = parse_merge_name(pat, &len, module_dirlen)))
                                        add_rule(listp, p, len, new_mflags, 0);                                                add_rule(listp, p, len, rule, 0);
                                         else
                                                 free_filter(rule);
                                         continue;                                          continue;
                                 }                                  }
                         } else {                          } else {
                                if (!(p = parse_merge_name(cp, &len, 0)))                                const char *p;
                                        continue;                                unsigned int len = pat_len;
                                parse_filter_file(listp, p, new_mflags,                                if ((p = parse_merge_name(pat, &len, 0)))
                                                  XFLG_FATAL_ERRORS);                                        parse_filter_file(listp, p, rule, XFLG_FATAL_ERRORS);
                                 free_filter(rule);
                                 continue;                                  continue;
                         }                          }
                 }                  }
   
                add_rule(listp, cp, pat_len, new_mflags, xflags);                add_rule(listp, pat, pat_len, rule, xflags);
   
                if (new_mflags & MATCHFLG_CVS_IGNORE                if (new_rflags & FILTRULE_CVS_IGNORE
                    && !(new_mflags & MATCHFLG_MERGE_FILE))                    && !(new_rflags & FILTRULE_MERGE_FILE))
                        get_cvs_excludes(new_mflags);                        get_cvs_excludes(new_rflags);
         }          }
 }  }
   
void parse_filter_file(filter_rule_list *listp, const char *fname, const filter_rule *template, int xflags)
void parse_filter_file(struct filter_list_struct *listp, const char *fname, 
                       uint32 mflags, int xflags) 
 {  {
         FILE *fp;          FILE *fp;
         char line[BIGPATHBUFLEN];          char line[BIGPATHBUFLEN];
         char *eob = line + sizeof line - 1;          char *eob = line + sizeof line - 1;
        int word_split = mflags & MATCHFLG_WORD_SPLIT;        BOOL word_split = (template->rflags & FILTRULE_WORD_SPLIT) != 0;
   
         if (!fname || !*fname)          if (!fname || !*fname)
                 return;                  return;
Line 1043  void parse_filter_file(struct filter_list_struct *list Line 1163  void parse_filter_file(struct filter_list_struct *list
         } else          } else
                 fp = stdin;                  fp = stdin;
   
        if (verbose > 2) {        if (DEBUG_GTE(FILTER, 2)) {
                 rprintf(FINFO, "[%s] parse_filter_file(%s,%x,%x)%s\n",                  rprintf(FINFO, "[%s] parse_filter_file(%s,%x,%x)%s\n",
                        who_am_i(), fname, mflags, xflags,                        who_am_i(), fname, template->rflags, xflags,
                         fp ? "" : " [not found]");                          fp ? "" : " [not found]");
         }          }
   
Line 1053  void parse_filter_file(struct filter_list_struct *list Line 1173  void parse_filter_file(struct filter_list_struct *list
                 if (xflags & XFLG_FATAL_ERRORS) {                  if (xflags & XFLG_FATAL_ERRORS) {
                         rsyserr(FERROR, errno,                          rsyserr(FERROR, errno,
                                 "failed to open %sclude file %s",                                  "failed to open %sclude file %s",
                                mflags & MATCHFLG_INCLUDE ? "in" : "ex",                                template->rflags & FILTRULE_INCLUDE ? "in" : "ex",
                                 fname);                                  fname);
                         exit_cleanup(RERR_FILEIO);                          exit_cleanup(RERR_FILEIO);
                 }                  }
Line 1088  void parse_filter_file(struct filter_list_struct *list Line 1208  void parse_filter_file(struct filter_list_struct *list
                 *s = '\0';                  *s = '\0';
                 /* Skip an empty token and (when line parsing) comments. */                  /* Skip an empty token and (when line parsing) comments. */
                 if (*line && (word_split || (*line != ';' && *line != '#')))                  if (*line && (word_split || (*line != ';' && *line != '#')))
                        parse_rule(listp, line, mflags, xflags);                        parse_filter_str(listp, line, template, xflags);
                 if (ch == EOF)                  if (ch == EOF)
                         break;                          break;
         }          }
Line 1098  void parse_filter_file(struct filter_list_struct *list Line 1218  void parse_filter_file(struct filter_list_struct *list
 /* If the "for_xfer" flag is set, 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   * current protocol_version (if possible) or a NULL is returned (if not
  * possible). */   * possible). */
char *get_rule_prefix(int match_flags, const char *pat, int for_xfer,char *get_rule_prefix(filter_rule *rule, const char *pat, int for_xfer,
                       unsigned int *plen_ptr)                        unsigned int *plen_ptr)
 {  {
         static char buf[MAX_RULE_PREFIX+1];          static char buf[MAX_RULE_PREFIX+1];
         char *op = buf;          char *op = buf;
         int legal_len = for_xfer && protocol_version < 29 ? 1 : MAX_RULE_PREFIX-1;          int legal_len = for_xfer && protocol_version < 29 ? 1 : MAX_RULE_PREFIX-1;
   
        if (match_flags & MATCHFLG_PERDIR_MERGE) {        if (rule->rflags & FILTRULE_PERDIR_MERGE) {
                 if (legal_len == 1)                  if (legal_len == 1)
                         return NULL;                          return NULL;
                 *op++ = ':';                  *op++ = ':';
        } else if (match_flags & MATCHFLG_INCLUDE)        } else if (rule->rflags & FILTRULE_INCLUDE)
                 *op++ = '+';                  *op++ = '+';
         else if (legal_len != 1          else if (legal_len != 1
             || ((*pat == '-' || *pat == '+') && pat[1] == ' '))              || ((*pat == '-' || *pat == '+') && pat[1] == ' '))
Line 1117  char *get_rule_prefix(int match_flags, const char *pat Line 1237  char *get_rule_prefix(int match_flags, const char *pat
         else          else
                 legal_len = 0;                  legal_len = 0;
   
        if (match_flags & MATCHFLG_ABS_PATH)        if (rule->rflags & FILTRULE_ABS_PATH)
                 *op++ = '/';                  *op++ = '/';
        if (match_flags & MATCHFLG_NEGATE)        if (rule->rflags & FILTRULE_NEGATE)
                 *op++ = '!';                  *op++ = '!';
        if (match_flags & MATCHFLG_CVS_IGNORE)        if (rule->rflags & FILTRULE_CVS_IGNORE)
                 *op++ = 'C';                  *op++ = 'C';
         else {          else {
                if (match_flags & MATCHFLG_NO_INHERIT)                if (rule->rflags & FILTRULE_NO_INHERIT)
                         *op++ = 'n';                          *op++ = 'n';
                if (match_flags & MATCHFLG_WORD_SPLIT)                if (rule->rflags & FILTRULE_WORD_SPLIT)
                         *op++ = 'w';                          *op++ = 'w';
                if (match_flags & MATCHFLG_NO_PREFIXES) {                if (rule->rflags & FILTRULE_NO_PREFIXES) {
                        if (match_flags & MATCHFLG_INCLUDE)                        if (rule->rflags & FILTRULE_INCLUDE)
                                 *op++ = '+';                                  *op++ = '+';
                         else                          else
                                 *op++ = '-';                                  *op++ = '-';
                 }                  }
         }          }
        if (match_flags & MATCHFLG_EXCLUDE_SELF)        if (rule->rflags & FILTRULE_EXCLUDE_SELF)
                 *op++ = 'e';                  *op++ = 'e';
        if (match_flags & MATCHFLG_SENDER_SIDE        if (rule->rflags & FILTRULE_SENDER_SIDE
             && (!for_xfer || protocol_version >= 29))              && (!for_xfer || protocol_version >= 29))
                 *op++ = 's';                  *op++ = 's';
        if (match_flags & MATCHFLG_RECEIVER_SIDE        if (rule->rflags & FILTRULE_RECEIVER_SIDE
             && (!for_xfer || protocol_version >= 29              && (!for_xfer || protocol_version >= 29
              || (delete_excluded && am_sender)))               || (delete_excluded && am_sender)))
                 *op++ = 'r';                  *op++ = 'r';
        if (match_flags & MATCHFLG_PERISHABLE) {        if (rule->rflags & FILTRULE_PERISHABLE) {
                 if (!for_xfer || protocol_version >= 30)                  if (!for_xfer || protocol_version >= 30)
                         *op++ = 'p';                          *op++ = 'p';
                 else if (am_sender)                  else if (am_sender)
Line 1160  char *get_rule_prefix(int match_flags, const char *pat Line 1280  char *get_rule_prefix(int match_flags, const char *pat
         return buf;          return buf;
 }  }
   
static void send_rules(int f_out, struct filter_list_struct *flp)static void send_rules(int f_out, filter_rule_list *flp)
 {  {
        struct filter_struct *ent, *prev = NULL;        filter_rule *ent, *prev = NULL;
   
         for (ent = flp->head; ent; ent = ent->next) {          for (ent = flp->head; ent; ent = ent->next) {
                 unsigned int len, plen, dlen;                  unsigned int len, plen, dlen;
Line 1176  static void send_rules(int f_out, struct filter_list_s Line 1296  static void send_rules(int f_out, struct filter_list_s
                  * backward compatibility problem, and we elide any no-prefix                   * backward compatibility problem, and we elide any no-prefix
                  * merge files as an optimization (since they can only have                   * merge files as an optimization (since they can only have
                  * include/exclude rules). */                   * include/exclude rules). */
                if (ent->match_flags & MATCHFLG_SENDER_SIDE)                if (ent->rflags & FILTRULE_SENDER_SIDE)
                         elide = am_sender ? 1 : -1;                          elide = am_sender ? 1 : -1;
                if (ent->match_flags & MATCHFLG_RECEIVER_SIDE)                if (ent->rflags & FILTRULE_RECEIVER_SIDE)
                         elide = elide ? 0 : am_sender ? -1 : 1;                          elide = elide ? 0 : am_sender ? -1 : 1;
                 else if (delete_excluded && !elide                  else if (delete_excluded && !elide
                 && (!(ent->match_flags & MATCHFLG_PERDIR_MERGE)                 && (!(ent->rflags & FILTRULE_PERDIR_MERGE)
                  || ent->match_flags & MATCHFLG_NO_PREFIXES))                  || ent->rflags & FILTRULE_NO_PREFIXES))
                         elide = am_sender ? 1 : -1;                          elide = am_sender ? 1 : -1;
                 if (elide < 0) {                  if (elide < 0) {
                         if (prev)                          if (prev)
Line 1193  static void send_rules(int f_out, struct filter_list_s Line 1313  static void send_rules(int f_out, struct filter_list_s
                         prev = ent;                          prev = ent;
                 if (elide > 0)                  if (elide > 0)
                         continue;                          continue;
                if (ent->match_flags & MATCHFLG_CVS_IGNORE                if (ent->rflags & FILTRULE_CVS_IGNORE
                    && !(ent->match_flags & MATCHFLG_MERGE_FILE)) {                    && !(ent->rflags & FILTRULE_MERGE_FILE)) {
                         int f = am_sender || protocol_version < 29 ? f_out : -2;                          int f = am_sender || protocol_version < 29 ? f_out : -2;
                         send_rules(f, &cvs_filter_list);                          send_rules(f, &cvs_filter_list);
                         if (f == f_out)                          if (f == f_out)
                                 continue;                                  continue;
                 }                  }
                p = get_rule_prefix(ent->match_flags, ent->pattern, 1, &plen);                p = get_rule_prefix(ent, ent->pattern, 1, &plen);
                 if (!p) {                  if (!p) {
                         rprintf(FERROR,                          rprintf(FERROR,
                                 "filter rules are too modern for remote rsync.\n");                                  "filter rules are too modern for remote rsync.\n");
Line 1209  static void send_rules(int f_out, struct filter_list_s Line 1329  static void send_rules(int f_out, struct filter_list_s
                 if (f_out < 0)                  if (f_out < 0)
                         continue;                          continue;
                 len = strlen(ent->pattern);                  len = strlen(ent->pattern);
                dlen = ent->match_flags & MATCHFLG_DIRECTORY ? 1 : 0;                dlen = ent->rflags & FILTRULE_DIRECTORY ? 1 : 0;
                 if (!(plen + len + dlen))                  if (!(plen + len + dlen))
                         continue;                          continue;
                 write_int(f_out, plen + len + dlen);                  write_int(f_out, plen + len + dlen);
Line 1232  void send_filter_list(int f_out) Line 1352  void send_filter_list(int f_out)
                 f_out = -1;                  f_out = -1;
         if (cvs_exclude && am_sender) {          if (cvs_exclude && am_sender) {
                 if (protocol_version >= 29)                  if (protocol_version >= 29)
                        parse_rule(&filter_list, ":C", 0, 0);                        parse_filter_str(&filter_list, ":C", rule_template(0), 0);
                parse_rule(&filter_list, "-C", 0, 0);                parse_filter_str(&filter_list, "-C", rule_template(0), 0);
         }          }
   
         send_rules(f_out, &filter_list);          send_rules(f_out, &filter_list);
Line 1243  void send_filter_list(int f_out) Line 1363  void send_filter_list(int f_out)
   
         if (cvs_exclude) {          if (cvs_exclude) {
                 if (!am_sender || protocol_version < 29)                  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)                  if (!am_sender)
                        parse_rule(&filter_list, "-C", 0, 0);                        parse_filter_str(&filter_list, "-C", rule_template(0), 0);
         }          }
 }  }
   
Line 1264  void recv_filter_list(int f_in) Line 1384  void recv_filter_list(int f_in)
                         if (len >= sizeof line)                          if (len >= sizeof line)
                                 overflow_exit("recv_rules");                                  overflow_exit("recv_rules");
                         read_sbuf(f_in, line, len);                          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 (cvs_exclude) {
                 if (local_server || am_sender || protocol_version < 29)                  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)                  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. */          if (local_server) /* filter out any rules that aren't for us. */

Removed from v.1.1.1.1  
changed lines
  Added in v.1.1.1.3


FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>