+ 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 */
+ const 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++;
+ }
+
+ if (!anchored_match && ex->u.slash_cnt
+ && !(ex->match_flags & MATCHFLG_WILD2)) {
+ /* A non-anchored match with an infix slash and no "**"
+ * needs to match the last slash_cnt+1 name elements. */
+ slash_handling = ex->u.slash_cnt + 1;
+ } else if (!anchored_match && !(ex->match_flags & MATCHFLG_WILD2_PREFIX)
+ && ex->match_flags & MATCHFLG_WILD2) {
+ /* A non-anchored match with an infix or trailing "**" (but not
+ * a prefixed "**") needs to try matching after every slash. */
+ slash_handling = -1;
+ } else {
+ /* The pattern matches only at the start of the path or name. */
+ slash_handling = 0;
+ }
+
+ if (ex->match_flags & MATCHFLG_WILD) {
+ if (wildmatch_array(pattern, strings, slash_handling))
+ return ret_match;
+ } else if (str_cnt > 1) {
+ if (litmatch_array(pattern, strings, slash_handling))
+ return ret_match;
+ } else if (anchored_match) {
+ if (strcmp(name, pattern) == 0)
+ return ret_match;
+ } else {
+ int l1 = strlen(name);
+ int l2 = strlen(pattern);
+ if (l2 <= l1 &&
+ strcmp(name+(l1-l2),pattern) == 0 &&
+ (l1==l2 || name[l1-(l2+1)] == '/')) {
+ return ret_match;
+ }
+ }
+
+ return !ret_match;
+}
+
+
+static void report_filter_result(char const *name,
+ struct filter_struct const *ent,
+ int name_is_dir, const char *type)
+{
+ /* If a trailing slash is present to match only directories,
+ * then it is stripped out by add_rule(). So as a special
+ * case we add it back in here. */
+
+ if (verbose >= 2) {
+ static char *actions[2][2]
+ = { {"show", "hid"}, {"risk", "protect"} };
+ const char *w = who_am_i();
+ rprintf(FINFO, "[%s] %sing %s %s because of pattern %s%s%s\n",
+ w, actions[*w!='s'][!(ent->match_flags&MATCHFLG_INCLUDE)],
+ name_is_dir ? "directory" : "file", name, ent->pattern,
+ ent->match_flags & MATCHFLG_DIRECTORY ? "/" : "", type);
+ }
+}
+
+
+/*
+ * 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.
+ */
+int check_filter(struct filter_list_struct *listp, const char *name, int name_is_dir)
+{
+ struct filter_struct *ent;
+
+ for (ent = listp->head; ent; ent = ent->next) {
+ if (ignore_perishable && ent->match_flags & MATCHFLG_PERISHABLE)
+ continue;
+ if (ent->match_flags & MATCHFLG_PERDIR_MERGE) {
+ int rc = check_filter(ent->u.mergelist, name,
+ name_is_dir);
+ if (rc)
+ return rc;
+ continue;
+ }
+ if (ent->match_flags & MATCHFLG_CVS_IGNORE) {
+ int rc = check_filter(&cvs_filter_list, name,
+ name_is_dir);
+ if (rc)
+ return rc;
+ continue;
+ }
+ if (rule_matches(name, ent, name_is_dir)) {
+ report_filter_result(name, ent, name_is_dir,
+ listp->debug_type);
+ return ent->match_flags & MATCHFLG_INCLUDE ? 1 : -1;
+ }
+ }
+
+ return 0;
+}
+
+#define RULE_STRCMP(s,r) rule_strcmp((s), (r), sizeof (r) - 1)
+
+static const uchar *rule_strcmp(const uchar *str, const char *rule, int rule_len)
+{
+ if (strncmp((char*)str, rule, rule_len) != 0)
+ return NULL;
+ if (isspace(str[rule_len]) || str[rule_len] == '_' || !str[rule_len])
+ return str + rule_len - 1;
+ if (str[rule_len] == ',')
+ return str + rule_len;
+ return NULL;
+}
+
+/* Get the next include/exclude arg from the string. The token will not
+ * be '\0' terminated, so use the returned length to limit the string.
+ * Also, be sure to add this length to the returned pointer before passing
+ * it back to ask for the next token. This routine parses the "!" (list-
+ * clearing) token and (depending on the mflags) the various prefixes.
+ * The *mflags_ptr value will be set on exit to the new MATCHFLG_* bits
+ * for the current token. */
+static const char *parse_rule_tok(const char *p, uint32 mflags, int xflags,
+ unsigned int *len_ptr, uint32 *mflags_ptr)
+{
+ const uchar *s = (const uchar *)p;
+ uint32 new_mflags;
+ unsigned int len;
+
+ if (mflags & MATCHFLG_WORD_SPLIT) {
+ /* Skip over any initial whitespace. */
+ while (isspace(*s))
+ s++;
+ /* Update to point to real start of rule. */
+ p = (const char *)s;
+ }
+ if (!*s)
+ return NULL;
+
+ new_mflags = mflags & MATCHFLGS_FROM_CONTAINER;
+
+ /* 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
+ * or an exclude based on the inheritance of the MATCHFLG_INCLUDE
+ * flag (above). XFLG_OLD_PREFIXES indicates a compatibility mode
+ * for old include/exclude patterns where just "+ " and "- " are
+ * allowed as optional prefixes. */
+ if (mflags & MATCHFLG_NO_PREFIXES) {
+ if (*s == '!' && mflags & MATCHFLG_CVS_IGNORE)
+ new_mflags |= MATCHFLG_CLEAR_LIST; /* Tentative! */
+ } else if (xflags & XFLG_OLD_PREFIXES) {
+ if (*s == '-' && s[1] == ' ') {
+ new_mflags &= ~MATCHFLG_INCLUDE;
+ s += 2;
+ } else if (*s == '+' && s[1] == ' ') {
+ new_mflags |= MATCHFLG_INCLUDE;
+ s += 2;
+ } else if (*s == '!')
+ new_mflags |= MATCHFLG_CLEAR_LIST; /* Tentative! */
+ } else {
+ char ch = 0, *mods = "";
+ switch (*s) {
+ case 'c':
+ if ((s = RULE_STRCMP(s, "clear")) != NULL)
+ ch = '!';
+ break;
+ case 'd':
+ if ((s = RULE_STRCMP(s, "dir-merge")) != NULL)
+ ch = ':';
+ break;
+ case 'e':
+ if ((s = RULE_STRCMP(s, "exclude")) != NULL)
+ ch = '-';
+ break;
+ case 'h':
+ if ((s = RULE_STRCMP(s, "hide")) != NULL)
+ ch = 'H';
+ break;
+ case 'i':
+ if ((s = RULE_STRCMP(s, "include")) != NULL)
+ ch = '+';
+ break;
+ case 'm':
+ if ((s = RULE_STRCMP(s, "merge")) != NULL)
+ ch = '.';
+ break;
+ case 'p':
+ if ((s = RULE_STRCMP(s, "protect")) != NULL)
+ ch = 'P';
+ break;
+ case 'r':
+ if ((s = RULE_STRCMP(s, "risk")) != NULL)
+ ch = 'R';
+ break;
+ case 's':
+ if ((s = RULE_STRCMP(s, "show")) != NULL)
+ ch = 'S';
+ break;
+ default:
+ ch = *s;
+ if (s[1] == ',')
+ s++;
+ break;
+ }
+ switch (ch) {
+ case ':':
+ new_mflags |= MATCHFLG_PERDIR_MERGE
+ | MATCHFLG_FINISH_SETUP;
+ /* FALL THROUGH */
+ case '.':
+ new_mflags |= MATCHFLG_MERGE_FILE;
+ mods = MODIFIERS_INCL_EXCL MODIFIERS_MERGE_FILE;
+ break;
+ case '+':
+ new_mflags |= MATCHFLG_INCLUDE;
+ /* FALL THROUGH */
+ case '-':
+ mods = MODIFIERS_INCL_EXCL;
+ break;
+ case 'S':
+ new_mflags |= MATCHFLG_INCLUDE;
+ /* FALL THROUGH */
+ case 'H':
+ new_mflags |= MATCHFLG_SENDER_SIDE;
+ mods = MODIFIERS_HIDE_PROTECT;
+ break;
+ case 'R':
+ new_mflags |= MATCHFLG_INCLUDE;
+ /* FALL THROUGH */
+ case 'P':
+ new_mflags |= MATCHFLG_RECEIVER_SIDE;
+ mods = MODIFIERS_HIDE_PROTECT;
+ break;
+ case '!':
+ new_mflags |= MATCHFLG_CLEAR_LIST;
+ mods = NULL;
+ break;
+ default:
+ rprintf(FERROR, "Unknown filter rule: `%s'\n", p);
+ exit_cleanup(RERR_SYNTAX);
+ }
+ while (mods && *++s && *s != ' ' && *s != '_') {
+ if (strchr(mods, *s) == NULL) {
+ if (mflags & MATCHFLG_WORD_SPLIT && isspace(*s)) {
+ s--;
+ break;
+ }
+ invalid:
+ rprintf(FERROR,
+ "invalid modifier sequence at '%c' in filter rule: %s\n",
+ *s, p);
+ exit_cleanup(RERR_SYNTAX);
+ }
+ switch (*s) {
+ case '-':
+ if (new_mflags & MATCHFLG_NO_PREFIXES)
+ goto invalid;
+ new_mflags |= MATCHFLG_NO_PREFIXES;
+ break;
+ case '+':
+ if (new_mflags & MATCHFLG_NO_PREFIXES)
+ goto invalid;
+ new_mflags |= MATCHFLG_NO_PREFIXES
+ | MATCHFLG_INCLUDE;
+ break;
+ case '/':
+ new_mflags |= MATCHFLG_ABS_PATH;
+ break;
+ case '!':
+ new_mflags |= MATCHFLG_NEGATE;
+ break;
+ case 'C':
+ if (new_mflags & MATCHFLG_NO_PREFIXES)
+ goto invalid;
+ new_mflags |= MATCHFLG_NO_PREFIXES
+ | MATCHFLG_WORD_SPLIT
+ | MATCHFLG_NO_INHERIT
+ | MATCHFLG_CVS_IGNORE;
+ break;
+ case 'e':
+ new_mflags |= MATCHFLG_EXCLUDE_SELF;
+ break;
+ case 'n':
+ new_mflags |= MATCHFLG_NO_INHERIT;
+ break;
+ case 'p':
+ new_mflags |= MATCHFLG_PERISHABLE;
+ break;
+ case 'r':
+ new_mflags |= MATCHFLG_RECEIVER_SIDE;
+ break;
+ case 's':
+ new_mflags |= MATCHFLG_SENDER_SIDE;
+ break;
+ case 'w':
+ new_mflags |= MATCHFLG_WORD_SPLIT;
+ break;
+ }
+ }
+ if (*s)
+ s++;
+ }
+
+ if (mflags & MATCHFLG_WORD_SPLIT) {
+ const uchar *cp = s;
+ /* Token ends at whitespace or the end of the string. */
+ while (!isspace(*cp) && *cp != '\0')
+ cp++;
+ len = cp - s;
+ } else
+ len = strlen((char*)s);
+
+ if (new_mflags & MATCHFLG_CLEAR_LIST) {
+ if (!(mflags & MATCHFLG_NO_PREFIXES)
+ && !(xflags & XFLG_OLD_PREFIXES) && len) {
+ rprintf(FERROR,
+ "'!' rule has trailing characters: %s\n", p);
+ exit_cleanup(RERR_SYNTAX);
+ }
+ if (len > 1)
+ new_mflags &= ~MATCHFLG_CLEAR_LIST;
+ } else if (!len && !(new_mflags & MATCHFLG_CVS_IGNORE)) {
+ rprintf(FERROR, "unexpected end of filter rule: %s\n", p);
+ exit_cleanup(RERR_SYNTAX);
+ }
+
+ /* --delete-excluded turns an un-modified include/exclude into a
+ * sender-side rule. We also affect per-dir merge files that take
+ * no prefixes as a simple optimization. */
+ if (delete_excluded
+ && !(new_mflags & (MATCHFLG_RECEIVER_SIDE|MATCHFLG_SENDER_SIDE))
+ && (!(new_mflags & MATCHFLG_PERDIR_MERGE)
+ || new_mflags & MATCHFLG_NO_PREFIXES))
+ new_mflags |= MATCHFLG_SENDER_SIDE;
+
+ *len_ptr = len;
+ *mflags_ptr = new_mflags;
+ return (const char *)s;