-
Notifications
You must be signed in to change notification settings - Fork 8
/
fileflags.diff
1120 lines (1066 loc) · 37.7 KB
/
fileflags.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
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
This patch provides --fileflags, which preserves the st_flags stat() field.
Modified from a patch that was written by Rolf Grossmann.
To use this patch, run these commands for a successful build:
patch -p1 <patches/fileflags.diff
./configure (optional if already run)
make
based-on: 6c8ca91c731b7bf2b081694bda85b7dadc2b7aff
diff --git a/compat.c b/compat.c
--- a/compat.c
+++ b/compat.c
@@ -40,6 +40,7 @@ extern int checksum_seed;
extern int basis_dir_cnt;
extern int prune_empty_dirs;
extern int protocol_version;
+extern int force_change;
extern int protect_args;
extern int preserve_uid;
extern int preserve_gid;
@@ -47,6 +48,7 @@ extern int preserve_atimes;
extern int preserve_crtimes;
extern int preserve_acls;
extern int preserve_xattrs;
+extern int preserve_fileflags;
extern int xfer_flags_as_varint;
extern int need_messages_from_generator;
extern int delete_mode, delete_before, delete_during, delete_after;
@@ -86,7 +88,7 @@ struct name_num_item *xattr_sum_nni;
int xattr_sum_len = 0;
/* These index values are for the file-list's extra-attribute array. */
-int pathname_ndx, depth_ndx, atimes_ndx, crtimes_ndx, uid_ndx, gid_ndx, acls_ndx, xattrs_ndx, unsort_ndx;
+int pathname_ndx, depth_ndx, atimes_ndx, crtimes_ndx, uid_ndx, gid_ndx, fileflags_ndx, acls_ndx, xattrs_ndx, unsort_ndx;
int receiver_symlink_times = 0; /* receiver can set the time on a symlink */
int sender_symlink_iconv = 0; /* sender should convert symlink content */
@@ -588,6 +590,8 @@ void setup_protocol(int f_out,int f_in)
uid_ndx = ++file_extra_cnt;
if (preserve_gid)
gid_ndx = ++file_extra_cnt;
+ if (preserve_fileflags || (force_change && !am_sender))
+ fileflags_ndx = ++file_extra_cnt;
if (preserve_acls && !am_sender)
acls_ndx = ++file_extra_cnt;
if (preserve_xattrs)
@@ -751,6 +755,10 @@ void setup_protocol(int f_out,int f_in)
fprintf(stderr, "Both rsync versions must be at least 3.2.0 for --crtimes.\n");
exit_cleanup(RERR_PROTOCOL);
}
+ if (!xfer_flags_as_varint && preserve_fileflags) {
+ fprintf(stderr, "Both rsync versions must be at least 3.2.0 for --fileflags.\n");
+ exit_cleanup(RERR_PROTOCOL);
+ }
if (am_sender) {
receiver_symlink_times = am_server
? strchr(client_info, 'L') != NULL
diff --git a/delete.c b/delete.c
--- a/delete.c
+++ b/delete.c
@@ -25,6 +25,7 @@
extern int am_root;
extern int make_backups;
extern int max_delete;
+extern int force_change;
extern char *backup_dir;
extern char *backup_suffix;
extern int backup_suffix_len;
@@ -97,8 +98,12 @@ static enum delret delete_dir_contents(char *fname, uint16 flags)
}
strlcpy(p, fp->basename, remainder);
+#ifdef SUPPORT_FORCE_CHANGE
+ if (force_change)
+ make_mutable(fname, fp->mode, F_FFLAGS(fp), force_change);
+#endif
if (!(fp->mode & S_IWUSR) && !am_root && fp->flags & FLAG_OWNED_BY_US)
- do_chmod(fname, fp->mode | S_IWUSR);
+ do_chmod(fname, fp->mode | S_IWUSR, NO_FFLAGS);
/* Save stack by recursing to ourself directly. */
if (S_ISDIR(fp->mode)) {
if (delete_dir_contents(fname, flags | DEL_RECURSE) != DR_SUCCESS)
@@ -139,11 +144,18 @@ enum delret delete_item(char *fbuf, uint16 mode, uint16 flags)
}
if (flags & DEL_NO_UID_WRITE)
- do_chmod(fbuf, mode | S_IWUSR);
+ do_chmod(fbuf, mode | S_IWUSR, NO_FFLAGS);
if (S_ISDIR(mode) && !(flags & DEL_DIR_IS_EMPTY)) {
/* This only happens on the first call to delete_item() since
* delete_dir_contents() always calls us w/DEL_DIR_IS_EMPTY. */
+#ifdef SUPPORT_FORCE_CHANGE
+ if (force_change) {
+ STRUCT_STAT st;
+ if (x_lstat(fbuf, &st, NULL) == 0)
+ make_mutable(fbuf, st.st_mode, st.st_flags, force_change);
+ }
+#endif
ignore_perishable = 1;
/* If DEL_RECURSE is not set, this just reports emptiness. */
ret = delete_dir_contents(fbuf, flags);
diff --git a/flist.c b/flist.c
--- a/flist.c
+++ b/flist.c
@@ -52,6 +52,7 @@ extern int preserve_links;
extern int preserve_hard_links;
extern int preserve_devices;
extern int preserve_specials;
+extern int preserve_fileflags;
extern int delete_during;
extern int missing_args;
extern int eol_nulls;
@@ -388,6 +389,9 @@ static void send_file_entry(int f, const char *fname, struct file_struct *file,
static time_t crtime;
#endif
static mode_t mode;
+#ifdef SUPPORT_FILEFLAGS
+ static uint32 fileflags;
+#endif
#ifdef SUPPORT_HARD_LINKS
static int64 dev;
#endif
@@ -431,6 +435,14 @@ static void send_file_entry(int f, const char *fname, struct file_struct *file,
xflags |= XMIT_SAME_MODE;
else
mode = file->mode;
+#ifdef SUPPORT_FILEFLAGS
+ if (preserve_fileflags) {
+ if (F_FFLAGS(file) == fileflags)
+ xflags |= XMIT_SAME_FLAGS;
+ else
+ fileflags = F_FFLAGS(file);
+ }
+#endif
if (preserve_devices && IS_DEVICE(mode)) {
if (protocol_version < 28) {
@@ -592,6 +604,10 @@ static void send_file_entry(int f, const char *fname, struct file_struct *file,
#endif
if (!(xflags & XMIT_SAME_MODE))
write_int(f, to_wire_mode(mode));
+#ifdef SUPPORT_FILEFLAGS
+ if (preserve_fileflags && !(xflags & XMIT_SAME_FLAGS))
+ write_int(f, (int)fileflags);
+#endif
if (atimes_ndx && !S_ISDIR(mode) && !(xflags & XMIT_SAME_ATIME))
write_varlong(f, atime, 4);
if (preserve_uid && !(xflags & XMIT_SAME_UID)) {
@@ -686,6 +702,9 @@ static struct file_struct *recv_file_entry(int f, struct file_list *flist, int x
static time_t crtime;
#endif
static mode_t mode;
+#ifdef SUPPORT_FILEFLAGS
+ static uint32 fileflags;
+#endif
#ifdef SUPPORT_HARD_LINKS
static int64 dev;
#endif
@@ -803,6 +822,10 @@ static struct file_struct *recv_file_entry(int f, struct file_list *flist, int x
#ifdef SUPPORT_CRTIMES
if (crtimes_ndx)
crtime = F_CRTIME(first);
+#endif
+#ifdef SUPPORT_FILEFLAGS
+ if (preserve_fileflags)
+ fileflags = F_FFLAGS(first);
#endif
if (preserve_uid)
uid = F_OWNER(first);
@@ -876,6 +899,10 @@ static struct file_struct *recv_file_entry(int f, struct file_list *flist, int x
if (chmod_modes && !S_ISLNK(mode) && mode)
mode = tweak_mode(mode, chmod_modes);
+#ifdef SUPPORT_FILEFLAGS
+ if (preserve_fileflags && !(xflags & XMIT_SAME_FLAGS))
+ fileflags = (uint32)read_int(f);
+#endif
if (preserve_uid && !(xflags & XMIT_SAME_UID)) {
if (protocol_version < 30)
@@ -1057,6 +1084,10 @@ static struct file_struct *recv_file_entry(int f, struct file_list *flist, int x
}
#endif
file->mode = mode;
+#ifdef SUPPORT_FILEFLAGS
+ if (preserve_fileflags)
+ F_FFLAGS(file) = fileflags;
+#endif
if (preserve_uid)
F_OWNER(file) = uid;
if (preserve_gid) {
@@ -1470,6 +1501,10 @@ struct file_struct *make_file(const char *fname, struct file_list *flist,
}
#endif
file->mode = st.st_mode;
+#if defined SUPPORT_FILEFLAGS || defined SUPPORT_FORCE_CHANGE
+ if (fileflags_ndx)
+ F_FFLAGS(file) = st.st_flags;
+#endif
if (preserve_uid)
F_OWNER(file) = st.st_uid;
if (preserve_gid)
diff --git a/generator.c b/generator.c
--- a/generator.c
+++ b/generator.c
@@ -43,10 +43,12 @@ extern int preserve_devices;
extern int preserve_specials;
extern int preserve_hard_links;
extern int preserve_executability;
+extern int preserve_fileflags;
extern int preserve_perms;
extern int preserve_mtimes;
extern int omit_dir_times;
extern int omit_link_times;
+extern int force_change;
extern int delete_mode;
extern int delete_before;
extern int delete_during;
@@ -486,6 +488,10 @@ int unchanged_attrs(const char *fname, struct file_struct *file, stat_x *sxp)
return 0;
if (perms_differ(file, sxp))
return 0;
+#ifdef SUPPORT_FILEFLAGS
+ if (preserve_fileflags && sxp->st.st_flags != F_FFLAGS(file))
+ return 0;
+#endif
if (ownership_differs(file, sxp))
return 0;
#ifdef SUPPORT_ACLS
@@ -547,6 +553,11 @@ void itemize(const char *fnamecmp, struct file_struct *file, int ndx, int statre
iflags |= ITEM_REPORT_OWNER;
if (gid_ndx && !(file->flags & FLAG_SKIP_GROUP) && sxp->st.st_gid != (gid_t)F_GROUP(file))
iflags |= ITEM_REPORT_GROUP;
+#ifdef SUPPORT_FILEFLAGS
+ if (preserve_fileflags && !S_ISLNK(file->mode)
+ && sxp->st.st_flags != F_FFLAGS(file))
+ iflags |= ITEM_REPORT_FFLAGS;
+#endif
#ifdef SUPPORT_ACLS
if (preserve_acls && !S_ISLNK(file->mode)) {
if (!ACL_READY(*sxp))
@@ -1454,6 +1465,10 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx,
if (!preserve_perms) { /* See comment in non-dir code below. */
file->mode = dest_mode(file->mode, sx.st.st_mode, dflt_perms, statret == 0);
}
+#ifdef SUPPORT_FORCE_CHANGE
+ if (force_change && !preserve_fileflags)
+ F_FFLAGS(file) = sx.st.st_flags;
+#endif
if (statret != 0 && basis_dir[0] != NULL) {
int j = try_dests_non(file, fname, ndx, fnamecmpbuf, &sx, itemizing, code);
if (j == -2) {
@@ -1496,10 +1511,15 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx,
* readable and writable permissions during the time we are
* putting files within them. This is then restored to the
* former permissions after the transfer is done. */
+#ifdef SUPPORT_FORCE_CHANGE
+ if (force_change && F_FFLAGS(file) & force_change
+ && make_mutable(fname, file->mode, F_FFLAGS(file), force_change))
+ need_retouch_dir_perms = 1;
+#endif
#ifdef HAVE_CHMOD
if (!am_root && (file->mode & S_IRWXU) != S_IRWXU && dir_tweaking) {
mode_t mode = file->mode | S_IRWXU;
- if (do_chmod(fname, mode) < 0) {
+ if (do_chmod(fname, mode, 0) < 0) {
rsyserr(FERROR_XFER, errno,
"failed to modify permissions on %s",
full_fname(fname));
@@ -1534,6 +1554,10 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx,
int exists = statret == 0 && stype != FT_DIR;
file->mode = dest_mode(file->mode, sx.st.st_mode, dflt_perms, exists);
}
+#ifdef SUPPORT_FORCE_CHANGE
+ if (force_change && !preserve_fileflags)
+ F_FFLAGS(file) = sx.st.st_flags;
+#endif
#ifdef SUPPORT_HARD_LINKS
if (preserve_hard_links && F_HLINK_NOT_FIRST(file)
@@ -2107,17 +2131,25 @@ static void touch_up_dirs(struct file_list *flist, int ndx)
continue;
fname = f_name(file, NULL);
if (fix_dir_perms)
- do_chmod(fname, file->mode);
+ do_chmod(fname, file->mode, 0);
if (need_retouch_dir_times) {
STRUCT_STAT st;
if (link_stat(fname, &st, 0) == 0 && mtime_differs(&st, file)) {
st.st_mtime = file->modtime;
#ifdef ST_MTIME_NSEC
st.ST_MTIME_NSEC = F_MOD_NSEC_or_0(file);
+#endif
+#ifdef SUPPORT_FORCE_CHANGE
+ st.st_mode = file->mode;
+ st.st_flags = 0;
#endif
set_times(fname, &st);
}
}
+#ifdef SUPPORT_FORCE_CHANGE
+ if (force_change && F_FFLAGS(file) & force_change)
+ undo_make_mutable(fname, F_FFLAGS(file));
+#endif
if (counter >= loopchk_limit) {
if (allowed_lull)
maybe_send_keepalive(time(NULL), MSK_ALLOW_FLUSH);
diff --git a/log.c b/log.c
--- a/log.c
+++ b/log.c
@@ -725,7 +725,8 @@ static void log_formatted(enum logcode code, const char *format, const char *op,
: iflags & ITEM_REPORT_ATIME ? 'u' : 'n';
c[9] = !(iflags & ITEM_REPORT_ACL) ? '.' : 'a';
c[10] = !(iflags & ITEM_REPORT_XATTR) ? '.' : 'x';
- c[11] = '\0';
+ c[11] = !(iflags & ITEM_REPORT_FFLAGS) ? '.' : 'f';
+ c[12] = '\0';
if (iflags & (ITEM_IS_NEW|ITEM_MISSING_DATA)) {
char ch = iflags & ITEM_IS_NEW ? '+' : '?';
diff --git a/main.c b/main.c
--- a/main.c
+++ b/main.c
@@ -31,6 +31,9 @@
#ifdef __TANDEM
#include <floss.h(floss_execlp)>
#endif
+#ifdef SUPPORT_FORCE_CHANGE
+#include <sys/sysctl.h>
+#endif
extern int dry_run;
extern int list_only;
@@ -49,6 +52,7 @@ extern int need_messages_from_generator;
extern int kluge_around_eof;
extern int got_xfer_error;
extern int old_style_args;
+extern int force_change;
extern int msgs2stderr;
extern int module_id;
extern int read_only;
@@ -977,6 +981,22 @@ static int do_recv(int f_in, int f_out, char *local_name)
* points to an identical file won't be replaced by the referent. */
copy_links = copy_dirlinks = copy_unsafe_links = 0;
+#ifdef SUPPORT_FORCE_CHANGE
+ if (force_change & SYS_IMMUTABLE) {
+ /* Determine whether we'll be able to unlock a system immutable item. */
+ int mib[2];
+ int securityLevel = 0;
+ size_t len = sizeof securityLevel;
+
+ mib[0] = CTL_KERN;
+ mib[1] = KERN_SECURELVL;
+ if (sysctl(mib, 2, &securityLevel, &len, NULL, 0) == 0 && securityLevel > 0) {
+ rprintf(FERROR, "System security level is too high to force mutability on system immutable files and directories.\n");
+ exit_cleanup(RERR_UNSUPPORTED);
+ }
+ }
+#endif
+
#ifdef SUPPORT_HARD_LINKS
if (preserve_hard_links && !inc_recurse)
match_hard_links(first_flist);
diff --git a/options.c b/options.c
--- a/options.c
+++ b/options.c
@@ -56,6 +56,7 @@ int preserve_hard_links = 0;
int preserve_acls = 0;
int preserve_xattrs = 0;
int preserve_perms = 0;
+int preserve_fileflags = 0;
int preserve_executability = 0;
int preserve_devices = 0;
int preserve_specials = 0;
@@ -98,6 +99,7 @@ int msgs2stderr = 2; /* Default: send errors to stderr for local & remote-shell
int saw_stderr_opt = 0;
int allow_8bit_chars = 0;
int force_delete = 0;
+int force_change = 0;
int io_timeout = 0;
int prune_empty_dirs = 0;
int use_qsort = 0;
@@ -623,6 +625,8 @@ static struct poptOption long_options[] = {
{"perms", 'p', POPT_ARG_VAL, &preserve_perms, 1, 0, 0 },
{"no-perms", 0, POPT_ARG_VAL, &preserve_perms, 0, 0, 0 },
{"no-p", 0, POPT_ARG_VAL, &preserve_perms, 0, 0, 0 },
+ {"fileflags", 0, POPT_ARG_VAL, &preserve_fileflags, 1, 0, 0 },
+ {"no-fileflags", 0, POPT_ARG_VAL, &preserve_fileflags, 0, 0, 0 },
{"executability", 'E', POPT_ARG_NONE, &preserve_executability, 0, 0, 0 },
{"acls", 'A', POPT_ARG_NONE, 0, 'A', 0, 0 },
{"no-acls", 0, POPT_ARG_VAL, &preserve_acls, 0, 0, 0 },
@@ -721,6 +725,12 @@ static struct poptOption long_options[] = {
{"remove-source-files",0,POPT_ARG_VAL, &remove_source_files, 1, 0, 0 },
{"force", 0, POPT_ARG_VAL, &force_delete, 1, 0, 0 },
{"no-force", 0, POPT_ARG_VAL, &force_delete, 0, 0, 0 },
+ {"force-delete", 0, POPT_ARG_VAL, &force_delete, 1, 0, 0 },
+ {"no-force-delete", 0, POPT_ARG_VAL, &force_delete, 0, 0, 0 },
+ {"force-change", 0, POPT_ARG_VAL, &force_change, ALL_IMMUTABLE, 0, 0 },
+ {"no-force-change", 0, POPT_ARG_VAL, &force_change, 0, 0, 0 },
+ {"force-uchange", 0, POPT_ARG_VAL, &force_change, USR_IMMUTABLE, 0, 0 },
+ {"force-schange", 0, POPT_ARG_VAL, &force_change, SYS_IMMUTABLE, 0, 0 },
{"ignore-errors", 0, POPT_ARG_VAL, &ignore_errors, 1, 0, 0 },
{"no-ignore-errors", 0, POPT_ARG_VAL, &ignore_errors, 0, 0, 0 },
{"max-delete", 0, POPT_ARG_INT, &max_delete, 0, 0, 0 },
@@ -1016,6 +1026,14 @@ static void set_refuse_options(void)
#ifndef SUPPORT_CRTIMES
parse_one_refuse_match(0, "crtimes", list_end);
#endif
+#ifndef SUPPORT_FILEFLAGS
+ parse_one_refuse_match(0, "fileflags", list_end);
+#endif
+#ifndef SUPPORT_FORCE_CHANGE
+ parse_one_refuse_match(0, "force-change", list_end);
+ parse_one_refuse_match(0, "force-uchange", list_end);
+ parse_one_refuse_match(0, "force-schange", list_end);
+#endif
/* Now we use the descrip values to actually mark the options for refusal. */
for (op = long_options; op != list_end; op++) {
@@ -2734,6 +2752,9 @@ void server_options(char **args, int *argc_p)
if (xfer_dirs && !recurse && delete_mode && am_sender)
args[ac++] = "--no-r";
+ if (preserve_fileflags)
+ args[ac++] = "--fileflags";
+
if (do_compression && do_compression_level != CLVL_NOT_SPECIFIED) {
if (asprintf(&arg, "--compress-level=%d", do_compression_level) < 0)
goto oom;
@@ -2829,6 +2850,16 @@ void server_options(char **args, int *argc_p)
args[ac++] = "--delete-excluded";
if (force_delete)
args[ac++] = "--force";
+#ifdef SUPPORT_FORCE_CHANGE
+ if (force_change) {
+ if (force_change == ALL_IMMUTABLE)
+ args[ac++] = "--force-change";
+ else if (force_change == USR_IMMUTABLE)
+ args[ac++] = "--force-uchange";
+ else if (force_change == SYS_IMMUTABLE)
+ args[ac++] = "--force-schange";
+ }
+#endif
if (write_batch < 0)
args[ac++] = "--only-write-batch=X";
if (am_root > 1)
diff --git a/rsync.1.md b/rsync.1.md
--- a/rsync.1.md
+++ b/rsync.1.md
@@ -446,6 +446,7 @@ has its own detailed description later in this manpage.
--keep-dirlinks, -K treat symlinked dir on receiver as dir
--hard-links, -H preserve hard links
--perms, -p preserve permissions
+--fileflags preserve file-flags (aka chflags)
--executability, -E preserve executability
--chmod=CHMOD affect file and/or directory permissions
--acls, -A preserve ACLs (implies --perms)
@@ -487,7 +488,10 @@ has its own detailed description later in this manpage.
--ignore-missing-args ignore missing source args without error
--delete-missing-args delete missing source args from destination
--ignore-errors delete even if there are I/O errors
---force force deletion of dirs even if not empty
+--force-delete force deletion of directories even if not empty
+--force-change affect user-/system-immutable files/dirs
+--force-uchange affect user-immutable files/dirs
+--force-schange affect system-immutable files/dirs
--max-delete=NUM don't delete more than NUM files
--max-size=SIZE don't transfer any file larger than SIZE
--min-size=SIZE don't transfer any file smaller than SIZE
@@ -831,6 +835,7 @@ expand it.
recursion and want to preserve almost everything. Be aware that it does
**not** include preserving ACLs (`-A`), xattrs (`-X`), atimes (`-U`),
crtimes (`-N`), nor the finding and preserving of hardlinks (`-H`).
+ It also does **not** imply [`--fileflags`](#opt).
The only exception to the above equivalence is when [`--files-from`](#opt)
is specified, in which case [`-r`](#opt) is not implied.
@@ -1295,7 +1300,7 @@ expand it.
Without this option, if the sending side has replaced a directory with a
symlink to a directory, the receiving side will delete anything that is in
the way of the new symlink, including a directory hierarchy (as long as
- [`--force`](#opt) or [`--delete`](#opt) is in effect).
+ [`--force-delete`](#opt) or [`--delete`](#opt) is in effect).
See also [`--keep-dirlinks`](#opt) for an analogous option for the
receiving side.
@@ -1490,6 +1495,37 @@ expand it.
those used by [`--fake-super`](#opt)) unless you repeat the option (e.g. `-XX`).
This "copy all xattrs" mode cannot be used with [`--fake-super`](#opt).
+0. `--fileflags`
+
+ This option causes rsync to update the file-flags to be the same as the
+ source files and directories (if your OS supports the **chflags**(2) system
+ call). Some flags can only be altered by the super-user and some might
+ only be unset below a certain secure-level (usually single-user mode). It
+ will not make files alterable that are set to immutable on the receiver.
+ To do that, see [`--force-change`](#opt), [`--force-uchange`](#opt), and
+ [`--force-schange`](#opt).
+
+0. `--force-change`
+
+ This option causes rsync to disable both user-immutable and
+ system-immutable flags on files and directories that are being updated or
+ deleted on the receiving side. This option overrides
+ [`--force-uchange`](#opt) and [`--force-schange`](#opt).
+
+0. `--force-uchange`
+
+ This option causes rsync to disable user-immutable flags on files and
+ directories that are being updated or deleted on the receiving side. It
+ does not try to affect system flags. This option overrides
+ [`--force-change`](#opt) and [`--force-schange`](#opt).
+
+0. `--force-schange`
+
+ This option causes rsync to disable system-immutable flags on files and
+ directories that are being updated or deleted on the receiving side. It
+ does not try to affect user flags. This option overrides
+ [`--force-change`](#opt) and [`--force-uchange`](#opt).
+
0. `--chmod=CHMOD`
This option tells rsync to apply one or more comma-separated "chmod" modes
@@ -2019,8 +2055,8 @@ expand it.
[`--ignore-missing-args`](#opt) option a step farther: each missing arg
will become a deletion request of the corresponding destination file on the
receiving side (should it exist). If the destination file is a non-empty
- directory, it will only be successfully deleted if [`--force`](#opt) or
- [`--delete`](#opt) are in effect. Other than that, this option is
+ directory, it will only be successfully deleted if [`--force-delete`](#opt)
+ or [`--delete`](#opt) are in effect. Other than that, this option is
independent of any other type of delete processing.
The missing source files are represented by special file-list entries which
@@ -2031,14 +2067,14 @@ expand it.
Tells [`--delete`](#opt) to go ahead and delete files even when there are
I/O errors.
-0. `--force`
+0. `--force-delete`, `--force`
This option tells rsync to delete a non-empty directory when it is to be
replaced by a non-directory. This is only relevant if deletions are not
active (see [`--delete`](#opt) for details).
- Note for older rsync versions: `--force` used to still be required when
- using [`--delete-after`](#opt), and it used to be non-functional unless the
+ Note that some older rsync versions used to require `--force` when using
+ [`--delete-after`](#opt), and it used to be non-functional unless the
[`--recursive`](#opt) option was also enabled.
0. `--max-delete=NUM`
@@ -3099,7 +3135,7 @@ expand it.
also turns on the output of other verbose messages).
The "%i" escape has a cryptic output that is 11 letters long. The general
- format is like the string `YXcstpoguax`, where **Y** is replaced by the type
+ format is like the string `YXcstpoguaxf`, where **Y** is replaced by the type
of update being done, **X** is replaced by the file-type, and the other
letters represent attributes that may be output if they are being modified.
diff --git a/rsync.c b/rsync.c
--- a/rsync.c
+++ b/rsync.c
@@ -31,6 +31,7 @@ extern int dry_run;
extern int preserve_acls;
extern int preserve_xattrs;
extern int preserve_perms;
+extern int preserve_fileflags;
extern int preserve_executability;
extern int preserve_mtimes;
extern int omit_dir_times;
@@ -468,6 +469,39 @@ mode_t dest_mode(mode_t flist_mode, mode_t stat_mode, int dflt_perms,
return new_mode;
}
+#if defined SUPPORT_FILEFLAGS || defined SUPPORT_FORCE_CHANGE
+/* Set a file's st_flags. */
+static int set_fileflags(const char *fname, uint32 fileflags)
+{
+ if (do_chflags(fname, fileflags) != 0) {
+ rsyserr(FERROR_XFER, errno,
+ "failed to set file flags on %s",
+ full_fname(fname));
+ return 0;
+ }
+
+ return 1;
+}
+
+/* Remove immutable flags from an object, so it can be altered/removed. */
+int make_mutable(const char *fname, mode_t mode, uint32 fileflags, uint32 iflags)
+{
+ if (S_ISLNK(mode) || !(fileflags & iflags))
+ return 0;
+ if (!set_fileflags(fname, fileflags & ~iflags))
+ return -1;
+ return 1;
+}
+
+/* Undo a prior make_mutable() call that returned a 1. */
+int undo_make_mutable(const char *fname, uint32 fileflags)
+{
+ if (!set_fileflags(fname, fileflags))
+ return -1;
+ return 1;
+}
+#endif
+
static int same_mtime(struct file_struct *file, STRUCT_STAT *st, int extra_accuracy)
{
#ifdef ST_MTIME_NSEC
@@ -544,7 +578,7 @@ int set_file_attrs(const char *fname, struct file_struct *file, stat_x *sxp,
if (am_root >= 0) {
uid_t uid = change_uid ? (uid_t)F_OWNER(file) : sxp->st.st_uid;
gid_t gid = change_gid ? (gid_t)F_GROUP(file) : sxp->st.st_gid;
- if (do_lchown(fname, uid, gid) != 0) {
+ if (do_lchown(fname, uid, gid, sxp->st.st_mode, ST_FLAGS(sxp->st)) != 0) {
/* We shouldn't have attempted to change uid
* or gid unless have the privilege. */
rsyserr(FERROR_XFER, errno, "%s %s failed",
@@ -654,7 +688,7 @@ int set_file_attrs(const char *fname, struct file_struct *file, stat_x *sxp,
#ifdef HAVE_CHMOD
if (!BITS_EQUAL(sxp->st.st_mode, new_mode, CHMOD_BITS)) {
- int ret = am_root < 0 ? 0 : do_chmod(fname, new_mode);
+ int ret = am_root < 0 ? 0 : do_chmod(fname, new_mode, ST_FLAGS(sxp->st));
if (ret < 0) {
rsyserr(FERROR_XFER, errno,
"failed to set permissions on %s",
@@ -666,6 +700,19 @@ int set_file_attrs(const char *fname, struct file_struct *file, stat_x *sxp,
}
#endif
+#ifdef SUPPORT_FILEFLAGS
+ if (preserve_fileflags && !S_ISLNK(sxp->st.st_mode)
+ && sxp->st.st_flags != F_FFLAGS(file)) {
+ uint32 fileflags = F_FFLAGS(file);
+ if (flags & ATTRS_DELAY_IMMUTABLE)
+ fileflags &= ~ALL_IMMUTABLE;
+ if (sxp->st.st_flags != fileflags
+ && !set_fileflags(fname, fileflags))
+ goto cleanup;
+ updated = 1;
+ }
+#endif
+
if (INFO_GTE(NAME, 2) && flags & ATTRS_REPORT) {
if (updated)
rprintf(FCLIENT, "%s\n", fname);
@@ -743,7 +790,8 @@ int finish_transfer(const char *fname, const char *fnametmp,
/* Change permissions before putting the file into place. */
set_file_attrs(fnametmp, file, NULL, fnamecmp,
- ok_to_set_time ? ATTRS_ACCURATE_TIME : ATTRS_SKIP_MTIME | ATTRS_SKIP_ATIME | ATTRS_SKIP_CRTIME);
+ ATTRS_DELAY_IMMUTABLE
+ | (ok_to_set_time ? ATTRS_ACCURATE_TIME : ATTRS_SKIP_MTIME | ATTRS_SKIP_ATIME | ATTRS_SKIP_CRTIME));
/* move tmp file over real file */
if (DEBUG_GTE(RECV, 1))
@@ -760,6 +808,10 @@ int finish_transfer(const char *fname, const char *fnametmp,
}
if (ret == 0) {
/* The file was moved into place (not copied), so it's done. */
+#ifdef SUPPORT_FILEFLAGS
+ if (preserve_fileflags && F_FFLAGS(file) & ALL_IMMUTABLE)
+ set_fileflags(fname, F_FFLAGS(file));
+#endif
return 1;
}
/* The file was copied, so tweak the perms of the copied file. If it
diff --git a/rsync.h b/rsync.h
--- a/rsync.h
+++ b/rsync.h
@@ -69,7 +69,7 @@
/* The following XMIT flags require an rsync that uses a varint for the flag values */
-#define XMIT_RESERVED_16 (1<<16) /* reserved for future fileflags use */
+#define XMIT_SAME_FLAGS (1<<16) /* any protocol - restricted by command-line option */
#define XMIT_CRTIME_EQ_MTIME (1<<17) /* any protocol - restricted by command-line option */
/* These flags are used in the live flist data. */
@@ -192,6 +192,7 @@
#define ATTRS_SKIP_MTIME (1<<1)
#define ATTRS_ACCURATE_TIME (1<<2)
#define ATTRS_SKIP_ATIME (1<<3)
+#define ATTRS_DELAY_IMMUTABLE (1<<4)
#define ATTRS_SKIP_CRTIME (1<<5)
#define MSG_FLUSH 2
@@ -220,6 +221,7 @@
#define ITEM_REPORT_GROUP (1<<6)
#define ITEM_REPORT_ACL (1<<7)
#define ITEM_REPORT_XATTR (1<<8)
+#define ITEM_REPORT_FFLAGS (1<<9)
#define ITEM_REPORT_CRTIME (1<<10)
#define ITEM_BASIS_TYPE_FOLLOWS (1<<11)
#define ITEM_XNAME_FOLLOWS (1<<12)
@@ -587,6 +589,31 @@ typedef unsigned int size_t;
#define SUPPORT_CRTIMES 1
#endif
+#define NO_FFLAGS ((uint32)-1)
+
+#ifdef HAVE_CHFLAGS
+#define SUPPORT_FILEFLAGS 1
+#define SUPPORT_FORCE_CHANGE 1
+#endif
+
+#if defined SUPPORT_FILEFLAGS || defined SUPPORT_FORCE_CHANGE
+#ifndef UF_NOUNLINK
+#define UF_NOUNLINK 0
+#endif
+#ifndef SF_NOUNLINK
+#define SF_NOUNLINK 0
+#endif
+#define USR_IMMUTABLE (UF_IMMUTABLE|UF_NOUNLINK|UF_APPEND)
+#define SYS_IMMUTABLE (SF_IMMUTABLE|SF_NOUNLINK|SF_APPEND)
+#define ALL_IMMUTABLE (USR_IMMUTABLE|SYS_IMMUTABLE)
+#define ST_FLAGS(st) ((st).st_flags)
+#else
+#define USR_IMMUTABLE 0
+#define SYS_IMMUTABLE 0
+#define ALL_IMMUTABLE 0
+#define ST_FLAGS(st) NO_FFLAGS
+#endif
+
/* Find a variable that is either exactly 32-bits or longer.
* If some code depends on 32-bit truncation, it will need to
* take special action in a "#if SIZEOF_INT32 > 4" section. */
@@ -818,6 +845,7 @@ extern int pathname_ndx;
extern int depth_ndx;
extern int uid_ndx;
extern int gid_ndx;
+extern int fileflags_ndx;
extern int acls_ndx;
extern int xattrs_ndx;
extern int file_sum_extra_cnt;
@@ -873,6 +901,11 @@ extern int file_sum_extra_cnt;
/* When the associated option is on, all entries will have these present: */
#define F_OWNER(f) REQ_EXTRA(f, uid_ndx)->unum
#define F_GROUP(f) REQ_EXTRA(f, gid_ndx)->unum
+#if defined SUPPORT_FILEFLAGS || defined SUPPORT_FORCE_CHANGE
+#define F_FFLAGS(f) REQ_EXTRA(f, fileflags_ndx)->unum
+#else
+#define F_FFLAGS(f) NO_FFLAGS
+#endif
#define F_ACL(f) REQ_EXTRA(f, acls_ndx)->num
#define F_XATTR(f) REQ_EXTRA(f, xattrs_ndx)->num
#define F_NDX(f) REQ_EXTRA(f, unsort_ndx)->num
diff --git a/syscall.c b/syscall.c
--- a/syscall.c
+++ b/syscall.c
@@ -38,6 +38,7 @@ extern int am_root;
extern int am_sender;
extern int read_only;
extern int list_only;
+extern int force_change;
extern int inplace;
extern int preallocate_files;
extern int preserve_perms;
@@ -81,7 +82,23 @@ int do_unlink(const char *path)
{
if (dry_run) return 0;
RETURN_ERROR_IF_RO_OR_LO;
- return unlink(path);
+ if (unlink(path) == 0)
+ return 0;
+#ifdef SUPPORT_FORCE_CHANGE
+ if (force_change && errno == EPERM) {
+ STRUCT_STAT st;
+
+ if (x_lstat(path, &st, NULL) == 0
+ && make_mutable(path, st.st_mode, st.st_flags, force_change) > 0) {
+ if (unlink(path) == 0)
+ return 0;
+ undo_make_mutable(path, st.st_flags);
+ }
+ /* TODO: handle immutable directories */
+ errno = EPERM;
+ }
+#endif
+ return -1;
}
#ifdef SUPPORT_LINKS
@@ -146,14 +163,35 @@ int do_link(const char *old_path, const char *new_path)
}
#endif
-int do_lchown(const char *path, uid_t owner, gid_t group)
+int do_lchown(const char *path, uid_t owner, gid_t group, UNUSED(mode_t mode), UNUSED(uint32 fileflags))
{
if (dry_run) return 0;
RETURN_ERROR_IF_RO_OR_LO;
#ifndef HAVE_LCHOWN
#define lchown chown
#endif
- return lchown(path, owner, group);
+ if (lchown(path, owner, group) == 0)
+ return 0;
+#ifdef SUPPORT_FORCE_CHANGE
+ if (force_change && errno == EPERM) {
+ if (fileflags == NO_FFLAGS) {
+ STRUCT_STAT st;
+ if (x_lstat(path, &st, NULL) == 0) {
+ mode = st.st_mode;
+ fileflags = st.st_flags;
+ }
+ }
+ if (fileflags != NO_FFLAGS
+ && make_mutable(path, mode, fileflags, force_change) > 0) {
+ int ret = lchown(path, owner, group);
+ undo_make_mutable(path, fileflags);
+ if (ret == 0)
+ return 0;
+ }
+ errno = EPERM;
+ }
+#endif
+ return -1;
}
int do_mknod(const char *pathname, mode_t mode, dev_t dev)
@@ -193,7 +231,7 @@ int do_mknod(const char *pathname, mode_t mode, dev_t dev)
return -1;
close(sock);
#ifdef HAVE_CHMOD
- return do_chmod(pathname, mode);
+ return do_chmod(pathname, mode, 0);
#else
return 0;
#endif
@@ -210,7 +248,22 @@ int do_rmdir(const char *pathname)
{
if (dry_run) return 0;
RETURN_ERROR_IF_RO_OR_LO;
- return rmdir(pathname);
+ if (rmdir(pathname) == 0)
+ return 0;
+#ifdef SUPPORT_FORCE_CHANGE
+ if (force_change && errno == EPERM) {
+ STRUCT_STAT st;
+
+ if (x_lstat(pathname, &st, NULL) == 0
+ && make_mutable(pathname, st.st_mode, st.st_flags, force_change) > 0) {
+ if (rmdir(pathname) == 0)
+ return 0;
+ undo_make_mutable(pathname, st.st_flags);
+ }
+ errno = EPERM;
+ }
+#endif
+ return -1;
}
int do_open(const char *pathname, int flags, mode_t mode)
@@ -229,7 +282,7 @@ int do_open(const char *pathname, int flags, mode_t mode)
}
#ifdef HAVE_CHMOD
-int do_chmod(const char *path, mode_t mode)
+int do_chmod(const char *path, mode_t mode, UNUSED(uint32 fileflags))
{
static int switch_step = 0;
int code;
@@ -268,17 +321,72 @@ int do_chmod(const char *path, mode_t mode)
code = chmod(path, mode & CHMOD_BITS); /* DISCOURAGED FUNCTION */
break;
}
+#ifdef SUPPORT_FORCE_CHANGE
+ if (code < 0 && force_change && errno == EPERM && !S_ISLNK(mode)) {
+ if (fileflags == NO_FFLAGS) {
+ STRUCT_STAT st;
+ if (x_lstat(path, &st, NULL) == 0)
+ fileflags = st.st_flags;
+ }
+ if (fileflags != NO_FFLAGS
+ && make_mutable(path, mode, fileflags, force_change) > 0) {
+ code = chmod(path, mode & CHMOD_BITS);
+ undo_make_mutable(path, fileflags);
+ if (code == 0)
+ return 0;
+ }
+ errno = EPERM;
+ }
+#endif
if (code != 0 && (preserve_perms || preserve_executability))
return code;
return 0;
}
#endif
+#ifdef HAVE_CHFLAGS
+int do_chflags(const char *path, uint32 fileflags)
+{
+ if (dry_run) return 0;
+ RETURN_ERROR_IF_RO_OR_LO;
+ return chflags(path, fileflags);
+}
+#endif
+
int do_rename(const char *old_path, const char *new_path)
{
if (dry_run) return 0;
RETURN_ERROR_IF_RO_OR_LO;
- return rename(old_path, new_path);
+ if (rename(old_path, new_path) == 0)
+ return 0;
+#ifdef SUPPORT_FORCE_CHANGE
+ if (force_change && errno == EPERM) {
+ STRUCT_STAT st1, st2;
+ int became_mutable;
+
+ if (x_lstat(old_path, &st1, NULL) != 0)
+ goto failed;
+ became_mutable = make_mutable(old_path, st1.st_mode, st1.st_flags, force_change) > 0;
+ if (became_mutable && rename(old_path, new_path) == 0)
+ goto success;
+ if (x_lstat(new_path, &st2, NULL) == 0
+ && make_mutable(new_path, st2.st_mode, st2.st_flags, force_change) > 0) {
+ if (rename(old_path, new_path) == 0) {
+ success:
+ if (became_mutable) /* Yes, use new_path and st1! */
+ undo_make_mutable(new_path, st1.st_flags);
+ return 0;
+ }
+ undo_make_mutable(new_path, st2.st_flags);
+ }
+ /* TODO: handle immutable directories */
+ if (became_mutable)
+ undo_make_mutable(old_path, st1.st_flags);
+ failed:
+ errno = EPERM;
+ }
+#endif
+ return -1;
}
#ifdef HAVE_FTRUNCATE
diff --git a/t_stub.c b/t_stub.c
--- a/t_stub.c
+++ b/t_stub.c
@@ -29,6 +29,8 @@ int protect_args = 0;
int module_id = -1;
int relative_paths = 0;
int module_dirlen = 0;
+int force_change = 0;
+int preserve_acls = 0;
int preserve_xattrs = 0;
int preserve_perms = 0;
int preserve_executability = 0;
@@ -111,3 +113,23 @@ filter_rule_list daemon_filter_list;
{
return cst ? 0 : 0;
}
+
+#if defined SUPPORT_FILEFLAGS || defined SUPPORT_FORCE_CHANGE
+ int make_mutable(UNUSED(const char *fname), UNUSED(mode_t mode), UNUSED(uint32 fileflags), UNUSED(uint32 iflags))
+{
+ return 0;
+}
+
+/* Undo a prior make_mutable() call that returned a 1. */
+ int undo_make_mutable(UNUSED(const char *fname), UNUSED(uint32 fileflags))
+{
+ return 0;
+}
+#endif
+
+#ifdef SUPPORT_XATTRS
+ int x_lstat(UNUSED(const char *fname), UNUSED(STRUCT_STAT *fst), UNUSED(STRUCT_STAT *xst))
+{
+ return -1;
+}
+#endif
diff --git a/testsuite/rsync.fns b/testsuite/rsync.fns
--- a/testsuite/rsync.fns
+++ b/testsuite/rsync.fns
@@ -26,9 +26,9 @@ chkfile="$scratchdir/rsync.chk"
outfile="$scratchdir/rsync.out"
# For itemized output:
-all_plus='+++++++++'
-allspace=' '
-dots='.....' # trailing dots after changes
+all_plus='++++++++++'
+allspace=' '
+dots='......' # trailing dots after changes
tab_ch=' ' # a single tab character
# Berkley's nice.
diff --git a/usage.c b/usage.c
--- a/usage.c
+++ b/usage.c