forked from RsyncProject/rsync
-
Notifications
You must be signed in to change notification settings - Fork 0
/
main.c
1855 lines (1622 loc) · 49.7 KB
/
main.c
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
/*
* The startup routines, including main(), for rsync.
*
* Copyright (C) 1996-2001 Andrew Tridgell <[email protected]>
* Copyright (C) 1996 Paul Mackerras
* Copyright (C) 2001, 2002 Martin Pool <[email protected]>
* Copyright (C) 2003-2022 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
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, visit the http://fsf.org website.
*/
#include "rsync.h"
#include "inums.h"
#include "ifuncs.h"
#include "io.h"
#if defined CONFIG_LOCALE && defined HAVE_LOCALE_H
#include <locale.h>
#endif
#include <popt.h>
#ifdef __TANDEM
#include <floss.h(floss_execlp)>
#endif
extern int dry_run;
extern int list_only;
extern int io_timeout;
extern int am_root;
extern int am_server;
extern int am_sender;
extern int am_daemon;
extern int inc_recurse;
extern int blocking_io;
extern int always_checksum;
extern int remove_source_files;
extern int output_needs_newline;
extern int called_from_signal_handler;
extern int need_messages_from_generator;
extern int kluge_around_eof;
extern int got_xfer_error;
extern int old_style_args;
extern int msgs2stderr;
extern int module_id;
extern int read_only;
extern int copy_links;
extern int copy_dirlinks;
extern int copy_unsafe_links;
extern int keep_dirlinks;
extern int preserve_hard_links;
extern int protocol_version;
extern int mkpath_dest_arg;
extern int file_total;
extern int recurse;
extern int xfer_dirs;
extern int protect_args;
extern int relative_paths;
extern int sanitize_paths;
extern int curr_dir_depth;
extern int curr_dir_len;
extern int module_id;
extern int rsync_port;
extern int whole_file;
extern int read_batch;
extern int write_batch;
extern int batch_fd;
extern int sock_f_in;
extern int sock_f_out;
extern int filesfrom_fd;
extern int connect_timeout;
extern int send_msgs_to_gen;
extern dev_t filesystem_dev;
extern pid_t cleanup_child_pid;
extern size_t bwlimit_writemax;
extern unsigned int module_dirlen;
extern BOOL flist_receiving_enabled;
extern BOOL want_progress_now;
extern BOOL shutting_down;
extern int backup_dir_len;
extern int basis_dir_cnt;
extern int default_af_hint;
extern int stdout_format_has_i;
extern int trust_sender_filter;
extern int trust_sender_args;
extern struct stats stats;
extern char *stdout_format;
extern char *logfile_format;
extern char *filesfrom_host;
extern char *partial_dir;
extern char *rsync_path;
extern char *shell_cmd;
extern char *password_file;
extern char *backup_dir;
extern char *copy_as;
extern char *tmpdir;
extern char curr_dir[MAXPATHLEN];
extern char backup_dir_buf[MAXPATHLEN];
extern char *basis_dir[MAX_BASIS_DIRS+1];
extern struct file_list *first_flist;
extern filter_rule_list daemon_filter_list, implied_filter_list;
uid_t our_uid;
gid_t our_gid;
int am_receiver = 0; /* Only set to 1 after the receiver/generator fork. */
int am_generator = 0; /* Only set to 1 after the receiver/generator fork. */
int local_server = 0;
int daemon_connection = 0; /* 0 = no daemon, 1 = daemon via remote shell, -1 = daemon via socket */
mode_t orig_umask = 0;
int batch_gen_fd = -1;
int sender_keeps_checksum = 0;
int raw_argc, cooked_argc;
char **raw_argv, **cooked_argv;
/* There's probably never more than at most 2 outstanding child processes,
* but set it higher, just in case. */
#define MAXCHILDPROCS 7
#ifdef HAVE_SIGACTION
# ifdef HAVE_SIGPROCMASK
# define SIGACTMASK(n,h) SIGACTION(n,h), sigaddset(&sigmask,(n))
# else
# define SIGACTMASK(n,h) SIGACTION(n,h)
# endif
static struct sigaction sigact;
#endif
struct pid_status {
pid_t pid;
int status;
} pid_stat_table[MAXCHILDPROCS];
static time_t starttime, endtime;
static int64 total_read, total_written;
static void show_malloc_stats(void);
/* Works like waitpid(), but if we already harvested the child pid in our
* remember_children(), we succeed instead of returning an error. */
pid_t wait_process(pid_t pid, int *status_ptr, int flags)
{
pid_t waited_pid;
do {
waited_pid = waitpid(pid, status_ptr, flags);
} while (waited_pid == -1 && errno == EINTR);
if (waited_pid == -1 && errno == ECHILD) {
/* Status of requested child no longer available: check to
* see if it was processed by remember_children(). */
int cnt;
for (cnt = 0; cnt < MAXCHILDPROCS; cnt++) {
if (pid == pid_stat_table[cnt].pid) {
*status_ptr = pid_stat_table[cnt].status;
pid_stat_table[cnt].pid = 0;
return pid;
}
}
}
return waited_pid;
}
int shell_exec(const char *cmd)
{
char *shell = getenv("RSYNC_SHELL");
int status;
pid_t pid;
if (!shell)
return system(cmd);
if ((pid = fork()) < 0)
return -1;
if (pid == 0) {
execlp(shell, shell, "-c", cmd, NULL);
_exit(1);
}
int ret = wait_process(pid, &status, 0);
return ret < 0 ? -1 : status;
}
/* Wait for a process to exit, calling io_flush while waiting. */
static void wait_process_with_flush(pid_t pid, int *exit_code_ptr)
{
pid_t waited_pid;
int status;
while ((waited_pid = wait_process(pid, &status, WNOHANG)) == 0) {
msleep(20);
io_flush(FULL_FLUSH);
}
/* TODO: If the child exited on a signal, then log an
* appropriate error message. Perhaps we should also accept a
* message describing the purpose of the child. Also indicate
* this to the caller so that they know something went wrong. */
if (waited_pid < 0) {
rsyserr(FERROR, errno, "waitpid");
*exit_code_ptr = RERR_WAITCHILD;
} else if (!WIFEXITED(status)) {
#ifdef WCOREDUMP
if (WCOREDUMP(status))
*exit_code_ptr = RERR_CRASHED;
else
#endif
if (WIFSIGNALED(status))
*exit_code_ptr = RERR_TERMINATED;
else
*exit_code_ptr = RERR_WAITCHILD;
} else
*exit_code_ptr = WEXITSTATUS(status);
}
void write_del_stats(int f)
{
if (read_batch)
write_int(f, NDX_DEL_STATS);
else
write_ndx(f, NDX_DEL_STATS);
write_varint(f, stats.deleted_files - stats.deleted_dirs
- stats.deleted_symlinks - stats.deleted_devices
- stats.deleted_specials);
write_varint(f, stats.deleted_dirs);
write_varint(f, stats.deleted_symlinks);
write_varint(f, stats.deleted_devices);
write_varint(f, stats.deleted_specials);
}
void read_del_stats(int f)
{
stats.deleted_files = read_varint(f);
stats.deleted_files += stats.deleted_dirs = read_varint(f);
stats.deleted_files += stats.deleted_symlinks = read_varint(f);
stats.deleted_files += stats.deleted_devices = read_varint(f);
stats.deleted_files += stats.deleted_specials = read_varint(f);
}
static void become_copy_as_user()
{
char *gname;
uid_t uid;
gid_t gid;
if (!copy_as)
return;
if (DEBUG_GTE(CMD, 2))
rprintf(FINFO, "[%s] copy_as=%s\n", who_am_i(), copy_as);
if ((gname = strchr(copy_as, ':')) != NULL)
*gname++ = '\0';
if (!user_to_uid(copy_as, &uid, True)) {
rprintf(FERROR, "Invalid copy-as user: %s\n", copy_as);
exit_cleanup(RERR_SYNTAX);
}
if (gname) {
if (!group_to_gid(gname, &gid, True)) {
rprintf(FERROR, "Invalid copy-as group: %s\n", gname);
exit_cleanup(RERR_SYNTAX);
}
} else {
struct passwd *pw;
if ((pw = getpwuid(uid)) == NULL) {
rsyserr(FERROR, errno, "getpwuid failed");
exit_cleanup(RERR_SYNTAX);
}
gid = pw->pw_gid;
}
if (setgid(gid) < 0) {
rsyserr(FERROR, errno, "setgid failed");
exit_cleanup(RERR_SYNTAX);
}
#ifdef HAVE_SETGROUPS
if (setgroups(1, &gid)) {
rsyserr(FERROR, errno, "setgroups failed");
exit_cleanup(RERR_SYNTAX);
}
#endif
#ifdef HAVE_INITGROUPS
if (!gname && initgroups(copy_as, gid) < 0) {
rsyserr(FERROR, errno, "initgroups failed");
exit_cleanup(RERR_SYNTAX);
}
#endif
if (setuid(uid) < 0
#ifdef HAVE_SETEUID
|| seteuid(uid) < 0
#endif
) {
rsyserr(FERROR, errno, "setuid failed");
exit_cleanup(RERR_SYNTAX);
}
our_uid = MY_UID();
our_gid = MY_GID();
am_root = (our_uid == ROOT_UID);
if (gname)
gname[-1] = ':';
}
/* This function gets called from all 3 processes. We want the client side
* to actually output the text, but the sender is the only process that has
* all the stats we need. So, if we're a client sender, we do the report.
* If we're a server sender, we write the stats on the supplied fd. If
* we're the client receiver we read the stats from the supplied fd and do
* the report. All processes might also generate a set of debug stats, if
* the verbose level is high enough (this is the only thing that the
* generator process and the server receiver ever do here). */
static void handle_stats(int f)
{
endtime = time(NULL);
/* Cache two stats because the read/write code can change it. */
total_read = stats.total_read;
total_written = stats.total_written;
if (INFO_GTE(STATS, 3)) {
/* These come out from every process */
show_malloc_stats();
show_flist_stats();
}
if (am_generator)
return;
if (am_daemon) {
if (f == -1 || !am_sender)
return;
}
if (am_server) {
if (am_sender) {
write_varlong30(f, total_read, 3);
write_varlong30(f, total_written, 3);
write_varlong30(f, stats.total_size, 3);
if (protocol_version >= 29) {
write_varlong30(f, stats.flist_buildtime, 3);
write_varlong30(f, stats.flist_xfertime, 3);
}
}
return;
}
/* this is the client */
if (f < 0 && !am_sender) /* e.g. when we got an empty file list. */
;
else if (!am_sender) {
/* Read the first two in opposite order because the meaning of
* read/write swaps when switching from sender to receiver. */
total_written = read_varlong30(f, 3);
total_read = read_varlong30(f, 3);
stats.total_size = read_varlong30(f, 3);
if (protocol_version >= 29) {
stats.flist_buildtime = read_varlong30(f, 3);
stats.flist_xfertime = read_varlong30(f, 3);
}
} else if (write_batch) {
/* The --read-batch process is going to be a client
* receiver, so we need to give it the stats. */
write_varlong30(batch_fd, total_read, 3);
write_varlong30(batch_fd, total_written, 3);
write_varlong30(batch_fd, stats.total_size, 3);
if (protocol_version >= 29) {
write_varlong30(batch_fd, stats.flist_buildtime, 3);
write_varlong30(batch_fd, stats.flist_xfertime, 3);
}
}
}
static void output_itemized_counts(const char *prefix, int *counts)
{
static char *labels[] = { "reg", "dir", "link", "dev", "special" };
char buf[1024], *pre = " (";
int j, len = 0;
int total = counts[0];
if (total) {
counts[0] -= counts[1] + counts[2] + counts[3] + counts[4];
for (j = 0; j < 5; j++) {
if (counts[j]) {
len += snprintf(buf+len, sizeof buf - len - 2,
"%s%s: %s",
pre, labels[j], comma_num(counts[j]));
pre = ", ";
}
}
buf[len++] = ')';
}
buf[len] = '\0';
rprintf(FINFO, "%s: %s%s\n", prefix, comma_num(total), buf);
}
static const char *bytes_per_sec_human_dnum(void)
{
if (starttime == (time_t)-1 || endtime == (time_t)-1)
return "UNKNOWN";
return human_dnum((total_written + total_read) / (0.5 + (endtime - starttime)), 2);
}
static void output_summary(void)
{
if (INFO_GTE(STATS, 2)) {
rprintf(FCLIENT, "\n");
output_itemized_counts("Number of files", &stats.num_files);
if (protocol_version >= 29)
output_itemized_counts("Number of created files", &stats.created_files);
if (protocol_version >= 31)
output_itemized_counts("Number of deleted files", &stats.deleted_files);
rprintf(FINFO,"Number of regular files transferred: %s\n",
comma_num(stats.xferred_files));
rprintf(FINFO,"Total file size: %s bytes\n",
human_num(stats.total_size));
rprintf(FINFO,"Total transferred file size: %s bytes\n",
human_num(stats.total_transferred_size));
rprintf(FINFO,"Literal data: %s bytes\n",
human_num(stats.literal_data));
rprintf(FINFO,"Matched data: %s bytes\n",
human_num(stats.matched_data));
rprintf(FINFO,"File list size: %s\n",
human_num(stats.flist_size));
if (stats.flist_buildtime) {
rprintf(FINFO,
"File list generation time: %s seconds\n",
comma_dnum((double)stats.flist_buildtime / 1000, 3));
rprintf(FINFO,
"File list transfer time: %s seconds\n",
comma_dnum((double)stats.flist_xfertime / 1000, 3));
}
rprintf(FINFO,"Total bytes sent: %s\n",
human_num(total_written));
rprintf(FINFO,"Total bytes received: %s\n",
human_num(total_read));
}
if (INFO_GTE(STATS, 1)) {
rprintf(FCLIENT, "\n");
rprintf(FINFO,
"sent %s bytes received %s bytes %s bytes/sec\n",
human_num(total_written), human_num(total_read),
bytes_per_sec_human_dnum());
rprintf(FINFO, "total size is %s speedup is %s%s\n",
human_num(stats.total_size),
comma_dnum((double)stats.total_size / (total_written+total_read), 2),
write_batch < 0 ? " (BATCH ONLY)" : dry_run ? " (DRY RUN)" : "");
}
fflush(stdout);
fflush(stderr);
}
/**
* If our C library can get malloc statistics, then show them to FINFO
**/
static void show_malloc_stats(void)
{
#ifdef MEM_ALLOC_INFO
struct MEM_ALLOC_INFO mi = MEM_ALLOC_INFO(); /* mallinfo or mallinfo2 */
rprintf(FCLIENT, "\n");
rprintf(FINFO, RSYNC_NAME "[%d] (%s%s%s) heap statistics:\n",
(int)getpid(), am_server ? "server " : "",
am_daemon ? "daemon " : "", who_am_i());
#define PRINT_ALLOC_NUM(title, descr, num) \
rprintf(FINFO, " %-11s%10" SIZE_T_FMT_MOD "d (" descr ")\n", \
title ":", (SIZE_T_FMT_CAST)(num));
PRINT_ALLOC_NUM("arena", "bytes from sbrk", mi.arena);
PRINT_ALLOC_NUM("ordblks", "chunks not in use", mi.ordblks);
PRINT_ALLOC_NUM("smblks", "free fastbin blocks", mi.smblks);
PRINT_ALLOC_NUM("hblks", "chunks from mmap", mi.hblks);
PRINT_ALLOC_NUM("hblkhd", "bytes from mmap", mi.hblkhd);
PRINT_ALLOC_NUM("allmem", "bytes from sbrk + mmap", mi.arena + mi.hblkhd);
PRINT_ALLOC_NUM("usmblks", "always 0", mi.usmblks);
PRINT_ALLOC_NUM("fsmblks", "bytes in freed fastbin blocks", mi.fsmblks);
PRINT_ALLOC_NUM("uordblks", "bytes used", mi.uordblks);
PRINT_ALLOC_NUM("fordblks", "bytes free", mi.fordblks);
PRINT_ALLOC_NUM("keepcost", "bytes in releasable chunk", mi.keepcost);
#undef PRINT_ALLOC_NUM
#endif /* MEM_ALLOC_INFO */
}
/* Start the remote shell. cmd may be NULL to use the default. */
static pid_t do_cmd(char *cmd, char *machine, char *user, char **remote_argv, int remote_argc,
int *f_in_p, int *f_out_p)
{
int i, argc = 0;
char *args[MAX_ARGS], *need_to_free = NULL;
pid_t pid;
int dash_l_set = 0;
if (!read_batch && !local_server) {
char *t, *f, in_quote = '\0';
char *rsh_env = getenv(RSYNC_RSH_ENV);
if (!cmd)
cmd = rsh_env;
if (!cmd)
cmd = RSYNC_RSH;
cmd = need_to_free = strdup(cmd);
for (t = f = cmd; *f; f++) {
if (*f == ' ')
continue;
/* Comparison leaves rooms for server_options(). */
if (argc >= MAX_ARGS - MAX_SERVER_ARGS)
goto arg_overflow;
args[argc++] = t;
while (*f != ' ' || in_quote) {
if (!*f) {
if (in_quote) {
rprintf(FERROR,
"Missing trailing-%c in remote-shell command.\n",
in_quote);
exit_cleanup(RERR_SYNTAX);
}
f--;
break;
}
if (*f == '\'' || *f == '"') {
if (!in_quote) {
in_quote = *f++;
continue;
}
if (*f == in_quote && *++f != in_quote) {
in_quote = '\0';
continue;
}
}
*t++ = *f++;
}
*t++ = '\0';
}
/* NOTE: must preserve t == start of command name until the end of the args handling! */
if ((t = strrchr(cmd, '/')) != NULL)
t++;
else
t = cmd;
/* Check to see if we've already been given '-l user' in the remote-shell command. */
for (i = 0; i < argc-1; i++) {
if (!strcmp(args[i], "-l") && args[i+1][0] != '-')
dash_l_set = 1;
}
#ifdef HAVE_REMSH
/* remsh (on HPUX) takes the arguments the other way around */
args[argc++] = machine;
if (user && !(daemon_connection && dash_l_set)) {
args[argc++] = "-l";
args[argc++] = user;
}
#else
if (user && !(daemon_connection && dash_l_set)) {
args[argc++] = "-l";
args[argc++] = user;
}
#ifdef AF_INET
if (default_af_hint == AF_INET && strcmp(t, "ssh") == 0)
args[argc++] = "-4"; /* we're using ssh so we can add a -4 option */
#endif
#ifdef AF_INET6
if (default_af_hint == AF_INET6 && strcmp(t, "ssh") == 0)
args[argc++] = "-6"; /* we're using ssh so we can add a -6 option */
#endif
args[argc++] = machine;
#endif
args[argc++] = rsync_path;
if (blocking_io < 0 && (strcmp(t, "rsh") == 0 || strcmp(t, "remsh") == 0))
blocking_io = 1;
if (daemon_connection > 0) {
args[argc++] = "--server";
args[argc++] = "--daemon";
} else
server_options(args, &argc);
if (argc >= MAX_ARGS - 2)
goto arg_overflow;
}
args[argc++] = ".";
if (!daemon_connection) {
while (remote_argc > 0) {
if (argc >= MAX_ARGS - 1) {
arg_overflow:
rprintf(FERROR, "internal: args[] overflowed in do_cmd()\n");
exit_cleanup(RERR_SYNTAX);
}
args[argc++] = safe_arg(NULL, *remote_argv++);
remote_argc--;
}
}
args[argc] = NULL;
if (DEBUG_GTE(CMD, 2)) {
for (i = 0; i < argc; i++)
rprintf(FCLIENT, "cmd[%d]=%s ", i, args[i]);
rprintf(FCLIENT, "\n");
}
if (read_batch) {
int from_gen_pipe[2];
set_allow_inc_recurse();
if (fd_pair(from_gen_pipe) < 0) {
rsyserr(FERROR, errno, "pipe");
exit_cleanup(RERR_IPC);
}
batch_gen_fd = from_gen_pipe[0];
*f_out_p = from_gen_pipe[1];
*f_in_p = batch_fd;
pid = (pid_t)-1; /* no child pid */
#ifdef ICONV_CONST
setup_iconv();
#endif
} else if (local_server) {
/* If the user didn't request --[no-]whole-file, force
* it on, but only if we're not batch processing. */
if (whole_file < 0 && !write_batch)
whole_file = 1;
set_allow_inc_recurse();
pid = local_child(argc, args, f_in_p, f_out_p, child_main);
#ifdef ICONV_CONST
setup_iconv();
#endif
} else {
pid = piped_child(args, f_in_p, f_out_p);
#ifdef ICONV_CONST
setup_iconv();
#endif
if (protect_args && !daemon_connection)
send_protected_args(*f_out_p, args);
}
if (need_to_free)
free(need_to_free);
return pid;
}
/* Older versions turn an empty string as a reference to the current directory.
* We now treat this as an error unless --old-args was used. */
static char *dot_dir_or_error()
{
if (old_style_args || am_server)
return ".";
rprintf(FERROR, "Empty destination arg specified (use \".\" or see --old-args).\n");
exit_cleanup(RERR_SYNTAX);
}
/* The receiving side operates in one of two modes:
*
* 1. it receives any number of files into a destination directory,
* placing them according to their names in the file-list.
*
* 2. it receives a single file and saves it using the name in the
* destination path instead of its file-list name. This requires a
* "local name" for writing out the destination file.
*
* So, our task is to figure out what mode/local-name we need.
* For mode 1, we change into the destination directory and return NULL.
* For mode 2, we change into the directory containing the destination
* file (if we aren't already there) and return the local-name. */
static char *get_local_name(struct file_list *flist, char *dest_path)
{
STRUCT_STAT st;
int statret, trailing_slash;
char *cp;
if (DEBUG_GTE(RECV, 1)) {
rprintf(FINFO, "get_local_name count=%d %s\n",
file_total, NS(dest_path));
}
if (!dest_path || list_only)
return NULL;
if (!*dest_path)
dest_path = dot_dir_or_error();
if (daemon_filter_list.head) {
char *slash = strrchr(dest_path, '/');
if (slash && (slash[1] == '\0' || (slash[1] == '.' && slash[2] == '\0')))
*slash = '\0';
else
slash = NULL;
if ((*dest_path != '.' || dest_path[1] != '\0')
&& (check_filter(&daemon_filter_list, FLOG, dest_path, 0) < 0
|| check_filter(&daemon_filter_list, FLOG, dest_path, 1) < 0)) {
rprintf(FERROR, "ERROR: daemon has excluded destination \"%s\"\n",
dest_path);
exit_cleanup(RERR_FILESELECT);
}
if (slash)
*slash = '/';
}
/* See what currently exists at the destination. */
statret = do_stat(dest_path, &st);
cp = strrchr(dest_path, '/');
trailing_slash = cp && !cp[1];
if (mkpath_dest_arg && statret < 0 && (cp || file_total > 1)) {
int save_errno = errno;
int ret = make_path(dest_path, file_total > 1 && !trailing_slash ? 0 : MKP_DROP_NAME);
if (ret < 0)
goto mkdir_error;
if (ret && (INFO_GTE(NAME, 1) || stdout_format_has_i)) {
if (file_total == 1 || trailing_slash)
*cp = '\0';
rprintf(FINFO, "created %d director%s for %s\n", ret, ret == 1 ? "y" : "ies", dest_path);
if (file_total == 1 || trailing_slash)
*cp = '/';
}
if (ret)
statret = do_stat(dest_path, &st);
else
errno = save_errno;
}
if (statret == 0) {
/* If the destination is a dir, enter it and use mode 1. */
if (S_ISDIR(st.st_mode)) {
if (!change_dir(dest_path, CD_NORMAL)) {
rsyserr(FERROR, errno, "change_dir#1 %s failed",
full_fname(dest_path));
exit_cleanup(RERR_FILESELECT);
}
filesystem_dev = st.st_dev; /* ensures --force works right w/-x */
return NULL;
}
if (file_total > 1) {
rprintf(FERROR,
"ERROR: destination must be a directory when"
" copying more than 1 file\n");
exit_cleanup(RERR_FILESELECT);
}
if (file_total == 1 && S_ISDIR(flist->files[0]->mode)) {
rprintf(FERROR,
"ERROR: cannot overwrite non-directory"
" with a directory\n");
exit_cleanup(RERR_FILESELECT);
}
} else if (errno != ENOENT) {
/* If we don't know what's at the destination, fail. */
rsyserr(FERROR, errno, "ERROR: cannot stat destination %s",
full_fname(dest_path));
exit_cleanup(RERR_FILESELECT);
}
/* If we need a destination directory because the transfer is not
* of a single non-directory or the user has requested one via a
* destination path ending in a slash, create one and use mode 1. */
if (file_total > 1 || trailing_slash) {
if (trailing_slash)
*cp = '\0'; /* Lop off the final slash (if any). */
if (statret == 0) {
rprintf(FERROR, "ERROR: destination path is not a directory\n");
exit_cleanup(RERR_SYNTAX);
}
if (do_mkdir(dest_path, ACCESSPERMS) != 0) {
mkdir_error:
rsyserr(FERROR, errno, "mkdir %s failed",
full_fname(dest_path));
exit_cleanup(RERR_FILEIO);
}
if (flist->high >= flist->low
&& strcmp(flist->files[flist->low]->basename, ".") == 0)
flist->files[0]->flags |= FLAG_DIR_CREATED;
if (INFO_GTE(NAME, 1) || stdout_format_has_i)
rprintf(FINFO, "created directory %s\n", dest_path);
if (dry_run) {
/* Indicate that dest dir doesn't really exist. */
dry_run++;
}
if (!change_dir(dest_path, dry_run > 1 ? CD_SKIP_CHDIR : CD_NORMAL)) {
rsyserr(FERROR, errno, "change_dir#2 %s failed",
full_fname(dest_path));
exit_cleanup(RERR_FILESELECT);
}
return NULL;
}
/* Otherwise, we are writing a single file, possibly on top of an
* existing non-directory. Change to the item's parent directory
* (if it has a path component), return the basename of the
* destination file as the local name, and use mode 2. */
if (!cp)
return dest_path;
if (cp == dest_path)
dest_path = "/";
*cp = '\0';
if (!change_dir(dest_path, CD_NORMAL)) {
rsyserr(FERROR, errno, "change_dir#3 %s failed",
full_fname(dest_path));
exit_cleanup(RERR_FILESELECT);
}
*cp = '/';
return cp + 1;
}
/* This function checks on our alternate-basis directories. If we're in
* dry-run mode and the destination dir does not yet exist, we'll try to
* tweak any dest-relative paths to make them work for a dry-run (the
* destination dir must be in curr_dir[] when this function is called).
* We also warn about any arg that is non-existent or not a directory. */
static void check_alt_basis_dirs(void)
{
STRUCT_STAT st;
char *slash = strrchr(curr_dir, '/');
int j;
for (j = 0; j < basis_dir_cnt; j++) {
char *bdir = basis_dir[j];
int bd_len = strlen(bdir);
if (bd_len > 1 && bdir[bd_len-1] == '/')
bdir[--bd_len] = '\0';
if (dry_run > 1 && *bdir != '/') {
int len = curr_dir_len + 1 + bd_len + 1;
char *new = new_array(char, len);
if (slash && strncmp(bdir, "../", 3) == 0) {
/* We want to remove only one leading "../" prefix for
* the directory we couldn't create in dry-run mode:
* this ensures that any other ".." references get
* evaluated the same as they would for a live copy. */
*slash = '\0';
pathjoin(new, len, curr_dir, bdir + 3);
*slash = '/';
} else
pathjoin(new, len, curr_dir, bdir);
basis_dir[j] = bdir = new;
}
if (do_stat(bdir, &st) < 0)
rprintf(FWARNING, "%s arg does not exist: %s\n", alt_dest_opt(0), bdir);
else if (!S_ISDIR(st.st_mode))
rprintf(FWARNING, "%s arg is not a dir: %s\n", alt_dest_opt(0), bdir);
}
}
/* This is only called by the sender. */
static void read_final_goodbye(int f_in, int f_out)
{
int i, iflags, xlen;
uchar fnamecmp_type;
char xname[MAXPATHLEN];
shutting_down = True;
if (protocol_version < 29)
i = read_int(f_in);
else {
i = read_ndx_and_attrs(f_in, f_out, &iflags, &fnamecmp_type, xname, &xlen);
if (protocol_version >= 31 && i == NDX_DONE) {
if (am_sender)
write_ndx(f_out, NDX_DONE);
else {
if (batch_gen_fd >= 0) {
while (read_int(batch_gen_fd) != NDX_DEL_STATS) {}
read_del_stats(batch_gen_fd);
}
write_int(f_out, NDX_DONE);
}
i = read_ndx_and_attrs(f_in, f_out, &iflags, &fnamecmp_type, xname, &xlen);
}
}
if (i != NDX_DONE) {
rprintf(FERROR, "Invalid packet at end of run (%d) [%s]\n",
i, who_am_i());
exit_cleanup(RERR_PROTOCOL);
}
}
static void do_server_sender(int f_in, int f_out, int argc, char *argv[])
{
struct file_list *flist;
char *dir;
if (DEBUG_GTE(SEND, 1))
rprintf(FINFO, "server_sender starting pid=%d\n", (int)getpid());
if (am_daemon && lp_write_only(module_id)) {
rprintf(FERROR, "ERROR: module is write only\n");
exit_cleanup(RERR_SYNTAX);
}
if (am_daemon && read_only && remove_source_files) {
rprintf(FERROR,
"ERROR: --remove-%s-files cannot be used with a read-only module\n",
remove_source_files == 1 ? "source" : "sent");
exit_cleanup(RERR_SYNTAX);
}
if (argc < 1) {
rprintf(FERROR, "ERROR: do_server_sender called without args\n");
exit_cleanup(RERR_SYNTAX);
}
become_copy_as_user();
dir = argv[0];
if (!relative_paths) {
if (!change_dir(dir, CD_NORMAL)) {
rsyserr(FERROR, errno, "change_dir#3 %s failed",
full_fname(dir));
exit_cleanup(RERR_FILESELECT);
}
}
argc--;
argv++;
if (argc == 0 && (recurse || xfer_dirs || list_only)) {
argc = 1;
argv--;
argv[0] = ".";
}
flist = send_file_list(f_out,argc,argv);
if (!flist || flist->used == 0) {
/* Make sure input buffering is off so we can't hang in noop_io_until_death(). */
io_end_buffering_in(0);
/* TODO: we should really exit in a more controlled manner. */
exit_cleanup(0);
}
io_start_buffering_in(f_in);
send_files(f_in, f_out);
io_flush(FULL_FLUSH);
handle_stats(f_out);
if (protocol_version >= 24)
read_final_goodbye(f_in, f_out);
io_flush(FULL_FLUSH);
exit_cleanup(0);
}
static int do_recv(int f_in, int f_out, char *local_name)
{
int pid;
int exit_code = 0;
int error_pipe[2];
/* The receiving side mustn't obey this, or an existing symlink that
* points to an identical file won't be replaced by the referent. */
copy_links = copy_dirlinks = copy_unsafe_links = 0;
#ifdef SUPPORT_HARD_LINKS
if (preserve_hard_links && !inc_recurse)
match_hard_links(first_flist);
#endif
if (fd_pair(error_pipe) < 0) {
rsyserr(FERROR, errno, "pipe failed in do_recv");
exit_cleanup(RERR_IPC);
}
if (backup_dir) {
STRUCT_STAT st;
int ret;
if (backup_dir_len > 1)
backup_dir_buf[backup_dir_len-1] = '\0';
ret = do_stat(backup_dir_buf, &st);
if (ret != 0 || !S_ISDIR(st.st_mode)) {
if (ret == 0) {
rprintf(FERROR, "The backup-dir is not a directory: %s\n", backup_dir_buf);
exit_cleanup(RERR_SYNTAX);
}