Commit | Line | Data |
---|---|---|
3a42736d MD |
1 | /*- |
2 | * CPDUP.C | |
3 | * | |
4 | * CPDUP <options> source destination | |
5 | * | |
6 | * (c) Copyright 1997-1999 by Matthew Dillon and Dima Ruban. Permission to | |
7 | * use and distribute based on the FreeBSD copyright. Supplied as-is, | |
8 | * USE WITH EXTREME CAUTION. | |
9 | * | |
d0a6ae34 | 10 | * This program attempts to duplicate the source onto the destination as |
3a42736d | 11 | * exactly as possible, retaining modify times, flags, perms, uid, and gid. |
d0a6ae34 | 12 | * It can duplicate devices, files (including hardlinks), softlinks, |
3a42736d MD |
13 | * directories, and so forth. It is recursive by default! The duplication |
14 | * is inclusive of removal of files/directories on the destination that do | |
15 | * not exist on the source. This program supports a per-directory exception | |
16 | * file called .cpignore, or a user-specified exception file. | |
17 | * | |
18 | * Safety features: | |
19 | * | |
20 | * - does not cross partition boundries on source | |
21 | * - asks for confirmation on deletions unless -i0 is specified | |
22 | * - refuses to replace a destination directory with a source file | |
23 | * unless -s0 is specified. | |
24 | * - terminates on error | |
25 | * | |
26 | * Copying features: | |
27 | * | |
28 | * - does not copy file if mtime, flags, perms, and size match unless | |
29 | * forced | |
30 | * | |
31 | * - copies to temporary and renames-over the original, allowing | |
32 | * you to update live systems | |
33 | * | |
34 | * - copies uid, gid, mtime, perms, flags, softlinks, devices, hardlinks, | |
35 | * and recurses through directories. | |
36 | * | |
d0a6ae34 | 37 | * - accesses a per-directory exclusion file, .cpignore, containing |
3a42736d MD |
38 | * standard wildcarded ( ? / * style, NOT regex) exclusions. |
39 | * | |
d0a6ae34 | 40 | * - tries to play permissions and flags smart in regards to overwriting |
3a42736d MD |
41 | * schg files and doing related stuff. |
42 | * | |
43 | * - Can do MD5 consistancy checks | |
44 | * | |
4e316ad5 MD |
45 | * - Is able to do incremental mirroring/backups via hardlinks from |
46 | * the 'previous' version (supplied with -H path). | |
3a42736d MD |
47 | */ |
48 | ||
49 | /*- | |
de78d61c | 50 | * Example: cc -O cpdup.c -o cpdup -lcrypto |
3a42736d MD |
51 | * |
52 | * ".MD5.CHECKSUMS" contains md5 checksumms for the current directory. | |
53 | * This file is stored on the source. | |
54 | */ | |
55 | ||
56 | #include "cpdup.h" | |
4d858d58 MD |
57 | #include "hclink.h" |
58 | #include "hcproto.h" | |
3a42736d | 59 | |
a2dc574c | 60 | #define HSIZE 8192 |
3a42736d | 61 | #define HMASK (HSIZE-1) |
4e316ad5 MD |
62 | #define HLSIZE 8192 |
63 | #define HLMASK (HLSIZE - 1) | |
3a42736d | 64 | |
975200d7 MD |
65 | #define GETBUFSIZE 8192 |
66 | #define GETPATHSIZE 2048 | |
67 | #define GETLINKSIZE 1024 | |
68 | #define GETIOSIZE 65536 | |
69 | ||
4d858d58 MD |
70 | #ifndef _ST_FLAGS_PRESENT_ |
71 | #define st_flags st_mode | |
72 | #endif | |
73 | ||
3a42736d MD |
74 | typedef struct Node { |
75 | struct Node *no_Next; | |
76 | struct Node *no_HNext; | |
c0538630 | 77 | struct stat *no_Stat; |
3a42736d MD |
78 | int no_Value; |
79 | char no_Name[4]; | |
80 | } Node; | |
81 | ||
82 | typedef struct List { | |
83 | Node li_Node; | |
84 | Node *li_Hash[HSIZE]; | |
85 | } List; | |
86 | ||
87 | struct hlink { | |
88 | ino_t ino; | |
89 | ino_t dino; | |
975200d7 | 90 | int refs; |
3a42736d MD |
91 | struct hlink *next; |
92 | struct hlink *prev; | |
02141299 | 93 | nlink_t nlinked; |
c0538630 | 94 | char name[]; |
3a42736d MD |
95 | }; |
96 | ||
a2dc574c MD |
97 | typedef struct copy_info { |
98 | char *spath; | |
99 | char *dpath; | |
100 | dev_t sdevNo; | |
101 | dev_t ddevNo; | |
a2dc574c MD |
102 | } *copy_info_t; |
103 | ||
75bd842a | 104 | static struct hlink *hltable[HLSIZE]; |
58860d7d | 105 | |
75bd842a MD |
106 | static void RemoveRecur(const char *dpath, dev_t devNo, struct stat *dstat); |
107 | static void InitList(List *list); | |
108 | static void ResetList(List *list); | |
109 | static Node *IterateList(List *list, Node *node, int n); | |
110 | static int AddList(List *list, const char *name, int n, struct stat *st); | |
560e4370 | 111 | static int CheckList(List *list, const char *path, const char *name); |
293141b7 | 112 | static int getbool(const char *str); |
d72200ed | 113 | static char *SplitRemote(char **pathp); |
293141b7 MD |
114 | static int ChgrpAllowed(gid_t g); |
115 | static int OwnerMatch(struct stat *st1, struct stat *st2); | |
116 | #ifdef _ST_FLAGS_PRESENT_ | |
117 | static int FlagsMatch(struct stat *st1, struct stat *st2); | |
118 | #else | |
119 | #define FlagsMatch(st1, st2) 1 | |
120 | #endif | |
fce2564b JR |
121 | static struct hlink *hltlookup(struct stat *); |
122 | static struct hlink *hltadd(struct stat *, const char *); | |
4e316ad5 | 123 | static char *checkHLPath(struct stat *st, const char *spath, const char *dpath); |
d5fdcd00 | 124 | static int validate_check(const char *spath, const char *dpath); |
fce2564b JR |
125 | static int shash(const char *s); |
126 | static void hltdelete(struct hlink *); | |
44dd1628 | 127 | static void hltsetdino(struct hlink *, ino_t); |
75bd842a | 128 | static int YesNo(const char *path); |
fce2564b JR |
129 | static int xrename(const char *src, const char *dst, u_long flags); |
130 | static int xlink(const char *src, const char *dst, u_long flags); | |
77133d96 | 131 | static int xremove(struct HostConf *host, const char *path); |
9d626b29 | 132 | static int xrmdir(struct HostConf *host, const char *path); |
c0538630 | 133 | static int DoCopy(copy_info_t info, struct stat *stat1, int depth); |
293141b7 MD |
134 | static int ScanDir(List *list, struct HostConf *host, const char *path, |
135 | int64_t *CountReadBytes, int n); | |
0212bfce | 136 | static int mtimecmp(struct stat *st1, struct stat *st2); |
2b7dbe20 MD |
137 | static int symlink_mfo_test(struct HostConf *hc, struct stat *st1, |
138 | struct stat *st2); | |
3a42736d MD |
139 | |
140 | int AskConfirmation = 1; | |
141 | int SafetyOpt = 1; | |
577109ea | 142 | int ForceOpt; |
ac482a7e | 143 | int DeviceOpt = 1; |
577109ea | 144 | int VerboseOpt; |
293141b7 | 145 | int DirShowOpt; |
698d0b11 | 146 | int NotForRealOpt; |
577109ea MD |
147 | int QuietOpt; |
148 | int NoRemoveOpt; | |
149 | int UseMD5Opt; | |
577109ea | 150 | int SummaryOpt; |
44dd1628 | 151 | int CompressOpt; |
4d858d58 | 152 | int SlaveOpt; |
c0538630 | 153 | int ReadOnlyOpt; |
d5fdcd00 | 154 | int ValidateOpt; |
8f0e7bc1 MD |
155 | int ssh_argc; |
156 | const char *ssh_argv[16]; | |
293141b7 | 157 | int DstRootPrivs; |
75bd842a | 158 | |
1c755102 | 159 | const char *UseCpFile; |
577109ea | 160 | const char *MD5CacheFile; |
75bd842a MD |
161 | const char *UseHLPath; |
162 | ||
163 | static int DstBaseLen; | |
164 | static int HardLinkCount; | |
165 | static int GroupCount; | |
166 | static gid_t *GroupList; | |
3a42736d | 167 | |
577109ea MD |
168 | int64_t CountSourceBytes; |
169 | int64_t CountSourceItems; | |
170 | int64_t CountCopiedItems; | |
d5fdcd00 MD |
171 | int64_t CountSourceReadBytes; |
172 | int64_t CountTargetReadBytes; | |
577109ea MD |
173 | int64_t CountWriteBytes; |
174 | int64_t CountRemovedItems; | |
d5fdcd00 | 175 | int64_t CountLinkedItems; |
1c755102 | 176 | |
75bd842a MD |
177 | static struct HostConf SrcHost; |
178 | static struct HostConf DstHost; | |
4d858d58 | 179 | |
3a42736d MD |
180 | int |
181 | main(int ac, char **av) | |
182 | { | |
183 | int i; | |
293141b7 | 184 | int opt; |
3a42736d MD |
185 | char *src = NULL; |
186 | char *dst = NULL; | |
4d858d58 | 187 | char *ptr; |
3a42736d | 188 | struct timeval start; |
a2dc574c | 189 | struct copy_info info; |
3a42736d | 190 | |
d8bce52d MD |
191 | signal(SIGPIPE, SIG_IGN); |
192 | ||
3a42736d | 193 | gettimeofday(&start, NULL); |
293141b7 | 194 | opterr = 0; |
5ca0a96d | 195 | while ((opt = getopt(ac, av, ":CdF:fH:hIi:j:lM:mnoqRSs:uVvX:x")) != -1) { |
293141b7 | 196 | switch (opt) { |
44dd1628 MD |
197 | case 'C': |
198 | CompressOpt = 1; | |
199 | break; | |
293141b7 MD |
200 | case 'd': |
201 | DirShowOpt = 1; | |
3a42736d | 202 | break; |
8f0e7bc1 MD |
203 | case 'F': |
204 | if (ssh_argc >= 16) | |
205 | fatal("too many -F options"); | |
293141b7 | 206 | ssh_argv[ssh_argc++] = optarg; |
8f0e7bc1 | 207 | break; |
3a42736d | 208 | case 'f': |
293141b7 | 209 | ForceOpt = 1; |
3a42736d | 210 | break; |
7651dbc3 AL |
211 | case 'H': |
212 | UseHLPath = optarg; | |
213 | break; | |
214 | case 'h': | |
215 | fatal(NULL); | |
216 | /* not reached */ | |
217 | break; | |
218 | case 'I': | |
219 | SummaryOpt = 1; | |
220 | break; | |
3a42736d | 221 | case 'i': |
293141b7 | 222 | AskConfirmation = getbool(optarg); |
3a42736d | 223 | break; |
ac482a7e | 224 | case 'j': |
293141b7 | 225 | DeviceOpt = getbool(optarg); |
a2dc574c | 226 | break; |
7651dbc3 AL |
227 | case 'l': |
228 | setlinebuf(stdout); | |
229 | setlinebuf(stderr); | |
577109ea | 230 | break; |
3a42736d | 231 | case 'M': |
293141b7 MD |
232 | UseMD5Opt = 1; |
233 | MD5CacheFile = optarg; | |
3a42736d MD |
234 | break; |
235 | case 'm': | |
293141b7 | 236 | UseMD5Opt = 1; |
3a42736d MD |
237 | MD5CacheFile = ".MD5.CHECKSUMS"; |
238 | break; | |
7651dbc3 AL |
239 | case 'n': |
240 | NotForRealOpt = 1; | |
241 | break; | |
242 | case 'o': | |
243 | NoRemoveOpt = 1; | |
244 | break; | |
245 | case 'q': | |
246 | QuietOpt = 1; | |
247 | break; | |
248 | case 'R': | |
249 | ReadOnlyOpt = 1; | |
250 | break; | |
251 | case 'S': | |
252 | SlaveOpt = 1; | |
253 | break; | |
254 | case 's': | |
255 | SafetyOpt = getbool(optarg); | |
256 | break; | |
4d2076d3 CP |
257 | case 'u': |
258 | setvbuf(stdout, NULL, _IOLBF, 0); | |
259 | break; | |
7651dbc3 AL |
260 | case 'V': |
261 | ++ValidateOpt; | |
262 | break; | |
263 | case 'v': | |
264 | ++VerboseOpt; | |
265 | break; | |
266 | case 'X': | |
267 | UseCpFile = optarg; | |
268 | break; | |
269 | case 'x': | |
270 | UseCpFile = ".cpignore"; | |
271 | break; | |
293141b7 MD |
272 | case ':': |
273 | fatal("missing argument for option: -%c\n", optopt); | |
274 | /* not reached */ | |
275 | break; | |
276 | case '?': | |
277 | fatal("illegal option: -%c\n", optopt); | |
278 | /* not reached */ | |
279 | break; | |
3a42736d | 280 | default: |
293141b7 | 281 | fatal(NULL); |
3a42736d MD |
282 | /* not reached */ |
283 | break; | |
284 | } | |
285 | } | |
293141b7 MD |
286 | ac -= optind; |
287 | av += optind; | |
288 | if (ac > 0) | |
289 | src = av[0]; | |
290 | if (ac > 1) | |
291 | dst = av[1]; | |
292 | if (ac > 2) | |
293 | fatal("too many arguments"); | |
3a42736d | 294 | |
4d858d58 MD |
295 | /* |
296 | * If we are told to go into slave mode, run the HC protocol | |
297 | */ | |
298 | if (SlaveOpt) { | |
293141b7 | 299 | DstRootPrivs = (geteuid() == 0); |
4d858d58 MD |
300 | hc_slave(0, 1); |
301 | exit(0); | |
302 | } | |
303 | ||
304 | /* | |
305 | * Extract the source and/or/neither target [user@]host and | |
306 | * make any required connections. | |
307 | */ | |
d72200ed | 308 | if (src && (ptr = SplitRemote(&src)) != NULL) { |
293141b7 MD |
309 | SrcHost.host = src; |
310 | src = ptr; | |
c0538630 MD |
311 | if (UseMD5Opt) |
312 | fatal("The MD5 options are not currently supported for remote sources"); | |
313 | if (hc_connect(&SrcHost, ReadOnlyOpt) < 0) | |
69301941 | 314 | exit(1); |
2b7dbe20 MD |
315 | } else { |
316 | SrcHost.version = HCPROTO_VERSION; | |
317 | if (ReadOnlyOpt) | |
318 | fatal("The -R option is only supported for remote sources"); | |
319 | } | |
c0538630 | 320 | |
d72200ed | 321 | if (dst && (ptr = SplitRemote(&dst)) != NULL) { |
293141b7 MD |
322 | DstHost.host = dst; |
323 | dst = ptr; | |
c0538630 | 324 | if (hc_connect(&DstHost, 0) < 0) |
e7624993 | 325 | exit(1); |
2b7dbe20 MD |
326 | } else { |
327 | DstHost.version = HCPROTO_VERSION; | |
4d858d58 MD |
328 | } |
329 | ||
3a42736d MD |
330 | /* |
331 | * dst may be NULL only if -m option is specified, | |
332 | * which forces an update of the MD5 checksums | |
333 | */ | |
3a42736d MD |
334 | if (dst == NULL && UseMD5Opt == 0) { |
335 | fatal(NULL); | |
336 | /* not reached */ | |
337 | } | |
293141b7 MD |
338 | |
339 | if (dst) { | |
340 | DstRootPrivs = (hc_geteuid(&DstHost) == 0); | |
341 | if (!DstRootPrivs) | |
342 | GroupCount = hc_getgroups(&DstHost, &GroupList); | |
343 | } | |
344 | #if 0 | |
345 | /* XXXX DEBUG */ | |
346 | fprintf(stderr, "DstRootPrivs == %s\n", DstRootPrivs ? "true" : "false"); | |
347 | fprintf(stderr, "GroupCount == %d\n", GroupCount); | |
348 | for (i = 0; i < GroupCount; i++) | |
349 | fprintf(stderr, "Group[%d] == %d\n", i, GroupList[i]); | |
a2dc574c | 350 | #endif |
293141b7 MD |
351 | |
352 | bzero(&info, sizeof(info)); | |
3a42736d | 353 | if (dst) { |
4e316ad5 | 354 | DstBaseLen = strlen(dst); |
a2dc574c MD |
355 | info.spath = src; |
356 | info.dpath = dst; | |
357 | info.sdevNo = (dev_t)-1; | |
358 | info.ddevNo = (dev_t)-1; | |
c0538630 | 359 | i = DoCopy(&info, NULL, -1); |
3a42736d | 360 | } else { |
a2dc574c MD |
361 | info.spath = src; |
362 | info.dpath = NULL; | |
363 | info.sdevNo = (dev_t)-1; | |
364 | info.ddevNo = (dev_t)-1; | |
c0538630 | 365 | i = DoCopy(&info, NULL, -1); |
3a42736d | 366 | } |
4d858d58 | 367 | #ifndef NOMD5 |
3a42736d | 368 | md5_flush(); |
4d858d58 | 369 | #endif |
3a42736d MD |
370 | |
371 | if (SummaryOpt && i == 0) { | |
4a159c4c | 372 | double duration; |
3a42736d MD |
373 | struct timeval end; |
374 | ||
375 | gettimeofday(&end, NULL); | |
d5fdcd00 MD |
376 | #if 0 |
377 | /* don't count stat's in our byte statistics */ | |
3a42736d | 378 | CountSourceBytes += sizeof(struct stat) * CountSourceItems; |
d5fdcd00 | 379 | CountSourceReadBytes += sizeof(struct stat) * CountSourceItems; |
3a42736d MD |
380 | CountWriteBytes += sizeof(struct stat) * CountCopiedItems; |
381 | CountWriteBytes += sizeof(struct stat) * CountRemovedItems; | |
d5fdcd00 | 382 | #endif |
3a42736d | 383 | |
4a159c4c MD |
384 | duration = (end.tv_sec - start.tv_sec); |
385 | duration += (double)(end.tv_usec - start.tv_usec) / 1000000.0; | |
386 | if (duration == 0.0) | |
387 | duration = 1.0; | |
d0d91865 | 388 | logstd("cpdup completed successfully\n"); |
d5fdcd00 MD |
389 | logstd("%lld bytes source, %lld src bytes read, %lld tgt bytes read\n" |
390 | "%lld bytes written (%.1fX speedup)\n", | |
3a42736d | 391 | (long long)CountSourceBytes, |
d5fdcd00 MD |
392 | (long long)CountSourceReadBytes, |
393 | (long long)CountTargetReadBytes, | |
3a42736d | 394 | (long long)CountWriteBytes, |
d5fdcd00 MD |
395 | ((double)CountSourceBytes * 2.0) / ((double)(CountSourceReadBytes + CountTargetReadBytes + CountWriteBytes))); |
396 | logstd("%lld source items, %lld items copied, %lld items linked, " | |
397 | "%lld things deleted\n", | |
3a42736d MD |
398 | (long long)CountSourceItems, |
399 | (long long)CountCopiedItems, | |
d5fdcd00 | 400 | (long long)CountLinkedItems, |
3a42736d MD |
401 | (long long)CountRemovedItems); |
402 | logstd("%.1f seconds %5d Kbytes/sec synced %5d Kbytes/sec scanned\n", | |
4a159c4c MD |
403 | duration, |
404 | (int)((CountSourceReadBytes + CountTargetReadBytes + CountWriteBytes) / duration / 1024.0), | |
405 | (int)(CountSourceBytes / duration / 1024.0)); | |
3a42736d MD |
406 | } |
407 | exit((i == 0) ? 0 : 1); | |
408 | } | |
409 | ||
293141b7 MD |
410 | static int |
411 | getbool(const char *str) | |
412 | { | |
413 | if (strcmp(str, "0") == 0) | |
414 | return (0); | |
415 | if (strcmp(str, "1") == 0) | |
416 | return (1); | |
417 | fatal("option requires boolean argument (0 or 1): -%c\n", optopt); | |
418 | /* not reached */ | |
419 | return (0); | |
420 | } | |
421 | ||
422 | /* | |
423 | * Check if path specifies a remote path, using the same syntax as scp(1), | |
424 | * i.e. a path is considered remote if the first colon is not preceded by | |
425 | * a slash, so e.g. "./foo:bar" is considered local. | |
426 | * If a remote path is detected, the colon is replaced with a null byte, | |
427 | * and the return value is a pointer to the next character. | |
428 | * Otherwise NULL is returned. | |
d72200ed MD |
429 | * |
430 | * A path prefix of localhost is the same as a locally specified file or | |
431 | * directory path, but prevents any further interpretation of the path | |
432 | * as being a remote hostname (for paths that have colons in them). | |
293141b7 MD |
433 | */ |
434 | static char * | |
d72200ed | 435 | SplitRemote(char **pathp) |
293141b7 MD |
436 | { |
437 | int cindex; | |
d72200ed | 438 | char *path = *pathp; |
293141b7 MD |
439 | |
440 | if (path[(cindex = strcspn(path, ":/"))] == ':') { | |
441 | path[cindex++] = 0; | |
d72200ed MD |
442 | if (strcmp(path, "localhost") != 0) |
443 | return (path + cindex); | |
444 | *pathp = path + cindex; | |
293141b7 MD |
445 | } |
446 | return (NULL); | |
447 | } | |
448 | ||
449 | /* | |
450 | * Check if group g is in our GroupList. | |
451 | * | |
452 | * Typically the number of groups a user belongs to isn't large | |
453 | * enough to warrant more effort than a simple linear search. | |
454 | * However, we perform an optimization by moving a group to the | |
455 | * top of the list when we have a hit. This assumes that there | |
456 | * isn't much variance in the gids of files that a non-root user | |
457 | * copies. So most of the time the search will terminate on the | |
458 | * first element of the list. | |
459 | */ | |
460 | static int | |
461 | ChgrpAllowed(gid_t g) | |
462 | { | |
463 | int i; | |
464 | ||
465 | for (i = 0; i < GroupCount; i++) | |
466 | if (GroupList[i] == g) { | |
467 | if (i > 0) { | |
468 | /* Optimize: Move g to the front of the list. */ | |
469 | for (; i > 0; i--) | |
470 | GroupList[i] = GroupList[i - 1]; | |
471 | GroupList[0] = g; | |
472 | } | |
473 | return (1); | |
474 | } | |
475 | return (0); | |
476 | } | |
477 | ||
478 | /* | |
479 | * The following two functions return true if the ownership (UID + GID) | |
480 | * or the flags of two files match, respectively. | |
481 | * | |
482 | * Only perform weak checking if we don't have sufficient privileges on | |
483 | * the target machine, so we don't waste transfers with things that are | |
484 | * bound to fail anyway. | |
485 | */ | |
486 | static int | |
487 | OwnerMatch(struct stat *st1, struct stat *st2) | |
488 | { | |
489 | if (DstRootPrivs) | |
490 | /* Both UID and GID must match. */ | |
491 | return (st1->st_uid == st2->st_uid && st1->st_gid == st2->st_gid); | |
492 | else | |
493 | /* Ignore UID, and also ignore GID if we can't chgrp to that group. */ | |
494 | return (st1->st_gid == st2->st_gid || !ChgrpAllowed(st1->st_gid)); | |
495 | } | |
496 | ||
497 | #ifdef _ST_FLAGS_PRESENT_ | |
498 | static int | |
499 | FlagsMatch(struct stat *st1, struct stat *st2) | |
500 | { | |
9d626b29 SW |
501 | /* |
502 | * Ignore UF_ARCHIVE. It gets set automatically by the filesystem, for | |
503 | * filesystems that support it. If the destination filesystem supports it, but | |
504 | * it's cleared on the source file, then multiple invocations of cpdup would | |
505 | * all try to copy the file because the flags wouldn't match. | |
506 | * | |
507 | * When unpriveleged, ignore flags we can't set | |
508 | */ | |
509 | u_long ignored = DstRootPrivs ? 0 : SF_SETTABLE; | |
510 | ||
511 | #ifdef UF_ARCHIVE | |
512 | ignored |= UF_ARCHIVE; | |
513 | #endif | |
514 | return (((st1->st_flags ^ st2->st_flags) & ~ignored) == 0); | |
293141b7 MD |
515 | } |
516 | #endif | |
517 | ||
518 | ||
fce2564b | 519 | static struct hlink * |
3a42736d MD |
520 | hltlookup(struct stat *stp) |
521 | { | |
522 | struct hlink *hl; | |
523 | int n; | |
524 | ||
4e316ad5 | 525 | n = stp->st_ino & HLMASK; |
3a42736d | 526 | |
44dd1628 MD |
527 | for (hl = hltable[n]; hl; hl = hl->next) { |
528 | if (hl->ino == stp->st_ino) { | |
975200d7 | 529 | ++hl->refs; |
44dd1628 MD |
530 | return hl; |
531 | } | |
532 | } | |
3a42736d MD |
533 | |
534 | return NULL; | |
535 | } | |
536 | ||
fce2564b | 537 | static struct hlink * |
3a42736d MD |
538 | hltadd(struct stat *stp, const char *path) |
539 | { | |
540 | struct hlink *new; | |
71de6efc | 541 | int plen = strlen(path); |
3a42736d MD |
542 | int n; |
543 | ||
71de6efc | 544 | new = malloc(offsetof(struct hlink, name[plen + 1])); |
c0538630 MD |
545 | if (new == NULL) |
546 | fatal("out of memory"); | |
975200d7 | 547 | ++HardLinkCount; |
3a42736d MD |
548 | |
549 | /* initialize and link the new element into the table */ | |
550 | new->ino = stp->st_ino; | |
44dd1628 | 551 | new->dino = (ino_t)-1; |
975200d7 | 552 | new->refs = 1; |
71de6efc | 553 | bcopy(path, new->name, plen + 1); |
3a42736d MD |
554 | new->nlinked = 1; |
555 | new->prev = NULL; | |
4e316ad5 | 556 | n = stp->st_ino & HLMASK; |
3a42736d MD |
557 | new->next = hltable[n]; |
558 | if (hltable[n]) | |
559 | hltable[n]->prev = new; | |
560 | hltable[n] = new; | |
561 | ||
562 | return new; | |
563 | } | |
564 | ||
44dd1628 MD |
565 | static void |
566 | hltsetdino(struct hlink *hl, ino_t inum) | |
567 | { | |
568 | hl->dino = inum; | |
569 | } | |
570 | ||
fce2564b | 571 | static void |
3a42736d MD |
572 | hltdelete(struct hlink *hl) |
573 | { | |
975200d7 MD |
574 | assert(hl->refs == 1); |
575 | --hl->refs; | |
3a42736d MD |
576 | if (hl->prev) { |
577 | if (hl->next) | |
578 | hl->next->prev = hl->prev; | |
579 | hl->prev->next = hl->next; | |
580 | } else { | |
581 | if (hl->next) | |
582 | hl->next->prev = NULL; | |
583 | ||
4e316ad5 | 584 | hltable[hl->ino & HLMASK] = hl->next; |
3a42736d | 585 | } |
975200d7 | 586 | --HardLinkCount; |
3a42736d MD |
587 | free(hl); |
588 | } | |
589 | ||
975200d7 MD |
590 | static void |
591 | hltrels(struct hlink *hl) | |
592 | { | |
593 | assert(hl->refs == 1); | |
594 | --hl->refs; | |
595 | } | |
596 | ||
4e316ad5 MD |
597 | /* |
598 | * If UseHLPath is defined check to see if the file in question is | |
599 | * the same as the source file, and if it is return a pointer to the | |
600 | * -H path based file for hardlinking. Else return NULL. | |
601 | */ | |
602 | static char * | |
603 | checkHLPath(struct stat *st1, const char *spath, const char *dpath) | |
604 | { | |
605 | struct stat sthl; | |
606 | char *hpath; | |
d5fdcd00 | 607 | int error; |
4e316ad5 | 608 | |
60374ee7 AL |
609 | if (asprintf(&hpath, "%s%s", UseHLPath, dpath + DstBaseLen) < 0) |
610 | fatal("out of memory"); | |
4e316ad5 MD |
611 | |
612 | /* | |
613 | * stat info matches ? | |
614 | */ | |
4d858d58 | 615 | if (hc_stat(&DstHost, hpath, &sthl) < 0 || |
4e316ad5 | 616 | st1->st_size != sthl.st_size || |
0212bfce | 617 | mtimecmp(st1, &sthl) != 0 || |
293141b7 MD |
618 | !OwnerMatch(st1, &sthl) || |
619 | !FlagsMatch(st1, &sthl) | |
4e316ad5 MD |
620 | ) { |
621 | free(hpath); | |
622 | return(NULL); | |
623 | } | |
624 | ||
625 | /* | |
d5fdcd00 | 626 | * If ForceOpt or ValidateOpt is set we have to compare the files |
4e316ad5 | 627 | */ |
d5fdcd00 MD |
628 | if (ForceOpt || ValidateOpt) { |
629 | error = validate_check(spath, hpath); | |
630 | if (error) { | |
4e316ad5 MD |
631 | free(hpath); |
632 | hpath = NULL; | |
633 | } | |
634 | } | |
635 | return(hpath); | |
636 | } | |
637 | ||
d5fdcd00 MD |
638 | /* |
639 | * Return 0 if the contents of the file <spath> matches the contents of | |
640 | * the file <dpath>. | |
641 | */ | |
642 | static int | |
643 | validate_check(const char *spath, const char *dpath) | |
644 | { | |
645 | int error; | |
646 | int fd1; | |
647 | int fd2; | |
648 | ||
649 | fd1 = hc_open(&SrcHost, spath, O_RDONLY, 0); | |
650 | fd2 = hc_open(&DstHost, dpath, O_RDONLY, 0); | |
651 | error = -1; | |
652 | ||
653 | if (fd1 >= 0 && fd2 >= 0) { | |
654 | int n; | |
655 | int x; | |
975200d7 MD |
656 | char *iobuf1 = malloc(GETIOSIZE); |
657 | char *iobuf2 = malloc(GETIOSIZE); | |
d5fdcd00 | 658 | |
975200d7 | 659 | while ((n = hc_read(&SrcHost, fd1, iobuf1, GETIOSIZE)) > 0) { |
d5fdcd00 | 660 | CountSourceReadBytes += n; |
975200d7 | 661 | x = hc_read(&DstHost, fd2, iobuf2, GETIOSIZE); |
d5fdcd00 MD |
662 | if (x > 0) |
663 | CountTargetReadBytes += x; | |
664 | if (x != n) | |
665 | break; | |
975200d7 | 666 | if (bcmp(iobuf1, iobuf2, n) != 0) |
d5fdcd00 MD |
667 | break; |
668 | } | |
975200d7 MD |
669 | free(iobuf1); |
670 | free(iobuf2); | |
d5fdcd00 MD |
671 | if (n == 0) |
672 | error = 0; | |
673 | } | |
674 | if (fd1 >= 0) | |
675 | hc_close(&SrcHost, fd1); | |
676 | if (fd2 >= 0) | |
677 | hc_close(&DstHost, fd2); | |
678 | return (error); | |
679 | } | |
680 | ||
3a42736d | 681 | int |
c0538630 | 682 | DoCopy(copy_info_t info, struct stat *stat1, int depth) |
3a42736d | 683 | { |
a2dc574c MD |
684 | const char *spath = info->spath; |
685 | const char *dpath = info->dpath; | |
686 | dev_t sdevNo = info->sdevNo; | |
687 | dev_t ddevNo = info->ddevNo; | |
3a42736d MD |
688 | struct stat st1; |
689 | struct stat st2; | |
17e9c4cc | 690 | unsigned long st2_flags; |
577109ea | 691 | int r, mres, fres, st2Valid; |
58860d7d | 692 | struct hlink *hln; |
c0538630 | 693 | uint64_t size; |
3a42736d | 694 | |
577109ea | 695 | r = mres = fres = st2Valid = 0; |
17e9c4cc | 696 | st2_flags = 0; |
58860d7d MD |
697 | size = 0; |
698 | hln = NULL; | |
699 | ||
c0538630 MD |
700 | if (stat1 == NULL) { |
701 | if (hc_lstat(&SrcHost, spath, &st1) != 0) { | |
702 | r = 1; | |
703 | goto done; | |
704 | } | |
705 | stat1 = &st1; | |
a2dc574c | 706 | } |
8f0e7bc1 MD |
707 | #ifdef SF_SNAPSHOT |
708 | /* skip snapshot files because they're sparse and _huge_ */ | |
c0538630 | 709 | if (stat1->st_flags & SF_SNAPSHOT) |
8f0e7bc1 MD |
710 | return(0); |
711 | #endif | |
3a42736d MD |
712 | st2.st_mode = 0; /* in case lstat fails */ |
713 | st2.st_flags = 0; /* in case lstat fails */ | |
17e9c4cc | 714 | if (dpath && hc_lstat(&DstHost, dpath, &st2) == 0) { |
3a42736d | 715 | st2Valid = 1; |
17e9c4cc MD |
716 | #ifdef _ST_FLAGS_PRESENT_ |
717 | st2_flags = st2.st_flags; | |
718 | #endif | |
719 | } | |
3a42736d | 720 | |
c0538630 MD |
721 | if (S_ISREG(stat1->st_mode)) |
722 | size = stat1->st_size; | |
3a42736d MD |
723 | |
724 | /* | |
725 | * Handle hardlinks | |
726 | */ | |
727 | ||
c0538630 MD |
728 | if (S_ISREG(stat1->st_mode) && stat1->st_nlink > 1 && dpath) { |
729 | if ((hln = hltlookup(stat1)) != NULL) { | |
3a42736d MD |
730 | hln->nlinked++; |
731 | ||
732 | if (st2Valid) { | |
733 | if (st2.st_ino == hln->dino) { | |
734 | /* | |
735 | * hard link is already correct, nothing to do | |
736 | */ | |
737 | if (VerboseOpt >= 3) | |
738 | logstd("%-32s nochange\n", (dpath) ? dpath : spath); | |
c0538630 | 739 | if (hln->nlinked == stat1->st_nlink) { |
3a42736d | 740 | hltdelete(hln); |
975200d7 MD |
741 | hln = NULL; |
742 | } | |
3a42736d | 743 | CountSourceItems++; |
a2dc574c MD |
744 | r = 0; |
745 | goto done; | |
3a42736d MD |
746 | } else { |
747 | /* | |
748 | * hard link is not correct, attempt to unlink it | |
749 | */ | |
77133d96 | 750 | if (xremove(&DstHost, dpath) < 0) { |
d0a6ae34 | 751 | logerr("%-32s hardlink: unable to unlink: %s\n", |
3a42736d MD |
752 | ((dpath) ? dpath : spath), strerror(errno)); |
753 | hltdelete(hln); | |
975200d7 MD |
754 | hln = NULL; |
755 | ++r; | |
756 | goto done; | |
3a42736d MD |
757 | } |
758 | } | |
759 | } | |
760 | ||
c0538630 | 761 | if (xlink(hln->name, dpath, stat1->st_flags) < 0) { |
9b2d9484 | 762 | int tryrelink = (errno == EMLINK); |
3a42736d MD |
763 | logerr("%-32s hardlink: unable to link to %s: %s\n", |
764 | (dpath ? dpath : spath), hln->name, strerror(errno) | |
765 | ); | |
766 | hltdelete(hln); | |
767 | hln = NULL; | |
9b2d9484 | 768 | if (tryrelink) { |
b58f1e66 SW |
769 | logerr("%-20s hardlink: will attempt to copy normally\n", |
770 | (dpath ? dpath : spath)); | |
9b2d9484 MD |
771 | goto relink; |
772 | } | |
3a42736d MD |
773 | ++r; |
774 | } else { | |
c0538630 | 775 | if (hln->nlinked == stat1->st_nlink) { |
3a42736d MD |
776 | hltdelete(hln); |
777 | hln = NULL; | |
778 | } | |
779 | if (r == 0) { | |
780 | if (VerboseOpt) { | |
d0a6ae34 | 781 | logstd("%-32s hardlink: %s\n", |
3a42736d MD |
782 | (dpath ? dpath : spath), |
783 | (st2Valid ? "relinked" : "linked") | |
784 | ); | |
785 | } | |
786 | CountSourceItems++; | |
787 | CountCopiedItems++; | |
a2dc574c MD |
788 | r = 0; |
789 | goto done; | |
3a42736d MD |
790 | } |
791 | } | |
792 | } else { | |
793 | /* | |
794 | * first instance of hardlink must be copied normally | |
795 | */ | |
9b2d9484 | 796 | relink: |
c0538630 | 797 | hln = hltadd(stat1, dpath); |
3a42736d MD |
798 | } |
799 | } | |
800 | ||
801 | /* | |
802 | * Do we need to copy the file/dir/link/whatever? Early termination | |
5ca0a96d | 803 | * if we do not. Always traverse directories. Always redo links. |
3a42736d MD |
804 | * |
805 | * NOTE: st2Valid is true only if dpath != NULL *and* dpath stats good. | |
806 | */ | |
807 | ||
808 | if ( | |
4d858d58 | 809 | st2Valid |
c0538630 MD |
810 | && stat1->st_mode == st2.st_mode |
811 | && FlagsMatch(stat1, &st2) | |
3a42736d | 812 | ) { |
c0538630 | 813 | if (S_ISLNK(stat1->st_mode) || S_ISDIR(stat1->st_mode)) { |
5ca0a96d | 814 | ; |
3a42736d MD |
815 | } else { |
816 | if (ForceOpt == 0 && | |
c0538630 | 817 | stat1->st_size == st2.st_size && |
0212bfce | 818 | (ValidateOpt == 2 || mtimecmp(stat1, &st2) == 0) && |
c0538630 | 819 | OwnerMatch(stat1, &st2) |
4d858d58 | 820 | #ifndef NOMD5 |
c0538630 | 821 | && (UseMD5Opt == 0 || !S_ISREG(stat1->st_mode) || |
d5fdcd00 | 822 | (mres = md5_check(spath, dpath)) == 0) |
577109ea | 823 | #endif |
c0538630 | 824 | && (ValidateOpt == 0 || !S_ISREG(stat1->st_mode) || |
d5fdcd00 | 825 | validate_check(spath, dpath) == 0) |
3a42736d | 826 | ) { |
17e9c4cc | 827 | /* |
a9fa3ead | 828 | * The files are identical, but if we are running as |
17e9c4cc MD |
829 | * root we might need to adjust ownership/group/flags. |
830 | */ | |
831 | int changedown = 0; | |
832 | int changedflags = 0; | |
a9fa3ead | 833 | |
3a42736d | 834 | if (hln) |
44dd1628 | 835 | hltsetdino(hln, st2.st_ino); |
17e9c4cc | 836 | |
c0538630 MD |
837 | if (!OwnerMatch(stat1, &st2)) { |
838 | hc_chown(&DstHost, dpath, stat1->st_uid, stat1->st_gid); | |
293141b7 | 839 | changedown = 1; |
17e9c4cc MD |
840 | } |
841 | #ifdef _ST_FLAGS_PRESENT_ | |
c0538630 MD |
842 | if (!FlagsMatch(stat1, &st2)) { |
843 | hc_chflags(&DstHost, dpath, stat1->st_flags); | |
293141b7 | 844 | changedflags = 1; |
17e9c4cc MD |
845 | } |
846 | #endif | |
3a42736d | 847 | if (VerboseOpt >= 3) { |
4d858d58 | 848 | #ifndef NOMD5 |
17e9c4cc MD |
849 | if (UseMD5Opt) { |
850 | logstd("%-32s md5-nochange", | |
851 | (dpath ? dpath : spath)); | |
852 | } else | |
4d858d58 | 853 | #endif |
5ca0a96d | 854 | if (ValidateOpt) { |
17e9c4cc MD |
855 | logstd("%-32s nochange (contents validated)", |
856 | (dpath ? dpath : spath)); | |
857 | } else { | |
858 | logstd("%-32s nochange", (dpath ? dpath : spath)); | |
859 | } | |
860 | if (changedown) | |
861 | logstd(" (uid/gid differ)"); | |
862 | if (changedflags) | |
863 | logstd(" (flags differ)"); | |
864 | logstd("\n"); | |
3a42736d MD |
865 | } |
866 | CountSourceBytes += size; | |
867 | CountSourceItems++; | |
a2dc574c MD |
868 | r = 0; |
869 | goto done; | |
3a42736d MD |
870 | } |
871 | } | |
872 | } | |
c0538630 | 873 | if (st2Valid && !S_ISDIR(stat1->st_mode) && S_ISDIR(st2.st_mode)) { |
3a42736d MD |
874 | if (SafetyOpt) { |
875 | logerr("%-32s SAFETY - refusing to copy file over directory\n", | |
876 | (dpath ? dpath : spath) | |
877 | ); | |
878 | ++r; /* XXX */ | |
a2dc574c MD |
879 | r = 0; |
880 | goto done; /* continue with the cpdup anyway */ | |
3a42736d MD |
881 | } |
882 | if (QuietOpt == 0 || AskConfirmation) { | |
883 | logstd("%-32s WARNING: non-directory source will blow away\n" | |
884 | "%-32s preexisting dest directory, continuing anyway!\n", | |
885 | ((dpath) ? dpath : spath), ""); | |
886 | } | |
887 | if (dpath) | |
c0538630 | 888 | RemoveRecur(dpath, ddevNo, &st2); |
8f0e7bc1 | 889 | st2Valid = 0; |
3a42736d MD |
890 | } |
891 | ||
577109ea MD |
892 | /* |
893 | * The various comparisons failed, copy it. | |
894 | */ | |
c0538630 | 895 | if (S_ISDIR(stat1->st_mode)) { |
293141b7 | 896 | int skipdir = 0; |
3a42736d | 897 | |
293141b7 MD |
898 | if (dpath) { |
899 | if (!st2Valid || S_ISDIR(st2.st_mode) == 0) { | |
900 | if (st2Valid) | |
77133d96 | 901 | xremove(&DstHost, dpath); |
c0538630 | 902 | if (hc_mkdir(&DstHost, dpath, stat1->st_mode | 0700) != 0) { |
293141b7 MD |
903 | logerr("%s: mkdir failed: %s\n", |
904 | (dpath ? dpath : spath), strerror(errno)); | |
905 | r = 1; | |
906 | skipdir = 1; | |
907 | } | |
908 | if (hc_lstat(&DstHost, dpath, &st2) != 0) { | |
698d0b11 MD |
909 | if (NotForRealOpt == 0) |
910 | logerr("%s: lstat of newly made dir failed: %s\n", | |
911 | (dpath ? dpath : spath), strerror(errno)); | |
293141b7 MD |
912 | st2Valid = 0; |
913 | r = 1; | |
914 | skipdir = 1; | |
915 | } | |
916 | else { | |
917 | st2Valid = 1; | |
c0538630 MD |
918 | if (!OwnerMatch(stat1, &st2) && |
919 | hc_chown(&DstHost, dpath, stat1->st_uid, stat1->st_gid) != 0 | |
293141b7 | 920 | ) { |
74fa57e3 MD |
921 | logerr("%s: chown of newly made dir failed: %s\n", |
922 | (dpath ? dpath : spath), strerror(errno)); | |
923 | r = 1; | |
293141b7 | 924 | /* Note that we should not set skipdir = 1 here. */ |
74fa57e3 | 925 | } |
3a42736d | 926 | } |
ae24b5e0 SW |
927 | if (VerboseOpt) |
928 | logstd("%-32s mkdir-ok\n", (dpath ? dpath : spath)); | |
293141b7 | 929 | CountCopiedItems++; |
3a42736d | 930 | } else { |
293141b7 MD |
931 | /* |
932 | * Directory must be scanable by root for cpdup to | |
933 | * work. We'll fix it later if the directory isn't | |
934 | * supposed to be readable ( which is why we fixup | |
935 | * st2.st_mode to match what we did ). | |
936 | */ | |
937 | if ((st2.st_mode & 0700) != 0700) { | |
938 | hc_chmod(&DstHost, dpath, st2.st_mode | 0700); | |
939 | st2.st_mode |= 0700; | |
940 | } | |
941 | if (VerboseOpt >= 2) | |
942 | logstd("%s\n", dpath ? dpath : spath); | |
3a42736d | 943 | } |
293141b7 | 944 | } |
3a42736d | 945 | |
293141b7 MD |
946 | /* |
947 | * When copying a directory, stop if the source crosses a mount | |
948 | * point. | |
949 | */ | |
c0538630 | 950 | if (sdevNo != (dev_t)-1 && stat1->st_dev != sdevNo) |
293141b7 MD |
951 | skipdir = 1; |
952 | else | |
c0538630 | 953 | sdevNo = stat1->st_dev; |
3a42736d | 954 | |
293141b7 MD |
955 | /* |
956 | * When copying a directory, stop if the destination crosses | |
957 | * a mount point. | |
958 | * | |
959 | * The target directory will have been created and stat'd | |
960 | * for st2 if it did not previously exist. st2Valid is left | |
961 | * as a flag. If the stat failed st2 will still only have its | |
962 | * default initialization. | |
963 | * | |
964 | * So we simply assume here that the directory is within the | |
965 | * current target mount if we had to create it (aka st2Valid is 0) | |
966 | * and we leave ddevNo alone. | |
967 | */ | |
968 | if (st2Valid) { | |
969 | if (ddevNo != (dev_t)-1 && st2.st_dev != ddevNo) | |
970 | skipdir = 1; | |
971 | else | |
972 | ddevNo = st2.st_dev; | |
973 | } | |
3a42736d | 974 | |
293141b7 MD |
975 | if (!skipdir) { |
976 | List *list = malloc(sizeof(List)); | |
8ac50aa3 | 977 | Node *node; |
3a42736d | 978 | |
293141b7 MD |
979 | if (DirShowOpt) |
980 | logstd("Scanning %s ...\n", spath); | |
981 | InitList(list); | |
982 | if (ScanDir(list, &SrcHost, spath, &CountSourceReadBytes, 0) == 0) { | |
c0538630 MD |
983 | node = NULL; |
984 | while ((node = IterateList(list, node, 0)) != NULL) { | |
293141b7 MD |
985 | char *nspath; |
986 | char *ndpath = NULL; | |
3a42736d | 987 | |
c0538630 | 988 | nspath = mprintf("%s/%s", spath, node->no_Name); |
293141b7 | 989 | if (dpath) |
c0538630 | 990 | ndpath = mprintf("%s/%s", dpath, node->no_Name); |
3a42736d | 991 | |
a2dc574c MD |
992 | info->spath = nspath; |
993 | info->dpath = ndpath; | |
994 | info->sdevNo = sdevNo; | |
995 | info->ddevNo = ddevNo; | |
975200d7 | 996 | if (depth < 0) |
c0538630 | 997 | r += DoCopy(info, node->no_Stat, depth); |
975200d7 | 998 | else |
c0538630 | 999 | r += DoCopy(info, node->no_Stat, depth + 1); |
a2dc574c MD |
1000 | free(nspath); |
1001 | if (ndpath) | |
1002 | free(ndpath); | |
975200d7 MD |
1003 | info->spath = NULL; |
1004 | info->dpath = NULL; | |
a2dc574c | 1005 | } |
3a42736d | 1006 | |
293141b7 MD |
1007 | /* |
1008 | * Remove files/directories from destination that do not appear | |
1009 | * in the source. | |
1010 | */ | |
1011 | if (dpath && ScanDir(list, &DstHost, dpath, | |
8ac50aa3 MD |
1012 | &CountTargetReadBytes, 3) == 0) { |
1013 | node = NULL; | |
c0538630 | 1014 | while ((node = IterateList(list, node, 3)) != NULL) { |
293141b7 MD |
1015 | /* |
1016 | * If object does not exist in source or .cpignore | |
1017 | * then recursively remove it. | |
1018 | */ | |
3a42736d MD |
1019 | char *ndpath; |
1020 | ||
c0538630 MD |
1021 | ndpath = mprintf("%s/%s", dpath, node->no_Name); |
1022 | RemoveRecur(ndpath, ddevNo, node->no_Stat); | |
3a42736d MD |
1023 | free(ndpath); |
1024 | } | |
1025 | } | |
3a42736d | 1026 | } |
293141b7 MD |
1027 | ResetList(list); |
1028 | free(list); | |
1029 | } | |
3a42736d | 1030 | |
293141b7 MD |
1031 | if (dpath && st2Valid) { |
1032 | struct timeval tv[2]; | |
8c2e6bdc | 1033 | |
c0538630 MD |
1034 | if (ForceOpt || !OwnerMatch(stat1, &st2)) |
1035 | hc_chown(&DstHost, dpath, stat1->st_uid, stat1->st_gid); | |
1036 | if (stat1->st_mode != st2.st_mode) | |
1037 | hc_chmod(&DstHost, dpath, stat1->st_mode); | |
4d858d58 | 1038 | #ifdef _ST_FLAGS_PRESENT_ |
c0538630 MD |
1039 | if (!FlagsMatch(stat1, &st2)) |
1040 | hc_chflags(&DstHost, dpath, stat1->st_flags); | |
4d858d58 | 1041 | #endif |
0212bfce | 1042 | if (ForceOpt || mtimecmp(stat1, &st2) != 0) { |
293141b7 | 1043 | bzero(tv, sizeof(tv)); |
c0538630 MD |
1044 | tv[0].tv_sec = stat1->st_mtime; |
1045 | tv[1].tv_sec = stat1->st_mtime; | |
1a05b9d1 AL |
1046 | #if defined(st_mtime) /* A macro, so very likely on modern POSIX */ |
1047 | tv[0].tv_usec = stat1->st_mtim.tv_nsec / 1000; | |
1048 | tv[1].tv_usec = stat1->st_mtim.tv_nsec / 1000; | |
0212bfce | 1049 | #endif |
293141b7 | 1050 | hc_utimes(&DstHost, dpath, tv); |
3a42736d MD |
1051 | } |
1052 | } | |
1053 | } else if (dpath == NULL) { | |
1054 | /* | |
1055 | * If dpath is NULL, we are just updating the MD5 | |
1056 | */ | |
4d858d58 | 1057 | #ifndef NOMD5 |
c0538630 | 1058 | if (UseMD5Opt && S_ISREG(stat1->st_mode)) { |
3a42736d MD |
1059 | mres = md5_check(spath, NULL); |
1060 | ||
1061 | if (VerboseOpt > 1) { | |
1062 | if (mres < 0) | |
1063 | logstd("%-32s md5-update\n", (dpath) ? dpath : spath); | |
1064 | else | |
1065 | logstd("%-32s md5-ok\n", (dpath) ? dpath : spath); | |
1066 | } else if (!QuietOpt && mres < 0) { | |
1067 | logstd("%-32s md5-update\n", (dpath) ? dpath : spath); | |
1068 | } | |
1069 | } | |
4d858d58 | 1070 | #endif |
c0538630 | 1071 | } else if (S_ISREG(stat1->st_mode)) { |
3a42736d | 1072 | char *path; |
4e316ad5 | 1073 | char *hpath; |
3a42736d MD |
1074 | int fd1; |
1075 | int fd2; | |
1076 | ||
0e9fad5e MD |
1077 | if (st2Valid) |
1078 | path = mprintf("%s.tmp%d", dpath, (int)getpid()); | |
1079 | else | |
1080 | path = mprintf("%s", dpath); | |
3a42736d MD |
1081 | |
1082 | /* | |
1083 | * Handle check failure message. | |
1084 | */ | |
4d858d58 | 1085 | #ifndef NOMD5 |
3a42736d MD |
1086 | if (mres < 0) |
1087 | logerr("%-32s md5-CHECK-FAILED\n", (dpath) ? dpath : spath); | |
4d858d58 | 1088 | #endif |
3a42736d | 1089 | |
4e316ad5 MD |
1090 | /* |
1091 | * Not quite ready to do the copy yet. If UseHLPath is defined, | |
1092 | * see if we can hardlink instead. | |
6db8f15d MD |
1093 | * |
1094 | * If we can hardlink, and the target exists, we have to remove it | |
1095 | * first or the hardlink will fail. This can occur in a number of | |
293141b7 | 1096 | * situations but most typically when the '-f -H' combination is |
6db8f15d | 1097 | * used. |
4e316ad5 | 1098 | */ |
c0538630 | 1099 | if (UseHLPath && (hpath = checkHLPath(stat1, spath, dpath)) != NULL) { |
6db8f15d | 1100 | if (st2Valid) |
77133d96 | 1101 | xremove(&DstHost, dpath); |
4d858d58 | 1102 | if (hc_link(&DstHost, hpath, dpath) == 0) { |
d5fdcd00 | 1103 | ++CountLinkedItems; |
4e316ad5 MD |
1104 | if (VerboseOpt) { |
1105 | logstd("%-32s hardlinked(-H)\n", | |
1106 | (dpath ? dpath : spath)); | |
1107 | } | |
1108 | free(hpath); | |
1109 | goto skip_copy; | |
1110 | } | |
1111 | /* | |
1112 | * Shucks, we may have hit a filesystem hard linking limit, | |
1113 | * we have to copy instead. | |
1114 | */ | |
1115 | free(hpath); | |
1116 | } | |
1117 | ||
4d858d58 MD |
1118 | if ((fd1 = hc_open(&SrcHost, spath, O_RDONLY, 0)) >= 0) { |
1119 | if ((fd2 = hc_open(&DstHost, path, O_WRONLY|O_CREAT|O_EXCL, 0600)) < 0) { | |
3a42736d MD |
1120 | /* |
1121 | * There could be a .tmp file from a previously interrupted | |
1122 | * run, delete and retry. Fail if we still can't get at it. | |
1123 | */ | |
4d858d58 MD |
1124 | #ifdef _ST_FLAGS_PRESENT_ |
1125 | hc_chflags(&DstHost, path, 0); | |
1126 | #endif | |
1127 | hc_remove(&DstHost, path); | |
1128 | fd2 = hc_open(&DstHost, path, O_WRONLY|O_CREAT|O_EXCL|O_TRUNC, 0600); | |
3a42736d MD |
1129 | } |
1130 | if (fd2 >= 0) { | |
1c755102 | 1131 | const char *op; |
975200d7 | 1132 | char *iobuf1 = malloc(GETIOSIZE); |
3a42736d MD |
1133 | int n; |
1134 | ||
1135 | /* | |
1136 | * Matt: What about holes? | |
1137 | */ | |
1138 | op = "read"; | |
975200d7 | 1139 | while ((n = hc_read(&SrcHost, fd1, iobuf1, GETIOSIZE)) > 0) { |
3a42736d | 1140 | op = "write"; |
975200d7 | 1141 | if (hc_write(&DstHost, fd2, iobuf1, n) != n) |
3a42736d MD |
1142 | break; |
1143 | op = "read"; | |
1144 | } | |
4d858d58 | 1145 | hc_close(&DstHost, fd2); |
3a42736d MD |
1146 | if (n == 0) { |
1147 | struct timeval tv[2]; | |
1148 | ||
1149 | bzero(tv, sizeof(tv)); | |
c0538630 MD |
1150 | tv[0].tv_sec = stat1->st_mtime; |
1151 | tv[1].tv_sec = stat1->st_mtime; | |
1a05b9d1 AL |
1152 | #if defined(st_mtime) |
1153 | tv[0].tv_usec = stat1->st_mtim.tv_nsec / 1000; | |
1154 | tv[1].tv_usec = stat1->st_mtim.tv_nsec / 1000; | |
0212bfce | 1155 | #endif |
3a42736d | 1156 | |
c0538630 MD |
1157 | if (DstRootPrivs || ChgrpAllowed(stat1->st_gid)) |
1158 | hc_chown(&DstHost, path, stat1->st_uid, stat1->st_gid); | |
1159 | hc_chmod(&DstHost, path, stat1->st_mode); | |
17e9c4cc | 1160 | #ifdef _ST_FLAGS_PRESENT_ |
c0538630 | 1161 | if (stat1->st_flags & (UF_IMMUTABLE|SF_IMMUTABLE)) |
17e9c4cc MD |
1162 | hc_utimes(&DstHost, path, tv); |
1163 | #else | |
1164 | hc_utimes(&DstHost, path, tv); | |
1165 | #endif | |
0e9fad5e | 1166 | if (st2Valid && xrename(path, dpath, st2_flags) != 0) { |
3a42736d MD |
1167 | logerr("%-32s rename-after-copy failed: %s\n", |
1168 | (dpath ? dpath : spath), strerror(errno) | |
1169 | ); | |
2b7dbe20 | 1170 | xremove(&DstHost, path); |
3a42736d MD |
1171 | ++r; |
1172 | } else { | |
1173 | if (VerboseOpt) | |
1174 | logstd("%-32s copy-ok\n", (dpath ? dpath : spath)); | |
4d858d58 | 1175 | #ifdef _ST_FLAGS_PRESENT_ |
c0538630 MD |
1176 | if (DstRootPrivs ? stat1->st_flags : stat1->st_flags & UF_SETTABLE) |
1177 | hc_chflags(&DstHost, dpath, stat1->st_flags); | |
4d858d58 | 1178 | #endif |
3a42736d | 1179 | } |
17e9c4cc | 1180 | #ifdef _ST_FLAGS_PRESENT_ |
c0538630 | 1181 | if ((stat1->st_flags & (UF_IMMUTABLE|SF_IMMUTABLE)) == 0) |
17e9c4cc MD |
1182 | hc_utimes(&DstHost, dpath, tv); |
1183 | #endif | |
d5fdcd00 | 1184 | CountSourceReadBytes += size; |
3a42736d MD |
1185 | CountWriteBytes += size; |
1186 | CountSourceBytes += size; | |
1187 | CountSourceItems++; | |
1188 | CountCopiedItems++; | |
1189 | } else { | |
1190 | logerr("%-32s %s failed: %s\n", | |
1191 | (dpath ? dpath : spath), op, strerror(errno) | |
1192 | ); | |
4d858d58 | 1193 | hc_remove(&DstHost, path); |
3a42736d MD |
1194 | ++r; |
1195 | } | |
975200d7 | 1196 | free(iobuf1); |
3a42736d MD |
1197 | } else { |
1198 | logerr("%-32s create (uid %d, euid %d) failed: %s\n", | |
1199 | (dpath ? dpath : spath), getuid(), geteuid(), | |
1200 | strerror(errno) | |
1201 | ); | |
1202 | ++r; | |
1203 | } | |
4d858d58 | 1204 | hc_close(&SrcHost, fd1); |
3a42736d MD |
1205 | } else { |
1206 | logerr("%-32s copy: open failed: %s\n", | |
1207 | (dpath ? dpath : spath), | |
1208 | strerror(errno) | |
1209 | ); | |
1210 | ++r; | |
1211 | } | |
4e316ad5 | 1212 | skip_copy: |
3a42736d MD |
1213 | free(path); |
1214 | ||
1215 | if (hln) { | |
44dd1628 MD |
1216 | if (!r && hc_stat(&DstHost, dpath, &st2) == 0) { |
1217 | hltsetdino(hln, st2.st_ino); | |
1218 | } else { | |
3a42736d | 1219 | hltdelete(hln); |
44dd1628 MD |
1220 | hln = NULL; |
1221 | } | |
3a42736d | 1222 | } |
c0538630 | 1223 | } else if (S_ISLNK(stat1->st_mode)) { |
975200d7 MD |
1224 | char *link1 = malloc(GETLINKSIZE); |
1225 | char *link2 = malloc(GETLINKSIZE); | |
0e9fad5e | 1226 | char *path; |
3a42736d MD |
1227 | int n1; |
1228 | int n2; | |
1229 | ||
975200d7 | 1230 | n1 = hc_readlink(&SrcHost, spath, link1, GETLINKSIZE - 1); |
0e9fad5e MD |
1231 | if (st2Valid) { |
1232 | path = mprintf("%s.tmp%d", dpath, (int)getpid()); | |
1233 | n2 = hc_readlink(&DstHost, dpath, link2, GETLINKSIZE - 1); | |
1234 | } else { | |
1235 | path = mprintf("%s", dpath); | |
1236 | n2 = -1; | |
1237 | } | |
3a42736d | 1238 | if (n1 >= 0) { |
2b7dbe20 MD |
1239 | if (ForceOpt || n1 != n2 || bcmp(link1, link2, n1) != 0 || |
1240 | (st2Valid && symlink_mfo_test(&DstHost, stat1, &st2)) | |
1241 | ) { | |
1242 | struct timeval tv[2]; | |
1243 | ||
1244 | bzero(tv, sizeof(tv)); | |
1245 | tv[0].tv_sec = stat1->st_mtime; | |
1246 | tv[1].tv_sec = stat1->st_mtime; | |
1247 | #if defined(st_mtime) | |
1248 | tv[0].tv_usec = stat1->st_mtim.tv_nsec / 1000; | |
1249 | tv[1].tv_usec = stat1->st_mtim.tv_nsec / 1000; | |
1250 | #endif | |
1251 | ||
c0538630 | 1252 | hc_umask(&DstHost, ~stat1->st_mode); |
77133d96 | 1253 | xremove(&DstHost, path); |
3a42736d | 1254 | link1[n1] = 0; |
4d858d58 | 1255 | if (hc_symlink(&DstHost, link1, path) < 0) { |
3a42736d MD |
1256 | logerr("%-32s symlink (%s->%s) failed: %s\n", |
1257 | (dpath ? dpath : spath), link1, path, | |
1258 | strerror(errno) | |
1259 | ); | |
1260 | ++r; | |
1261 | } else { | |
c0538630 MD |
1262 | if (DstRootPrivs || ChgrpAllowed(stat1->st_gid)) |
1263 | hc_lchown(&DstHost, path, stat1->st_uid, stat1->st_gid); | |
2b7dbe20 MD |
1264 | |
1265 | /* | |
1266 | * lutimes, lchmod if supported by destination. | |
1267 | */ | |
1268 | if (DstHost.version >= HCPROTO_VERSION_LUCC) { | |
1269 | hc_lchmod(&DstHost, path, stat1->st_mode); | |
1270 | hc_lutimes(&DstHost, path, tv); | |
1271 | } | |
1272 | ||
3a42736d | 1273 | /* |
2b7dbe20 | 1274 | * rename (and set flags if supported by destination) |
3a42736d | 1275 | */ |
0e9fad5e | 1276 | if (st2Valid && xrename(path, dpath, st2_flags) != 0) { |
3a42736d MD |
1277 | logerr("%-32s rename softlink (%s->%s) failed: %s\n", |
1278 | (dpath ? dpath : spath), | |
1279 | path, dpath, strerror(errno)); | |
2b7dbe20 MD |
1280 | xremove(&DstHost, path); |
1281 | } else { | |
1282 | #ifdef _ST_FLAGS_PRESENT_ | |
1283 | if (DstHost.version >= HCPROTO_VERSION_LUCC) | |
1284 | hc_lchflags(&DstHost, dpath, stat1->st_flags); | |
1285 | #endif | |
1286 | if (VerboseOpt) { | |
1287 | logstd("%-32s softlink-ok\n", | |
1288 | (dpath ? dpath : spath)); | |
1289 | } | |
3a42736d | 1290 | } |
4d858d58 | 1291 | hc_umask(&DstHost, 000); |
3a42736d MD |
1292 | CountWriteBytes += n1; |
1293 | CountCopiedItems++; | |
c0538630 | 1294 | } |
3a42736d MD |
1295 | } else { |
1296 | if (VerboseOpt >= 3) | |
293141b7 | 1297 | logstd("%-32s nochange", (dpath ? dpath : spath)); |
c0538630 MD |
1298 | if (!OwnerMatch(stat1, &st2)) { |
1299 | hc_lchown(&DstHost, dpath, stat1->st_uid, stat1->st_gid); | |
293141b7 MD |
1300 | if (VerboseOpt >= 3) |
1301 | logstd(" (uid/gid differ)"); | |
1302 | } | |
1303 | if (VerboseOpt >= 3) | |
1304 | logstd("\n"); | |
3a42736d MD |
1305 | } |
1306 | CountSourceBytes += n1; | |
d5fdcd00 | 1307 | CountSourceReadBytes += n1; |
d0a6ae34 | 1308 | if (n2 > 0) |
d5fdcd00 | 1309 | CountTargetReadBytes += n2; |
3a42736d MD |
1310 | CountSourceItems++; |
1311 | } else { | |
1312 | r = 1; | |
1313 | logerr("%-32s softlink-failed\n", (dpath ? dpath : spath)); | |
1314 | } | |
975200d7 MD |
1315 | free(link1); |
1316 | free(link2); | |
1317 | free(path); | |
c0538630 | 1318 | } else if ((S_ISCHR(stat1->st_mode) || S_ISBLK(stat1->st_mode)) && DeviceOpt) { |
0e9fad5e | 1319 | char *path = NULL; |
3a42736d MD |
1320 | |
1321 | if (ForceOpt || | |
d0a6ae34 | 1322 | st2Valid == 0 || |
c0538630 MD |
1323 | stat1->st_mode != st2.st_mode || |
1324 | stat1->st_rdev != st2.st_rdev || | |
1325 | !OwnerMatch(stat1, &st2) | |
3a42736d | 1326 | ) { |
0e9fad5e MD |
1327 | if (st2Valid) { |
1328 | path = mprintf("%s.tmp%d", dpath, (int)getpid()); | |
1329 | xremove(&DstHost, path); | |
1330 | } else { | |
1331 | path = mprintf("%s", dpath); | |
1332 | } | |
3a42736d | 1333 | |
c0538630 MD |
1334 | if (hc_mknod(&DstHost, path, stat1->st_mode, stat1->st_rdev) == 0) { |
1335 | hc_chmod(&DstHost, path, stat1->st_mode); | |
1336 | hc_chown(&DstHost, path, stat1->st_uid, stat1->st_gid); | |
0e9fad5e MD |
1337 | if (st2Valid) |
1338 | xremove(&DstHost, dpath); | |
1339 | if (st2Valid && xrename(path, dpath, st2_flags) != 0) { | |
3a42736d MD |
1340 | logerr("%-32s dev-rename-after-create failed: %s\n", |
1341 | (dpath ? dpath : spath), | |
1342 | strerror(errno) | |
1343 | ); | |
1344 | } else if (VerboseOpt) { | |
1345 | logstd("%-32s dev-ok\n", (dpath ? dpath : spath)); | |
1346 | } | |
1347 | CountCopiedItems++; | |
1348 | } else { | |
1349 | r = 1; | |
d0a6ae34 | 1350 | logerr("%-32s dev failed: %s\n", |
3a42736d MD |
1351 | (dpath ? dpath : spath), strerror(errno) |
1352 | ); | |
1353 | } | |
1354 | } else { | |
1355 | if (VerboseOpt >= 3) | |
1356 | logstd("%-32s nochange\n", (dpath ? dpath : spath)); | |
1357 | } | |
0e9fad5e MD |
1358 | if (path) |
1359 | free(path); | |
3a42736d MD |
1360 | CountSourceItems++; |
1361 | } | |
a2dc574c | 1362 | done: |
975200d7 MD |
1363 | if (hln) { |
1364 | if (hln->dino == (ino_t)-1) { | |
1365 | hltdelete(hln); | |
1366 | /*hln = NULL; unneeded */ | |
1367 | } else { | |
1368 | hltrels(hln); | |
1369 | } | |
1370 | } | |
577109ea | 1371 | return (r); |
3a42736d MD |
1372 | } |
1373 | ||
293141b7 MD |
1374 | int |
1375 | ScanDir(List *list, struct HostConf *host, const char *path, | |
1376 | int64_t *CountReadBytes, int n) | |
1377 | { | |
1378 | DIR *dir; | |
16802529 | 1379 | struct HostConf *cphost; |
c0538630 MD |
1380 | struct HCDirEntry *den; |
1381 | struct stat *statptr; | |
293141b7 MD |
1382 | |
1383 | if (n == 0) { | |
1384 | /* | |
1385 | * scan .cpignore file for files/directories to ignore | |
1386 | * (only in the source directory, i.e. if n == 0). | |
1387 | */ | |
1388 | if (UseCpFile) { | |
1389 | int fd; | |
1390 | int nread; | |
1391 | int bufused; | |
1392 | char *buf = malloc(GETBUFSIZE); | |
1393 | char *nl, *next; | |
1394 | char *fpath; | |
1395 | ||
1396 | if (UseCpFile[0] == '/') { | |
1397 | fpath = mprintf("%s", UseCpFile); | |
16802529 | 1398 | cphost = NULL; |
293141b7 MD |
1399 | } else { |
1400 | fpath = mprintf("%s/%s", path, UseCpFile); | |
16802529 MD |
1401 | AddList(list, strrchr(fpath, '/') + 1, 1, NULL); |
1402 | cphost = host; | |
293141b7 | 1403 | } |
16802529 MD |
1404 | fd = hc_open(cphost, fpath, O_RDONLY, 0); |
1405 | if (fd >= 0) { | |
293141b7 | 1406 | bufused = 0; |
16802529 | 1407 | while ((nread = hc_read(cphost, fd, buf + bufused, |
c0538630 | 1408 | GETBUFSIZE - bufused - 1)) > 0) { |
293141b7 MD |
1409 | *CountReadBytes += nread; |
1410 | bufused += nread; | |
1411 | buf[bufused] = 0; | |
1412 | for (next = buf; (nl = strchr(next, '\n')); next = nl+1) { | |
1413 | *nl = 0; | |
c0538630 | 1414 | AddList(list, next, 1, NULL); |
293141b7 MD |
1415 | } |
1416 | bufused = strlen(next); | |
1417 | if (bufused) | |
1418 | bcopy(next, buf, bufused); | |
1419 | } | |
1420 | if (bufused) { | |
1421 | /* last line has no trailing newline */ | |
1422 | buf[bufused] = 0; | |
c0538630 | 1423 | AddList(list, buf, 1, NULL); |
293141b7 | 1424 | } |
16802529 | 1425 | hc_close(cphost, fd); |
293141b7 MD |
1426 | } |
1427 | free(fpath); | |
1428 | free(buf); | |
1429 | } | |
1430 | ||
1431 | /* | |
1432 | * Automatically exclude MD5CacheFile that we create on the | |
1433 | * source from the copy to the destination. | |
293141b7 MD |
1434 | */ |
1435 | if (UseMD5Opt) | |
c0538630 | 1436 | AddList(list, MD5CacheFile, 1, NULL); |
293141b7 MD |
1437 | } |
1438 | ||
c0538630 MD |
1439 | if ((dir = hc_opendir(host, path)) == NULL) |
1440 | return (1); | |
1441 | while ((den = hc_readdir(host, dir, &statptr)) != NULL) { | |
293141b7 MD |
1442 | /* |
1443 | * ignore . and .. | |
1444 | */ | |
560e4370 MD |
1445 | if (strcmp(den->d_name, ".") != 0 && strcmp(den->d_name, "..") != 0) { |
1446 | if (UseCpFile && UseCpFile[0] == '/') { | |
1447 | if (CheckList(list, path, den->d_name) == 0) | |
1448 | continue; | |
1449 | } | |
c0538630 | 1450 | AddList(list, den->d_name, n, statptr); |
560e4370 | 1451 | } |
293141b7 MD |
1452 | } |
1453 | hc_closedir(host, dir); | |
1454 | ||
1455 | return (0); | |
1456 | } | |
1457 | ||
3a42736d MD |
1458 | /* |
1459 | * RemoveRecur() | |
1460 | */ | |
1461 | ||
75bd842a | 1462 | static void |
c0538630 | 1463 | RemoveRecur(const char *dpath, dev_t devNo, struct stat *dstat) |
3a42736d MD |
1464 | { |
1465 | struct stat st; | |
1466 | ||
c0538630 MD |
1467 | if (dstat == NULL) { |
1468 | if (hc_lstat(&DstHost, dpath, &st) == 0) | |
1469 | dstat = &st; | |
1470 | } | |
1471 | if (dstat != NULL) { | |
7cde0c8b | 1472 | if (devNo == (dev_t)-1) |
c0538630 MD |
1473 | devNo = dstat->st_dev; |
1474 | if (dstat->st_dev == devNo) { | |
1475 | if (S_ISDIR(dstat->st_mode)) { | |
3a42736d MD |
1476 | DIR *dir; |
1477 | ||
4d858d58 | 1478 | if ((dir = hc_opendir(&DstHost, dpath)) != NULL) { |
c0538630 MD |
1479 | List *list = malloc(sizeof(List)); |
1480 | Node *node = NULL; | |
1481 | struct HCDirEntry *den; | |
3a42736d | 1482 | |
c0538630 MD |
1483 | InitList(list); |
1484 | while ((den = hc_readdir(&DstHost, dir, &dstat)) != NULL) { | |
3a42736d MD |
1485 | if (strcmp(den->d_name, ".") == 0) |
1486 | continue; | |
1487 | if (strcmp(den->d_name, "..") == 0) | |
1488 | continue; | |
c0538630 | 1489 | AddList(list, den->d_name, 3, dstat); |
3a42736d | 1490 | } |
4d858d58 | 1491 | hc_closedir(&DstHost, dir); |
c0538630 MD |
1492 | while ((node = IterateList(list, node, 3)) != NULL) { |
1493 | char *ndpath; | |
1494 | ||
1495 | ndpath = mprintf("%s/%s", dpath, node->no_Name); | |
1496 | RemoveRecur(ndpath, devNo, node->no_Stat); | |
1497 | free(ndpath); | |
1498 | } | |
1499 | ResetList(list); | |
1500 | free(list); | |
3a42736d | 1501 | } |
c0d18d1d | 1502 | if (AskConfirmation && NoRemoveOpt == 0) { |
3a42736d | 1503 | if (YesNo(dpath)) { |
9d626b29 | 1504 | if (xrmdir(&DstHost, dpath) < 0) { |
3a42736d MD |
1505 | logerr("%-32s rmdir failed: %s\n", |
1506 | dpath, strerror(errno) | |
1507 | ); | |
1508 | } | |
1509 | CountRemovedItems++; | |
1510 | } | |
1511 | } else { | |
1512 | if (NoRemoveOpt) { | |
1513 | if (VerboseOpt) | |
1514 | logstd("%-32s not-removed\n", dpath); | |
9d626b29 | 1515 | } else if (xrmdir(&DstHost, dpath) == 0) { |
3a42736d MD |
1516 | if (VerboseOpt) |
1517 | logstd("%-32s rmdir-ok\n", dpath); | |
1518 | CountRemovedItems++; | |
1519 | } else { | |
1520 | logerr("%-32s rmdir failed: %s\n", | |
1521 | dpath, strerror(errno) | |
1522 | ); | |
1523 | } | |
1524 | } | |
1525 | } else { | |
c0d18d1d | 1526 | if (AskConfirmation && NoRemoveOpt == 0) { |
3a42736d | 1527 | if (YesNo(dpath)) { |
77133d96 | 1528 | if (xremove(&DstHost, dpath) < 0) { |
3a42736d MD |
1529 | logerr("%-32s remove failed: %s\n", |
1530 | dpath, strerror(errno) | |
1531 | ); | |
1532 | } | |
1533 | CountRemovedItems++; | |
1534 | } | |
1535 | } else { | |
1536 | if (NoRemoveOpt) { | |
1537 | if (VerboseOpt) | |
1538 | logstd("%-32s not-removed\n", dpath); | |
77133d96 | 1539 | } else if (xremove(&DstHost, dpath) == 0) { |
3a42736d MD |
1540 | if (VerboseOpt) |
1541 | logstd("%-32s remove-ok\n", dpath); | |
1542 | CountRemovedItems++; | |
1543 | } else { | |
1544 | logerr("%-32s remove failed: %s\n", | |
1545 | dpath, strerror(errno) | |
1546 | ); | |
1547 | } | |
1548 | } | |
1549 | } | |
1550 | } | |
1551 | } | |
1552 | } | |
1553 | ||
75bd842a | 1554 | static void |
3a42736d MD |
1555 | InitList(List *list) |
1556 | { | |
1557 | bzero(list, sizeof(List)); | |
1558 | list->li_Node.no_Next = &list->li_Node; | |
1559 | } | |
1560 | ||
75bd842a | 1561 | static void |
3a42736d MD |
1562 | ResetList(List *list) |
1563 | { | |
1564 | Node *node; | |
1565 | ||
1566 | while ((node = list->li_Node.no_Next) != &list->li_Node) { | |
1567 | list->li_Node.no_Next = node->no_Next; | |
c0538630 MD |
1568 | if (node->no_Stat != NULL) |
1569 | free(node->no_Stat); | |
3a42736d MD |
1570 | free(node); |
1571 | } | |
1572 | InitList(list); | |
1573 | } | |
1574 | ||
75bd842a | 1575 | static Node * |
c0538630 | 1576 | IterateList(List *list, Node *node, int n) |
293141b7 | 1577 | { |
293141b7 MD |
1578 | if (node == NULL) |
1579 | node = list->li_Node.no_Next; | |
c0538630 | 1580 | else |
293141b7 | 1581 | node = node->no_Next; |
c0538630 MD |
1582 | while (node->no_Value != n && node != &list->li_Node) |
1583 | node = node->no_Next; | |
1584 | return (node == &list->li_Node ? NULL : node); | |
293141b7 MD |
1585 | } |
1586 | ||
75bd842a | 1587 | static int |
c0538630 | 1588 | AddList(List *list, const char *name, int n, struct stat *st) |
3a42736d MD |
1589 | { |
1590 | Node *node; | |
58860d7d MD |
1591 | int hv; |
1592 | ||
3a42736d MD |
1593 | /* |
1594 | * Scan against wildcards. Only a node value of 1 can be a wildcard | |
1595 | * ( usually scanned from .cpignore ) | |
1596 | */ | |
3a42736d MD |
1597 | for (node = list->li_Hash[0]; node; node = node->no_HNext) { |
1598 | if (strcmp(name, node->no_Name) == 0 || | |
293141b7 MD |
1599 | (n != 1 && node->no_Value == 1 && |
1600 | fnmatch(node->no_Name, name, 0) == 0) | |
3a42736d MD |
1601 | ) { |
1602 | return(node->no_Value); | |
1603 | } | |
1604 | } | |
1605 | ||
1606 | /* | |
1607 | * Look for exact match | |
1608 | */ | |
1609 | ||
c0538630 | 1610 | hv = shash(name); |
3a42736d MD |
1611 | for (node = list->li_Hash[hv]; node; node = node->no_HNext) { |
1612 | if (strcmp(name, node->no_Name) == 0) { | |
1613 | return(node->no_Value); | |
1614 | } | |
1615 | } | |
1616 | node = malloc(sizeof(Node) + strlen(name) + 1); | |
c0538630 MD |
1617 | if (node == NULL) |
1618 | fatal("out of memory"); | |
3a42736d MD |
1619 | |
1620 | node->no_Next = list->li_Node.no_Next; | |
1621 | list->li_Node.no_Next = node; | |
1622 | ||
1623 | node->no_HNext = list->li_Hash[hv]; | |
1624 | list->li_Hash[hv] = node; | |
1625 | ||
1626 | strcpy(node->no_Name, name); | |
1627 | node->no_Value = n; | |
c0538630 | 1628 | node->no_Stat = st; |
3a42736d MD |
1629 | |
1630 | return(n); | |
1631 | } | |
1632 | ||
560e4370 MD |
1633 | /* |
1634 | * Match against n=1 (cpignore) entries | |
1635 | * | |
1636 | * Returns 0 on match, non-zero if no match | |
1637 | */ | |
1638 | static int | |
1639 | CheckList(List *list, const char *path, const char *name) | |
1640 | { | |
1641 | char *fpath = NULL; | |
1642 | Node *node; | |
1643 | int hv; | |
1644 | ||
60374ee7 AL |
1645 | if (asprintf(&fpath, "%s/%s", path, name) < 0) |
1646 | fatal("out of memory"); | |
560e4370 MD |
1647 | |
1648 | /* | |
1649 | * Scan against wildcards. Only a node value of 1 can be a wildcard | |
1650 | * ( usually scanned from .cpignore ) | |
1651 | */ | |
1652 | for (node = list->li_Hash[0]; node; node = node->no_HNext) { | |
1653 | if (node->no_Value != 1) | |
1654 | continue; | |
1655 | if (fnmatch(node->no_Name, fpath, 0) == 0) { | |
1656 | free(fpath); | |
1657 | return 0; | |
1658 | } | |
1659 | } | |
1660 | ||
1661 | /* | |
1662 | * Look for exact match | |
1663 | */ | |
1664 | hv = shash(fpath); | |
1665 | for (node = list->li_Hash[hv]; node; node = node->no_HNext) { | |
1666 | if (node->no_Value != 1) | |
1667 | continue; | |
1668 | if (strcmp(fpath, node->no_Name) == 0) { | |
1669 | free(fpath); | |
1670 | return 0; | |
1671 | } | |
1672 | } | |
1673 | ||
1674 | free(fpath); | |
1675 | return 1; | |
1676 | } | |
1677 | ||
fce2564b | 1678 | static int |
3a42736d MD |
1679 | shash(const char *s) |
1680 | { | |
58860d7d MD |
1681 | int hv; |
1682 | ||
1683 | hv = 0xA4FB3255; | |
3a42736d MD |
1684 | |
1685 | while (*s) { | |
d0a6ae34 | 1686 | if (*s == '*' || *s == '?' || |
1687 | *s == '{' || *s == '}' || | |
3a42736d MD |
1688 | *s == '[' || *s == ']' || |
1689 | *s == '|' | |
1690 | ) { | |
1691 | return(0); | |
1692 | } | |
1693 | hv = (hv << 5) ^ *s ^ (hv >> 23); | |
1694 | ++s; | |
1695 | } | |
1696 | return(((hv >> 16) ^ hv) & HMASK); | |
1697 | } | |
1698 | ||
75bd842a | 1699 | static int |
3a42736d MD |
1700 | YesNo(const char *path) |
1701 | { | |
1702 | int ch, first; | |
1703 | ||
58860d7d MD |
1704 | fprintf(stderr, "remove %s (Yes/No) [No]? ", path); |
1705 | fflush(stderr); | |
3a42736d MD |
1706 | |
1707 | first = ch = getchar(); | |
1708 | while (ch != '\n' && ch != EOF) | |
1709 | ch = getchar(); | |
1710 | return ((first == 'y' || first == 'Y')); | |
1711 | } | |
1712 | ||
3a42736d MD |
1713 | /* |
1714 | * xrename() - rename with override | |
1715 | * | |
d0a6ae34 | 1716 | * If the rename fails, attempt to override st_flags on the |
3a42736d MD |
1717 | * destination and rename again. If that fails too, try to |
1718 | * set the flags back the way they were and give up. | |
1719 | */ | |
1720 | ||
fce2564b | 1721 | static int |
3a42736d MD |
1722 | xrename(const char *src, const char *dst, u_long flags) |
1723 | { | |
58860d7d MD |
1724 | int r; |
1725 | ||
4d858d58 MD |
1726 | if ((r = hc_rename(&DstHost, src, dst)) < 0) { |
1727 | #ifdef _ST_FLAGS_PRESENT_ | |
2b7dbe20 MD |
1728 | if (DstHost.version >= HCPROTO_VERSION_LUCC) |
1729 | hc_lchflags(&DstHost, dst, 0); | |
1730 | else | |
1731 | hc_chflags(&DstHost, dst, 0); | |
1732 | ||
1733 | if ((r = hc_rename(&DstHost, src, dst)) < 0) { | |
1734 | if (DstHost.version >= HCPROTO_VERSION_LUCC) | |
1735 | hc_lchflags(&DstHost, dst, flags); | |
1736 | else | |
4d858d58 | 1737 | hc_chflags(&DstHost, dst, flags); |
2b7dbe20 | 1738 | } |
4d858d58 | 1739 | #endif |
3a42736d MD |
1740 | } |
1741 | return(r); | |
1742 | } | |
1743 | ||
fce2564b | 1744 | static int |
3a42736d MD |
1745 | xlink(const char *src, const char *dst, u_long flags) |
1746 | { | |
d8bce52d MD |
1747 | int r; |
1748 | #ifdef _ST_FLAGS_PRESENT_ | |
1749 | int e; | |
1750 | #endif | |
58860d7d | 1751 | |
4d858d58 MD |
1752 | if ((r = hc_link(&DstHost, src, dst)) < 0) { |
1753 | #ifdef _ST_FLAGS_PRESENT_ | |
2b7dbe20 | 1754 | if (DstHost.version >= HCPROTO_VERSION_LUCC) |
c9c5aa9e | 1755 | hc_lchflags(&DstHost, src, 0); |
2b7dbe20 MD |
1756 | else |
1757 | hc_chflags(&DstHost, src, 0); | |
4d858d58 | 1758 | r = hc_link(&DstHost, src, dst); |
3a42736d | 1759 | e = errno; |
4d858d58 | 1760 | hc_chflags(&DstHost, src, flags); |
3a42736d | 1761 | errno = e; |
4d858d58 | 1762 | #endif |
3a42736d | 1763 | } |
d5fdcd00 MD |
1764 | if (r == 0) |
1765 | ++CountLinkedItems; | |
3a42736d MD |
1766 | return(r); |
1767 | } | |
1768 | ||
77133d96 MD |
1769 | static int |
1770 | xremove(struct HostConf *host, const char *path) | |
1771 | { | |
1772 | int res; | |
1773 | ||
1774 | res = hc_remove(host, path); | |
1775 | #ifdef _ST_FLAGS_PRESENT_ | |
1776 | if (res == -EPERM) { | |
2b7dbe20 MD |
1777 | if (host->version >= HCPROTO_VERSION_LUCC) |
1778 | hc_lchflags(host, path, 0); | |
1779 | else | |
1780 | hc_chflags(host, path, 0); | |
77133d96 MD |
1781 | res = hc_remove(host, path); |
1782 | } | |
1783 | #endif | |
1784 | return(res); | |
1785 | } | |
1786 | ||
9d626b29 SW |
1787 | static int |
1788 | xrmdir(struct HostConf *host, const char *path) | |
1789 | { | |
1790 | int res; | |
1791 | ||
1792 | res = hc_rmdir(host, path); | |
1793 | #ifdef _ST_FLAGS_PRESENT_ | |
1794 | if (res == -EPERM) { | |
1795 | hc_chflags(host, path, 0); | |
1796 | res = hc_rmdir(host, path); | |
1797 | } | |
1798 | #endif | |
1799 | return(res); | |
1800 | } | |
0212bfce MD |
1801 | |
1802 | /* | |
1803 | * Compare mtimes. By default cpdup only compares the seconds field | |
1804 | * because different operating systems and filesystems will store time | |
1805 | * fields with varying amounts of precision. | |
1806 | * | |
1807 | * This subroutine can be adjusted to also compare to microseconds or | |
1808 | * nanoseconds precision. However, since cpdup() uses utimes() to | |
1809 | * set a file's timestamp and utimes() only takes timeval's (usec precision), | |
1810 | * I strongly recommend only comparing down to usec precision at best. | |
1811 | */ | |
1812 | static int | |
1813 | mtimecmp(struct stat *st1, struct stat *st2) | |
1814 | { | |
2b7dbe20 MD |
1815 | if (st1->st_mtime < st2->st_mtime) |
1816 | return -1; | |
1817 | if (st1->st_mtime == st2->st_mtime) | |
1818 | return 0; | |
1819 | return 1; | |
1820 | } | |
1821 | ||
1822 | /* | |
1823 | * Check to determine if a symlink's mtime, flags, or mode differ. | |
1824 | * | |
1825 | * This is only supported on targets that support lchflags, lutimes, | |
1826 | * and lchmod. | |
1827 | */ | |
1828 | static int | |
1829 | symlink_mfo_test(struct HostConf *hc, struct stat *st1, struct stat *st2) | |
1830 | { | |
1831 | int res = 0; | |
1832 | ||
1833 | if (hc->version >= HCPROTO_VERSION_LUCC) { | |
1834 | if (!FlagsMatch(st1, st2)) | |
1835 | res = 1; | |
1836 | if (mtimecmp(st1, st2) != 0) | |
1837 | res = 1; | |
1838 | if (st1->st_mode != st2->st_mode) | |
1839 | res = 1; | |
1840 | } | |
1841 | return res; | |
0212bfce | 1842 | } |