--- embedaddon/rsync/exclude.c 2013/10/14 07:51:14 1.1.1.2 +++ embedaddon/rsync/exclude.c 2021/03/17 00:32:36 1.1.1.4 @@ -4,7 +4,7 @@ * Copyright (C) 1996-2001 Andrew Tridgell * Copyright (C) 1996 Paul Mackerras * Copyright (C) 2002 Martin Pool - * Copyright (C) 2003-2013 Wayne Davison + * Copyright (C) 2003-2020 Wayne Davison * * 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 @@ -21,6 +21,7 @@ */ #include "rsync.h" +#include "ifuncs.h" extern int am_server; extern int am_sender; @@ -44,9 +45,14 @@ filter_rule_list filter_list = { .debug_type = "" }; filter_rule_list cvs_filter_list = { .debug_type = " [global CVS]" }; filter_rule_list daemon_filter_list = { .debug_type = " [daemon]" }; -/* Need room enough for ":MODS " prefix plus some room to grow. */ -#define MAX_RULE_PREFIX (16) +filter_rule *last_hit_filter_rule; +int saw_xattr_filter = 0; + +/* Need room enough for ":MODS " prefix, which can now include + * chmod/user/group values. */ +#define MAX_RULE_PREFIX (256) + #define SLASH_WILD3_SUFFIX "/***" /* The dirbuf is set by push_local_filters() to the current subdirectory @@ -100,53 +106,64 @@ static int mergelist_size = 0; static void teardown_mergelist(filter_rule *ex) { + int j; + + if (!ex->u.mergelist) + 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); } - /* We should deactivate mergelists in LIFO order. */ - assert(mergelist_cnt > 0); - assert(ex == mergelist_parents[mergelist_cnt - 1]); - - /* The parent_dirscan filters should have been freed. */ - assert(ex->u.mergelist->parent_dirscan_head == NULL); - free(ex->u.mergelist->debug_type); free(ex->u.mergelist); - mergelist_cnt--; + + 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 struct filter_chmod_struct *ref_filter_chmod(struct filter_chmod_struct *chmod) +{ + chmod->ref_cnt++; + assert(chmod->ref_cnt != 0); /* Catch overflow. */ + return chmod; +} + +static void unref_filter_chmod(struct filter_chmod_struct *chmod) +{ + chmod->ref_cnt--; + if (chmod->ref_cnt == 0) { + free(chmod->modestr); + free_chmod_mode(chmod->modes); + free(chmod); + } +} + static void free_filter(filter_rule *ex) { + if (ex->rflags & FILTRULE_CHMOD) + unref_filter_chmod(ex->chmod); + if (ex->rflags & FILTRULE_PERDIR_MERGE) + teardown_mergelist(ex); free(ex->pattern); free(ex); } -static void free_filters(filter_rule *head) +static void free_filters(filter_rule *ent) { - filter_rule *rev_head = NULL; - - /* Reverse the list so we deactivate mergelists in the proper LIFO - * order. */ - while (head) { - filter_rule *next = head->next; - head->next = rev_head; - rev_head = head; - head = next; + while (ent) { + filter_rule *next = ent->next; + free_filter(ent); + ent = next; } - - while (rev_head) { - filter_rule *prev = rev_head->next; - /* Tear down mergelists here, not in free_filter, so that we - * affect only real filter lists and not temporarily allocated - * filters. */ - if (rev_head->rflags & FILTRULE_PERDIR_MERGE) - teardown_mergelist(rev_head); - free_filter(rev_head); - rev_head = prev; - } } /* Build a filter structure given a filter pattern. The value in "pat" @@ -205,8 +222,7 @@ static void add_rule(filter_rule_list *listp, const ch } else suf_len = 0; - if (!(rule->pattern = new_array(char, pre_len + pat_len + suf_len + 1))) - out_of_memory("add_rule"); + rule->pattern = new_array(char, pre_len + pat_len + suf_len + 1); if (pre_len) { memcpy(rule->pattern, dirbuf + module_dirlen, pre_len); for (cp = rule->pattern; cp < rule->pattern + pre_len; cp++) { @@ -252,7 +268,10 @@ static void add_rule(filter_rule_list *listp, const ch * add it again. */ for (i = 0; i < mergelist_cnt; 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) s++; else @@ -264,20 +283,14 @@ static void add_rule(filter_rule_list *listp, const ch } } - if (!(lp = new_array(filter_rule_list, 1))) - out_of_memory("add_rule"); - lp->head = lp->tail = lp->parent_dirscan_head = NULL; + lp = new_array0(filter_rule_list, 1); if (asprintf(&lp->debug_type, " [per-dir %s]", cp) < 0) out_of_memory("add_rule"); rule->u.mergelist = lp; if (mergelist_cnt == mergelist_size) { mergelist_size += 5; - mergelist_parents = realloc_array(mergelist_parents, - filter_rule *, - mergelist_size); - if (!mergelist_parents) - out_of_memory("add_rule"); + mergelist_parents = realloc_array(mergelist_parents, filter_rule *, mergelist_size); } if (DEBUG_GTE(FILTER, 2)) { rprintf(FINFO, "[%s] activating mergelist #%d%s\n", @@ -297,16 +310,23 @@ static void add_rule(filter_rule_list *listp, const ch } } -static void clear_filter_list(filter_rule_list *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) { - /* Truncate any inherited items from the local list. */ - listp->tail->next = NULL; - /* Now free everything that is left. */ - free_filters(listp->head); - } + filter_rule *inherited; - 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 @@ -356,7 +376,7 @@ static char *parse_merge_name(const char *merge_file, 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) { int d_len = dirbuf_len - prefix_skip; if (d_len + fn_len >= MAXPATHLEN) { @@ -457,8 +477,6 @@ static BOOL setup_merge_file(int mergelist_num, filter 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", @@ -496,20 +514,23 @@ void *push_local_filters(const char *dir, unsigned int push = (struct local_filter_state *)new_array(char, sizeof (struct local_filter_state) + (mergelist_cnt-1) * sizeof (filter_rule_list)); - 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 (filter_rule_list)); + 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 * this loop separate from the above loop. */ for (i = 0; i < mergelist_cnt; i++) { filter_rule *ex = mergelist_parents[i]; - filter_rule_list *lp = ex->u.mergelist; + filter_rule_list *lp; + if (!ex) + continue; + lp = ex->u.mergelist; if (DEBUG_GTE(FILTER, 2)) { rprintf(FINFO, "[%s] pushing mergelist #%d%s\n", @@ -553,46 +574,40 @@ void pop_local_filters(void *mem) for (i = mergelist_cnt; i-- > 0; ) { filter_rule *ex = mergelist_parents[i]; - filter_rule_list *lp = ex->u.mergelist; + filter_rule_list *lp; + if (!ex) + continue; + 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.) */ + 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); } - free_filters(lp->parent_dirscan_head); - lp->parent_dirscan_head = NULL; + pop_filter_list(lp); } } - /* 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) + return; /* No state to restore. */ - if (!pop) { - /* No state to restore. */ - return; + for (i = 0; i < old_mergelist_cnt; i++) { + filter_rule *ex = mergelist_parents[i]; + if (!ex) + continue; + memcpy(ex->u.mergelist, &pop->mergelists[i], sizeof (filter_rule_list)); } - for (i = 0; i < mergelist_cnt; i++) { - memcpy(mergelist_parents[i]->u.mergelist, &pop->mergelists[i], - sizeof (filter_rule_list)); - } - free(pop); } @@ -624,7 +639,7 @@ void change_local_filter_dir(const char *dname, int dl filt_array[cur_depth] = push_local_filters(dname, dlen); } -static int rule_matches(const char *fname, filter_rule *ex, int name_is_dir) +static int rule_matches(const char *fname, filter_rule *ex, int name_flags) { int slash_handling, str_cnt = 0, anchored_match = 0; int ret_match = ex->rflags & FILTRULE_NEGATE ? 0 : 1; @@ -635,6 +650,9 @@ static int rule_matches(const char *fname, filter_rule if (!*name) return 0; + if (!(name_flags & NAME_IS_XATTR) ^ !(ex->rflags & FILTRULE_XATTR)) + return 0; + if (!ex->u.slash_cnt && !(ex->rflags & FILTRULE_WILD2)) { /* If the pattern does not have any slashes AND it does * not have a "**" (which could match a slash), then we @@ -652,7 +670,7 @@ static int rule_matches(const char *fname, filter_rule strings[str_cnt++] = "/"; } strings[str_cnt++] = name; - if (name_is_dir) { + if (name_flags & NAME_IS_DIR) { /* Allow a trailing "/"+"***" to match the directory. */ if (ex->rflags & FILTRULE_WILD3_SUFFIX) strings[str_cnt++] = "/"; @@ -687,16 +705,15 @@ static int rule_matches(const char *fname, filter_rule if (litmatch_array(pattern, strings, slash_handling)) return ret_match; } else if (anchored_match) { - if (strcmp(name, pattern) == 0) + if (ic_strEQ(name, pattern)) 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)] == '/')) { + if (l2 <= l1 + && ic_strEQ(name + (l1-l2), pattern) + && (l1 == l2 || name[l1 - (l2+1)] == '/')) return ret_match; - } } return !ret_match; @@ -704,7 +721,7 @@ static int rule_matches(const char *fname, filter_rule static void report_filter_result(enum logcode code, char const *name, filter_rule const *ent, - int name_is_dir, const char *type) + int name_flags, 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 @@ -714,17 +731,46 @@ static void report_filter_result(enum logcode code, ch static char *actions[2][2] = { {"show", "hid"}, {"risk", "protect"} }; const char *w = who_am_i(); + const char *t = name_flags & NAME_IS_XATTR ? "xattr" + : name_flags & NAME_IS_DIR ? "directory" + : "file"; rprintf(code, "[%s] %sing %s %s because of pattern %s%s%s\n", w, actions[*w!='s'][!(ent->rflags & FILTRULE_INCLUDE)], - name_is_dir ? "directory" : "file", name, ent->pattern, + t, name, ent->pattern, ent->rflags & FILTRULE_DIRECTORY ? "/" : "", type); } } +/* This function is used to check if a file should be included/excluded + * from the list of files based on its name and type etc. The value of + * filter_level is set to either SERVER_FILTERS or ALL_FILTERS. + * "last_hit_filter_rule" will be set to the operative filter, or NULL if none. */ + +int name_is_excluded(const char *fname, int name_flags, int filter_level) +{ + if (daemon_filter_list.head && check_filter(&daemon_filter_list, FLOG, fname, name_flags) < 0) { + if (!(name_flags & NAME_IS_XATTR)) + errno = ENOENT; + return 1; + } + + /* Don't leave a daemon include in last_hit_filter_rule. */ + last_hit_filter_rule = NULL; + + if (filter_level != ALL_FILTERS) + return 0; + + if (filter_list.head && check_filter(&filter_list, FINFO, fname, name_flags) < 0) + return 1; + + return 0; +} + /* 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. */ + * exclude list, 1 if it is included, and 0 if it was not matched. + * Sets last_hit_filter_rule to the filter that was hit, or NULL if none. */ int check_filter(filter_rule_list *listp, enum logcode code, - const char *name, int name_is_dir) + const char *name, int name_flags) { filter_rule *ent; @@ -732,26 +778,25 @@ int check_filter(filter_rule_list *listp, enum logcode if (ignore_perishable && ent->rflags & FILTRULE_PERISHABLE) continue; if (ent->rflags & FILTRULE_PERDIR_MERGE) { - int rc = check_filter(ent->u.mergelist, code, name, - name_is_dir); + int rc = check_filter(ent->u.mergelist, code, name, name_flags); if (rc) return rc; continue; } if (ent->rflags & FILTRULE_CVS_IGNORE) { - int rc = check_filter(&cvs_filter_list, code, name, - name_is_dir); + int rc = check_filter(&cvs_filter_list, code, name, name_flags); if (rc) return rc; continue; } - if (rule_matches(name, ent, name_is_dir)) { - report_filter_result(code, name, ent, name_is_dir, - listp->debug_type); + if (rule_matches(name, ent, name_flags)) { + report_filter_result(code, name, ent, name_flags, listp->debug_type); + last_hit_filter_rule = ent; return ent->rflags & FILTRULE_INCLUDE ? 1 : -1; } } + last_hit_filter_rule = NULL; return 0; } @@ -768,9 +813,45 @@ static const uchar *rule_strcmp(const uchar *str, cons return NULL; } +static char *grab_paren_value(const uchar **s_ptr) +{ + const uchar *start, *end; + int val_sz; + char *val; + + if ((*s_ptr)[1] != '(') + return NULL; + start = (*s_ptr) + 2; + + for (end = start; *end != ')'; end++) + if (!*end || *end == ' ' || *end == '_') + return NULL; + + val_sz = end - start + 1; + val = new_array(char, val_sz); + strlcpy(val, (const char *)start, val_sz); + *s_ptr = end; /* remember ++s in parse_rule_tok */ + return val; +} + +static struct filter_chmod_struct *make_chmod_struct(char *modestr) +{ + struct filter_chmod_struct *chmod; + struct chmod_mode_struct *modes = NULL; + + if (!parse_chmod(modestr, &modes)) + return NULL; + + chmod = new(struct filter_chmod_struct); + chmod->ref_cnt = 1; + chmod->modestr = modestr; + chmod->modes = modes; + return chmod; +} + #define FILTRULES_FROM_CONTAINER (FILTRULE_ABS_PATH | FILTRULE_INCLUDE \ | FILTRULE_DIRECTORY | FILTRULE_NEGATE \ - | FILTRULE_PERISHABLE) + | FILTRULE_PERISHABLE | FILTRULES_ATTRS) /* Gets the next include/exclude rule from *rulestr_ptr and advances * *rulestr_ptr to point beyond it. Stores the pattern's start (within @@ -785,6 +866,7 @@ static filter_rule *parse_rule_tok(const char **rulest const char **pat_ptr, unsigned int *pat_len_ptr) { const uchar *s = (const uchar *)*rulestr_ptr; + char *val; filter_rule *rule; unsigned int len; @@ -798,12 +880,17 @@ static filter_rule *parse_rule_tok(const char **rulest if (!*s) return NULL; - if (!(rule = new0(filter_rule))) - out_of_memory("parse_rule_tok"); + rule = new0(filter_rule); /* Inherit from the template. Don't inherit FILTRULES_SIDES; we check * that later. */ rule->rflags = template->rflags & FILTRULES_FROM_CONTAINER; + if (template->rflags & FILTRULE_CHMOD) + rule->chmod = ref_filter_chmod(template->chmod); + if (template->rflags & FILTRULE_FORCE_OWNER) + rule->force_uid = template->force_uid; + if (template->rflags & FILTRULE_FORCE_GROUP) + rule->force_gid = template->force_gid; /* Figure out what kind of a filter rule "s" is pointing at. Note * that if FILTRULE_NO_PREFIXES is set, the rule is either an include @@ -872,7 +959,7 @@ static filter_rule *parse_rule_tok(const char **rulest switch (ch) { case ':': rule->rflags |= FILTRULE_PERDIR_MERGE - | FILTRULE_FINISH_SETUP; + | FILTRULE_FINISH_SETUP; /* FALL THROUGH */ case '.': rule->rflags |= FILTRULE_MERGE_FILE; @@ -949,11 +1036,63 @@ static filter_rule *parse_rule_tok(const char **rulest goto invalid; rule->rflags |= FILTRULE_EXCLUDE_SELF; break; + case 'g': { + gid_t gid; + + if (!(val = grab_paren_value(&s))) + goto invalid; + if (group_to_gid(val, &gid, True)) { + rule->rflags |= FILTRULE_FORCE_GROUP; + rule->force_gid = gid; + } else { + rprintf(FERROR, + "unknown group '%s' in filter rule: %s\n", + val, *rulestr_ptr); + exit_cleanup(RERR_SYNTAX); + } + free(val); + break; + } + case 'm': { + struct filter_chmod_struct *chmod; + + if (!(val = grab_paren_value(&s))) + goto invalid; + if ((chmod = make_chmod_struct(val))) { + if (rule->rflags & FILTRULE_CHMOD) + unref_filter_chmod(rule->chmod); + rule->rflags |= FILTRULE_CHMOD; + rule->chmod = chmod; + } else { + rprintf(FERROR, + "unparseable chmod string '%s' in filter rule: %s\n", + val, *rulestr_ptr); + exit_cleanup(RERR_SYNTAX); + } + break; + } case 'n': if (!(rule->rflags & FILTRULE_MERGE_FILE)) goto invalid; rule->rflags |= FILTRULE_NO_INHERIT; break; + case 'o': { + uid_t uid; + + if (!(val = grab_paren_value(&s))) + goto invalid; + if (user_to_uid(val, &uid, True)) { + rule->rflags |= FILTRULE_FORCE_OWNER; + rule->force_uid = uid; + } else { + rprintf(FERROR, + "unknown user '%s' in filter rule: %s\n", + val, *rulestr_ptr); + exit_cleanup(RERR_SYNTAX); + } + free(val); + break; + } case 'p': rule->rflags |= FILTRULE_PERISHABLE; break; @@ -972,6 +1111,10 @@ static filter_rule *parse_rule_tok(const char **rulest goto invalid; rule->rflags |= FILTRULE_WORD_SPLIT; break; + case 'x': + rule->rflags |= FILTRULE_XATTR; + saw_xattr_filter = 1; + break; } } if (*s) @@ -1024,16 +1167,6 @@ static filter_rule *parse_rule_tok(const char **rulest return rule; } -static char default_cvsignore[] = - /* These default ignored items come from the CVS manual. */ - "RCS SCCS CVS CVS.adm RCSLOG cvslog.* tags TAGS" - " .make.state .nse_depinfo *~ #* .#* ,* _$* *$" - " *.old *.bak *.BAK *.orig *.rej .del-*" - " *.a *.olb *.o *.obj *.so *.exe" - " *.Z *.elc *.ln core" - /* The rest we added to suit ourself. */ - " .svn/ .git/ .hg/ .bzr/"; - static void get_cvs_excludes(uint32 rflags) { static int initialized = 0; @@ -1043,7 +1176,7 @@ static void get_cvs_excludes(uint32 rflags) return; initialized = 1; - parse_filter_str(&cvs_filter_list, default_cvsignore, + parse_filter_str(&cvs_filter_list, default_cvsignore(), rule_template(rflags | (protocol_version >= 30 ? FILTRULE_PERISHABLE : 0)), 0); @@ -1093,7 +1226,8 @@ void parse_filter_str(filter_rule_list *listp, const c "[%s] clearing filter list%s\n", who_am_i(), listp->debug_type); } - clear_filter_list(listp); + pop_filter_list(listp); + listp->head = NULL; goto free_continue; } @@ -1106,8 +1240,7 @@ void parse_filter_str(filter_rule_list *listp, const c const char *name; filter_rule *excl_self; - if (!(excl_self = new0(filter_rule))) - out_of_memory("parse_filter_str"); + excl_self = new0(filter_rule); /* Find the beginning of the basename and add an exclude for it. */ for (name = pat + pat_len; name > pat && name[-1] != '/'; name--) {} add_rule(listp, name, (pat + pat_len) - name, excl_self, 0); @@ -1258,6 +1391,8 @@ char *get_rule_prefix(filter_rule *rule, const char *p } if (rule->rflags & FILTRULE_EXCLUDE_SELF) *op++ = 'e'; + if (rule->rflags & FILTRULE_XATTR) + *op++ = 'x'; if (rule->rflags & FILTRULE_SENDER_SIDE && (!for_xfer || protocol_version >= 29)) *op++ = 's'; @@ -1271,6 +1406,23 @@ char *get_rule_prefix(filter_rule *rule, const char *p else if (am_sender) return NULL; } + if (rule->rflags & FILTRULES_ATTRS) { + if (!for_xfer || protocol_version >= 31) { + if (rule->rflags & FILTRULE_CHMOD) + if (!snappendf(&op, (buf + sizeof buf) - op, + "m(%s)", rule->chmod->modestr)) + return NULL; + if (rule->rflags & FILTRULE_FORCE_OWNER) + if (!snappendf(&op, (buf + sizeof buf) - op, + "o(%u)", (unsigned)rule->force_uid)) + return NULL; + if (rule->rflags & FILTRULE_FORCE_GROUP) + if (!snappendf(&op, (buf + sizeof buf) - op, + "g(%u)", (unsigned)rule->force_gid)) + return NULL; + } else if (!am_sender) + return NULL; + } if (op - buf > legal_len) return NULL; if (legal_len) @@ -1376,8 +1528,7 @@ void recv_filter_list(int f_in) char line[BIGPATHBUFLEN]; int xflags = protocol_version >= 29 ? 0 : XFLG_OLD_PREFIXES; int receiver_wants_list = prune_empty_dirs - || (delete_mode - && (!delete_excluded || protocol_version >= 29)); + || (delete_mode && (!delete_excluded || protocol_version >= 29)); unsigned int len; if (!local_server && (am_sender || receiver_wants_list)) {