+ static char buf[MAXPATHLEN];
+ char *fn, tmpbuf[MAXPATHLEN];
+ unsigned int fn_len;
+
+ if (!parent_dirscan && *merge_file != '/') {
+ /* Return the name unchanged it doesn't have any slashes. */
+ if (len_ptr) {
+ const char *p = merge_file + *len_ptr;
+ while (--p > merge_file && *p != '/') {}
+ if (p == merge_file) {
+ strlcpy(buf, merge_file, *len_ptr + 1);
+ return buf;
+ }
+ } else if (strchr(merge_file, '/') == NULL)
+ return (char *)merge_file;
+ }
+
+ fn = *merge_file == '/' ? buf : tmpbuf;
+ if (sanitize_paths) {
+ const char *r = prefix_skip ? "/" : NULL;
+ /* null-terminate the name if it isn't already */
+ if (len_ptr && merge_file[*len_ptr]) {
+ char *to = fn == buf ? tmpbuf : buf;
+ strlcpy(to, merge_file, *len_ptr + 1);
+ merge_file = to;
+ }
+ if (!sanitize_path(fn, merge_file, r, dirbuf_depth, SP_DEFAULT)) {
+ rprintf(FERROR, "merge-file name overflows: %s\n",
+ merge_file);
+ return NULL;
+ }
+ fn_len = strlen(fn);
+ } else {
+ strlcpy(fn, merge_file, len_ptr ? *len_ptr + 1 : MAXPATHLEN);
+ fn_len = clean_fname(fn, CFN_COLLAPSE_DOT_DOT_DIRS);
+ }
+
+ /* If the name isn't in buf yet, it's wasn't absolute. */
+ if (fn != buf) {
+ int d_len = dirbuf_len - prefix_skip;
+ if (d_len + fn_len >= MAXPATHLEN) {
+ rprintf(FERROR, "merge-file name overflows: %s\n", fn);
+ return NULL;
+ }
+ memcpy(buf, dirbuf + prefix_skip, d_len);
+ memcpy(buf + d_len, fn, fn_len + 1);
+ fn_len = clean_fname(buf, CFN_COLLAPSE_DOT_DOT_DIRS);
+ }
+
+ if (len_ptr)
+ *len_ptr = fn_len;
+ return buf;
+}
+
+/* Sets the dirbuf and dirbuf_len values. */
+void set_filter_dir(const char *dir, unsigned int dirlen)
+{
+ 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(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;
+
+ 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)
+ lp->head = NULL;
+ lp->tail = NULL;
+ strlcpy(y, save, MAXPATHLEN);
+ while ((*x++ = *y++) != '/') {}
+ }
+ parent_dirscan = False;
+ free(pat);
+ return 1;