-
Notifications
You must be signed in to change notification settings - Fork 8
/
filter-attribute-mods.diff
473 lines (435 loc) · 16.3 KB
/
filter-attribute-mods.diff
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
From: Matt McCutchen <[email protected]>
Implement the "m", "o", "g" include modifiers to tweak the permissions,
owner, or group of matching files.
To use this patch, run these commands for a successful build:
patch -p1 <patches/filter-attribute-mods.diff
./configure (optional if already run)
make
based-on: 6c8ca91c731b7bf2b081694bda85b7dadc2b7aff
diff --git a/exclude.c b/exclude.c
--- a/exclude.c
+++ b/exclude.c
@@ -51,12 +51,15 @@ filter_rule_list cvs_filter_list = { .debug_type = " [global CVS]" };
filter_rule_list daemon_filter_list = { .debug_type = " [daemon]" };
filter_rule_list implied_filter_list = { .debug_type = " [implied]" };
+filter_rule *last_hit_filter_rule;
+
int saw_xattr_filter = 0;
int trust_sender_args = 0;
int trust_sender_filter = 0;
-/* Need room enough for ":MODS " prefix plus some room to grow. */
-#define MAX_RULE_PREFIX (16)
+/* Need room enough for ":MODS " prefix, which can now include
+ * chmod/user/group values. */
+#define MAX_RULE_PREFIX (256)
#define SLASH_WILD3_SUFFIX "/***"
@@ -139,8 +142,27 @@ static void teardown_mergelist(filter_rule *ex)
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);
@@ -1006,7 +1028,9 @@ static void report_filter_result(enum logcode code, char const *name,
/* 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_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) {
@@ -1015,6 +1039,9 @@ int name_is_excluded(const char *fname, int name_flags, int filter_level)
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;
@@ -1034,7 +1061,8 @@ int check_server_filter(filter_rule_list *listp, enum logcode code, const char *
}
/* 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_flags)
{
@@ -1057,10 +1085,12 @@ int check_filter(filter_rule_list *listp, enum logcode code,
}
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;
}
@@ -1077,9 +1107,45 @@ static const uchar *rule_strcmp(const uchar *str, const char *rule, int rule_len
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
@@ -1094,6 +1160,7 @@ static filter_rule *parse_rule_tok(const char **rulestr_ptr,
const char **pat_ptr, unsigned int *pat_len_ptr)
{
const uchar *s = (const uchar *)*rulestr_ptr;
+ char *val;
filter_rule *rule;
unsigned int len;
@@ -1112,6 +1179,12 @@ static filter_rule *parse_rule_tok(const char **rulestr_ptr,
/* 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
@@ -1258,11 +1331,63 @@ static filter_rule *parse_rule_tok(const char **rulestr_ptr,
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;
@@ -1576,6 +1701,23 @@ char *get_rule_prefix(filter_rule *rule, const char *pat, int for_xfer,
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)
diff --git a/flist.c b/flist.c
--- a/flist.c
+++ b/flist.c
@@ -86,6 +86,7 @@ extern char curr_dir[MAXPATHLEN];
extern struct chmod_mode_struct *chmod_modes;
extern filter_rule_list filter_list, implied_filter_list, daemon_filter_list;
+extern filter_rule *last_hit_filter_rule;
#ifdef ICONV_OPTION
extern int filesfrom_convert;
@@ -1259,7 +1260,7 @@ struct file_struct *make_file(const char *fname, struct file_list *flist,
} 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)
@@ -1304,6 +1305,12 @@ struct file_struct *make_file(const char *fname, struct file_list *flist,
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_rule 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) {
@@ -1536,12 +1543,23 @@ static struct file_struct *send_file_name(int f, struct file_list *flist,
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_rule) {
+ if ((last_hit_filter_rule->rflags & FILTRULE_CHMOD) && can_tweak_mode)
+ file->mode = tweak_mode(file->mode, last_hit_filter_rule->chmod->modes);
+ if ((last_hit_filter_rule->rflags & FILTRULE_FORCE_OWNER) && uid_ndx)
+ F_OWNER(file) = last_hit_filter_rule->force_uid;
+ if ((last_hit_filter_rule->rflags & FILTRULE_FORCE_GROUP) && gid_ndx)
+ F_GROUP(file) = last_hit_filter_rule->force_gid;
+ }
+ if (chmod_modes && can_tweak_mode)
file->mode = tweak_mode(file->mode, chmod_modes);
if (f >= 0) {
@@ -2443,7 +2461,7 @@ struct file_list *send_file_list(int f, int argc, char *argv[])
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) {
@@ -2457,7 +2475,7 @@ struct file_list *send_file_list(int f, int argc, char *argv[])
} 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);
}
if (reenable_multiplex >= 0)
diff --git a/rsync.1.md b/rsync.1.md
--- a/rsync.1.md
+++ b/rsync.1.md
@@ -1513,7 +1513,9 @@ expand it.
> --chmod=D2775,F664
It is also legal to specify multiple `--chmod` options, as each additional
- option is just appended to the list of changes to make.
+ 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 `m`
+ modifier, which takes effect before any `--chmod` options.
See the [`--perms`](#opt) and [`--executability`](#opt) options for how the
resulting permission value can be applied to the files in the transfer.
@@ -3033,6 +3035,10 @@ expand it.
An older rsync client may need to use [`-s`](#opt) to avoid a complaint
about wildcard characters, but a modern rsync handles this automatically.
+ To change ownership of files matching a pattern, use an include filter with
+ a `o` or `g` modifier, which take effect before uid/gid mapping and
+ therefore *can* be mixed with [`--usermap` & `--groupmap`](#opt--usermap).
+
0. `--timeout=SECONDS`
This option allows you to set a maximum I/O timeout in seconds. If no data
@@ -4187,6 +4193,15 @@ The following modifiers are accepted after an include (+) or exclude (-) rule:
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.
+- An `m(CHMOD)` on an include rule tweaks the permissions of matching source
+ files in the same way as [`--chmod`](#opt). This happens before any tweaks
+ requested via [`--chmod`](#opt).
+- An `o(USER)` on an include rule pretends that matching source files are owned
+ by `USER` (a name or numeric uid). This happens before any uid mapping by
+ name or [`--usermap`](#opt).
+- A `g(GROUP)` on an include rule pretends that matching source files are owned
+ by `GROUP` (a name or numeric gid). This happens before any gid mapping by
+ name or [`--groupmap`](#opt--usermap).
- An `x` indicates that a rule affects xattr names in xattr copy/delete
operations (and is thus ignored when matching file/dir names). If no
xattr-matching rules are specified, a default xattr filtering rule is used
@@ -4244,6 +4259,12 @@ The following modifiers are accepted after a merge or dir-merge rule:
rules in the file must not specify sides (via a modifier or a rule prefix
such as `hide`).
+The attribute-affecting modifiers `m`, `o`, and `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 [`--super`](#opt) is enabled, the rule
+"`+o(root),g(root),m(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 from its parents, which
diff --git a/rsync.h b/rsync.h
--- a/rsync.h
+++ b/rsync.h
@@ -181,6 +181,9 @@
#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 FILTRULES_ATTRS. */
+#define ALL_FILTERS_NO_EXCLUDE 3
#define XFLG_FATAL_ERRORS (1<<0)
#define XFLG_OLD_PREFIXES (1<<1)
@@ -982,6 +985,8 @@ struct map_struct {
int status; /* first errno from read errors */
};
+struct chmod_mode_struct;
+
#define NAME_IS_FILE (0) /* filter name as a file */
#define NAME_IS_DIR (1<<0) /* filter name as a dir */
#define NAME_IS_XATTR (1<<2) /* filter name as an xattr */
@@ -1007,8 +1012,18 @@ struct map_struct {
#define FILTRULE_CLEAR_LIST (1<<18)/* this item is the "!" token */
#define FILTRULE_PERISHABLE (1<<19)/* perishable if parent dir goes away */
#define FILTRULE_XATTR (1<<20)/* rule only applies to xattr names */
+#define FILTRULE_CHMOD (1<<21)/* chmod-tweak matching files */
+#define FILTRULE_FORCE_OWNER (1<<22)/* force owner of matching files */
+#define FILTRULE_FORCE_GROUP (1<<23)/* force group of matching files */
#define FILTRULES_SIDES (FILTRULE_SENDER_SIDE | FILTRULE_RECEIVER_SIDE)
+#define FILTRULES_ATTRS (FILTRULE_CHMOD | FILTRULE_FORCE_OWNER | FILTRULE_FORCE_GROUP)
+
+struct filter_chmod_struct {
+ unsigned int ref_cnt;
+ char *modestr;
+ struct chmod_mode_struct *modes;
+};
typedef struct filter_struct {
struct filter_struct *next;
@@ -1018,6 +1033,11 @@ typedef struct filter_struct {
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;
uchar elide;
} filter_rule;
diff --git a/util1.c b/util1.c
--- a/util1.c
+++ b/util1.c
@@ -918,6 +918,25 @@ size_t stringjoin(char *dest, size_t destsize, ...)
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;