+ unsigned int len;
+ if (*dir != '/') {
+ memcpy(dirbuf, curr_dir, curr_dir_len);
+ dirbuf[curr_dir_len] = '/';
+ len = curr_dir_len + 1;
+ if (len + dirlen >= MAXPATHLEN)
+ dirlen = 0;
+ } else
+ len = 0;
+ memcpy(dirbuf + len, dir, dirlen);
+ dirbuf[dirlen + len] = '\0';
+ dirbuf_len = clean_fname(dirbuf, CFN_COLLAPSE_DOT_DOT_DIRS);
+ if (dirbuf_len > 1 && dirbuf[dirbuf_len-1] == '.'
+ && dirbuf[dirbuf_len-2] == '/')
+ dirbuf_len -= 2;
+ if (dirbuf_len != 1)
+ dirbuf[dirbuf_len++] = '/';
+ dirbuf[dirbuf_len] = '\0';
+ if (sanitize_paths)
+ dirbuf_depth = count_dir_elements(dirbuf + module_dirlen);
+}
+
+/* This routine takes a per-dir merge-file entry and finishes its setup.
+ * If the name has a path portion then we check to see if it refers to a
+ * 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
+ * for the per-dir merge-file in each one. */
+static BOOL setup_merge_file(int mergelist_num, struct filter_struct *ex,
+ struct filter_list_struct *lp)
+{
+ char buf[MAXPATHLEN];
+ char *x, *y, *pat = ex->pattern;
+ unsigned int len;
+
+ if (!(x = parse_merge_name(pat, NULL, 0)) || *x != '/')
+ 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 = '\0';
+ ex->pattern = strdup(y+1);
+ if (!*x)
+ x = "/";
+ if (*x == '/')
+ strlcpy(buf, x, MAXPATHLEN);
+ else
+ pathjoin(buf, MAXPATHLEN, dirbuf, x);
+
+ len = clean_fname(buf, CFN_COLLAPSE_DOT_DOT_DIRS);
+ if (len != 1 && len < MAXPATHLEN-1) {
+ buf[len++] = '/';
+ buf[len] = '\0';
+ }
+ /* This ensures that the specified dir is a parent of the transfer. */
+ for (x = buf, y = dirbuf; *x && *x == *y; x++, y++) {}
+ if (*x)
+ y += strlen(y); /* nope -- skip the scan */
+
+ parent_dirscan = True;
+ while (*y) {
+ char save[MAXPATHLEN];
+ strlcpy(save, y, MAXPATHLEN);
+ *y = '\0';
+ dirbuf_len = y - dirbuf;
+ strlcpy(x, ex->pattern, MAXPATHLEN - (x - buf));
+ parse_filter_file(lp, buf, ex->match_flags, XFLG_ANCHORED2ABS);
+ if (ex->match_flags & MATCHFLG_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->tail = NULL;
+ strlcpy(y, save, MAXPATHLEN);
+ while ((*x++ = *y++) != '/') {}
+ }
+ /* Save current head for freeing when the mergelist becomes inactive. */
+ lp->parent_dirscan_head = lp->head;
+ 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);
+ return 1;
+}
+
+struct local_filter_state {
+ int mergelist_cnt;
+ struct filter_list_struct mergelists[1];
+};
+
+/* 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 local_filter_state *push;
+ int i;
+
+ 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) {
+ /* No old state to save and no new merge files to push. */
+ return NULL;
+ }
+
+ push = (struct local_filter_state *)new_array(char,
+ sizeof (struct local_filter_state)
+ + (mergelist_cnt-1) * sizeof (struct filter_list_struct));
+ if (!push)
+ out_of_memory("push_local_filters");
+
+ push->mergelist_cnt = mergelist_cnt;
+ for (i = 0; i < mergelist_cnt; i++) {
+ memcpy(&push->mergelists[i], 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 (DEBUG_GTE(FILTER, 2)) {
+ rprintf(FINFO, "[%s] pushing mergelist #%d%s\n",
+ who_am_i(), 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(i, 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(FERROR,
+ "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 local_filter_state *pop = (struct local_filter_state *)mem;
+ 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; ) {
+ struct filter_struct *ex = mergelist_parents[i];
+ struct filter_list_struct *lp = ex->u.mergelist;
+
+ if (DEBUG_GTE(FILTER, 2)) {
+ rprintf(FINFO, "[%s] popping mergelist #%d%s\n",
+ who_am_i(), i, lp->debug_type);
+ }
+
+ clear_filter_list(lp);
+
+ if (i >= old_mergelist_cnt) {
+ /* This mergelist does not exist in the state to be
+ * restored. Free its parent_dirscan list to clean up
+ * any per-dir mergelists defined there so we don't
+ * crash trying to restore nonexistent state for them
+ * below. (Counterpart to setup_merge_file call in
+ * push_local_filters. Must be done here, not in
+ * free_filter, for LIFO order.) */
+ 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);
+ }
+ free_filters(lp->parent_dirscan_head);
+ lp->parent_dirscan_head = NULL;
+ }
+ }
+
+ /* If we cleaned things up properly, the only still-active mergelists
+ * should be those with a state to be restored. */
+ assert(mergelist_cnt == old_mergelist_cnt);
+
+ if (!pop) {
+ /* No state to restore. */
+ return;