owner, or group of matching files.
struct filter_list_struct cvs_filter_list = { .debug_type = " [global CVS]" };
struct filter_list_struct daemon_filter_list = { .debug_type = " [daemon]" };
-/* Need room enough for ":MODS " prefix plus some room to grow. */
-#define MAX_RULE_PREFIX (16)
+struct filter_struct *last_hit_filter;
-#define MODIFIERS_MERGE_FILE "-+Cenw"
-#define MODIFIERS_INCL_EXCL "/!Crsp"
-#define MODIFIERS_HIDE_PROTECT "/!p"
+/* Need room enough for ":MODS " prefix, which can now include
+ * chmod/user/group values. */
+#define MAX_RULE_PREFIX (256)
#define SLASH_WILD3_SUFFIX "/***"
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(struct filter_struct *ex)
{
+ if (ex->flags & MATCHFLG_CHMOD)
+ unref_filter_chmod(ex->chmod);
free(ex->pattern);
free(ex);
}
/*
* 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.
+ * Sets last_hit_filter to the filter that was hit, or NULL if none.
*/
int check_filter(struct filter_list_struct *listp, enum logcode code,
const char *name, int name_is_dir)
if (rule_matches(name, ent, name_is_dir)) {
report_filter_result(code, name, ent, name_is_dir,
listp->debug_type);
+ last_hit_filter = ent;
return ent->flags & MATCHFLG_INCLUDE ? 1 : -1;
}
}
+ last_hit_filter = NULL;
return 0;
}
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;
+
+ if (!(chmod = new(struct filter_chmod_struct)))
+ out_of_memory("make_chmod_struct");
+ chmod->ref_cnt = 1;
+ chmod->modestr = modestr;
+ chmod->modes = modes;
+ return chmod;
+}
+
#define MATCHFLGS_FROM_CONTAINER (MATCHFLG_ABS_PATH | MATCHFLG_INCLUDE \
| MATCHFLG_DIRECTORY | MATCHFLG_NEGATE \
- | MATCHFLG_PERISHABLE)
+ | MATCHFLG_PERISHABLE | MATCHFLGS_ATTRS)
/* Gets the next include/exclude rule from *rulestr_ptr and advances
* *rulestr_ptr to point beyond it. Stores the pattern's start (within
const char **pat_ptr, unsigned int *pat_len_ptr)
{
const uchar *s = (const uchar *)*rulestr_ptr;
+ char *val;
struct filter_struct *filter;
unsigned int len;
/* Inherit from the template. Don't inherit MATCHFLGS_SIDES; we check
* that later. */
filter->flags = template->flags & MATCHFLGS_FROM_CONTAINER;
+ if (template->flags & MATCHFLG_CHMOD)
+ filter->chmod = ref_filter_chmod(template->chmod);
+ if (template->flags & MATCHFLG_FORCE_OWNER)
+ filter->force_uid = template->force_uid;
+ if (template->flags & MATCHFLG_FORCE_GROUP)
+ filter->force_gid = template->force_gid;
/* 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
goto invalid;
filter->flags |= MATCHFLG_EXCLUDE_SELF;
break;
+ case 'g': {
+ gid_t gid;
+
+ if (!(val = grab_paren_value(&s)))
+ goto invalid;
+ if (name_to_gid(val, &gid, True)) {
+ filter->flags |= MATCHFLG_FORCE_GROUP;
+ filter->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 (filter->flags & MATCHFLG_CHMOD)
+ unref_filter_chmod(filter->chmod);
+ filter->flags |= MATCHFLG_CHMOD;
+ filter->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 (!(filter->flags & MATCHFLG_MERGE_FILE))
goto invalid;
filter->flags |= MATCHFLG_NO_INHERIT;
break;
+ case 'o': {
+ uid_t uid;
+
+ if (!(val = grab_paren_value(&s)))
+ goto invalid;
+ if (name_to_uid(val, &uid, True)) {
+ filter->flags |= MATCHFLG_FORCE_OWNER;
+ filter->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':
filter->flags |= MATCHFLG_PERISHABLE;
break;
else if (am_sender)
return NULL;
}
+ if (filter->flags & MATCHFLGS_ATTRS) {
+ if (!for_xfer || protocol_version >= 31) {
+ if (filter->flags & MATCHFLG_CHMOD)
+ if (!snappendf(&op, (buf + sizeof buf) - op,
+ "m(%s)", filter->chmod->modestr))
+ return NULL;
+ if (filter->flags & MATCHFLG_FORCE_OWNER)
+ if (!snappendf(&op, (buf + sizeof buf) - op,
+ "o(%u)", (unsigned)filter->force_uid))
+ return NULL;
+ if (filter->flags & MATCHFLG_FORCE_GROUP)
+ if (!snappendf(&op, (buf + sizeof buf) - op,
+ "g(%u)", (unsigned)filter->force_gid))
+ return NULL;
+ } else if (!am_sender)
+ return NULL;
+ }
if (op - buf > legal_len)
return NULL;
if (legal_len)
extern struct filter_list_struct filter_list;
extern struct filter_list_struct daemon_filter_list;
+extern struct filter_struct *last_hit_filter;
#ifdef ICONV_OPTION
extern int filesfrom_convert;
/* 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. */
+ * filter_level is set to either SERVER_FILTERS or ALL_FILTERS.
+ * "last_hit_filter" will be set to the operative filter, or NULL if none. */
static int is_excluded(const char *fname, int is_dir, int filter_level)
{
#if 0 /* This currently never happens, so avoid a useless compare. */
#endif
if (is_daemon_excluded(fname, is_dir))
return 1;
+ /* Don't leave a daemon include in last_hit_filter. */
+ last_hit_filter = NULL;
if (filter_level != ALL_FILTERS)
return 0;
if (filter_list.head
} else if (readlink_stat(thisname, &st, linkname) != 0) {
int save_errno = errno;
/* See if file is excluded before reporting an error. */
- if (filter_level != NO_FILTERS
+ if (filter_level != NO_FILTERS && filter_level != ALL_FILTERS_NO_EXCLUDE
&& (is_excluded(thisname, 0, filter_level)
|| is_excluded(thisname, 1, filter_level))) {
if (ignore_perishable && save_errno != ENOENT)
if (filter_level == NO_FILTERS)
goto skip_filters;
+ if (filter_level == ALL_FILTERS_NO_EXCLUDE) {
+ /* Call only for the side effect of setting last_hit_filter to
+ * any operative include filter, which might affect attributes. */
+ is_excluded(thisname, S_ISDIR(st.st_mode) != 0, ALL_FILTERS);
+ goto skip_filters;
+ }
if (S_ISDIR(st.st_mode)) {
if (!xfer_dirs) {
int flags, int filter_level)
{
struct file_struct *file;
+ BOOL can_tweak_mode;
file = make_file(fname, flist, stp, flags, filter_level);
if (!file)
return NULL;
- if (chmod_modes && !S_ISLNK(file->mode) && file->mode)
+ can_tweak_mode = !S_ISLNK(file->mode) && file->mode;
+ if ((filter_level == ALL_FILTERS || filter_level == ALL_FILTERS_NO_EXCLUDE)
+ && last_hit_filter) {
+ if ((last_hit_filter->flags & MATCHFLG_CHMOD) && can_tweak_mode)
+ file->mode = tweak_mode(file->mode, last_hit_filter->chmod->modes);
+ if ((last_hit_filter->flags & MATCHFLG_FORCE_OWNER) && uid_ndx)
+ F_OWNER(file) = last_hit_filter->force_uid;
+ if ((last_hit_filter->flags & MATCHFLG_FORCE_GROUP) && gid_ndx)
+ F_GROUP(file) = last_hit_filter->force_gid;
+ }
+ if (chmod_modes && can_tweak_mode)
file->mode = tweak_mode(file->mode, chmod_modes);
if (f >= 0) {
struct file_struct *file;
file = send_file_name(f, flist, fbuf, &st,
FLAG_TOP_DIR | FLAG_CONTENT_DIR | flags,
- NO_FILTERS);
+ ALL_FILTERS_NO_EXCLUDE);
if (!file)
continue;
if (inc_recurse) {
} else
send_if_directory(f, flist, file, fbuf, len, flags);
} else
- send_file_name(f, flist, fbuf, &st, flags, NO_FILTERS);
+ send_file_name(f, flist, fbuf, &st, flags, ALL_FILTERS_NO_EXCLUDE);
}
gettimeofday(&end_tv, NULL);
#define NO_FILTERS 0
#define SERVER_FILTERS 1
#define ALL_FILTERS 2
+/* Don't let the file be excluded, but check for a filter that might affect
+ * its attributes via MATCHFLGS_ATTRS. */
+#define ALL_FILTERS_NO_EXCLUDE 3
#define XFLG_FATAL_ERRORS (1<<0)
#define XFLG_OLD_PREFIXES (1<<1)
int status; /* first errno from read errors */
};
+struct chmod_mode_struct;
+
#define MATCHFLG_WILD (1<<0) /* pattern has '*', '[', and/or '?' */
#define MATCHFLG_WILD2 (1<<1) /* pattern has '**' */
#define MATCHFLG_WILD2_PREFIX (1<<2) /* pattern starts with "**" */
#define MATCHFLG_RECEIVER_SIDE (1<<17)/* rule applies to the receiving side */
#define MATCHFLG_CLEAR_LIST (1<<18)/* this item is the "!" token */
#define MATCHFLG_PERISHABLE (1<<19)/* perishable if parent dir goes away */
+#define MATCHFLG_CHMOD (1<<20)/* chmod-tweak matching files */
+#define MATCHFLG_FORCE_OWNER (1<<21)/* force owner of matching files */
+#define MATCHFLG_FORCE_GROUP (1<<22)/* force group of matching files */
#define MATCHFLGS_SIDES (MATCHFLG_SENDER_SIDE | MATCHFLG_RECEIVER_SIDE)
+#define MATCHFLGS_ATTRS (MATCHFLG_CHMOD | MATCHFLG_FORCE_OWNER | MATCHFLG_FORCE_GROUP)
+
+struct filter_chmod_struct {
+ unsigned int ref_cnt;
+ char *modestr;
+ struct chmod_mode_struct *modes;
+};
struct filter_struct {
struct filter_struct *next;
int slash_cnt;
struct filter_list_struct *mergelist;
} u;
+ /* TODO: Use an "extras" mechanism to avoid
+ * allocating this memory when we don't need it. */
+ struct filter_chmod_struct *chmod;
+ uid_t force_uid;
+ gid_t force_gid;
};
struct filter_list_struct {
It is also legal to specify multiple bf(--chmod) options, as each
additional option is just appended to the list of changes to make.
+To change permissions of files matching a pattern, use an include filter with
+the bf(m) modifier, which takes effect before any bf(--chmod) options.
See the bf(--perms) and bf(--executability) options for how the resulting
permission value can be applied to the files in the transfer.
If you specify "--chown=foo:bar, this is exactly the same as specifying
"--usermap=*:foo --groupmap=*:bar", only easier.
+To change ownership of files matching a pattern, use an include filter with
+the bf(o) and bf(g) modifiers, which take effect before uid/gid mapping and
+therefore em(can) be mixed with bf(--usermap) and bf(--groupmap).
+
dit(bf(--timeout=TIMEOUT)) This option allows you to set a maximum I/O
timeout in seconds. If no data is transferred for the specified time
then rsync will exit. The default is 0, which means no timeout.
option's default rules that exclude things like "CVS" and "*.o" are
marked as perishable, and will not prevent a directory that was removed
on the source from being deleted on the destination.
+ it() An bf(m+nop()(CHMOD)) on an include rule tweaks the permissions of matching
+ source files in the same way as bf(--chmod). This happens before any
+ tweaks requested via bf(--chmod) options.
+ it() An bf(o+nop()(USER)) on an include rule pretends that matching source files
+ are owned by bf(USER) (a name or numeric uid). This happens before any uid
+ mapping by name or bf(--usermap).
+ it() A bf(g+nop()(GROUP)) on an include rule pretends that matching source files
+ are owned by bf(GROUP) (a name or numeric gid). This happens before any gid
+ mapping by name or bf(--groupmap).
)
manpagesection(MERGE-FILE FILTER RULES)
a rule prefix such as bf(hide)).
)
+The attribute-affecting modifiers bf(m), bf(o), and bf(g) work only in client
+filters (not in daemon filters), and only the modifiers of the first matching
+rule are applied. As an example, assuming bf(--super) is enabled, the
+rule "+o+nop()(root)g+nop()(root)m+nop()(go=) *~" would ensure that all "backup" files belong to
+root and are not accessible to anyone else.
+
Per-directory rules are inherited in all subdirectories of the directory
where the merge-file was found unless the 'n' modifier was used. Each
subdirectory's rules are prefixed to the inherited per-directory rules
return ret;
}
+/* Append formatted text at *dest_ptr up to a maximum of sz (like snprintf).
+ * On success, advance *dest_ptr and return True; on overflow, return False. */
+BOOL snappendf(char **dest_ptr, size_t sz, const char *format, ...)
+{
+ va_list ap;
+ size_t len;
+
+ va_start(ap, format);
+ len = vsnprintf(*dest_ptr, sz, format, ap);
+ va_end(ap);
+
+ if (len >= sz)
+ return False;
+ else {
+ *dest_ptr += len;
+ return True;
+ }
+}
+
int count_dir_elements(const char *p)
{
int cnt = 0, new_component = 1;