+}
+
+/* Each time rsync changes to a new directory it call this function to
+ * handle all the per-dir merge-files. The "dir" value is the current path
+ * relative to curr_dir (which might not be null-terminated). We copy it
+ * into dirbuf so that we can easily append a file name on the end. */
+void *push_local_filters(const char *dir, unsigned int dirlen)
+{
+ struct filter_list_struct *ap, *push;
+ int i;
+
+ set_filter_dir(dir, dirlen);
+
+ if (!mergelist_cnt)
+ return NULL;
+
+ push = new_array(struct filter_list_struct, mergelist_cnt);
+ if (!push)
+ out_of_memory("push_local_filters");
+
+ for (i = 0, ap = push; i < mergelist_cnt; i++) {
+ memcpy(ap++, mergelist_parents[i]->u.mergelist,
+ sizeof (struct filter_list_struct));
+ }
+
+ /* Note: parse_filter_file() might increase mergelist_cnt, so keep
+ * this loop separate from the above loop. */
+ for (i = 0; i < mergelist_cnt; i++) {
+ struct filter_struct *ex = mergelist_parents[i];
+ struct filter_list_struct *lp = ex->u.mergelist;
+
+ if (verbose > 2) {
+ rprintf(FINFO, "[%s] pushing filter list%s\n",
+ who_am_i(), lp->debug_type);
+ }
+
+ lp->tail = NULL; /* Switch any local rules to inherited. */
+ if (ex->match_flags & MATCHFLG_NO_INHERIT)
+ lp->head = NULL;
+
+ if (ex->match_flags & MATCHFLG_FINISH_SETUP) {
+ ex->match_flags &= ~MATCHFLG_FINISH_SETUP;
+ if (setup_merge_file(ex, lp))
+ set_filter_dir(dir, dirlen);
+ }
+
+ if (strlcpy(dirbuf + dirbuf_len, ex->pattern,
+ MAXPATHLEN - dirbuf_len) < MAXPATHLEN - dirbuf_len) {
+ parse_filter_file(lp, dirbuf, ex->match_flags,
+ XFLG_ANCHORED2ABS);
+ } else {
+ io_error |= IOERR_GENERAL;
+ rprintf(FINFO,
+ "cannot add local filter rules in long-named directory: %s\n",
+ full_fname(dirbuf));
+ }
+ dirbuf[dirbuf_len] = '\0';
+ }
+
+ return (void*)push;
+}
+
+void pop_local_filters(void *mem)
+{
+ struct filter_list_struct *ap, *pop = (struct filter_list_struct*)mem;
+ int i;
+
+ for (i = mergelist_cnt; i-- > 0; ) {
+ struct filter_struct *ex = mergelist_parents[i];
+ struct filter_list_struct *lp = ex->u.mergelist;
+
+ if (verbose > 2) {
+ rprintf(FINFO, "[%s] popping filter list%s\n",
+ who_am_i(), lp->debug_type);
+ }
+
+ clear_filter_list(lp);
+ }
+
+ if (!pop)
+ return;
+
+ for (i = 0, ap = pop; i < mergelist_cnt; i++) {
+ memcpy(mergelist_parents[i]->u.mergelist, ap++,
+ sizeof (struct filter_list_struct));
+ }
+
+ free(pop);
+}
+
+void change_local_filter_dir(const char *dname, int dlen, int dir_depth)
+{
+ static int cur_depth = -1;
+ static void *filt_array[MAXPATHLEN/2+1];
+
+ if (!dname) {
+ for ( ; cur_depth >= 0; cur_depth--) {
+ if (filt_array[cur_depth]) {
+ pop_local_filters(filt_array[cur_depth]);
+ filt_array[cur_depth] = NULL;
+ }
+ }
+ return;
+ }
+
+ assert(dir_depth < MAXPATHLEN/2+1);
+
+ for ( ; cur_depth >= dir_depth; cur_depth--) {
+ if (filt_array[cur_depth]) {
+ pop_local_filters(filt_array[cur_depth]);
+ filt_array[cur_depth] = NULL;
+ }
+ }
+
+ cur_depth = dir_depth;
+ filt_array[cur_depth] = push_local_filters(dname, dlen);
+}
+
+static int rule_matches(char *fname, struct filter_struct *ex, int name_is_dir)
+{
+ int slash_handling, str_cnt = 0, anchored_match = 0;
+ int ret_match = ex->match_flags & MATCHFLG_NEGATE ? 0 : 1;
+ char *p, *pattern = ex->pattern;
+ const char *strings[16]; /* more than enough */
+ char *name = fname + (*fname == '/');
+
+ if (!*name)
+ return 0;
+
+ 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 && *fname != '/'
+ && curr_dir_len > module_dirlen + 1) {
+ /* 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 && *fname != '/') {
+ /* Allow "**"+"/" to match at the start of the string. */
+ strings[str_cnt++] = "/";
+ }
+ 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 == '/') {
+ anchored_match = 1;
+ pattern++;
+ }